• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright © 2015 Samsung Electronics Co., Ltd
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining
5  * a copy of this software and associated documentation files (the
6  * "Software"), to deal in the Software without restriction, including
7  * without limitation the rights to use, copy, modify, merge, publish,
8  * distribute, sublicense, and/or sell copies of the Software, and to
9  * permit persons to whom the Software is furnished to do so, subject to
10  * the following conditions:
11  *
12  * The above copyright notice and this permission notice (including the
13  * next paragraph) shall be included in all copies or substantial
14  * portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19  * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
20  * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
21  * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22  * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23  * SOFTWARE.
24  */
25 
26 #include "config.h"
27 
28 #include "zuc_junit_reporter.h"
29 
30 #if ENABLE_JUNIT_XML
31 
32 #include <fcntl.h>
33 #include <inttypes.h>
34 #include <libxml/parser.h>
35 #include <memory.h>
36 #include <stdio.h>
37 #include <sys/stat.h>
38 #include <sys/types.h>
39 #include <time.h>
40 #include <unistd.h>
41 
42 #include "zuc_event_listener.h"
43 #include "zuc_types.h"
44 
45 #include <libweston/zalloc.h>
46 
47 /**
48  * Hardcoded output name.
49  * @todo follow-up with refactoring to avoid filename hardcoding.
50  * Will allow for better testing in parallel etc. in general.
51  */
52 #define XML_FNAME "test_detail.xml"
53 
54 #define ISO_8601_FORMAT "%Y-%m-%dT%H:%M:%SZ"
55 
56 #if LIBXML_VERSION >= 20904
57 #define STRPRINTF_CAST
58 #else
59 #define STRPRINTF_CAST BAD_CAST
60 #endif
61 
62 /**
63  * Internal data.
64  */
65 struct junit_data
66 {
67 	int fd;
68 	time_t begin;
69 };
70 
71 #define MAX_64BIT_STRLEN 20
72 
73 static void
set_attribute(xmlNodePtr node,const char * name,int value)74 set_attribute(xmlNodePtr node, const char *name, int value)
75 {
76 	xmlChar scratch[MAX_64BIT_STRLEN + 1] = {};
77 	xmlStrPrintf(scratch, sizeof(scratch), STRPRINTF_CAST "%d", value);
78 	xmlSetProp(node, BAD_CAST name, scratch);
79 }
80 
81 /**
82  * Output the given event.
83  *
84  * @param parent the parent node to add new content to.
85  * @param event the event to write out.
86  */
87 static void
emit_event(xmlNodePtr parent,struct zuc_event * event)88 emit_event(xmlNodePtr parent, struct zuc_event *event)
89 {
90 	char *msg = NULL;
91 
92 	switch (event->op) {
93 	case ZUC_OP_TRUE:
94 		if (asprintf(&msg, "%s:%d: error: Value of: %s\n"
95 			     "  Actual: false\n"
96 			     "Expected: true\n", event->file, event->line,
97 			     event->expr1) < 0) {
98 			msg = NULL;
99 		}
100 		break;
101 	case ZUC_OP_FALSE:
102 		if (asprintf(&msg, "%s:%d: error: Value of: %s\n"
103 			     "  Actual: true\n"
104 			     "Expected: false\n", event->file, event->line,
105 			     event->expr1) < 0) {
106 			msg = NULL;
107 		}
108 		break;
109 	case ZUC_OP_NULL:
110 		if (asprintf(&msg, "%s:%d: error: Value of: %s\n"
111 			     "  Actual: %p\n"
112 			     "Expected: %p\n", event->file, event->line,
113 			     event->expr1, (void *)event->val1, NULL) < 0) {
114 			msg = NULL;
115 		}
116 		break;
117 	case ZUC_OP_NOT_NULL:
118 		if (asprintf(&msg, "%s:%d: error: Value of: %s\n"
119 			     "  Actual: %p\n"
120 			     "Expected: not %p\n", event->file, event->line,
121 			     event->expr1, (void *)event->val1, NULL) < 0) {
122 			msg = NULL;
123 		}
124 		break;
125 	case ZUC_OP_EQ:
126 		if (event->valtype == ZUC_VAL_CSTR) {
127 			if (asprintf(&msg, "%s:%d: error: Value of: %s\n"
128 				     "  Actual: %s\n"
129 				     "Expected: %s\n"
130 				     "Which is: %s\n",
131 				     event->file, event->line, event->expr2,
132 				     (char *)event->val2, event->expr1,
133 				     (char *)event->val1) < 0) {
134 				msg = NULL;
135 			}
136 		} else {
137 			if (asprintf(&msg, "%s:%d: error: Value of: %s\n"
138 			             "  Actual: %"PRIdPTR"\n"
139 			             "Expected: %s\n"
140 			             "Which is: %"PRIdPTR"\n",
141 			             event->file, event->line, event->expr2,
142 			             event->val2, event->expr1,
143 			             event->val1) < 0) {
144 				msg = NULL;
145 			}
146 		}
147 		break;
148 	case ZUC_OP_NE:
149 		if (event->valtype == ZUC_VAL_CSTR) {
150 			if (asprintf(&msg, "%s:%d: error: "
151 				     "Expected: (%s) %s (%s),"
152 				     " actual: %s == %s\n",
153 				     event->file, event->line,
154 				     event->expr1, zuc_get_opstr(event->op),
155 				     event->expr2, (char *)event->val1,
156 				     (char *)event->val2) < 0) {
157 				msg = NULL;
158 			}
159 		} else {
160 			if (asprintf(&msg, "%s:%d: error: "
161 			             "Expected: (%s) %s (%s),"
162 			             " actual: %"PRIdPTR" vs %"PRIdPTR"\n",
163 			             event->file, event->line,
164 			             event->expr1, zuc_get_opstr(event->op),
165 			             event->expr2, event->val1,
166 			             event->val2) < 0) {
167 				msg = NULL;
168 			}
169 		}
170 		break;
171 	case ZUC_OP_TERMINATE:
172 	{
173 		char const *level = (event->val1 == 0) ? "error"
174 			: (event->val1 == 1) ? "warning"
175 			: "note";
176 		if (asprintf(&msg, "%s:%d: %s: %s\n",
177 			     event->file, event->line, level,
178 			     event->expr1) < 0) {
179 			msg = NULL;
180 		}
181 		break;
182 	}
183 	case ZUC_OP_TRACEPOINT:
184 		if (asprintf(&msg, "%s:%d: note: %s\n",
185 			     event->file, event->line, event->expr1) < 0) {
186 			msg = NULL;
187 		}
188 		break;
189 	default:
190 		if (asprintf(&msg, "%s:%d: error: "
191 		             "Expected: (%s) %s (%s), actual: %"PRIdPTR" vs "
192 		             "%"PRIdPTR"\n",
193 		             event->file, event->line,
194 		             event->expr1, zuc_get_opstr(event->op),
195 		             event->expr2, event->val1, event->val2) < 0) {
196 			msg = NULL;
197 		}
198 	}
199 
200 	if ((event->op == ZUC_OP_TERMINATE) && (event->val1 > 1)) {
201 		xmlNewChild(parent, NULL, BAD_CAST "skipped", NULL);
202 	} else {
203 		xmlNodePtr node = xmlNewChild(parent, NULL,
204 					      BAD_CAST "failure", NULL);
205 
206 		if (msg) {
207 			xmlSetProp(node, BAD_CAST "message", BAD_CAST msg);
208 		}
209 		xmlSetProp(node, BAD_CAST "type", BAD_CAST "");
210 		if (msg) {
211 			xmlNodePtr cdata = xmlNewCDataBlock(node->doc,
212 							    BAD_CAST msg,
213 							    strlen(msg));
214 			xmlAddChild(node, cdata);
215 		}
216 	}
217 
218 	free(msg);
219 }
220 
221 /**
222  * Formats a time in milliseconds to the normal JUnit elapsed form, or
223  * NULL if there is a problem.
224  * The caller should release this with free()
225  *
226  * @return the formatted time string upon success, NULL otherwise.
227  */
228 static char *
as_duration(long ms)229 as_duration(long ms)
230 {
231 	char *str = NULL;
232 
233 	if (asprintf(&str, "%1.3f", ms / 1000.0) < 0) {
234 		str = NULL;
235 	} else {
236 		/*
237 		 * Special case to match behavior of standard JUnit output
238 		 * writers. Assumption is certain readers might have
239 		 * limitations, etc. so it is best to keep 100% identical
240 		 * output.
241 		 */
242 		if (!strcmp("0.000", str)) {
243 			free(str);
244 			str = strdup("0");
245 		}
246 	}
247 	return str;
248 }
249 
250 /**
251  * Returns the status string for the tests (run/notrun).
252  *
253  * @param test the test to check status of.
254  * @return the status string.
255  */
256 static char const *
get_test_status(struct zuc_test * test)257 get_test_status(struct zuc_test *test)
258 {
259 	if (test->disabled || test->skipped)
260 		return "notrun";
261 	else
262 		return "run";
263 }
264 
265 /**
266  * Output the given test.
267  *
268  * @param parent the parent node to add new content to.
269  * @param test the test to write out.
270  */
271 static void
emit_test(xmlNodePtr parent,struct zuc_test * test)272 emit_test(xmlNodePtr parent, struct zuc_test *test)
273 {
274 	char *time_str = as_duration(test->elapsed);
275 	xmlNodePtr node = xmlNewChild(parent, NULL, BAD_CAST "testcase", NULL);
276 
277 	xmlSetProp(node, BAD_CAST "name", BAD_CAST test->name);
278 	xmlSetProp(node, BAD_CAST "status", BAD_CAST get_test_status(test));
279 
280 	if (time_str) {
281 		xmlSetProp(node, BAD_CAST "time", BAD_CAST time_str);
282 
283 		free(time_str);
284 		time_str = NULL;
285 	}
286 
287 	xmlSetProp(node, BAD_CAST "classname", BAD_CAST test->test_case->name);
288 
289 	if ((test->failed || test->fatal || test->skipped) && test->events) {
290 		struct zuc_event *evt;
291 		for (evt = test->events; evt; evt = evt->next)
292 			emit_event(node, evt);
293 	}
294 }
295 
296 /**
297  * Output the given test case.
298  *
299  * @param parent the parent node to add new content to.
300  * @param test_case the test case to write out.
301  */
302 static void
emit_case(xmlNodePtr parent,struct zuc_case * test_case)303 emit_case(xmlNodePtr parent, struct zuc_case *test_case)
304 {
305 	int i;
306 	int skipped = 0;
307 	int disabled = 0;
308 	int failures = 0;
309 	xmlNodePtr node = NULL;
310 	char *time_str = as_duration(test_case->elapsed);
311 
312 	for (i = 0; i < test_case->test_count; ++i) {
313 		if (test_case->tests[i]->disabled )
314 			disabled++;
315 		if (test_case->tests[i]->skipped )
316 			skipped++;
317 		if (test_case->tests[i]->failed
318 		    || test_case->tests[i]->fatal )
319 			failures++;
320 	}
321 
322 	node = xmlNewChild(parent, NULL, BAD_CAST "testsuite", NULL);
323 	xmlSetProp(node, BAD_CAST "name", BAD_CAST test_case->name);
324 
325 	set_attribute(node, "tests", test_case->test_count);
326 	set_attribute(node, "failures", failures);
327 	set_attribute(node, "disabled", disabled);
328 	set_attribute(node, "skipped", skipped);
329 
330 	if (time_str) {
331 		xmlSetProp(node, BAD_CAST "time", BAD_CAST time_str);
332 		free(time_str);
333 		time_str = NULL;
334 	}
335 
336 	for (i = 0; i < test_case->test_count; ++i)
337 		emit_test(node, test_case->tests[i]);
338 }
339 
340 /**
341  * Formats a time in milliseconds to the full ISO-8601 date/time string
342  * format, or NULL if there is a problem.
343  * The caller should release this with free()
344  *
345  * @return the formatted time string upon success, NULL otherwise.
346  */
347 static char *
as_iso_8601(time_t const * t)348 as_iso_8601(time_t const *t)
349 {
350 	char *result = NULL;
351 	char buf[32] = {};
352 	struct tm when;
353 
354 	if (gmtime_r(t, &when) != NULL)
355 		if (strftime(buf, sizeof(buf), ISO_8601_FORMAT, &when))
356 			result = strdup(buf);
357 
358 	return result;
359 }
360 
361 
362 static void
run_started(void * data,int live_case_count,int live_test_count,int disabled_count)363 run_started(void *data, int live_case_count, int live_test_count,
364 	    int disabled_count)
365 {
366 	struct junit_data *jdata = data;
367 
368 	jdata->begin = time(NULL);
369 	jdata->fd = open(XML_FNAME, O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC,
370 			 S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH);
371 }
372 
373 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)374 run_ended(void *data, int case_count, struct zuc_case **cases,
375 	  int live_case_count, int live_test_count, int total_passed,
376 	  int total_failed, int total_disabled, long total_elapsed)
377 {
378 	int i;
379 	long time = 0;
380 	char *time_str = NULL;
381 	char *timestamp = NULL;
382 	xmlNodePtr root = NULL;
383 	xmlDocPtr doc = NULL;
384 	xmlChar *xmlchars = NULL;
385 	int xmlsize = 0;
386 	struct junit_data *jdata = data;
387 
388 	for (i = 0; i < case_count; ++i)
389 		time += cases[i]->elapsed;
390 
391 	time_str = as_duration(time);
392 	timestamp = as_iso_8601(&jdata->begin);
393 
394 	/* here would be where to add errors? */
395 
396 	doc = xmlNewDoc(BAD_CAST "1.0");
397 	root = xmlNewNode(NULL, BAD_CAST "testsuites");
398 	xmlDocSetRootElement(doc, root);
399 
400 	set_attribute(root, "tests", live_test_count);
401 	set_attribute(root, "failures", total_failed);
402 	set_attribute(root, "disabled", total_disabled);
403 
404 	if (timestamp) {
405 		xmlSetProp(root, BAD_CAST "timestamp", BAD_CAST timestamp);
406 		free(timestamp);
407 		timestamp = NULL;
408 	}
409 
410 	if (time_str) {
411 		xmlSetProp(root, BAD_CAST "time", BAD_CAST time_str);
412 		free(time_str);
413 		time_str = NULL;
414 	}
415 
416 	xmlSetProp(root, BAD_CAST "name", BAD_CAST "AllTests");
417 
418 	for (i = 0; i < case_count; ++i) {
419 		emit_case(root, cases[i]);
420 	}
421 
422 	xmlDocDumpFormatMemoryEnc(doc, &xmlchars, &xmlsize, "UTF-8", 1);
423 	dprintf(jdata->fd, "%s", (char *) xmlchars);
424 	xmlFree(xmlchars);
425 	xmlchars = NULL;
426 	xmlFreeDoc(doc);
427 
428 	if ((jdata->fd != fileno(stdout))
429 	    && (jdata->fd != fileno(stderr))
430 	    && (jdata->fd != -1)) {
431 		close(jdata->fd);
432 		jdata->fd = -1;
433 	}
434 }
435 
436 static void
destroy(void * data)437 destroy(void *data)
438 {
439 	xmlCleanupParser();
440 
441 	free(data);
442 }
443 
444 struct zuc_event_listener *
zuc_junit_reporter_create(void)445 zuc_junit_reporter_create(void)
446 {
447 	struct zuc_event_listener *listener =
448 		zalloc(sizeof(struct zuc_event_listener));
449 
450 	struct junit_data *data = zalloc(sizeof(struct junit_data));
451 	data->fd = -1;
452 
453 	listener->data = data;
454 	listener->destroy = destroy;
455 	listener->run_started = run_started;
456 	listener->run_ended = run_ended;
457 
458 	return listener;
459 }
460 
461 #else /* ENABLE_JUNIT_XML */
462 
463 #include <stddef.h>
464 #include "zuc_event_listener.h"
465 
466 /*
467  * Simple stub version if junit output (including libxml2 support) has
468  * been disabled.
469  * Will return NULL to cause failures as calling this when the #define
470  * has not been enabled is an invalid scenario.
471  */
472 
473 struct zuc_event_listener *
zuc_junit_reporter_create(void)474 zuc_junit_reporter_create(void)
475 {
476 	return NULL;
477 }
478 
479 #endif /* ENABLE_JUNIT_XML */
480