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