/* * Copyright © 2015 Samsung Electronics Co., Ltd * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice (including the * next paragraph) shall be included in all copies or substantial * portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ #include "config.h" #include "zuc_junit_reporter.h" #if ENABLE_JUNIT_XML #include #include #include #include #include #include #include #include #include #include "zuc_event_listener.h" #include "zuc_types.h" #include /** * Hardcoded output name. * @todo follow-up with refactoring to avoid filename hardcoding. * Will allow for better testing in parallel etc. in general. */ #define XML_FNAME "test_detail.xml" #define ISO_8601_FORMAT "%Y-%m-%dT%H:%M:%SZ" #if LIBXML_VERSION >= 20904 #define STRPRINTF_CAST #else #define STRPRINTF_CAST BAD_CAST #endif /** * Internal data. */ struct junit_data { int fd; time_t begin; }; #define MAX_64BIT_STRLEN 20 static void set_attribute(xmlNodePtr node, const char *name, int value) { xmlChar scratch[MAX_64BIT_STRLEN + 1] = {}; xmlStrPrintf(scratch, sizeof(scratch), STRPRINTF_CAST "%d", value); xmlSetProp(node, BAD_CAST name, scratch); } /** * Output the given event. * * @param parent the parent node to add new content to. * @param event the event to write out. */ static void emit_event(xmlNodePtr parent, struct zuc_event *event) { char *msg = NULL; switch (event->op) { case ZUC_OP_TRUE: if (asprintf(&msg, "%s:%d: error: Value of: %s\n" " Actual: false\n" "Expected: true\n", event->file, event->line, event->expr1) < 0) { msg = NULL; } break; case ZUC_OP_FALSE: if (asprintf(&msg, "%s:%d: error: Value of: %s\n" " Actual: true\n" "Expected: false\n", event->file, event->line, event->expr1) < 0) { msg = NULL; } break; case ZUC_OP_NULL: if (asprintf(&msg, "%s:%d: error: Value of: %s\n" " Actual: %p\n" "Expected: %p\n", event->file, event->line, event->expr1, (void *)event->val1, NULL) < 0) { msg = NULL; } break; case ZUC_OP_NOT_NULL: if (asprintf(&msg, "%s:%d: error: Value of: %s\n" " Actual: %p\n" "Expected: not %p\n", event->file, event->line, event->expr1, (void *)event->val1, NULL) < 0) { msg = NULL; } break; case ZUC_OP_EQ: if (event->valtype == ZUC_VAL_CSTR) { if (asprintf(&msg, "%s:%d: error: Value of: %s\n" " Actual: %s\n" "Expected: %s\n" "Which is: %s\n", event->file, event->line, event->expr2, (char *)event->val2, event->expr1, (char *)event->val1) < 0) { msg = NULL; } } else { if (asprintf(&msg, "%s:%d: error: Value of: %s\n" " Actual: %"PRIdPTR"\n" "Expected: %s\n" "Which is: %"PRIdPTR"\n", event->file, event->line, event->expr2, event->val2, event->expr1, event->val1) < 0) { msg = NULL; } } break; case ZUC_OP_NE: if (event->valtype == ZUC_VAL_CSTR) { if (asprintf(&msg, "%s:%d: error: " "Expected: (%s) %s (%s)," " actual: %s == %s\n", event->file, event->line, event->expr1, zuc_get_opstr(event->op), event->expr2, (char *)event->val1, (char *)event->val2) < 0) { msg = NULL; } } else { if (asprintf(&msg, "%s:%d: error: " "Expected: (%s) %s (%s)," " actual: %"PRIdPTR" vs %"PRIdPTR"\n", event->file, event->line, event->expr1, zuc_get_opstr(event->op), event->expr2, event->val1, event->val2) < 0) { msg = NULL; } } break; case ZUC_OP_TERMINATE: { char const *level = (event->val1 == 0) ? "error" : (event->val1 == 1) ? "warning" : "note"; if (asprintf(&msg, "%s:%d: %s: %s\n", event->file, event->line, level, event->expr1) < 0) { msg = NULL; } break; } case ZUC_OP_TRACEPOINT: if (asprintf(&msg, "%s:%d: note: %s\n", event->file, event->line, event->expr1) < 0) { msg = NULL; } break; default: if (asprintf(&msg, "%s:%d: error: " "Expected: (%s) %s (%s), actual: %"PRIdPTR" vs " "%"PRIdPTR"\n", event->file, event->line, event->expr1, zuc_get_opstr(event->op), event->expr2, event->val1, event->val2) < 0) { msg = NULL; } } if ((event->op == ZUC_OP_TERMINATE) && (event->val1 > 1)) { xmlNewChild(parent, NULL, BAD_CAST "skipped", NULL); } else { xmlNodePtr node = xmlNewChild(parent, NULL, BAD_CAST "failure", NULL); if (msg) { xmlSetProp(node, BAD_CAST "message", BAD_CAST msg); } xmlSetProp(node, BAD_CAST "type", BAD_CAST ""); if (msg) { xmlNodePtr cdata = xmlNewCDataBlock(node->doc, BAD_CAST msg, strlen(msg)); xmlAddChild(node, cdata); } } free(msg); } /** * Formats a time in milliseconds to the normal JUnit elapsed form, or * NULL if there is a problem. * The caller should release this with free() * * @return the formatted time string upon success, NULL otherwise. */ static char * as_duration(long ms) { char *str = NULL; if (asprintf(&str, "%1.3f", ms / 1000.0) < 0) { str = NULL; } else { /* * Special case to match behavior of standard JUnit output * writers. Assumption is certain readers might have * limitations, etc. so it is best to keep 100% identical * output. */ if (!strcmp("0.000", str)) { free(str); str = strdup("0"); } } return str; } /** * Returns the status string for the tests (run/notrun). * * @param test the test to check status of. * @return the status string. */ static char const * get_test_status(struct zuc_test *test) { if (test->disabled || test->skipped) return "notrun"; else return "run"; } /** * Output the given test. * * @param parent the parent node to add new content to. * @param test the test to write out. */ static void emit_test(xmlNodePtr parent, struct zuc_test *test) { char *time_str = as_duration(test->elapsed); xmlNodePtr node = xmlNewChild(parent, NULL, BAD_CAST "testcase", NULL); xmlSetProp(node, BAD_CAST "name", BAD_CAST test->name); xmlSetProp(node, BAD_CAST "status", BAD_CAST get_test_status(test)); if (time_str) { xmlSetProp(node, BAD_CAST "time", BAD_CAST time_str); free(time_str); time_str = NULL; } xmlSetProp(node, BAD_CAST "classname", BAD_CAST test->test_case->name); if ((test->failed || test->fatal || test->skipped) && test->events) { struct zuc_event *evt; for (evt = test->events; evt; evt = evt->next) emit_event(node, evt); } } /** * Output the given test case. * * @param parent the parent node to add new content to. * @param test_case the test case to write out. */ static void emit_case(xmlNodePtr parent, struct zuc_case *test_case) { int i; int skipped = 0; int disabled = 0; int failures = 0; xmlNodePtr node = NULL; char *time_str = as_duration(test_case->elapsed); for (i = 0; i < test_case->test_count; ++i) { if (test_case->tests[i]->disabled ) disabled++; if (test_case->tests[i]->skipped ) skipped++; if (test_case->tests[i]->failed || test_case->tests[i]->fatal ) failures++; } node = xmlNewChild(parent, NULL, BAD_CAST "testsuite", NULL); xmlSetProp(node, BAD_CAST "name", BAD_CAST test_case->name); set_attribute(node, "tests", test_case->test_count); set_attribute(node, "failures", failures); set_attribute(node, "disabled", disabled); set_attribute(node, "skipped", skipped); if (time_str) { xmlSetProp(node, BAD_CAST "time", BAD_CAST time_str); free(time_str); time_str = NULL; } for (i = 0; i < test_case->test_count; ++i) emit_test(node, test_case->tests[i]); } /** * Formats a time in milliseconds to the full ISO-8601 date/time string * format, or NULL if there is a problem. * The caller should release this with free() * * @return the formatted time string upon success, NULL otherwise. */ static char * as_iso_8601(time_t const *t) { char *result = NULL; char buf[32] = {}; struct tm when; if (gmtime_r(t, &when) != NULL) if (strftime(buf, sizeof(buf), ISO_8601_FORMAT, &when)) result = strdup(buf); return result; } static void run_started(void *data, int live_case_count, int live_test_count, int disabled_count) { struct junit_data *jdata = data; jdata->begin = time(NULL); jdata->fd = open(XML_FNAME, O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH); } static void run_ended(void *data, int case_count, struct zuc_case **cases, int live_case_count, int live_test_count, int total_passed, int total_failed, int total_disabled, long total_elapsed) { int i; long time = 0; char *time_str = NULL; char *timestamp = NULL; xmlNodePtr root = NULL; xmlDocPtr doc = NULL; xmlChar *xmlchars = NULL; int xmlsize = 0; struct junit_data *jdata = data; for (i = 0; i < case_count; ++i) time += cases[i]->elapsed; time_str = as_duration(time); timestamp = as_iso_8601(&jdata->begin); /* here would be where to add errors? */ doc = xmlNewDoc(BAD_CAST "1.0"); root = xmlNewNode(NULL, BAD_CAST "testsuites"); xmlDocSetRootElement(doc, root); set_attribute(root, "tests", live_test_count); set_attribute(root, "failures", total_failed); set_attribute(root, "disabled", total_disabled); if (timestamp) { xmlSetProp(root, BAD_CAST "timestamp", BAD_CAST timestamp); free(timestamp); timestamp = NULL; } if (time_str) { xmlSetProp(root, BAD_CAST "time", BAD_CAST time_str); free(time_str); time_str = NULL; } xmlSetProp(root, BAD_CAST "name", BAD_CAST "AllTests"); for (i = 0; i < case_count; ++i) { emit_case(root, cases[i]); } xmlDocDumpFormatMemoryEnc(doc, &xmlchars, &xmlsize, "UTF-8", 1); dprintf(jdata->fd, "%s", (char *) xmlchars); xmlFree(xmlchars); xmlchars = NULL; xmlFreeDoc(doc); if ((jdata->fd != fileno(stdout)) && (jdata->fd != fileno(stderr)) && (jdata->fd != -1)) { close(jdata->fd); jdata->fd = -1; } } static void destroy(void *data) { xmlCleanupParser(); free(data); } struct zuc_event_listener * zuc_junit_reporter_create(void) { struct zuc_event_listener *listener = zalloc(sizeof(struct zuc_event_listener)); struct junit_data *data = zalloc(sizeof(struct junit_data)); data->fd = -1; listener->data = data; listener->destroy = destroy; listener->run_started = run_started; listener->run_ended = run_ended; return listener; } #else /* ENABLE_JUNIT_XML */ #include "zuc_event_listener.h" /* * Simple stub version if junit output (including libxml2 support) has * been disabled. * Will return NULL to cause failures as calling this when the #define * has not been enabled is an invalid scenario. */ struct zuc_event_listener * zuc_junit_reporter_create(void) { return NULL; } #endif /* ENABLE_JUNIT_XML */