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