[PATCH v4 4/6] Enables output in the JUnit XML format.

Jon A. Cruz jonc at osg.samsung.com
Thu Jun 11 22:01:30 PDT 2015


Adds basic support fo optionally outputting in the XML format
commonly used by JUnit compatible tools.

This format is supported by default by many tools, including
the Jenkins build system. It also is more detailed and
captures more information than the more simplistic TAP
format.

Signed-off-by: Jon A. Cruz <jonc at osg.samsung.com>
---
 Makefile.am                           |   2 +
 tools/zunitc/inc/zunitc/zunitc.h      |   8 +
 tools/zunitc/src/zuc_junit_reporter.c | 473 ++++++++++++++++++++++++++++++++++
 tools/zunitc/src/zuc_junit_reporter.h |  35 +++
 tools/zunitc/src/zunitc_impl.c        |  12 +
 5 files changed, 530 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 1333b8b..9bd4b29 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -962,6 +962,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 18f75c2..b015bc3 100644
--- a/tools/zunitc/inc/zunitc/zunitc.h
+++ b/tools/zunitc/inc/zunitc/zunitc.h
@@ -241,6 +241,14 @@ 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..c417cf2
--- /dev/null
+++ b/tools/zunitc/src/zuc_junit_reporter.c
@@ -0,0 +1,473 @@
+/*
+ * 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_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: %ld\n"
+				     "Expected: %s\n"
+				     "Which is: %ld\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: %ld vs %ld\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: %ld vs %ld\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..639ed7d
--- /dev/null
+++ b/tools/zunitc/src/zuc_junit_reporter.h
@@ -0,0 +1,35 @@
+/*
+ * 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 94c572a..d02f0e4 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"
@@ -295,6 +296,12 @@ zuc_set_output_tap(bool enable)
 	g_ctx.output_tap = enable;
 }
 
+void
+zuc_set_output_junit(bool enable)
+{
+	g_ctx.output_junit = enable;
+}
+
 const char *
 zuc_get_program_name(void)
 {
@@ -318,6 +325,7 @@ 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;
@@ -331,6 +339,7 @@ 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 },
 	};
 
@@ -437,6 +446,7 @@ 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;
 	}
 
@@ -971,6 +981,8 @@ 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