blob: df906401eec210b959de0a6871c3602d8ef1c347 [file] [log] [blame]
Jon A. Cruz646aef52015-07-15 19:22:41 -07001/*
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>
Emmanuel Gil Peyrot1764d222016-05-18 17:18:18 +010033#include <inttypes.h>
Jon A. Cruz646aef52015-07-15 19:22:41 -070034#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 "shared/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
Quentin Glidic9c36eb92016-07-10 11:00:56 +020056#if LIBXML_VERSION >= 20904
57#define STRPRINTF_CAST
58#else
59#define STRPRINTF_CAST BAD_CAST
60#endif
61
Jon A. Cruz646aef52015-07-15 19:22:41 -070062/**
63 * Internal data.
64 */
65struct junit_data
66{
67 int fd;
68 time_t begin;
69};
70
71#define MAX_64BIT_STRLEN 20
72
73static void
74set_attribute(xmlNodePtr node, const char *name, int value)
75{
76 xmlChar scratch[MAX_64BIT_STRLEN + 1] = {};
Quentin Glidic9c36eb92016-07-10 11:00:56 +020077 xmlStrPrintf(scratch, sizeof(scratch), STRPRINTF_CAST "%d", value);
Jon A. Cruz646aef52015-07-15 19:22:41 -070078 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 */
87static void
88emit_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"
Emmanuel Gil Peyrot1764d222016-05-18 17:18:18 +0100138 " 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) {
Jon A. Cruz646aef52015-07-15 19:22:41 -0700144 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: "
Emmanuel Gil Peyrot1764d222016-05-18 17:18:18 +0100161 "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) {
Jon A. Cruz646aef52015-07-15 19:22:41 -0700167 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: "
Emmanuel Gil Peyrot1764d222016-05-18 17:18:18 +0100191 "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) {
Jon A. Cruz646aef52015-07-15 19:22:41 -0700196 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 */
228static char *
Dawid Gajownik74a635b2015-08-06 17:12:19 -0300229as_duration(long ms)
230{
Jon A. Cruz646aef52015-07-15 19:22:41 -0700231 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
Abdur Rehman3caed392017-01-01 19:46:44 +0500238 * writers. Assumption is certain readers might have
Jon A. Cruz646aef52015-07-15 19:22:41 -0700239 * 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 */
256static char const *
257get_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 */
271static void
272emit_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 */
302static void
303emit_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 */
347static char *
348as_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
362static void
363run_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
373static void
374run_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
436static void
437destroy(void *data)
438{
439 xmlCleanupParser();
440
441 free(data);
442}
443
444struct zuc_event_listener *
445zuc_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 "shared/zalloc.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
473struct zuc_event_listener *
474zuc_junit_reporter_create(void)
475{
476 return NULL;
477}
478
479#endif /* ENABLE_JUNIT_XML */