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/doc/env.rst b/doc/env.rst index 97ab252..d3e6aee 100644 --- a/doc/env.rst +++ b/doc/env.rst @@ -23,6 +23,7 @@ Command line arguments * ``-S or --short-filename``: The filenames are displayed in their short form. * ``--always-succeed``: The process shall exit with a status of ``0``. * ``--tap``: Enables the TAP (Test Anything Protocol) output format. +* ``--xml``: Enables the JUnit4 XML output format. * ``--verbose[=level]``: Makes the output verbose. When provided with an integer, sets the verbosity level to that integer. @@ -71,6 +72,7 @@ Environment variables are alternatives to command line switches when set to 1. * ``CRITERION_ALWAYS_SUCCEED``: Same as ``--always-succeed``. * ``CRITERION_NO_EARLY_EXIT``: Same as ``--no-early-exit``. * ``CRITERION_ENABLE_TAP``: Same as ``--tap``. +* ``CRITERION_ENABLE_XML``: Same as ``--xml``. * ``CRITERION_FAIL_FAST``: Same as ``--fail-fast``. * ``CRITERION_USE_ASCII``: Same as ``--ascii``. * ``CRITERION_JOBS``: Same as ``jobs``. Sets the number of jobs to 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/po/fr.po b/po/fr.po index 65717ee..e6c75a4 100644 --- a/po/fr.po +++ b/po/fr.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: criterion 2.0.0\n" "Report-Msgid-Bugs-To: franklinmathieu+criterion@gmail.com\n" -"POT-Creation-Date: 2015-10-01 23:09+0200\n" +"POT-Creation-Date: 2015-10-05 16:20+0200\n" "PO-Revision-Date: 2015-04-03 17:58+0200\n" "Last-Translator: \n" "Language-Team: French\n" @@ -18,70 +18,70 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" -#: src/log/normal.c:50 +#: src/log/normal.c:41 #, c-format msgid "Criterion v%s\n" msgstr "Criterion v%s\n" -#: src/log/normal.c:51 +#: src/log/normal.c:42 #, c-format msgid " %s\n" msgstr " %s\n" -#: src/log/normal.c:54 src/log/normal.c:56 +#: src/log/normal.c:45 src/log/normal.c:47 #, c-format msgid "%1$s::%2$s\n" msgstr "%1$s::%2$s\n" -#: src/log/normal.c:55 +#: src/log/normal.c:46 #, fuzzy, c-format msgid "%1$s::%2$s: (%3$3.2fs)\n" msgstr "%1$s::%2$s: (%3$3.2fs)\n" -#: src/log/normal.c:57 +#: src/log/normal.c:48 #, c-format msgid "%1$s::%2$s: Test is disabled\n" msgstr "%1$s::%2$s: Le test est désactivé\n" -#: src/log/normal.c:58 +#: src/log/normal.c:49 #, c-format msgid "%1$s::%2$s: Suite is disabled\n" msgstr "%1$s::%2$s: La suite est désactivée\n" -#: src/log/normal.c:59 +#: src/log/normal.c:50 #, c-format msgid "%1$s%2$s%3$s:%4$s%5$d%6$s: Assertion failed: %7$s\n" msgstr "%1$s%2$s%3$s:%4$s%5$d%6$s: Échec d'assertion: %7$s\n" -#: src/log/normal.c:60 +#: src/log/normal.c:51 #, fuzzy, c-format msgid " Theory %1$s::%2$s failed with the following parameters: (%3$s)\n" msgstr "" " La théorie %1$s::%2$s a échoué avec les paramètres suivants: (%3$s)\n" -#: src/log/normal.c:61 +#: src/log/normal.c:52 #, fuzzy, c-format msgid "%1$s::%2$s: Timed out. (%3$3.2fs)\n" msgstr "%1$s::%2$s: Délai expiré. (%3$3.2fs)\n" -#: src/log/normal.c:62 +#: src/log/normal.c:53 #, c-format msgid "%1$s%2$s%3$s:%4$s%5$u%6$s: Unexpected signal caught below this line!\n" msgstr "" "%1$s%2$s%3$s:%4$s%5$u%6$s: Un signal inattendu a été reçu après cette " "ligne!\n" -#: src/log/normal.c:63 +#: src/log/normal.c:54 #, c-format msgid "%1$s::%2$s: CRASH!\n" msgstr "%1$s::%2$s: PLANTAGE!\n" -#: src/log/normal.c:64 +#: src/log/normal.c:55 #, fuzzy, c-format msgid "%1$s::%2$s: %3$s\n" msgstr "%1$s::%2$s: (%3$3.2fs)\n" -#: src/log/normal.c:65 +#: src/log/normal.c:56 #, fuzzy, c-format msgid "" "%1$sWarning! The test `%2$s::%3$s` crashed during its setup or teardown." @@ -90,7 +90,7 @@ msgstr "" "%1$sAttention! Le test `%2$s::%3$s` a planté pendant son initialisation ou " "sa finalisation.%4$s\n" -#: src/log/normal.c:66 +#: src/log/normal.c:57 #, fuzzy, c-format msgid "" "%1$sWarning! The test `%2$s::%3$s` exited during its setup or teardown.%4$s\n" @@ -98,14 +98,14 @@ msgstr "" "%1$sAttention! Le test `%2$s::%3$s` a quitté pendant son initialisation ou " "sa finalisation.%4$s\n" -#: src/log/normal.c:67 +#: src/log/normal.c:58 #, c-format msgid "Running %1$s%2$lu%3$s test from %4$s%5$s%6$s:\n" msgid_plural "Running %1$s%2$lu%3$s tests from %4$s%5$s%6$s:\n" msgstr[0] "Lancement de %1$s%2$lu%3$s test dans %4$s%5$s%6$s:\n" msgstr[1] "Lancement de %1$s%2$lu%3$s tests dans %4$s%5$s%6$s:\n" -#: src/log/normal.c:69 +#: src/log/normal.c:60 #, c-format msgid "" "%1$sSynthesis: Tested: %2$s%3$lu%4$s | Passing: %5$s%6$lu%7$s | Failing: %8$s" @@ -114,7 +114,7 @@ msgstr "" "%1$sSynthèse: Testés: %2$s%3$lu%4$s | Validés: %5$s%6$lu%7$s | Échoués: %8$s" "%9$lu%10$s | Plantages: %11$s%12$lu%13$s %14$s\n" -#: src/log/normal.c:85 +#: src/log/normal.c:76 #, fuzzy, c-format msgid "%s::%s: %s\n" msgstr "%1$s::%2$s: (%3$3.2fs)\n" diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt index ada809f..954dd09 100644 --- a/samples/CMakeLists.txt +++ b/samples/CMakeLists.txt @@ -34,6 +34,7 @@ set(SAMPLES set(SCRIPTS tap_test + xml_test early_exit verbose list diff --git a/samples/tests/xml_test.sh b/samples/tests/xml_test.sh new file mode 100755 index 0000000..9f0ab26 --- /dev/null +++ b/samples/tests/xml_test.sh @@ -0,0 +1,7 @@ +#!/bin/sh +./simple.c.bin --xml --always-succeed +./signal.c.bin --xml --always-succeed +./asserts.c.bin --xml --always-succeed +./more-suites.c.bin --xml --always-succeed +./long-messages.c.bin --xml --always-succeed +./description.c.bin --xml --always-succeed diff --git a/src/compat/posix.h b/src/compat/posix.h index fc0e8d8..4468f63 100644 --- a/src/compat/posix.h +++ b/src/compat/posix.h @@ -24,9 +24,9 @@ #ifndef POSIX_COMPAT_H_ # define POSIX_COMPAT_H_ -#if defined(_WIN32) && !defined(__CYGWIN__) -# define VANILLA_WIN32 -#endif +# if defined(_WIN32) && !defined(__CYGWIN__) +# define VANILLA_WIN32 +# endif # if defined(BSD) \ || defined(__FreeBSD__) \ @@ -45,6 +45,7 @@ # define off64_t _off64_t # endif # include +# include # if defined(__MINGW32__) || defined(__MINGW64__) # undef off_t # undef off64_t @@ -62,6 +63,7 @@ # define SIGPROF 27 # define CR_EXCEPTION_TIMEOUT 0xC0001042 + # else # include # endif diff --git a/src/compat/strtok.h b/src/compat/strtok.h new file mode 100644 index 0000000..6dbe96c --- /dev/null +++ b/src/compat/strtok.h @@ -0,0 +1,43 @@ +/* + * 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. + */ +#ifndef COMPAT_STRTOK_H_ +# define COMPAT_STRTOK_H_ + +# include "config.h" + +# ifdef VANILLA_WIN32 +# if HAVE_STRTOK_S +# define strtok_r strtok_s +# else +static CR_INLINE char *strtok_r(char *str, const char *delim, CR_UNUSED char **saveptr) { + return strtok(str, delim); +} +# endif + +# ifdef _MSC_VER +# define strdup _strdup +# endif +# endif + +#endif /* !COMPAT_STRTOK_H_ */ diff --git a/src/entry/main.c b/src/entry/main.c index 6de122a..cac5eac 100644 --- a/src/entry/main.c +++ b/src/entry/main.c @@ -62,6 +62,7 @@ "name of the source file on a failure\n" \ PATTERN_USAGE \ " --tap: enables TAP formatting\n" \ + " --xml: enables XML formatting\n" \ " --always-succeed: always exit with 0\n" \ " --no-early-exit: do not exit the test worker " \ "prematurely after the test\n" \ @@ -130,6 +131,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 +184,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")); opt->measure_time = !!strcmp("1", DEF(getenv("CRITERION_DISABLE_TIME_MEASUREMENTS"), "0")); @@ -201,6 +204,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; @@ -209,6 +213,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/normal.c b/src/log/normal.c index ba86e6f..f7319d3 100644 --- a/src/log/normal.c +++ b/src/log/normal.c @@ -31,25 +31,12 @@ #include "criterion/options.h" #include "criterion/ordered-set.h" #include "compat/posix.h" +#include "compat/strtok.h" #include "compat/time.h" #include "string/i18n.h" #include "config.h" #include "common.h" -#ifdef VANILLA_WIN32 -# if HAVE_STRTOK_S -# define strtok_r strtok_s -# else - static char *strtok_r(char *str, const char *delim, CR_UNUSED char **saveptr) { - return strtok(str, delim); - } -# endif -#endif - -#ifdef _MSC_VER -# define strdup _strdup -#endif - typedef const char *const msg_t; static msg_t msg_pre_all = N_("Criterion v%s\n"); diff --git a/src/log/tap.c b/src/log/tap.c index 8e3bab9..c960951 100644 --- a/src/log/tap.c +++ b/src/log/tap.c @@ -30,24 +30,11 @@ #include "criterion/options.h" #include "criterion/ordered-set.h" #include "compat/posix.h" +#include "compat/strtok.h" #include "compat/time.h" #include "config.h" #include "common.h" -#ifdef VANILLA_WIN32 -# if HAVE_STRTOK_S -# define strtok_r strtok_s -# else - static char *strtok_r(char *str, const char *delim, CR_UNUSED char **saveptr) { - return strtok(str, delim); - } -# endif -#endif - -#ifdef _MSC_VER -# define strdup _strdup -#endif - static void print_prelude(struct criterion_global_stats *stats) { criterion_important("TAP version 13\n1.." CR_SIZE_T_FORMAT diff --git a/src/log/xml.c b/src/log/xml.c new file mode 100644 index 0000000..fd7c9d6 --- /dev/null +++ b/src/log/xml.c @@ -0,0 +1,189 @@ +/* + * 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/strtok.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, +};