[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