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