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

Jon A. Cruz jonc at osg.samsung.com
Sat Jun 20 15:47:46 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                           |  10 +
 configure.ac                          |   6 +
 tools/zunitc/inc/zunitc/zunitc.h      |  10 +
 tools/zunitc/src/zuc_junit_reporter.c | 464 ++++++++++++++++++++++++++++++++++
 tools/zunitc/src/zuc_junit_reporter.h |  38 +++
 tools/zunitc/src/zunitc_impl.c        |  25 ++
 6 files changed, 553 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 4b6bb97..f0ed4d2 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1031,6 +1031,16 @@ libzunitc_la_CFLAGS = \
 libzunitc_la_LIBADD = \
 	libshared.la
 
+if HAVE_LIBXML2
+libzunitc_la_SOURCES += \
+	tools/zunitc/src/zuc_junit_reporter.c	\
+	tools/zunitc/src/zuc_junit_reporter.h
+libzunitc_la_CFLAGS += \
+	$(LIBXML2_CFLAGS)
+libzunitc_la_LIBADD += \
+	$(LIBXML2_LIBS)
+endif
+
 libzunitcmain_la_SOURCES = \
 	tools/zunitc/src/main.c
 
diff --git a/configure.ac b/configure.ac
index e047fd5..4a89819 100644
--- a/configure.ac
+++ b/configure.ac
@@ -264,6 +264,11 @@ if test "x$cairo_modules" = "xcairo-glesv2"; then
 AC_DEFINE([USE_CAIRO_GLESV2], [1], [Use the GLESv2 GL cairo backend])
 fi
 
+PKG_CHECK_MODULES(LIBXML2, [libxml-2.0 >= 2.6],
+			   [AC_DEFINE(HAVE_LIBXML2, 1, [Define if libxml2 is available]) have_libxml2=yes],
+			   have_libxml2=no)
+AM_CONDITIONAL(HAVE_LIBXML2, [test "x$have_libxml2" = xyes])
+
 PKG_CHECK_MODULES(PIXMAN, [pixman-1])
 PKG_CHECK_MODULES(PNG, [libpng])
 PKG_CHECK_MODULES(WEBP, [libwebp], [have_webp=yes], [have_webp=no])
@@ -557,5 +562,6 @@ AC_MSG_RESULT([
 	LCMS2 Support			${have_lcms}
 	libwebp Support			${have_webp}
 	libunwind Support		${have_libunwind}
+	libxml2 Support			${have_libxml2}
 	VA H.264 encoding Support	${have_libva}
 ])
diff --git a/tools/zunitc/inc/zunitc/zunitc.h b/tools/zunitc/inc/zunitc/zunitc.h
index 559cdd0..bf0077a 100644
--- a/tools/zunitc/inc/zunitc/zunitc.h
+++ b/tools/zunitc/inc/zunitc/zunitc.h
@@ -243,6 +243,16 @@ zuc_set_spawn(bool spawn);
 void
 zuc_set_output_tap(bool enable);
 
+#if HAVE_LIBXML2
+/**
+ * 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);
+#endif
+
 /**
  * Defines a test case that can be registered to run.
  */
diff --git a/tools/zunitc/src/zuc_junit_reporter.c b/tools/zunitc/src/zuc_junit_reporter.c
new file mode 100644
index 0000000..a673c83
--- /dev/null
+++ b/tools/zunitc/src/zuc_junit_reporter.c
@@ -0,0 +1,464 @@
+/*
+ * 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"
+
+#include <fcntl.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"
+
+#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 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);
+
+/**
+ * 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);
+
+/**
+ * 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);
+
+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)
+{
+	xmlCleanupParser();
+
+	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";
+}
+
+#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), BAD_CAST "%d", value);
+	xmlSetProp(node, BAD_CAST name, scratch);
+}
+
+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: %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)) {
+		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);
+}
+
+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);
+	}
+}
+
+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]);
+}
+
+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;
+	}
+}
+
+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..66f3c7b
--- /dev/null
+++ b/tools/zunitc/src/zuc_junit_reporter.h
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+#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 7e34aca..3c0b2d7 100644
--- a/tools/zunitc/src/zunitc_impl.c
+++ b/tools/zunitc/src/zunitc_impl.c
@@ -46,6 +46,9 @@
 #include "zuc_collector.h"
 #include "zuc_context.h"
 #include "zuc_event_listener.h"
+#if HAVE_LIBXML2
+#include "zuc_junit_reporter.h"
+#endif
 #include "zuc_tap_logger.h"
 
 #include "shared/config-parser.h"
@@ -297,6 +300,14 @@ zuc_set_output_tap(bool enable)
 	g_ctx.output_tap = enable;
 }
 
+#if HAVE_LIBXML2
+void
+zuc_set_output_junit(bool enable)
+{
+	g_ctx.output_junit = enable;
+}
+#endif
+
 const char *
 zuc_get_program_name(void)
 {
@@ -320,6 +331,9 @@ zuc_initialize(int *argc, char *argv[], bool *help_flagged)
 	int opt_random = 0;
 	int opt_break_on_failure = 0;
 	int opt_tap = 0;
+#if HAVE_LIBXML2
+	int opt_junit = 0;
+#endif
 	char *opt_filter = NULL;
 
 	char *help_param = NULL;
@@ -333,6 +347,9 @@ 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 },
+#if HAVE_LIBXML2
+		{ WESTON_OPTION_BOOLEAN, "zuc-output-xml", 0, &opt_junit },
+#endif
 		{ WESTON_OPTION_STRING, "zuc-filter", 0, &opt_filter },
 	};
 
@@ -422,6 +439,7 @@ 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",
@@ -438,6 +456,9 @@ 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);
+#if HAVE_LIBXML2
+		zuc_set_output_junit(opt_junit);
+#endif
 		rc = EXIT_SUCCESS;
 	}
 
@@ -972,6 +993,10 @@ 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 HAVE_LIBXML2
+		if (g_ctx.output_junit)
+			zuc_add_event_listener(zuc_junit_reporter_create());
+#endif
 	}
 
 	if (g_ctx.case_count < 1) {
-- 
2.1.0



More information about the wayland-devel mailing list