1/* Miniature re-implementation of the "check" library. 2 3 This is intended to support just enough of check to run the Expat 4 tests. This interface is based entirely on the portion of the 5 check library being used. 6 __ __ _ 7 ___\ \/ /_ __ __ _| |_ 8 / _ \\ /| '_ \ / _` | __| 9 | __// \| |_) | (_| | |_ 10 \___/_/\_\ .__/ \__,_|\__| 11 |_| XML parser 12 13 Copyright (c) 2004-2006 Fred L. Drake, Jr. <fdrake@users.sourceforge.net> 14 Copyright (c) 2016-2023 Sebastian Pipping <sebastian@pipping.org> 15 Copyright (c) 2017 Rhodri James <rhodri@wildebeest.org.uk> 16 Copyright (c) 2018 Marco Maggi <marco.maggi-ipsu@poste.it> 17 Copyright (c) 2019 David Loffredo <loffredo@steptools.com> 18 Copyright (c) 2023-2024 Sony Corporation / Snild Dolkow <snild@sony.com> 19 Licensed under the MIT license: 20 21 Permission is hereby granted, free of charge, to any person obtaining 22 a copy of this software and associated documentation files (the 23 "Software"), to deal in the Software without restriction, including 24 without limitation the rights to use, copy, modify, merge, publish, 25 distribute, sublicense, and/or sell copies of the Software, and to permit 26 persons to whom the Software is furnished to do so, subject to the 27 following conditions: 28 29 The above copyright notice and this permission notice shall be included 30 in all copies or substantial portions of the Software. 31 32 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 33 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 34 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 35 NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 36 DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 37 OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 38 USE OR OTHER DEALINGS IN THE SOFTWARE. 39*/ 40 41#if defined(NDEBUG) 42# undef NDEBUG /* because test suite relies on assert(...) at the moment */ 43#endif 44 45#include <stdarg.h> 46#include <stdio.h> 47#include <stdlib.h> 48#include <setjmp.h> 49#include <assert.h> 50#include <string.h> 51 52#include "internal.h" /* for UNUSED_P only */ 53#include "minicheck.h" 54 55Suite * 56suite_create(const char *name) { 57 Suite *suite = (Suite *)calloc(1, sizeof(Suite)); 58 if (suite != NULL) { 59 suite->name = name; 60 } 61 return suite; 62} 63 64TCase * 65tcase_create(const char *name) { 66 TCase *tc = (TCase *)calloc(1, sizeof(TCase)); 67 if (tc != NULL) { 68 tc->name = name; 69 } 70 return tc; 71} 72 73void 74suite_add_tcase(Suite *suite, TCase *tc) { 75 assert(suite != NULL); 76 assert(tc != NULL); 77 assert(tc->next_tcase == NULL); 78 79 tc->next_tcase = suite->tests; 80 suite->tests = tc; 81} 82 83void 84tcase_add_checked_fixture(TCase *tc, tcase_setup_function setup, 85 tcase_teardown_function teardown) { 86 assert(tc != NULL); 87 tc->setup = setup; 88 tc->teardown = teardown; 89} 90 91void 92tcase_add_test(TCase *tc, tcase_test_function test) { 93 assert(tc != NULL); 94 if (tc->allocated == tc->ntests) { 95 int nalloc = tc->allocated + 100; 96 size_t new_size = sizeof(tcase_test_function) * nalloc; 97 tcase_test_function *const new_tests 98 = (tcase_test_function *)realloc(tc->tests, new_size); 99 assert(new_tests != NULL); 100 tc->tests = new_tests; 101 tc->allocated = nalloc; 102 } 103 tc->tests[tc->ntests] = test; 104 tc->ntests++; 105} 106 107static void 108tcase_free(TCase *tc) { 109 if (! tc) { 110 return; 111 } 112 113 free(tc->tests); 114 free(tc); 115} 116 117static void 118suite_free(Suite *suite) { 119 if (! suite) { 120 return; 121 } 122 123 while (suite->tests != NULL) { 124 TCase *next = suite->tests->next_tcase; 125 tcase_free(suite->tests); 126 suite->tests = next; 127 } 128 free(suite); 129} 130 131SRunner * 132srunner_create(Suite *suite) { 133 SRunner *const runner = (SRunner *)calloc(1, sizeof(SRunner)); 134 if (runner != NULL) { 135 runner->suite = suite; 136 } 137 return runner; 138} 139 140static jmp_buf env; 141 142#define SUBTEST_LEN (50) // informative, but not too long 143static char const *_check_current_function = NULL; 144static char _check_current_subtest[SUBTEST_LEN]; 145static int _check_current_lineno = -1; 146static char const *_check_current_filename = NULL; 147 148void 149_check_set_test_info(char const *function, char const *filename, int lineno) { 150 _check_current_function = function; 151 set_subtest("%s", ""); 152 _check_current_lineno = lineno; 153 _check_current_filename = filename; 154} 155 156void 157set_subtest(char const *fmt, ...) { 158 va_list ap; 159 va_start(ap, fmt); 160 vsnprintf(_check_current_subtest, SUBTEST_LEN, fmt, ap); 161 va_end(ap); 162 // replace line feeds with spaces, for nicer error logs 163 for (size_t i = 0; i < SUBTEST_LEN; ++i) { 164 if (_check_current_subtest[i] == '\n') { 165 _check_current_subtest[i] = ' '; 166 } 167 } 168 _check_current_subtest[SUBTEST_LEN - 1] = '\0'; // ensure termination 169} 170 171static void 172handle_success(int verbosity) { 173 if (verbosity >= CK_VERBOSE) { 174 printf("PASS: %s\n", _check_current_function); 175 } 176} 177 178static void 179handle_failure(SRunner *runner, int verbosity, const char *context, 180 const char *phase_info) { 181 runner->nfailures++; 182 if (verbosity != CK_SILENT) { 183 if (strlen(_check_current_subtest) != 0) { 184 phase_info = _check_current_subtest; 185 } 186 printf("FAIL [%s]: %s (%s at %s:%d)\n", context, _check_current_function, 187 phase_info, _check_current_filename, _check_current_lineno); 188 } 189} 190 191void 192srunner_run_all(SRunner *runner, const char *context, int verbosity) { 193 Suite *suite; 194 TCase *volatile tc; 195 assert(runner != NULL); 196 suite = runner->suite; 197 tc = suite->tests; 198 while (tc != NULL) { 199 volatile int i; 200 for (i = 0; i < tc->ntests; ++i) { 201 runner->nchecks++; 202 set_subtest("%s", ""); 203 204 if (tc->setup != NULL) { 205 /* setup */ 206 if (setjmp(env)) { 207 handle_failure(runner, verbosity, context, "during setup"); 208 continue; 209 } 210 tc->setup(); 211 } 212 /* test */ 213 if (setjmp(env)) { 214 handle_failure(runner, verbosity, context, "during actual test"); 215 continue; 216 } 217 (tc->tests[i])(); 218 set_subtest("%s", ""); 219 220 /* teardown */ 221 if (tc->teardown != NULL) { 222 if (setjmp(env)) { 223 handle_failure(runner, verbosity, context, "during teardown"); 224 continue; 225 } 226 tc->teardown(); 227 } 228 229 handle_success(verbosity); 230 } 231 tc = tc->next_tcase; 232 } 233} 234 235void 236srunner_summarize(SRunner *runner, int verbosity) { 237 if (verbosity != CK_SILENT) { 238 int passed = runner->nchecks - runner->nfailures; 239 double percentage = ((double)passed) / runner->nchecks; 240 int display = (int)(percentage * 100); 241 printf("%d%%: Checks: %d, Failed: %d\n", display, runner->nchecks, 242 runner->nfailures); 243 } 244} 245 246void 247_fail(const char *file, int line, const char *msg) { 248 /* Always print the error message so it isn't lost. In this case, 249 we have a failure, so there's no reason to be quiet about what 250 it is. 251 */ 252 _check_current_filename = file; 253 _check_current_lineno = line; 254 if (msg != NULL) { 255 const int has_newline = (msg[strlen(msg) - 1] == '\n'); 256 fprintf(stderr, "ERROR: %s%s", msg, has_newline ? "" : "\n"); 257 } 258 longjmp(env, 1); 259} 260 261int 262srunner_ntests_failed(SRunner *runner) { 263 assert(runner != NULL); 264 return runner->nfailures; 265} 266 267void 268srunner_free(SRunner *runner) { 269 if (! runner) { 270 return; 271 } 272 273 suite_free(runner->suite); 274 free(runner); 275} 276