[PATCH weston v3 4/5] Enables output in the JUnit XML format.
Jon A. Cruz
jonc at osg.samsung.com
Tue May 26 16:06:40 PDT 2015
Adds basic support fo optionally outputting in the XML format
commonly used by JUnit compatible tools.
Signed-off-by: Jon A. Cruz <jonc at osg.samsung.com>
---
Makefile.am | 2 +
tools/zunitc/inc/zunitc/zunitc.h | 7 +
tools/zunitc/src/zuc_junit_reporter.c | 414 ++++++++++++++++++++++++++++++++++
tools/zunitc/src/zuc_junit_reporter.h | 34 +++
tools/zunitc/src/zunitc_impl.c | 12 +
5 files changed, 469 insertions(+)
create mode 100644 tools/zunitc/src/zuc_junit_reporter.c
create mode 100644 tools/zunitc/src/zuc_junit_reporter.h
diff --git a/Makefile.am b/Makefile.am
index 7686a6d..b1b7c20 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -960,6 +960,8 @@ libzunitc_la_SOURCES = \
tools/zunitc/src/zuc_context.h \
tools/zunitc/src/zuc_event.h \
tools/zunitc/src/zuc_event_listener.h \
+ tools/zunitc/src/zuc_junit_reporter.c \
+ tools/zunitc/src/zuc_junit_reporter.h \
tools/zunitc/src/zuc_tap_logger.c \
tools/zunitc/src/zuc_tap_logger.h \
tools/zunitc/src/zuc_types.h \
diff --git a/tools/zunitc/inc/zunitc/zunitc.h b/tools/zunitc/inc/zunitc/zunitc.h
index 53724d2..2294966 100644
--- a/tools/zunitc/inc/zunitc/zunitc.h
+++ b/tools/zunitc/inc/zunitc/zunitc.h
@@ -198,6 +198,13 @@ void zuc_set_spawn(bool spawn);
void zuc_set_output_tap(bool enable);
/**
+ * Enables output in the JUnit XML format.
+ *
+ * @param enable true to generate JUnit XML output, false to disable.
+ */
+void zuc_set_output_junit(bool enable);
+
+/**
* Defines a test case that can be registered to run.
*/
#define ZUC_TEST(tcase, test) \
diff --git a/tools/zunitc/src/zuc_junit_reporter.c b/tools/zunitc/src/zuc_junit_reporter.c
new file mode 100644
index 0000000..537b1d7
--- /dev/null
+++ b/tools/zunitc/src/zuc_junit_reporter.c
@@ -0,0 +1,414 @@
+/*
+ * Copyright © 2015 Samsung Electronics Co., Ltd
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and
+ * its documentation for any purpose is hereby granted without fee, provided
+ * that the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of the copyright holders not be used in
+ * advertising or publicity pertaining to distribution of the software
+ * without specific, written prior permission. The copyright holders make
+ * no representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+ * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "config.h"
+
+#include "zuc_junit_reporter.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <memory.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <time.h>
+
+#include "zuc_event_listener.h"
+#include "zuc_types.h"
+
+#include "shared/zalloc.h"
+
+#define XML_FNAME "test_detail.xml"
+
+#define ISO_8601_FORMAT "%Y-%m-%dT%H:%M:%SZ"
+
+/**
+ * Internal data.
+ */
+struct junit_data
+{
+ int fd;
+ time_t begin;
+};
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+/**
+ * Output the given string in escaped XML form.
+ *
+ * @param fd the file descriptor to write to.
+ * @param str the string to write an escaped form of.
+ */
+static void emit_escaped(int fd, const char *str);
+
+/**
+ * Output the given event.
+ *
+ * @param fd the file descriptor to write to.
+ * @param event the event to write out.
+ */
+static void emit_event(int fd, struct zuc_event *event);
+
+/**
+ * Output the given test.
+ *
+ * @param fd the file descriptor to write to.
+ * @param test the test to write out.
+ */
+static void emit_test(int fd, struct zuc_test *test);
+
+/**
+ * Output the given test case.
+ *
+ * @param fd the file descriptor to write to.
+ * @param test_case the test case to write out.
+ */
+static void emit_case(int fd, struct zuc_case *test_case);
+
+static void destroy(void *data);
+static void run_started(void *data,
+ int live_case_count,
+ int live_test_count,
+ int disabled_count);
+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);
+
+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;
+}
+
+void destroy(void *data)
+{
+ free(data);
+}
+
+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);
+}
+
+char const *get_test_status(struct zuc_test *test)
+{
+ if (test->disabled || test->skipped)
+ return "notrun";
+ else
+ return "run";
+}
+
+void emit_escaped(int fd, const char *str)
+{
+ const char *ptr = str;
+ while (*ptr) {
+ switch (*ptr) {
+ case '\'':
+ dprintf(fd, "'");
+ break;
+ case '\"':
+ dprintf(fd, """);
+ break;
+ case '<':
+ dprintf(fd, "<");
+ break;
+ case '>':
+ dprintf(fd, ">");
+ break;
+ case '&':
+ dprintf(fd, "&");
+ break;
+ default:
+ if ((*ptr >= ' ') && (*ptr < 0x7f)) {
+ dprintf(fd, "%c", *ptr);
+ } else {
+ dprintf(fd, "&#x%02X;", 0x0ff & *ptr);
+ }
+ }
+ ptr++;
+ }
+}
+
+void emit_event(int fd, 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_EQ:
+ if (asprintf(&msg, "%s:%d: error: Value of: %s\n"
+ " Actual: %d\n"
+ "Expected: %s\n"
+ "Which is: %d\n",
+ event->file, event->line, event->expr2,
+ event->val2, event->expr1, event->val1) < 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: %d vs %d\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)) {
+ dprintf(fd, " <skipped/>\n");
+ } else {
+ dprintf(fd, " <failure");
+ if (msg) {
+ dprintf(fd, " message=\"");
+ emit_escaped(fd, msg);
+ dprintf(fd, "\"");
+ }
+ dprintf(fd, " type=\"\"");
+ if (msg) {
+ dprintf(fd, " >");
+ dprintf(fd, "<![CDATA[%s]]>", msg);
+ dprintf(fd, "</failure>\n");
+ } else {
+ dprintf(fd, " />");
+ }
+ }
+
+ free(msg);
+}
+
+void emit_test(int fd, struct zuc_test *test)
+{
+ char *time_str = as_duration(test->elapsed);
+ dprintf(fd, " <testcase name=\"%s\" status=\"%s\"",
+ test->name, get_test_status(test));
+ if (time_str) {
+ dprintf(fd, " time=\"%s\"", time_str);
+ free(time_str);
+ time_str = NULL;
+ }
+ dprintf(fd, " classname=\"%s\"", test->test_case->name);
+ if ((test->failed || test->fatal || test->skipped) && test->events) {
+ struct zuc_event *evt;
+ dprintf(fd, ">\n");
+
+ for (evt = test->events; evt; evt = evt->next)
+ emit_event(fd, evt);
+
+ dprintf(fd, " </testcase>\n");
+ } else {
+ dprintf(fd, " />\n");
+ }
+}
+
+void emit_case(int fd, struct zuc_case *test_case)
+{
+ int i;
+ int skipped = 0;
+ int disabled = 0;
+ int failures = 0;
+ 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++;
+ }
+
+ dprintf(fd,
+ " <testsuite name=\"%s\" tests=\"%d\" failures=\"%d\""
+ " disabled=\"%d\" skipped=\"%d\"",
+ test_case->name, test_case->test_count, failures, disabled,
+ skipped);
+ if (time_str) {
+ dprintf(fd, " time=\"%s\"", time_str);
+ free(time_str);
+ time_str = NULL;
+ }
+ dprintf(fd, ">\n");
+
+ for (i = 0; i < test_case->test_count; ++i)
+ emit_test(fd, test_case->tests[i]);
+
+
+ dprintf(fd, " </testsuite>\n");
+}
+
+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;
+ 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);
+
+ dprintf(jdata->fd, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+ /* here would be where to add errors? */
+ dprintf(jdata->fd,
+ "<testsuites tests=\"%d\" failures=\"%d\" disabled=\"%d\"",
+ live_test_count, total_failed, total_disabled);
+ if (timestamp) {
+ dprintf(jdata->fd, " timestamp=\"%s\"", timestamp);
+ free(timestamp);
+ timestamp = NULL;
+ }
+ if (time_str) {
+ dprintf(jdata->fd, " time=\"%s\"", time_str);
+ free(time_str);
+ time_str = NULL;
+ }
+ dprintf(jdata->fd, " name=\"AllTests\">\n");
+
+ for (i = 0; i < case_count; ++i)
+ emit_case(jdata->fd, cases[i]);
+
+ dprintf(jdata->fd, "</testsuites>\n");
+
+ if ((jdata->fd != fileno(stdout))
+ && (jdata->fd != fileno(stderr))
+ && (jdata->fd != -1)) {
+ close(jdata->fd);
+ jdata->fd = -1;
+ }
+}
+
+char *as_duration(long ms)
+{
+ char *str = NULL;
+ if (asprintf(&str, "%1.3f", ms / 1000.0) < 0) {
+ str = NULL;
+ } else {
+ if (!strcmp("0.000", str)) {
+ free(str);
+ str = strdup("0");
+ }
+ }
+ return str;
+}
+
+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;
+}
diff --git a/tools/zunitc/src/zuc_junit_reporter.h b/tools/zunitc/src/zuc_junit_reporter.h
new file mode 100644
index 0000000..13f0409
--- /dev/null
+++ b/tools/zunitc/src/zuc_junit_reporter.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright © 2015 Samsung Electronics Co., Ltd
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and
+ * its documentation for any purpose is hereby granted without fee, provided
+ * that the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation, and that the name of the copyright holders not be used in
+ * advertising or publicity pertaining to distribution of the software
+ * without specific, written prior permission. The copyright holders make
+ * no representations about the suitability of this software for any
+ * purpose. It is provided "as is" without express or implied warranty.
+ *
+ * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
+ * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+ * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
+ * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef ZUC_JUNIT_REPORTER_H
+#define ZUC_JUNIT_REPORTER_H
+
+struct zuc_event_listener;
+
+/**
+ * Creates an instance of a reporter that will write data in the JUnit
+ * XML format.
+ */
+struct zuc_event_listener * zuc_junit_reporter_create(void);
+
+#endif /* ZUC_JUNIT_REPORTER_H */
diff --git a/tools/zunitc/src/zunitc_impl.c b/tools/zunitc/src/zunitc_impl.c
index 758987f..1fcb954 100644
--- a/tools/zunitc/src/zunitc_impl.c
+++ b/tools/zunitc/src/zunitc_impl.c
@@ -43,6 +43,7 @@
#include "zuc_collector.h"
#include "zuc_context.h"
#include "zuc_event_listener.h"
+#include "zuc_junit_reporter.h"
#include "zuc_tap_logger.h"
#include "shared/config-parser.h"
@@ -248,6 +249,11 @@ void zuc_set_output_tap(bool enable)
g_ctx.output_tap = enable;
}
+void zuc_set_output_junit(bool enable)
+{
+ g_ctx.output_junit = enable;
+}
+
int zuc_initialize(int *argc, char *argv[], bool *help_flagged)
{
int rc = EXIT_FAILURE;
@@ -258,6 +264,7 @@ int zuc_initialize(int *argc, char *argv[], bool *help_flagged)
int opt_random = 0;
int opt_break_on_failure = 0;
int opt_tap = 0;
+ int opt_junit = 0;
char *opt_filter = NULL;
char *help_param = NULL;
@@ -271,6 +278,7 @@ int zuc_initialize(int *argc, char *argv[], bool *help_flagged)
{ WESTON_OPTION_BOOLEAN, "zuc-break-on-failure", 0,
&opt_break_on_failure },
{ WESTON_OPTION_BOOLEAN, "zuc-output-tap", 0, &opt_tap },
+ { WESTON_OPTION_BOOLEAN, "zuc-output-xml", 0, &opt_junit },
{ WESTON_OPTION_STRING, "zuc-filter", 0, &opt_filter },
};
@@ -330,6 +338,7 @@ int zuc_initialize(int *argc, char *argv[], bool *help_flagged)
" --zuc-list-tests\n"
" --zuc-nofork\n"
" --zuc-output-tap\n"
+ " --zuc-output-xml\n"
" --zuc-random=N [0|1|<seed number>]\n"
" --zuc-repeat=N\n"
" --help\n",
@@ -346,6 +355,7 @@ int zuc_initialize(int *argc, char *argv[], bool *help_flagged)
zuc_set_spawn(!opt_nofork);
zuc_set_break_on_failure(opt_break_on_failure);
zuc_set_output_tap(opt_tap);
+ zuc_set_output_junit(opt_junit);
rc = EXIT_SUCCESS;
}
@@ -850,6 +860,8 @@ int zucimpl_run_tests(void)
zuc_add_event_listener(zuc_base_logger_create());
if (g_ctx.output_tap)
zuc_add_event_listener(zuc_tap_logger_create());
+ if (g_ctx.output_junit)
+ zuc_add_event_listener(zuc_junit_reporter_create());
}
if (g_ctx.case_count < 1) {
--
2.1.0
More information about the wayland-devel
mailing list