From f940632c3a41d7c599fbef71d9ab47e1b3f192a5 Mon Sep 17 00:00:00 2001 From: Snaipe Date: Sat, 7 Nov 2015 22:37:24 +0100 Subject: [PATCH] Added json output provider --- CMakeLists.txt | 1 + src/entry/main.c | 2 + src/io/json.c | 209 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 212 insertions(+) create mode 100644 src/io/json.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 266d38f..4124c0a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -135,6 +135,7 @@ set(SOURCE_FILES src/io/output.h src/io/tap.c src/io/xml.c + src/io/json.c src/log/logging.c src/log/normal.c src/string/i18n.c diff --git a/src/entry/main.c b/src/entry/main.c index a1de7ed..6e561de 100644 --- a/src/entry/main.c +++ b/src/entry/main.c @@ -142,6 +142,7 @@ int criterion_handle_args(int argc, char *argv[], bool handle_unknown_arg) { {"version", no_argument, 0, 'v'}, {"tap", optional_argument, 0, 't'}, {"xml", optional_argument, 0, 'x'}, + {"json", optional_argument, 0, 'n'}, {"help", no_argument, 0, 'h'}, {"list", no_argument, 0, 'l'}, {"ascii", no_argument, 0, 'k'}, @@ -218,6 +219,7 @@ int criterion_handle_args(int argc, char *argv[], bool handle_unknown_arg) { case 'q': quiet = true; break; case 't': quiet = true; criterion_add_output("tap", DEF(optarg, "-")); break; case 'x': quiet = true; criterion_add_output("xml", DEF(optarg, "-")); break; + case 'n': quiet = true; criterion_add_output("json", DEF(optarg, "-")); break; case 'l': do_list_tests = true; break; case 'v': do_print_version = true; break; case 'h': do_print_usage = true; break; diff --git a/src/io/json.c b/src/io/json.c new file mode 100644 index 0000000..6c4d9bd --- /dev/null +++ b/src/io/json.c @@ -0,0 +1,209 @@ +/* + * 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 JSON_TEST_TEMPLATE_BEGIN \ + " {\n" \ + " \"name\": \"%s\",\n" \ + " \"assertions\": " CR_SIZE_T_FORMAT ",\n" \ + " \"status\": \"%s\"" + +#define JSON_TEST_TEMPLATE_END \ + "\n" \ + " }" + +#define JSON_TEST_FAILED_TEMPLATE_BEGIN \ + ",\n" \ + " \"messages\": [\n" + +#define JSON_TEST_FAILED_TEMPLATE_END \ + "\n" \ + " ]" + +#define JSON_FAILURE_MSG_ENTRY \ + " \"%s:%u: %s\"" + +#define JSON_CRASH_MSG_ENTRY \ + ",\n" \ + " \"messages\": [\"The test crashed.\"]" + +#define JSON_TIMEOUT_MSG_ENTRY \ + ",\n" \ + " \"messages\": [\"The test timed out.\"]" + +#define JSON_SKIPPED_MSG_ENTRY \ + ",\n" \ + " \"messages\": [\"The test was skipped.\"]" + +#define JSON_TEST_LIST_TEMPLATE_BEGIN \ + " \"tests\": [\n" + +#define JSON_TEST_LIST_TEMPLATE_END \ + " ]\n" + +#define JSON_TESTSUITE_TEMPLATE_BEGIN \ + " {\n" \ + " \"name\": \"%s\",\n" \ + " \"passed\": " CR_SIZE_T_FORMAT ",\n" \ + " \"failed\": " CR_SIZE_T_FORMAT ",\n" \ + " \"errored\": " CR_SIZE_T_FORMAT ",\n" \ + " \"skipped\": " CR_SIZE_T_FORMAT ",\n" + +#define JSON_TESTSUITE_TEMPLATE_END \ + " }" + +#define JSON_TESTSUITE_LIST_TEMPLATE_BEGIN \ + " \"test_suites\": [\n" + +#define JSON_TESTSUITE_LIST_TEMPLATE_END \ + " ]\n" + +#define JSON_BASE_TEMPLATE_BEGIN \ + "{\n" \ + " \"id\": \"Criterion v" VERSION "\",\n" \ + " \"passed\": " CR_SIZE_T_FORMAT ",\n" \ + " \"failed\": " CR_SIZE_T_FORMAT ",\n" \ + " \"errored\": " CR_SIZE_T_FORMAT ",\n" \ + " \"skipped\": " CR_SIZE_T_FORMAT ",\n" \ + +#define JSON_BASE_TEMPLATE_END \ + "}\n" + +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(JSON_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(JSON_SKIPPED_MSG_ENTRY); + } else if (ts->crashed) { + criterion_important(JSON_CRASH_MSG_ENTRY); + } else if (ts->timed_out) { + criterion_important(JSON_TIMEOUT_MSG_ENTRY); + } else if (ts->failed) { + criterion_important(JSON_TEST_FAILED_TEMPLATE_BEGIN); + + bool first = true; + for (struct criterion_assert_stats *asrt = ts->asserts; asrt; asrt = asrt->next) { + if (!asrt->passed) { + if (!first) { + criterion_important(",\n"); + } else { + first = false; + } + + 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(JSON_FAILURE_MSG_ENTRY, + sf ? basename_compat(asrt->file) : asrt->file, + asrt->line, + line + ); + + while ((line = strtok_r(NULL, "\n", &saveptr))) { + criterion_important(",\n \" %s\"\n", line); + } + free(dup); + } + } + criterion_important(JSON_TEST_FAILED_TEMPLATE_END); + } + + criterion_important(JSON_TEST_TEMPLATE_END); +} + +void json_log_post_all(struct criterion_global_stats *stats) { + criterion_important(JSON_BASE_TEMPLATE_BEGIN, + stats->tests_passed, + stats->tests_failed, + stats->tests_crashed, + stats->tests_skipped + ); + + criterion_important(JSON_TESTSUITE_LIST_TEMPLATE_BEGIN); + for (struct criterion_suite_stats *ss = stats->suites; ss; ss = ss->next) { + + criterion_important(JSON_TESTSUITE_TEMPLATE_BEGIN, + ss->suite->name, + ss->tests_passed, + ss->tests_failed, + ss->tests_crashed, + ss->tests_skipped + ); + + criterion_important(JSON_TEST_LIST_TEMPLATE_BEGIN); + for (struct criterion_test_stats *ts = ss->tests; ts; ts = ts->next) { + print_test(ts, ss); + criterion_important(ts->next ? ",\n" : "\n"); + } + criterion_important(JSON_TEST_LIST_TEMPLATE_END); + + criterion_important(JSON_TESTSUITE_TEMPLATE_END); + criterion_important(ss->next ? ",\n" : "\n"); + } + criterion_important(JSON_TESTSUITE_LIST_TEMPLATE_END); + + criterion_important(JSON_BASE_TEMPLATE_END); +} + +struct criterion_output_provider json_logging = { + .log_post_all = json_log_post_all, +};