| /* |
| * 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 <fcntl.h> |
| #include <inttypes.h> |
| #include <libxml/parser.h> |
| #include <memory.h> |
| #include <stdio.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <time.h> |
| #include <unistd.h> |
| |
| #include "zuc_event_listener.h" |
| #include "zuc_types.h" |
| |
| #include "shared/zalloc.h" |
| |
| /** |
| * 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 "shared/zalloc.h" |
| #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 */ |