xref: /freebsd/contrib/expat/tests/minicheck.c (revision 1edb7116)
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 
55 Suite *
56 suite_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 
64 TCase *
65 tcase_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 
73 void
74 suite_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 
83 void
84 tcase_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 
91 void
92 tcase_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 
107 static void
108 tcase_free(TCase *tc) {
109   if (! tc) {
110     return;
111   }
112 
113   free(tc->tests);
114   free(tc);
115 }
116 
117 static void
118 suite_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 
131 SRunner *
132 srunner_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 
140 static jmp_buf env;
141 
142 #define SUBTEST_LEN (50) // informative, but not too long
143 static char const *_check_current_function = NULL;
144 static char _check_current_subtest[SUBTEST_LEN];
145 static int _check_current_lineno = -1;
146 static char const *_check_current_filename = NULL;
147 
148 void
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 
156 void
157 set_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 
171 static void
172 handle_success(int verbosity) {
173   if (verbosity >= CK_VERBOSE) {
174     printf("PASS: %s\n", _check_current_function);
175   }
176 }
177 
178 static void
179 handle_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 
191 void
192 srunner_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 
235 void
236 srunner_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 
246 void
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 
261 int
262 srunner_ntests_failed(SRunner *runner) {
263   assert(runner != NULL);
264   return runner->nfailures;
265 }
266 
267 void
268 srunner_free(SRunner *runner) {
269   if (! runner) {
270     return;
271   }
272 
273   suite_free(runner->suite);
274   free(runner);
275 }
276