diff --git a/CMakeLists.txt b/CMakeLists.txt index 559f093..f3b6e5d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -129,6 +129,7 @@ set(SOURCE_FILES src/log/logging.c src/log/tap.c src/log/normal.c + src/log/xml.c src/string/i18n.c src/string/i18n.h src/entry/options.c diff --git a/include/criterion/logging.h b/include/criterion/logging.h index aca55ce..450023b 100644 --- a/include/criterion/logging.h +++ b/include/criterion/logging.h @@ -121,10 +121,12 @@ struct criterion_output_provider { extern struct criterion_output_provider normal_logging; extern struct criterion_output_provider tap_logging; +extern struct criterion_output_provider xml_logging; CR_END_C_API #define CR_NORMAL_LOGGING (&normal_logging) #define CR_TAP_LOGGING (&tap_logging) +#define CR_XML_LOGGING (&xml_logging) #endif /* !CRITERION_LOGGING_H_ */ diff --git a/src/entry/main.c b/src/entry/main.c index d67989f..5c1de36 100644 --- a/src/entry/main.c +++ b/src/entry/main.c @@ -130,6 +130,7 @@ int criterion_handle_args(int argc, char *argv[], bool handle_unknown_arg) { {"verbose", optional_argument, 0, 'b'}, {"version", no_argument, 0, 'v'}, {"tap", no_argument, 0, 't'}, + {"xml", no_argument, 0, 'x'}, {"help", no_argument, 0, 'h'}, {"list", no_argument, 0, 'l'}, {"ascii", no_argument, 0, 'k'}, @@ -182,6 +183,7 @@ int criterion_handle_args(int argc, char *argv[], bool handle_unknown_arg) { #endif bool use_tap = !strcmp("1", DEF(getenv("CRITERION_ENABLE_TAP"), "0")); + bool use_xml = !strcmp("1", DEF(getenv("CRITERION_ENABLE_XML"), "0")); bool do_list_tests = false; bool do_print_version = false; @@ -199,6 +201,7 @@ int criterion_handle_args(int argc, char *argv[], bool handle_unknown_arg) { case 'p': criterion_options.pattern = optarg; break; #endif case 't': use_tap = true; break; + case 'x': use_xml = true; break; case 'l': do_list_tests = true; break; case 'v': do_print_version = true; break; case 'h': do_print_usage = true; break; @@ -207,6 +210,8 @@ int criterion_handle_args(int argc, char *argv[], bool handle_unknown_arg) { } if (use_tap) criterion_options.output_provider = CR_TAP_LOGGING; + else if (use_xml) + criterion_options.output_provider = CR_XML_LOGGING; if (do_print_usage) return print_usage(argv[0]); if (do_print_version) diff --git a/src/log/xml.c b/src/log/xml.c new file mode 100644 index 0000000..eb9be51 --- /dev/null +++ b/src/log/xml.c @@ -0,0 +1,188 @@ +/* + * The MIT License (MIT) + * + * Copyright © 2015 Franklin "Snaipe" Mathieu + * + * 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 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. + */ +#define _GNU_SOURCE +#include +#include +#include +#include "criterion/stats.h" +#include "criterion/logging.h" +#include "criterion/options.h" +#include "criterion/ordered-set.h" +#include "compat/posix.h" +#include "compat/time.h" +#include "config.h" +#include "common.h" + +#define TESTSUITES_PROPERTIES \ + "name=\"Criterion Tests\" " \ + "tests=\"" CR_SIZE_T_FORMAT "\" " \ + "failures=\"" CR_SIZE_T_FORMAT "\" " \ + "errors=\"" CR_SIZE_T_FORMAT "\" " \ + "disabled=\"" CR_SIZE_T_FORMAT "\"" + +#define XML_BASE_TEMPLATE_BEGIN \ + "\n" \ + "\n" \ + "\n" \ + +#define XML_BASE_TEMPLATE_END \ + "\n" + +#define TESTSUITE_PROPERTIES \ + "name=\"%s\" " \ + "tests=\"" CR_SIZE_T_FORMAT "\" " \ + "failures=\"" CR_SIZE_T_FORMAT "\" " \ + "errors=\"" CR_SIZE_T_FORMAT "\" " \ + "disabled=\"" CR_SIZE_T_FORMAT "\" " \ + "skipped=\"" CR_SIZE_T_FORMAT "\"" + +#define XML_TESTSUITE_TEMPLATE_BEGIN \ + " \n" + +#define XML_TESTSUITE_TEMPLATE_END \ + " \n" + +#define TEST_PROPERTIES \ + "name=\"%s\" " \ + "assertions=\"" CR_SIZE_T_FORMAT "\" " \ + "status=\"%s\"" + +#define XML_TEST_TEMPLATE_BEGIN \ + " \n" \ + +#define XML_TEST_TEMPLATE_END \ + " \n" + +#define XML_TEST_SKIPPED " \n" + +#define LF " " + +#define XML_FAILURE_MSG_ENTRY \ + "%s:%u: %s" LF + +#define XML_TEST_FAILED_TEMPLATE_BEGIN \ + " " + +#define XML_TEST_FAILED_TEMPLATE_END \ + "\n" + +#define XML_CRASH_MSG_ENTRY \ + " " + +#define XML_TIMEOUT_MSG_ENTRY \ + " " + +static INLINE bool is_disabled(struct criterion_test *t, struct criterion_suite *s) { + return t->data->disabled || (s->data && s->data->disabled); +} + +static CR_INLINE +const char *get_status_string(struct criterion_test_stats *ts, + struct criterion_suite_stats *ss) { + + const char *status = "PASSED"; + if (ts->crashed || ts->timed_out) + status = "ERRORED"; + else if (ts->failed) + status = "FAILED"; + else if (is_disabled(ts->test, ss->suite)) + status = "SKIPPED"; + return status; +} + +static void print_test(struct criterion_test_stats *ts, + struct criterion_suite_stats *ss) { + + criterion_important(XML_TEST_TEMPLATE_BEGIN, + ts->test->name, + (size_t) (ts->passed_asserts + ts->failed_asserts), + get_status_string(ts, ss) + ); + + if (is_disabled(ts->test, ss->suite)) { + criterion_important(XML_TEST_SKIPPED); + } else if (ts->crashed) { + criterion_important(XML_CRASH_MSG_ENTRY); + } else if (ts->timed_out) { + criterion_important(XML_TIMEOUT_MSG_ENTRY); + } else { + if (ts->failed) { + criterion_important(XML_TEST_FAILED_TEMPLATE_BEGIN, ts->failed_asserts); + for (struct criterion_assert_stats *asrt = ts->asserts; asrt; asrt = asrt->next) { + if (!asrt->passed) { + bool sf = criterion_options.short_filename; + char *dup = strdup(*asrt->message ? asrt->message : ""); + char *saveptr = NULL; + char *line = strtok_r(dup, "\n", &saveptr); + + criterion_important(XML_FAILURE_MSG_ENTRY, + sf ? basename_compat(asrt->file) : asrt->file, + asrt->line, + line + ); + + while ((line = strtok_r(NULL, "\n", &saveptr))) + criterion_important(" %s" LF, line); + free(dup); + } + } + criterion_important(XML_TEST_FAILED_TEMPLATE_END); + } + } + + criterion_important(XML_TEST_TEMPLATE_END); +} + +void xml_log_post_all(struct criterion_global_stats *stats) { + criterion_important(XML_BASE_TEMPLATE_BEGIN, + stats->nb_tests, + stats->tests_failed, + stats->tests_crashed, + stats->tests_skipped + ); + + for (struct criterion_suite_stats *ss = stats->suites; ss; ss = ss->next) { + + criterion_important(XML_TESTSUITE_TEMPLATE_BEGIN, + ss->suite->name, + ss->nb_tests, + ss->tests_failed, + ss->tests_crashed, + ss->tests_skipped, + ss->tests_skipped + ); + + for (struct criterion_test_stats *ts = ss->tests; ts; ts = ts->next) { + print_test(ts, ss); + } + + criterion_important(XML_TESTSUITE_TEMPLATE_END); + } + + criterion_important(XML_BASE_TEMPLATE_END); +} + +struct criterion_output_provider xml_logging = { + .log_post_all = xml_log_post_all, +};