t: import the clar unit testing framework

Our unit testing framework is a homegrown solution. While it supports most of our needs, it is likely that the volume of unit tests will grow quite a bit in the future such that we can exercise low-level subsystems directly. This surfaces several shortcomings that the current solution has: - There is no way to run only one specific tests. While some of our unit tests wire this up manually, others don't. In general, it requires quite a bit of boilerplate to get this set up correctly. - Failures do not cause a test to stop execution directly. Instead, the test author needs to return manually whenever an assertion fails. This is rather verbose and is not done correctly in most of our unit tests. - Wiring up a new testcase requires both implementing the test function and calling it in the respective test suite's main function, which is creating code duplication. We can of course fix all of these issues ourselves, but that feels rather pointless when there are already so many unit testing frameworks out there that have those features. We line out some requirements for any unit testing framework in "Documentation/technical/unit-tests.txt". The "clar" unit testing framework, which isn't listed in that table yet, ticks many of the boxes: - It is licensed under ISC, which is compatible. - It is easily vendorable because it is rather tiny at around 1200 lines of code. - It is easily hackable due to the same reason. - It has TAP support. - It has skippable tests. - It preprocesses test files in order to extract test functions, which then get wired up automatically. While it's not perfect, the fact that clar originates from the libgit2 project means that it should be rather easy for us to collaborate with upstream to plug any gaps. Import the clar unit testing framework at commit 1516124 (Merge pull request #97 from pks-t/pks-whitespace-fixes, 2024-08-15). The framework will be wired up in subsequent commits. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Sep 4, 2024 at 16:16 UTC 9b7caa2809cba618f2af702d35b12bc99535f2b9
20 files changed +2943 -1
Documentation/technical/unit-tests.txt
+2
@@ -203,6 +203,7 @@ GitHub / GitLab stars to estimate this.
203 :criterion: https://github.com/Snaipe/Criterion[Criterion]
204 :c-tap: https://github.com/rra/c-tap-harness/[C TAP]
205 :check: https://libcheck.github.io/check/[Check]
206 +:clar: https://github.com/clar-test/clar[Clar]
207
208 [format="csv",options="header",width="33%",subs="specialcharacters,attributes,quotes,macros"]
209 |=====
@@ -212,6 +213,7 @@ Framework,"<<license,License>>","<<vendorable-or-ubiquitous,Vendorable or ubiqui
213 {criterion},{mit},{false},{partial},{true},{true},{true},{true},{true},{false},{true},19,1800
214 {c-tap},{expat},{true},{partial},{partial},{true},{false},{true},{false},{false},{false},4,33
215 {check},{lgpl},{false},{partial},{true},{true},{true},{false},{false},{false},{true},17,973
216 +{clar},{isc},{false},{partial},{true},{true},{true},{true},{false},{false},{true},1,192
217 |=====
218
219 === Additional framework candidates
Makefile
+3 -1
@@ -1331,6 +1331,8 @@ THIRD_PARTY_SOURCES += compat/poll/%
1331 THIRD_PARTY_SOURCES += compat/regex/%
1332 THIRD_PARTY_SOURCES += sha1collisiondetection/%
1333 THIRD_PARTY_SOURCES += sha1dc/%
1334 +THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/%
1335 +THIRD_PARTY_SOURCES += $(UNIT_TEST_DIR)/clar/clar/%
1336
1337 UNIT_TEST_PROGRAMS += t-ctype
1338 UNIT_TEST_PROGRAMS += t-example-decorate
@@ -3261,7 +3263,7 @@ $(SP_OBJ): %.sp: %.c %.o
3263 .PHONY: sparse
3264 sparse: $(SP_OBJ)
3265
3264 -EXCEPT_HDRS := $(GENERATED_H) unicode-width.h compat/% xdiff/%
3266 +EXCEPT_HDRS := $(GENERATED_H) unicode-width.h compat/% xdiff/% $(UNIT_TEST_DIR)/clar/% $(UNIT_TEST_DIR)/clar/clar/%
3267 ifndef OPENSSL_SHA1
3268 EXCEPT_HDRS += sha1/openssl.h
3269 endif
t/unit-tests/clar/.github/workflows/ci.yml new
+23
@@ -0,0 +1,23 @@
1 +name: CI
2 +
3 +on:
4 + push:
5 + branches: [ main ]
6 + pull_request:
7 + branches: [ main ]
8 +
9 +jobs:
10 + build:
11 + strategy:
12 + matrix:
13 + os: [ ubuntu-latest, macos-latest ]
14 +
15 + runs-on: ${{ matrix.os }}
16 +
17 + steps:
18 + - name: Check out
19 + uses: actions/checkout@v2
20 + - name: Build
21 + run: |
22 + cd test
23 + make
t/unit-tests/clar/COPYING new
+15
@@ -0,0 +1,15 @@
1 +ISC License
2 +
3 +Copyright (c) 2011-2015 Vicent Marti
4 +
5 +Permission to use, copy, modify, and/or distribute this software for any
6 +purpose with or without fee is hereby granted, provided that the above
7 +copyright notice and this permission notice appear in all copies.
8 +
9 +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
t/unit-tests/clar/README.md new
+329
@@ -0,0 +1,329 @@
1 +Come out and Clar
2 +=================
3 +
4 +In Catalan, "clar" means clear, easy to perceive. Using clar will make it
5 +easy to test and make clear the quality of your code.
6 +
7 +> _Historical note_
8 +>
9 +> Originally the clar project was named "clay" because the word "test" has its
10 +> roots in the latin word *"testum"*, meaning "earthen pot", and *"testa"*,
11 +> meaning "piece of burned clay"?
12 +>
13 +> This is because historically, testing implied melting metal in a pot to
14 +> check its quality. Clay is what tests are made of.
15 +
16 +## Quick Usage Overview
17 +
18 +Clar is a minimal C unit testing framework. It's been written to replace the
19 +old framework in [libgit2][libgit2], but it's both very versatile and
20 +straightforward to use.
21 +
22 +Can you count to funk?
23 +
24 +- **Zero: Initialize test directory**
25 +
26 + ~~~~ sh
27 + $ mkdir tests
28 + $ cp -r $CLAR_ROOT/clar* tests
29 + $ cp $CLAR_ROOT/test/clar_test.h tests
30 + $ cp $CLAR_ROOT/test/main.c.sample tests/main.c
31 + ~~~~
32 +
33 +- **One: Write some tests**
34 +
35 + File: tests/adding.c:
36 +
37 + ~~~~ c
38 + /* adding.c for the "Adding" suite */
39 + #include "clar.h"
40 +
41 + static int *answer;
42 +
43 + void test_adding__initialize(void)
44 + {
45 + answer = malloc(sizeof(int));
46 + cl_assert_(answer != NULL, "No memory left?");
47 + *answer = 42;
48 + }
49 +
50 + void test_adding__cleanup(void)
51 + {
52 + free(answer);
53 + }
54 +
55 + void test_adding__make_sure_math_still_works(void)
56 + {
57 + cl_assert_(5 > 3, "Five should probably be greater than three");
58 + cl_assert_(-5 < 2, "Negative numbers are small, I think");
59 + cl_assert_(*answer == 42, "The universe is doing OK. And the initializer too.");
60 + }
61 + ~~~~~
62 +
63 +- **Two: Build the test executable**
64 +
65 + ~~~~ sh
66 + $ cd tests
67 + $ $CLAR_PATH/generate.py .
68 + Written `clar.suite` (1 suites)
69 + $ gcc -I. clar.c main.c adding.c -o testit
70 + ~~~~
71 +
72 +- **Funk: Funk it.**
73 +
74 + ~~~~ sh
75 + $ ./testit
76 + ~~~~
77 +
78 +## The Clar Test Suite
79 +
80 +Writing a test suite is pretty straightforward. Each test suite is a `*.c`
81 +file with a descriptive name: this encourages modularity.
82 +
83 +Each test suite has optional initialize and cleanup methods. These methods
84 +will be called before and after running **each** test in the suite, even if
85 +such test fails. As a rule of thumb, if a test needs a different initializer
86 +or cleanup method than another test in the same module, that means it
87 +doesn't belong in that module. Keep that in mind when grouping tests
88 +together.
89 +
90 +The `initialize` and `cleanup` methods have the following syntax, with
91 +`suitename` being the current suite name, e.g. `adding` for the `adding.c`
92 +suite.
93 +
94 +~~~~ c
95 +void test_suitename__initialize(void)
96 +{
97 + /* init */
98 +}
99 +
100 +void test_suitename__cleanup(void)
101 +{
102 + /* cleanup */
103 +}
104 +~~~~
105 +
106 +These methods are encouraged to use static, global variables to store the state
107 +that will be used by all tests inside the suite.
108 +
109 +~~~~ c
110 +static git_repository *_repository;
111 +
112 +void test_status__initialize(void)
113 +{
114 + create_tmp_repo(STATUS_REPO);
115 + git_repository_open(_repository, STATUS_REPO);
116 +}
117 +
118 +void test_status__cleanup(void)
119 +{
120 + git_repository_close(_repository);
121 + git_path_rm(STATUS_REPO);
122 +}
123 +
124 +void test_status__simple_test(void)
125 +{
126 + /* do something with _repository */
127 +}
128 +~~~~
129 +
130 +Writing the actual tests is just as straightforward. Tests have the
131 +`void test_suitename__test_name(void)` signature, and they should **not**
132 +be static. Clar will automatically detect and list them.
133 +
134 +Tests are run as they appear on their original suites: they have no return
135 +value. A test is considered "passed" if it doesn't raise any errors. Check
136 +the "Clar API" section to see the various helper functions to check and
137 +raise errors during test execution.
138 +
139 +__Caution:__ If you use assertions inside of `test_suitename__initialize`,
140 +make sure that you do not rely on `__initialize` being completely run
141 +inside your `test_suitename__cleanup` function. Otherwise you might
142 +encounter ressource cleanup twice.
143 +
144 +## How does Clar work?
145 +
146 +To use Clar:
147 +
148 +1. copy the Clar boilerplate to your test directory
149 +2. copy (and probably modify) the sample `main.c` (from
150 + `$CLAR_PATH/test/main.c.sample`)
151 +3. run the Clar mixer (a.k.a. `generate.py`) to scan your test directory and
152 + write out the test suite metadata.
153 +4. compile your test files and the Clar boilerplate into a single test
154 + executable
155 +5. run the executable to test!
156 +
157 +The Clar boilerplate gives you a set of useful test assertions and features
158 +(like accessing or making sandbox copies of fixture data). It consists of
159 +the `clar.c` and `clar.h` files, plus the code in the `clar/` subdirectory.
160 +You should not need to edit these files.
161 +
162 +The sample `main.c` (i.e. `$CLAR_PATH/test/main.c.sample`) file invokes
163 +`clar_test(argc, argv)` to run the tests. Usually, you will edit this file
164 +to perform any framework specific initialization and teardown that you need.
165 +
166 +The Clar mixer (`generate.py`) recursively scans your test directory for
167 +any `.c` files, parses them, and writes the `clar.suite` file with all of
168 +the metadata about your tests. When you build, the `clar.suite` file is
169 +included into `clar.c`.
170 +
171 +The mixer can be run with **Python 2.5, 2.6, 2.7, 3.0, 3.1, 3.2 and PyPy 1.6**.
172 +
173 +Commandline usage of the mixer is as follows:
174 +
175 + $ ./generate.py .
176 +
177 +Where `.` is the folder where all the test suites can be found. The mixer
178 +will automatically locate all the relevant source files and build the
179 +testing metadata. The metadata will be written to `clar.suite`, in the same
180 +folder as all the test suites. This file is included by `clar.c` and so
181 +must be accessible via `#include` when building the test executable.
182 +
183 + $ gcc -I. clar.c main.c suite1.c test2.c -o run_tests
184 +
185 +**Note that the Clar mixer only needs to be ran when adding new tests to a
186 +suite, in order to regenerate the metadata**. As a result, the `clar.suite`
187 +file can be checked into version control if you wish to be able to build
188 +your test suite without having to re-run the mixer.
189 +
190 +This is handy when e.g. generating tests in a local computer, and then
191 +building and testing them on an embedded device or a platform where Python
192 +is not available.
193 +
194 +### Fixtures
195 +
196 +Clar can create sandboxed fixtures for you to use in your test. You'll need to compile *clar.c* with an additional `CFLAG`, `-DCLAR_FIXTURE_PATH`. This should be an absolute path to your fixtures directory.
197 +
198 +Once that's done, you can use the fixture API as defined below.
199 +
200 +## The Clar API
201 +
202 +Clar makes the following methods available from all functions in a test
203 +suite.
204 +
205 +- `cl_must_pass(call)`, `cl_must_pass_(call, message)`: Verify that the given
206 + function call passes, in the POSIX sense (returns a value greater or equal
207 + to 0).
208 +
209 +- `cl_must_fail(call)`, `cl_must_fail_(call, message)`: Verify that the given
210 + function call fails, in the POSIX sense (returns a value less than 0).
211 +
212 +- `cl_assert(expr)`, `cl_assert_(expr, message)`: Verify that `expr` is true.
213 +
214 +- `cl_check_pass(call)`, `cl_check_pass_(call, message)`: Verify that the
215 + given function call passes, in the POSIX sense (returns a value greater or
216 + equal to 0). If the function call doesn't succeed, a test failure will be
217 + logged but the test's execution will continue.
218 +
219 +- `cl_check_fail(call)`, `cl_check_fail_(call, message)`: Verify that the
220 + given function call fails, in the POSIX sense (returns a value less than
221 + 0). If the function call doesn't fail, a test failure will be logged but
222 + the test's execution will continue.
223 +
224 +- `cl_check(expr)`: Verify that `expr` is true. If `expr` is not
225 + true, a test failure will be logged but the test's execution will continue.
226 +
227 +- `cl_fail(message)`: Fail the current test with the given message.
228 +
229 +- `cl_warning(message)`: Issue a warning. This warning will be
230 + logged as a test failure but the test's execution will continue.
231 +
232 +- `cl_set_cleanup(void (*cleanup)(void *), void *opaque)`: Set the cleanup
233 + method for a single test. This method will be called with `opaque` as its
234 + argument before the test returns (even if the test has failed).
235 + If a global cleanup method is also available, the local cleanup will be
236 + called first, and then the global.
237 +
238 +- `cl_assert_equal_i(int,int)`: Verify that two integer values are equal.
239 + The advantage of this over a simple `cl_assert` is that it will format
240 + a much nicer error report if the values are not equal.
241 +
242 +- `cl_assert_equal_s(const char *,const char *)`: Verify that two strings
243 + are equal. The expected value can also be NULL and this will correctly
244 + test for that.
245 +
246 +- `cl_fixture_sandbox(const char *)`: Sets up a sandbox for a fixture
247 + so that you can mutate the file directly.
248 +
249 +- `cl_fixture_cleanup(const char *)`: Tears down the previous fixture
250 + sandbox.
251 +
252 +- `cl_fixture(const char *)`: Gets the full path to a fixture file.
253 +
254 +Please do note that these methods are *always* available whilst running a
255 +test, even when calling auxiliary/static functions inside the same file.
256 +
257 +It's strongly encouraged to perform test assertions in auxiliary methods,
258 +instead of returning error values. This is considered good Clar style.
259 +
260 +Style Example:
261 +
262 +~~~~ c
263 +/*
264 + * Bad style: auxiliary functions return an error code
265 + */
266 +
267 +static int check_string(const char *str)
268 +{
269 + const char *aux = process_string(str);
270 +
271 + if (aux == NULL)
272 + return -1;
273 +
274 + return strcmp(my_function(aux), str) == 0 ? 0 : -1;
275 +}
276 +
277 +void test_example__a_test_with_auxiliary_methods(void)
278 +{
279 + cl_must_pass_(
280 + check_string("foo"),
281 + "String differs after processing"
282 + );
283 +
284 + cl_must_pass_(
285 + check_string("bar"),
286 + "String differs after processing"
287 + );
288 +}
289 +~~~~
290 +
291 +~~~~ c
292 +/*
293 + * Good style: auxiliary functions perform assertions
294 + */
295 +
296 +static void check_string(const char *str)
297 +{
298 + const char *aux = process_string(str);
299 +
300 + cl_assert_(
301 + aux != NULL,
302 + "String processing failed"
303 + );
304 +
305 + cl_assert_(
306 + strcmp(my_function(aux), str) == 0,
307 + "String differs after processing"
308 + );
309 +}
310 +
311 +void test_example__a_test_with_auxiliary_methods(void)
312 +{
313 + check_string("foo");
314 + check_string("bar");
315 +}
316 +~~~~
317 +
318 +About Clar
319 +==========
320 +
321 +Clar has been written from scratch by [Vicent Martí](https://github.com/vmg),
322 +to replace the old testing framework in [libgit2][libgit2].
323 +
324 +Do you know what languages are *in* on the SF startup scene? Node.js *and*
325 +Latin. Follow [@vmg](https://www.twitter.com/vmg) on Twitter to
326 +receive more lessons on word etymology. You can be hip too.
327 +
328 +
329 +[libgit2]: https://github.com/libgit2/libgit2
t/unit-tests/clar/clar.c new
+842
@@ -0,0 +1,842 @@
1 +/*
2 + * Copyright (c) Vicent Marti. All rights reserved.
3 + *
4 + * This file is part of clar, distributed under the ISC license.
5 + * For full terms see the included COPYING file.
6 + */
7 +#include <assert.h>
8 +#include <setjmp.h>
9 +#include <stdlib.h>
10 +#include <stdio.h>
11 +#include <string.h>
12 +#include <math.h>
13 +#include <stdarg.h>
14 +#include <wchar.h>
15 +#include <time.h>
16 +
17 +/* required for sandboxing */
18 +#include <sys/types.h>
19 +#include <sys/stat.h>
20 +
21 +#ifdef _WIN32
22 +# include <windows.h>
23 +# include <io.h>
24 +# include <shellapi.h>
25 +# include <direct.h>
26 +
27 +# define _MAIN_CC __cdecl
28 +
29 +# ifndef stat
30 +# define stat(path, st) _stat(path, st)
31 +# endif
32 +# ifndef mkdir
33 +# define mkdir(path, mode) _mkdir(path)
34 +# endif
35 +# ifndef chdir
36 +# define chdir(path) _chdir(path)
37 +# endif
38 +# ifndef access
39 +# define access(path, mode) _access(path, mode)
40 +# endif
41 +# ifndef strdup
42 +# define strdup(str) _strdup(str)
43 +# endif
44 +# ifndef strcasecmp
45 +# define strcasecmp(a,b) _stricmp(a,b)
46 +# endif
47 +
48 +# ifndef __MINGW32__
49 +# pragma comment(lib, "shell32")
50 +# ifndef strncpy
51 +# define strncpy(to, from, to_size) strncpy_s(to, to_size, from, _TRUNCATE)
52 +# endif
53 +# ifndef W_OK
54 +# define W_OK 02
55 +# endif
56 +# ifndef S_ISDIR
57 +# define S_ISDIR(x) ((x & _S_IFDIR) != 0)
58 +# endif
59 +# define p_snprintf(buf,sz,fmt,...) _snprintf_s(buf,sz,_TRUNCATE,fmt,__VA_ARGS__)
60 +# else
61 +# define p_snprintf snprintf
62 +# endif
63 +
64 +# ifndef PRIuZ
65 +# define PRIuZ "Iu"
66 +# endif
67 +# ifndef PRIxZ
68 +# define PRIxZ "Ix"
69 +# endif
70 +
71 +# if defined(_MSC_VER) || defined(__MINGW32__)
72 + typedef struct stat STAT_T;
73 +# else
74 + typedef struct _stat STAT_T;
75 +# endif
76 +#else
77 +# include <sys/wait.h> /* waitpid(2) */
78 +# include <unistd.h>
79 +# define _MAIN_CC
80 +# define p_snprintf snprintf
81 +# ifndef PRIuZ
82 +# define PRIuZ "zu"
83 +# endif
84 +# ifndef PRIxZ
85 +# define PRIxZ "zx"
86 +# endif
87 + typedef struct stat STAT_T;
88 +#endif
89 +
90 +#define MAX(x, y) (((x) > (y)) ? (x) : (y))
91 +
92 +#include "clar.h"
93 +
94 +static void fs_rm(const char *_source);
95 +static void fs_copy(const char *_source, const char *dest);
96 +
97 +#ifdef CLAR_FIXTURE_PATH
98 +static const char *
99 +fixture_path(const char *base, const char *fixture_name);
100 +#endif
101 +
102 +struct clar_error {
103 + const char *file;
104 + const char *function;
105 + size_t line_number;
106 + const char *error_msg;
107 + char *description;
108 +
109 + struct clar_error *next;
110 +};
111 +
112 +struct clar_explicit {
113 + size_t suite_idx;
114 + const char *filter;
115 +
116 + struct clar_explicit *next;
117 +};
118 +
119 +struct clar_report {
120 + const char *test;
121 + int test_number;
122 + const char *suite;
123 +
124 + enum cl_test_status status;
125 + time_t start;
126 + double elapsed;
127 +
128 + struct clar_error *errors;
129 + struct clar_error *last_error;
130 +
131 + struct clar_report *next;
132 +};
133 +
134 +struct clar_summary {
135 + const char *filename;
136 + FILE *fp;
137 +};
138 +
139 +static struct {
140 + enum cl_test_status test_status;
141 +
142 + const char *active_test;
143 + const char *active_suite;
144 +
145 + int total_skipped;
146 + int total_errors;
147 +
148 + int tests_ran;
149 + int suites_ran;
150 +
151 + enum cl_output_format output_format;
152 +
153 + int report_errors_only;
154 + int exit_on_error;
155 + int verbosity;
156 +
157 + int write_summary;
158 + char *summary_filename;
159 + struct clar_summary *summary;
160 +
161 + struct clar_explicit *explicit;
162 + struct clar_explicit *last_explicit;
163 +
164 + struct clar_report *reports;
165 + struct clar_report *last_report;
166 +
167 + void (*local_cleanup)(void *);
168 + void *local_cleanup_payload;
169 +
170 + jmp_buf trampoline;
171 + int trampoline_enabled;
172 +
173 + cl_trace_cb *pfn_trace_cb;
174 + void *trace_payload;
175 +
176 +} _clar;
177 +
178 +struct clar_func {
179 + const char *name;
180 + void (*ptr)(void);
181 +};
182 +
183 +struct clar_suite {
184 + const char *name;
185 + struct clar_func initialize;
186 + struct clar_func cleanup;
187 + const struct clar_func *tests;
188 + size_t test_count;
189 + int enabled;
190 +};
191 +
192 +/* From clar_print_*.c */
193 +static void clar_print_init(int test_count, int suite_count, const char *suite_names);
194 +static void clar_print_shutdown(int test_count, int suite_count, int error_count);
195 +static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error);
196 +static void clar_print_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status failed);
197 +static void clar_print_onsuite(const char *suite_name, int suite_index);
198 +static void clar_print_onabort(const char *msg, ...);
199 +
200 +/* From clar_sandbox.c */
201 +static void clar_unsandbox(void);
202 +static int clar_sandbox(void);
203 +
204 +/* From summary.h */
205 +static struct clar_summary *clar_summary_init(const char *filename);
206 +static int clar_summary_shutdown(struct clar_summary *fp);
207 +
208 +/* Load the declarations for the test suite */
209 +#include "clar.suite"
210 +
211 +
212 +#define CL_TRACE(ev) \
213 + do { \
214 + if (_clar.pfn_trace_cb) \
215 + _clar.pfn_trace_cb(ev, \
216 + _clar.active_suite, \
217 + _clar.active_test, \
218 + _clar.trace_payload); \
219 + } while (0)
220 +
221 +void cl_trace_register(cl_trace_cb *cb, void *payload)
222 +{
223 + _clar.pfn_trace_cb = cb;
224 + _clar.trace_payload = payload;
225 +}
226 +
227 +
228 +/* Core test functions */
229 +static void
230 +clar_report_errors(struct clar_report *report)
231 +{
232 + struct clar_error *error;
233 + int i = 1;
234 +
235 + for (error = report->errors; error; error = error->next)
236 + clar_print_error(i++, _clar.last_report, error);
237 +}
238 +
239 +static void
240 +clar_report_all(void)
241 +{
242 + struct clar_report *report;
243 + struct clar_error *error;
244 + int i = 1;
245 +
246 + for (report = _clar.reports; report; report = report->next) {
247 + if (report->status != CL_TEST_FAILURE)
248 + continue;
249 +
250 + for (error = report->errors; error; error = error->next)
251 + clar_print_error(i++, report, error);
252 + }
253 +}
254 +
255 +#ifdef WIN32
256 +# define clar_time DWORD
257 +
258 +static void clar_time_now(clar_time *out)
259 +{
260 + *out = GetTickCount();
261 +}
262 +
263 +static double clar_time_diff(clar_time *start, clar_time *end)
264 +{
265 + return ((double)*end - (double)*start) / 1000;
266 +}
267 +#else
268 +# include <sys/time.h>
269 +
270 +# define clar_time struct timeval
271 +
272 +static void clar_time_now(clar_time *out)
273 +{
274 + struct timezone tz;
275 +
276 + gettimeofday(out, &tz);
277 +}
278 +
279 +static double clar_time_diff(clar_time *start, clar_time *end)
280 +{
281 + return ((double)end->tv_sec + (double)end->tv_usec / 1.0E6) -
282 + ((double)start->tv_sec + (double)start->tv_usec / 1.0E6);
283 +}
284 +#endif
285 +
286 +static void
287 +clar_run_test(
288 + const struct clar_suite *suite,
289 + const struct clar_func *test,
290 + const struct clar_func *initialize,
291 + const struct clar_func *cleanup)
292 +{
293 + clar_time start, end;
294 +
295 + _clar.trampoline_enabled = 1;
296 +
297 + CL_TRACE(CL_TRACE__TEST__BEGIN);
298 +
299 + _clar.last_report->start = time(NULL);
300 + clar_time_now(&start);
301 +
302 + if (setjmp(_clar.trampoline) == 0) {
303 + if (initialize->ptr != NULL)
304 + initialize->ptr();
305 +
306 + CL_TRACE(CL_TRACE__TEST__RUN_BEGIN);
307 + test->ptr();
308 + CL_TRACE(CL_TRACE__TEST__RUN_END);
309 + }
310 +
311 + clar_time_now(&end);
312 +
313 + _clar.trampoline_enabled = 0;
314 +
315 + if (_clar.last_report->status == CL_TEST_NOTRUN)
316 + _clar.last_report->status = CL_TEST_OK;
317 +
318 + _clar.last_report->elapsed = clar_time_diff(&start, &end);
319 +
320 + if (_clar.local_cleanup != NULL)
321 + _clar.local_cleanup(_clar.local_cleanup_payload);
322 +
323 + if (cleanup->ptr != NULL)
324 + cleanup->ptr();
325 +
326 + CL_TRACE(CL_TRACE__TEST__END);
327 +
328 + _clar.tests_ran++;
329 +
330 + /* remove any local-set cleanup methods */
331 + _clar.local_cleanup = NULL;
332 + _clar.local_cleanup_payload = NULL;
333 +
334 + if (_clar.report_errors_only) {
335 + clar_report_errors(_clar.last_report);
336 + } else {
337 + clar_print_ontest(suite->name, test->name, _clar.tests_ran, _clar.last_report->status);
338 + }
339 +}
340 +
341 +static void
342 +clar_run_suite(const struct clar_suite *suite, const char *filter)
343 +{
344 + const struct clar_func *test = suite->tests;
345 + size_t i, matchlen;
346 + struct clar_report *report;
347 + int exact = 0;
348 +
349 + if (!suite->enabled)
350 + return;
351 +
352 + if (_clar.exit_on_error && _clar.total_errors)
353 + return;
354 +
355 + if (!_clar.report_errors_only)
356 + clar_print_onsuite(suite->name, ++_clar.suites_ran);
357 +
358 + _clar.active_suite = suite->name;
359 + _clar.active_test = NULL;
360 + CL_TRACE(CL_TRACE__SUITE_BEGIN);
361 +
362 + if (filter) {
363 + size_t suitelen = strlen(suite->name);
364 + matchlen = strlen(filter);
365 + if (matchlen <= suitelen) {
366 + filter = NULL;
367 + } else {
368 + filter += suitelen;
369 + while (*filter == ':')
370 + ++filter;
371 + matchlen = strlen(filter);
372 +
373 + if (matchlen && filter[matchlen - 1] == '$') {
374 + exact = 1;
375 + matchlen--;
376 + }
377 + }
378 + }
379 +
380 + for (i = 0; i < suite->test_count; ++i) {
381 + if (filter && strncmp(test[i].name, filter, matchlen))
382 + continue;
383 +
384 + if (exact && strlen(test[i].name) != matchlen)
385 + continue;
386 +
387 + _clar.active_test = test[i].name;
388 +
389 + report = calloc(1, sizeof(struct clar_report));
390 + report->suite = _clar.active_suite;
391 + report->test = _clar.active_test;
392 + report->test_number = _clar.tests_ran;
393 + report->status = CL_TEST_NOTRUN;
394 +
395 + if (_clar.reports == NULL)
396 + _clar.reports = report;
397 +
398 + if (_clar.last_report != NULL)
399 + _clar.last_report->next = report;
400 +
401 + _clar.last_report = report;
402 +
403 + clar_run_test(suite, &test[i], &suite->initialize, &suite->cleanup);
404 +
405 + if (_clar.exit_on_error && _clar.total_errors)
406 + return;
407 + }
408 +
409 + _clar.active_test = NULL;
410 + CL_TRACE(CL_TRACE__SUITE_END);
411 +}
412 +
413 +static void
414 +clar_usage(const char *arg)
415 +{
416 + printf("Usage: %s [options]\n\n", arg);
417 + printf("Options:\n");
418 + printf(" -sname Run only the suite with `name` (can go to individual test name)\n");
419 + printf(" -iname Include the suite with `name`\n");
420 + printf(" -xname Exclude the suite with `name`\n");
421 + printf(" -v Increase verbosity (show suite names)\n");
422 + printf(" -q Only report tests that had an error\n");
423 + printf(" -Q Quit as soon as a test fails\n");
424 + printf(" -t Display results in tap format\n");
425 + printf(" -l Print suite names\n");
426 + printf(" -r[filename] Write summary file (to the optional filename)\n");
427 + exit(-1);
428 +}
429 +
430 +static void
431 +clar_parse_args(int argc, char **argv)
432 +{
433 + int i;
434 +
435 + /* Verify options before execute */
436 + for (i = 1; i < argc; ++i) {
437 + char *argument = argv[i];
438 +
439 + if (argument[0] != '-' || argument[1] == '\0'
440 + || strchr("sixvqQtlr", argument[1]) == NULL) {
441 + clar_usage(argv[0]);
442 + }
443 + }
444 +
445 + for (i = 1; i < argc; ++i) {
446 + char *argument = argv[i];
447 +
448 + switch (argument[1]) {
449 + case 's':
450 + case 'i':
451 + case 'x': { /* given suite name */
452 + int offset = (argument[2] == '=') ? 3 : 2, found = 0;
453 + char action = argument[1];
454 + size_t j, arglen, suitelen, cmplen;
455 +
456 + argument += offset;
457 + arglen = strlen(argument);
458 +
459 + if (arglen == 0)
460 + clar_usage(argv[0]);
461 +
462 + for (j = 0; j < _clar_suite_count; ++j) {
463 + suitelen = strlen(_clar_suites[j].name);
464 + cmplen = (arglen < suitelen) ? arglen : suitelen;
465 +
466 + if (strncmp(argument, _clar_suites[j].name, cmplen) == 0) {
467 + int exact = (arglen >= suitelen);
468 +
469 + /* Do we have a real suite prefix separated by a
470 + * trailing '::' or just a matching substring? */
471 + if (arglen > suitelen && (argument[suitelen] != ':'
472 + || argument[suitelen + 1] != ':'))
473 + continue;
474 +
475 + ++found;
476 +
477 + if (!exact)
478 + _clar.verbosity = MAX(_clar.verbosity, 1);
479 +
480 + switch (action) {
481 + case 's': {
482 + struct clar_explicit *explicit =
483 + calloc(1, sizeof(struct clar_explicit));
484 + assert(explicit);
485 +
486 + explicit->suite_idx = j;
487 + explicit->filter = argument;
488 +
489 + if (_clar.explicit == NULL)
490 + _clar.explicit = explicit;
491 +
492 + if (_clar.last_explicit != NULL)
493 + _clar.last_explicit->next = explicit;
494 +
495 + _clar_suites[j].enabled = 1;
496 + _clar.last_explicit = explicit;
497 + break;
498 + }
499 + case 'i': _clar_suites[j].enabled = 1; break;
500 + case 'x': _clar_suites[j].enabled = 0; break;
501 + }
502 +
503 + if (exact)
504 + break;
505 + }
506 + }
507 +
508 + if (!found) {
509 + clar_print_onabort("No suite matching '%s' found.\n", argument);
510 + exit(-1);
511 + }
512 + break;
513 + }
514 +
515 + case 'q':
516 + _clar.report_errors_only = 1;
517 + break;
518 +
519 + case 'Q':
520 + _clar.exit_on_error = 1;
521 + break;
522 +
523 + case 't':
524 + _clar.output_format = CL_OUTPUT_TAP;
525 + break;
526 +
527 + case 'l': {
528 + size_t j;
529 + printf("Test suites (use -s<name> to run just one):\n");
530 + for (j = 0; j < _clar_suite_count; ++j)
531 + printf(" %3d: %s\n", (int)j, _clar_suites[j].name);
532 +
533 + exit(0);
534 + }
535 +
536 + case 'v':
537 + _clar.verbosity++;
538 + break;
539 +
540 + case 'r':
541 + _clar.write_summary = 1;
542 + free(_clar.summary_filename);
543 + _clar.summary_filename = *(argument + 2) ? strdup(argument + 2) : NULL;
544 + break;
545 +
546 + default:
547 + assert(!"Unexpected commandline argument!");
548 + }
549 + }
550 +}
551 +
552 +void
553 +clar_test_init(int argc, char **argv)
554 +{
555 + const char *summary_env;
556 +
557 + if (argc > 1)
558 + clar_parse_args(argc, argv);
559 +
560 + clar_print_init(
561 + (int)_clar_callback_count,
562 + (int)_clar_suite_count,
563 + ""
564 + );
565 +
566 + if (!_clar.summary_filename &&
567 + (summary_env = getenv("CLAR_SUMMARY")) != NULL) {
568 + _clar.write_summary = 1;
569 + _clar.summary_filename = strdup(summary_env);
570 + }
571 +
572 + if (_clar.write_summary && !_clar.summary_filename)
573 + _clar.summary_filename = strdup("summary.xml");
574 +
575 + if (_clar.write_summary &&
576 + !(_clar.summary = clar_summary_init(_clar.summary_filename))) {
577 + clar_print_onabort("Failed to open the summary file\n");
578 + exit(-1);
579 + }
580 +
581 + if (clar_sandbox() < 0) {
582 + clar_print_onabort("Failed to sandbox the test runner.\n");
583 + exit(-1);
584 + }
585 +}
586 +
587 +int
588 +clar_test_run(void)
589 +{
590 + size_t i;
591 + struct clar_explicit *explicit;
592 +
593 + if (_clar.explicit) {
594 + for (explicit = _clar.explicit; explicit; explicit = explicit->next)
595 + clar_run_suite(&_clar_suites[explicit->suite_idx], explicit->filter);
596 + } else {
597 + for (i = 0; i < _clar_suite_count; ++i)
598 + clar_run_suite(&_clar_suites[i], NULL);
599 + }
600 +
601 + return _clar.total_errors;
602 +}
603 +
604 +void
605 +clar_test_shutdown(void)
606 +{
607 + struct clar_explicit *explicit, *explicit_next;
608 + struct clar_report *report, *report_next;
609 +
610 + clar_print_shutdown(
611 + _clar.tests_ran,
612 + (int)_clar_suite_count,
613 + _clar.total_errors
614 + );
615 +
616 + clar_unsandbox();
617 +
618 + if (_clar.write_summary && clar_summary_shutdown(_clar.summary) < 0) {
619 + clar_print_onabort("Failed to write the summary file\n");
620 + exit(-1);
621 + }
622 +
623 + for (explicit = _clar.explicit; explicit; explicit = explicit_next) {
624 + explicit_next = explicit->next;
625 + free(explicit);
626 + }
627 +
628 + for (report = _clar.reports; report; report = report_next) {
629 + report_next = report->next;
630 + free(report);
631 + }
632 +
633 + free(_clar.summary_filename);
634 +}
635 +
636 +int
637 +clar_test(int argc, char **argv)
638 +{
639 + int errors;
640 +
641 + clar_test_init(argc, argv);
642 + errors = clar_test_run();
643 + clar_test_shutdown();
644 +
645 + return errors;
646 +}
647 +
648 +static void abort_test(void)
649 +{
650 + if (!_clar.trampoline_enabled) {
651 + clar_print_onabort(
652 + "Fatal error: a cleanup method raised an exception.");
653 + clar_report_errors(_clar.last_report);
654 + exit(-1);
655 + }
656 +
657 + CL_TRACE(CL_TRACE__TEST__LONGJMP);
658 + longjmp(_clar.trampoline, -1);
659 +}
660 +
661 +void clar__skip(void)
662 +{
663 + _clar.last_report->status = CL_TEST_SKIP;
664 + _clar.total_skipped++;
665 + abort_test();
666 +}
667 +
668 +void clar__fail(
669 + const char *file,
670 + const char *function,
671 + size_t line,
672 + const char *error_msg,
673 + const char *description,
674 + int should_abort)
675 +{
676 + struct clar_error *error = calloc(1, sizeof(struct clar_error));
677 +
678 + if (_clar.last_report->errors == NULL)
679 + _clar.last_report->errors = error;
680 +
681 + if (_clar.last_report->last_error != NULL)
682 + _clar.last_report->last_error->next = error;
683 +
684 + _clar.last_report->last_error = error;
685 +
686 + error->file = file;
687 + error->function = function;
688 + error->line_number = line;
689 + error->error_msg = error_msg;
690 +
691 + if (description != NULL)
692 + error->description = strdup(description);
693 +
694 + _clar.total_errors++;
695 + _clar.last_report->status = CL_TEST_FAILURE;
696 +
697 + if (should_abort)
698 + abort_test();
699 +}
700 +
701 +void clar__assert(
702 + int condition,
703 + const char *file,
704 + const char *function,
705 + size_t line,
706 + const char *error_msg,
707 + const char *description,
708 + int should_abort)
709 +{
710 + if (condition)
711 + return;
712 +
713 + clar__fail(file, function, line, error_msg, description, should_abort);
714 +}
715 +
716 +void clar__assert_equal(
717 + const char *file,
718 + const char *function,
719 + size_t line,
720 + const char *err,
721 + int should_abort,
722 + const char *fmt,
723 + ...)
724 +{
725 + va_list args;
726 + char buf[4096];
727 + int is_equal = 1;
728 +
729 + va_start(args, fmt);
730 +
731 + if (!strcmp("%s", fmt)) {
732 + const char *s1 = va_arg(args, const char *);
733 + const char *s2 = va_arg(args, const char *);
734 + is_equal = (!s1 || !s2) ? (s1 == s2) : !strcmp(s1, s2);
735 +
736 + if (!is_equal) {
737 + if (s1 && s2) {
738 + int pos;
739 + for (pos = 0; s1[pos] == s2[pos] && s1[pos] && s2[pos]; ++pos)
740 + /* find differing byte offset */;
741 + p_snprintf(buf, sizeof(buf), "'%s' != '%s' (at byte %d)",
742 + s1, s2, pos);
743 + } else {
744 + p_snprintf(buf, sizeof(buf), "'%s' != '%s'", s1, s2);
745 + }
746 + }
747 + }
748 + else if(!strcmp("%.*s", fmt)) {
749 + const char *s1 = va_arg(args, const char *);
750 + const char *s2 = va_arg(args, const char *);
751 + int len = va_arg(args, int);
752 + is_equal = (!s1 || !s2) ? (s1 == s2) : !strncmp(s1, s2, len);
753 +
754 + if (!is_equal) {
755 + if (s1 && s2) {
756 + int pos;
757 + for (pos = 0; s1[pos] == s2[pos] && pos < len; ++pos)
758 + /* find differing byte offset */;
759 + p_snprintf(buf, sizeof(buf), "'%.*s' != '%.*s' (at byte %d)",
760 + len, s1, len, s2, pos);
761 + } else {
762 + p_snprintf(buf, sizeof(buf), "'%.*s' != '%.*s'", len, s1, len, s2);
763 + }
764 + }
765 + }
766 + else if (!strcmp("%ls", fmt)) {
767 + const wchar_t *wcs1 = va_arg(args, const wchar_t *);
768 + const wchar_t *wcs2 = va_arg(args, const wchar_t *);
769 + is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcscmp(wcs1, wcs2);
770 +
771 + if (!is_equal) {
772 + if (wcs1 && wcs2) {
773 + int pos;
774 + for (pos = 0; wcs1[pos] == wcs2[pos] && wcs1[pos] && wcs2[pos]; ++pos)
775 + /* find differing byte offset */;
776 + p_snprintf(buf, sizeof(buf), "'%ls' != '%ls' (at byte %d)",
777 + wcs1, wcs2, pos);
778 + } else {
779 + p_snprintf(buf, sizeof(buf), "'%ls' != '%ls'", wcs1, wcs2);
780 + }
781 + }
782 + }
783 + else if(!strcmp("%.*ls", fmt)) {
784 + const wchar_t *wcs1 = va_arg(args, const wchar_t *);
785 + const wchar_t *wcs2 = va_arg(args, const wchar_t *);
786 + int len = va_arg(args, int);
787 + is_equal = (!wcs1 || !wcs2) ? (wcs1 == wcs2) : !wcsncmp(wcs1, wcs2, len);
788 +
789 + if (!is_equal) {
790 + if (wcs1 && wcs2) {
791 + int pos;
792 + for (pos = 0; wcs1[pos] == wcs2[pos] && pos < len; ++pos)
793 + /* find differing byte offset */;
794 + p_snprintf(buf, sizeof(buf), "'%.*ls' != '%.*ls' (at byte %d)",
795 + len, wcs1, len, wcs2, pos);
796 + } else {
797 + p_snprintf(buf, sizeof(buf), "'%.*ls' != '%.*ls'", len, wcs1, len, wcs2);
798 + }
799 + }
800 + }
801 + else if (!strcmp("%"PRIuZ, fmt) || !strcmp("%"PRIxZ, fmt)) {
802 + size_t sz1 = va_arg(args, size_t), sz2 = va_arg(args, size_t);
803 + is_equal = (sz1 == sz2);
804 + if (!is_equal) {
805 + int offset = p_snprintf(buf, sizeof(buf), fmt, sz1);
806 + strncat(buf, " != ", sizeof(buf) - offset);
807 + p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, sz2);
808 + }
809 + }
810 + else if (!strcmp("%p", fmt)) {
811 + void *p1 = va_arg(args, void *), *p2 = va_arg(args, void *);
812 + is_equal = (p1 == p2);
813 + if (!is_equal)
814 + p_snprintf(buf, sizeof(buf), "%p != %p", p1, p2);
815 + }
816 + else {
817 + int i1 = va_arg(args, int), i2 = va_arg(args, int);
818 + is_equal = (i1 == i2);
819 + if (!is_equal) {
820 + int offset = p_snprintf(buf, sizeof(buf), fmt, i1);
821 + strncat(buf, " != ", sizeof(buf) - offset);
822 + p_snprintf(buf + offset + 4, sizeof(buf) - offset - 4, fmt, i2);
823 + }
824 + }
825 +
826 + va_end(args);
827 +
828 + if (!is_equal)
829 + clar__fail(file, function, line, err, buf, should_abort);
830 +}
831 +
832 +void cl_set_cleanup(void (*cleanup)(void *), void *opaque)
833 +{
834 + _clar.local_cleanup = cleanup;
835 + _clar.local_cleanup_payload = opaque;
836 +}
837 +
838 +#include "clar/sandbox.h"
839 +#include "clar/fixtures.h"
840 +#include "clar/fs.h"
841 +#include "clar/print.h"
842 +#include "clar/summary.h"
t/unit-tests/clar/clar.h new
+173
@@ -0,0 +1,173 @@
1 +/*
2 + * Copyright (c) Vicent Marti. All rights reserved.
3 + *
4 + * This file is part of clar, distributed under the ISC license.
5 + * For full terms see the included COPYING file.
6 + */
7 +#ifndef __CLAR_TEST_H__
8 +#define __CLAR_TEST_H__
9 +
10 +#include <stdlib.h>
11 +
12 +enum cl_test_status {
13 + CL_TEST_OK,
14 + CL_TEST_FAILURE,
15 + CL_TEST_SKIP,
16 + CL_TEST_NOTRUN,
17 +};
18 +
19 +enum cl_output_format {
20 + CL_OUTPUT_CLAP,
21 + CL_OUTPUT_TAP,
22 +};
23 +
24 +/** Setup clar environment */
25 +void clar_test_init(int argc, char *argv[]);
26 +int clar_test_run(void);
27 +void clar_test_shutdown(void);
28 +
29 +/** One shot setup & run */
30 +int clar_test(int argc, char *argv[]);
31 +
32 +const char *clar_sandbox_path(void);
33 +
34 +void cl_set_cleanup(void (*cleanup)(void *), void *opaque);
35 +void cl_fs_cleanup(void);
36 +
37 +/**
38 + * cl_trace_* is a hook to provide a simple global tracing
39 + * mechanism.
40 + *
41 + * The goal here is to let main() provide clar-proper
42 + * with a callback to optionally write log info for
43 + * test operations into the same stream used by their
44 + * actual tests. This would let them print test names
45 + * and maybe performance data as they choose.
46 + *
47 + * The goal is NOT to alter the flow of control or to
48 + * override test selection/skipping. (So the callback
49 + * does not return a value.)
50 + *
51 + * The goal is NOT to duplicate the existing
52 + * pass/fail/skip reporting. (So the callback
53 + * does not accept a status/errorcode argument.)
54 + *
55 + */
56 +typedef enum cl_trace_event {
57 + CL_TRACE__SUITE_BEGIN,
58 + CL_TRACE__SUITE_END,
59 + CL_TRACE__TEST__BEGIN,
60 + CL_TRACE__TEST__END,
61 + CL_TRACE__TEST__RUN_BEGIN,
62 + CL_TRACE__TEST__RUN_END,
63 + CL_TRACE__TEST__LONGJMP,
64 +} cl_trace_event;
65 +
66 +typedef void (cl_trace_cb)(
67 + cl_trace_event ev,
68 + const char *suite_name,
69 + const char *test_name,
70 + void *payload);
71 +
72 +/**
73 + * Register a callback into CLAR to send global trace events.
74 + * Pass NULL to disable.
75 + */
76 +void cl_trace_register(cl_trace_cb *cb, void *payload);
77 +
78 +
79 +#ifdef CLAR_FIXTURE_PATH
80 +const char *cl_fixture(const char *fixture_name);
81 +void cl_fixture_sandbox(const char *fixture_name);
82 +void cl_fixture_cleanup(const char *fixture_name);
83 +const char *cl_fixture_basename(const char *fixture_name);
84 +#endif
85 +
86 +/**
87 + * Assertion macros with explicit error message
88 + */
89 +#define cl_must_pass_(expr, desc) clar__assert((expr) >= 0, __FILE__, __func__, __LINE__, "Function call failed: " #expr, desc, 1)
90 +#define cl_must_fail_(expr, desc) clar__assert((expr) < 0, __FILE__, __func__, __LINE__, "Expected function call to fail: " #expr, desc, 1)
91 +#define cl_assert_(expr, desc) clar__assert((expr) != 0, __FILE__, __func__, __LINE__, "Expression is not true: " #expr, desc, 1)
92 +
93 +/**
94 + * Check macros with explicit error message
95 + */
96 +#define cl_check_pass_(expr, desc) clar__assert((expr) >= 0, __FILE__, __func__, __LINE__, "Function call failed: " #expr, desc, 0)
97 +#define cl_check_fail_(expr, desc) clar__assert((expr) < 0, __FILE__, __func__, __LINE__, "Expected function call to fail: " #expr, desc, 0)
98 +#define cl_check_(expr, desc) clar__assert((expr) != 0, __FILE__, __func__, __LINE__, "Expression is not true: " #expr, desc, 0)
99 +
100 +/**
101 + * Assertion macros with no error message
102 + */
103 +#define cl_must_pass(expr) cl_must_pass_(expr, NULL)
104 +#define cl_must_fail(expr) cl_must_fail_(expr, NULL)
105 +#define cl_assert(expr) cl_assert_(expr, NULL)
106 +
107 +/**
108 + * Check macros with no error message
109 + */
110 +#define cl_check_pass(expr) cl_check_pass_(expr, NULL)
111 +#define cl_check_fail(expr) cl_check_fail_(expr, NULL)
112 +#define cl_check(expr) cl_check_(expr, NULL)
113 +
114 +/**
115 + * Forced failure/warning
116 + */
117 +#define cl_fail(desc) clar__fail(__FILE__, __func__, __LINE__, "Test failed.", desc, 1)
118 +#define cl_warning(desc) clar__fail(__FILE__, __func__, __LINE__, "Warning during test execution:", desc, 0)
119 +
120 +#define cl_skip() clar__skip()
121 +
122 +/**
123 + * Typed assertion macros
124 + */
125 +#define cl_assert_equal_s(s1,s2) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2, 1, "%s", (s1), (s2))
126 +#define cl_assert_equal_s_(s1,s2,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%s", (s1), (s2))
127 +
128 +#define cl_assert_equal_wcs(wcs1,wcs2) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2, 1, "%ls", (wcs1), (wcs2))
129 +#define cl_assert_equal_wcs_(wcs1,wcs2,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%ls", (wcs1), (wcs2))
130 +
131 +#define cl_assert_equal_strn(s1,s2,len) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2, 1, "%.*s", (s1), (s2), (int)(len))
132 +#define cl_assert_equal_strn_(s1,s2,len,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #s1 " != " #s2 " (" #note ")", 1, "%.*s", (s1), (s2), (int)(len))
133 +
134 +#define cl_assert_equal_wcsn(wcs1,wcs2,len) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2, 1, "%.*ls", (wcs1), (wcs2), (int)(len))
135 +#define cl_assert_equal_wcsn_(wcs1,wcs2,len,note) clar__assert_equal(__FILE__,__func__,__LINE__,"String mismatch: " #wcs1 " != " #wcs2 " (" #note ")", 1, "%.*ls", (wcs1), (wcs2), (int)(len))
136 +
137 +#define cl_assert_equal_i(i1,i2) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2, 1, "%d", (int)(i1), (int)(i2))
138 +#define cl_assert_equal_i_(i1,i2,note) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2 " (" #note ")", 1, "%d", (i1), (i2))
139 +#define cl_assert_equal_i_fmt(i1,i2,fmt) clar__assert_equal(__FILE__,__func__,__LINE__,#i1 " != " #i2, 1, (fmt), (int)(i1), (int)(i2))
140 +
141 +#define cl_assert_equal_b(b1,b2) clar__assert_equal(__FILE__,__func__,__LINE__,#b1 " != " #b2, 1, "%d", (int)((b1) != 0),(int)((b2) != 0))
142 +
143 +#define cl_assert_equal_p(p1,p2) clar__assert_equal(__FILE__,__func__,__LINE__,"Pointer mismatch: " #p1 " != " #p2, 1, "%p", (p1), (p2))
144 +
145 +void clar__skip(void);
146 +
147 +void clar__fail(
148 + const char *file,
149 + const char *func,
150 + size_t line,
151 + const char *error,
152 + const char *description,
153 + int should_abort);
154 +
155 +void clar__assert(
156 + int condition,
157 + const char *file,
158 + const char *func,
159 + size_t line,
160 + const char *error,
161 + const char *description,
162 + int should_abort);
163 +
164 +void clar__assert_equal(
165 + const char *file,
166 + const char *func,
167 + size_t line,
168 + const char *err,
169 + int should_abort,
170 + const char *fmt,
171 + ...);
172 +
173 +#endif
t/unit-tests/clar/clar/fixtures.h new
+50
@@ -0,0 +1,50 @@
1 +#ifdef CLAR_FIXTURE_PATH
2 +static const char *
3 +fixture_path(const char *base, const char *fixture_name)
4 +{
5 + static char _path[4096];
6 + size_t root_len;
7 +
8 + root_len = strlen(base);
9 + strncpy(_path, base, sizeof(_path));
10 +
11 + if (_path[root_len - 1] != '/')
12 + _path[root_len++] = '/';
13 +
14 + if (fixture_name[0] == '/')
15 + fixture_name++;
16 +
17 + strncpy(_path + root_len,
18 + fixture_name,
19 + sizeof(_path) - root_len);
20 +
21 + return _path;
22 +}
23 +
24 +const char *cl_fixture(const char *fixture_name)
25 +{
26 + return fixture_path(CLAR_FIXTURE_PATH, fixture_name);
27 +}
28 +
29 +void cl_fixture_sandbox(const char *fixture_name)
30 +{
31 + fs_copy(cl_fixture(fixture_name), _clar_path);
32 +}
33 +
34 +const char *cl_fixture_basename(const char *fixture_name)
35 +{
36 + const char *p;
37 +
38 + for (p = fixture_name; *p; p++) {
39 + if (p[0] == '/' && p[1] && p[1] != '/')
40 + fixture_name = p+1;
41 + }
42 +
43 + return fixture_name;
44 +}
45 +
46 +void cl_fixture_cleanup(const char *fixture_name)
47 +{
48 + fs_rm(fixture_path(_clar_path, cl_fixture_basename(fixture_name)));
49 +}
50 +#endif
t/unit-tests/clar/clar/fs.h new
+522
@@ -0,0 +1,522 @@
1 +/*
2 + * By default, use a read/write loop to copy files on POSIX systems.
3 + * On Linux, use sendfile by default as it's slightly faster. On
4 + * macOS, we avoid fcopyfile by default because it's slightly slower.
5 + */
6 +#undef USE_FCOPYFILE
7 +#define USE_SENDFILE 1
8 +
9 +#ifdef _WIN32
10 +
11 +#ifdef CLAR_WIN32_LONGPATHS
12 +# define CLAR_MAX_PATH 4096
13 +#else
14 +# define CLAR_MAX_PATH MAX_PATH
15 +#endif
16 +
17 +#define RM_RETRY_COUNT 5
18 +#define RM_RETRY_DELAY 10
19 +
20 +#ifdef __MINGW32__
21 +
22 +/* These security-enhanced functions are not available
23 + * in MinGW, so just use the vanilla ones */
24 +#define wcscpy_s(a, b, c) wcscpy((a), (c))
25 +#define wcscat_s(a, b, c) wcscat((a), (c))
26 +
27 +#endif /* __MINGW32__ */
28 +
29 +static int
30 +fs__dotordotdot(WCHAR *_tocheck)
31 +{
32 + return _tocheck[0] == '.' &&
33 + (_tocheck[1] == '\0' ||
34 + (_tocheck[1] == '.' && _tocheck[2] == '\0'));
35 +}
36 +
37 +static int
38 +fs_rmdir_rmdir(WCHAR *_wpath)
39 +{
40 + unsigned retries = 1;
41 +
42 + while (!RemoveDirectoryW(_wpath)) {
43 + /* Only retry when we have retries remaining, and the
44 + * error was ERROR_DIR_NOT_EMPTY. */
45 + if (retries++ > RM_RETRY_COUNT ||
46 + ERROR_DIR_NOT_EMPTY != GetLastError())
47 + return -1;
48 +
49 + /* Give whatever has a handle to a child item some time
50 + * to release it before trying again */
51 + Sleep(RM_RETRY_DELAY * retries * retries);
52 + }
53 +
54 + return 0;
55 +}
56 +
57 +static void translate_path(WCHAR *path, size_t path_size)
58 +{
59 + size_t path_len, i;
60 +
61 + if (wcsncmp(path, L"\\\\?\\", 4) == 0)
62 + return;
63 +
64 + path_len = wcslen(path);
65 + cl_assert(path_size > path_len + 4);
66 +
67 + for (i = path_len; i > 0; i--) {
68 + WCHAR c = path[i - 1];
69 +
70 + if (c == L'/')
71 + path[i + 3] = L'\\';
72 + else
73 + path[i + 3] = path[i - 1];
74 + }
75 +
76 + path[0] = L'\\';
77 + path[1] = L'\\';
78 + path[2] = L'?';
79 + path[3] = L'\\';
80 + path[path_len + 4] = L'\0';
81 +}
82 +
83 +static void
84 +fs_rmdir_helper(WCHAR *_wsource)
85 +{
86 + WCHAR buffer[CLAR_MAX_PATH];
87 + HANDLE find_handle;
88 + WIN32_FIND_DATAW find_data;
89 + size_t buffer_prefix_len;
90 +
91 + /* Set up the buffer and capture the length */
92 + wcscpy_s(buffer, CLAR_MAX_PATH, _wsource);
93 + translate_path(buffer, CLAR_MAX_PATH);
94 + wcscat_s(buffer, CLAR_MAX_PATH, L"\\");
95 + buffer_prefix_len = wcslen(buffer);
96 +
97 + /* FindFirstFile needs a wildcard to match multiple items */
98 + wcscat_s(buffer, CLAR_MAX_PATH, L"*");
99 + find_handle = FindFirstFileW(buffer, &find_data);
100 + cl_assert(INVALID_HANDLE_VALUE != find_handle);
101 +
102 + do {
103 + /* FindFirstFile/FindNextFile gives back . and ..
104 + * entries at the beginning */
105 + if (fs__dotordotdot(find_data.cFileName))
106 + continue;
107 +
108 + wcscpy_s(buffer + buffer_prefix_len, CLAR_MAX_PATH - buffer_prefix_len, find_data.cFileName);
109 +
110 + if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes)
111 + fs_rmdir_helper(buffer);
112 + else {
113 + /* If set, the +R bit must be cleared before deleting */
114 + if (FILE_ATTRIBUTE_READONLY & find_data.dwFileAttributes)
115 + cl_assert(SetFileAttributesW(buffer, find_data.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY));
116 +
117 + cl_assert(DeleteFileW(buffer));
118 + }
119 + }
120 + while (FindNextFileW(find_handle, &find_data));
121 +
122 + /* Ensure that we successfully completed the enumeration */
123 + cl_assert(ERROR_NO_MORE_FILES == GetLastError());
124 +
125 + /* Close the find handle */
126 + FindClose(find_handle);
127 +
128 + /* Now that the directory is empty, remove it */
129 + cl_assert(0 == fs_rmdir_rmdir(_wsource));
130 +}
131 +
132 +static int
133 +fs_rm_wait(WCHAR *_wpath)
134 +{
135 + unsigned retries = 1;
136 + DWORD last_error;
137 +
138 + do {
139 + if (INVALID_FILE_ATTRIBUTES == GetFileAttributesW(_wpath))
140 + last_error = GetLastError();
141 + else
142 + last_error = ERROR_SUCCESS;
143 +
144 + /* Is the item gone? */
145 + if (ERROR_FILE_NOT_FOUND == last_error ||
146 + ERROR_PATH_NOT_FOUND == last_error)
147 + return 0;
148 +
149 + Sleep(RM_RETRY_DELAY * retries * retries);
150 + }
151 + while (retries++ <= RM_RETRY_COUNT);
152 +
153 + return -1;
154 +}
155 +
156 +static void
157 +fs_rm(const char *_source)
158 +{
159 + WCHAR wsource[CLAR_MAX_PATH];
160 + DWORD attrs;
161 +
162 + /* The input path is UTF-8. Convert it to wide characters
163 + * for use with the Windows API */
164 + cl_assert(MultiByteToWideChar(CP_UTF8,
165 + MB_ERR_INVALID_CHARS,
166 + _source,
167 + -1, /* Indicates NULL termination */
168 + wsource,
169 + CLAR_MAX_PATH));
170 +
171 + translate_path(wsource, CLAR_MAX_PATH);
172 +
173 + /* Does the item exist? If not, we have no work to do */
174 + attrs = GetFileAttributesW(wsource);
175 +
176 + if (INVALID_FILE_ATTRIBUTES == attrs)
177 + return;
178 +
179 + if (FILE_ATTRIBUTE_DIRECTORY & attrs)
180 + fs_rmdir_helper(wsource);
181 + else {
182 + /* The item is a file. Strip the +R bit */
183 + if (FILE_ATTRIBUTE_READONLY & attrs)
184 + cl_assert(SetFileAttributesW(wsource, attrs & ~FILE_ATTRIBUTE_READONLY));
185 +
186 + cl_assert(DeleteFileW(wsource));
187 + }
188 +
189 + /* Wait for the DeleteFile or RemoveDirectory call to complete */
190 + cl_assert(0 == fs_rm_wait(wsource));
191 +}
192 +
193 +static void
194 +fs_copydir_helper(WCHAR *_wsource, WCHAR *_wdest)
195 +{
196 + WCHAR buf_source[CLAR_MAX_PATH], buf_dest[CLAR_MAX_PATH];
197 + HANDLE find_handle;
198 + WIN32_FIND_DATAW find_data;
199 + size_t buf_source_prefix_len, buf_dest_prefix_len;
200 +
201 + wcscpy_s(buf_source, CLAR_MAX_PATH, _wsource);
202 + wcscat_s(buf_source, CLAR_MAX_PATH, L"\\");
203 + translate_path(buf_source, CLAR_MAX_PATH);
204 + buf_source_prefix_len = wcslen(buf_source);
205 +
206 + wcscpy_s(buf_dest, CLAR_MAX_PATH, _wdest);
207 + wcscat_s(buf_dest, CLAR_MAX_PATH, L"\\");
208 + translate_path(buf_dest, CLAR_MAX_PATH);
209 + buf_dest_prefix_len = wcslen(buf_dest);
210 +
211 + /* Get an enumerator for the items in the source. */
212 + wcscat_s(buf_source, CLAR_MAX_PATH, L"*");
213 + find_handle = FindFirstFileW(buf_source, &find_data);
214 + cl_assert(INVALID_HANDLE_VALUE != find_handle);
215 +
216 + /* Create the target directory. */
217 + cl_assert(CreateDirectoryW(_wdest, NULL));
218 +
219 + do {
220 + /* FindFirstFile/FindNextFile gives back . and ..
221 + * entries at the beginning */
222 + if (fs__dotordotdot(find_data.cFileName))
223 + continue;
224 +
225 + wcscpy_s(buf_source + buf_source_prefix_len, CLAR_MAX_PATH - buf_source_prefix_len, find_data.cFileName);
226 + wcscpy_s(buf_dest + buf_dest_prefix_len, CLAR_MAX_PATH - buf_dest_prefix_len, find_data.cFileName);
227 +
228 + if (FILE_ATTRIBUTE_DIRECTORY & find_data.dwFileAttributes)
229 + fs_copydir_helper(buf_source, buf_dest);
230 + else
231 + cl_assert(CopyFileW(buf_source, buf_dest, TRUE));
232 + }
233 + while (FindNextFileW(find_handle, &find_data));
234 +
235 + /* Ensure that we successfully completed the enumeration */
236 + cl_assert(ERROR_NO_MORE_FILES == GetLastError());
237 +
238 + /* Close the find handle */
239 + FindClose(find_handle);
240 +}
241 +
242 +static void
243 +fs_copy(const char *_source, const char *_dest)
244 +{
245 + WCHAR wsource[CLAR_MAX_PATH], wdest[CLAR_MAX_PATH];
246 + DWORD source_attrs, dest_attrs;
247 + HANDLE find_handle;
248 + WIN32_FIND_DATAW find_data;
249 +
250 + /* The input paths are UTF-8. Convert them to wide characters
251 + * for use with the Windows API. */
252 + cl_assert(MultiByteToWideChar(CP_UTF8,
253 + MB_ERR_INVALID_CHARS,
254 + _source,
255 + -1,
256 + wsource,
257 + CLAR_MAX_PATH));
258 +
259 + cl_assert(MultiByteToWideChar(CP_UTF8,
260 + MB_ERR_INVALID_CHARS,
261 + _dest,
262 + -1,
263 + wdest,
264 + CLAR_MAX_PATH));
265 +
266 + translate_path(wsource, CLAR_MAX_PATH);
267 + translate_path(wdest, CLAR_MAX_PATH);
268 +
269 + /* Check the source for existence */
270 + source_attrs = GetFileAttributesW(wsource);
271 + cl_assert(INVALID_FILE_ATTRIBUTES != source_attrs);
272 +
273 + /* Check the target for existence */
274 + dest_attrs = GetFileAttributesW(wdest);
275 +
276 + if (INVALID_FILE_ATTRIBUTES != dest_attrs) {
277 + /* Target exists; append last path part of source to target.
278 + * Use FindFirstFile to parse the path */
279 + find_handle = FindFirstFileW(wsource, &find_data);
280 + cl_assert(INVALID_HANDLE_VALUE != find_handle);
281 + wcscat_s(wdest, CLAR_MAX_PATH, L"\\");
282 + wcscat_s(wdest, CLAR_MAX_PATH, find_data.cFileName);
283 + FindClose(find_handle);
284 +
285 + /* Check the new target for existence */
286 + cl_assert(INVALID_FILE_ATTRIBUTES == GetFileAttributesW(wdest));
287 + }
288 +
289 + if (FILE_ATTRIBUTE_DIRECTORY & source_attrs)
290 + fs_copydir_helper(wsource, wdest);
291 + else
292 + cl_assert(CopyFileW(wsource, wdest, TRUE));
293 +}
294 +
295 +void
296 +cl_fs_cleanup(void)
297 +{
298 +#ifdef CLAR_FIXTURE_PATH
299 + fs_rm(fixture_path(_clar_path, "*"));
300 +#endif
301 +}
302 +
303 +#else
304 +
305 +#include <errno.h>
306 +#include <string.h>
307 +#include <limits.h>
308 +#include <dirent.h>
309 +#include <fcntl.h>
310 +#include <unistd.h>
311 +#include <sys/types.h>
312 +#include <sys/stat.h>
313 +
314 +#if defined(__linux__)
315 +# include <sys/sendfile.h>
316 +#endif
317 +
318 +#if defined(__APPLE__)
319 +# include <copyfile.h>
320 +#endif
321 +
322 +static void basename_r(const char **out, int *out_len, const char *in)
323 +{
324 + size_t in_len = strlen(in), start_pos;
325 +
326 + for (in_len = strlen(in); in_len; in_len--) {
327 + if (in[in_len - 1] != '/')
328 + break;
329 + }
330 +
331 + for (start_pos = in_len; start_pos; start_pos--) {
332 + if (in[start_pos - 1] == '/')
333 + break;
334 + }
335 +
336 + cl_assert(in_len - start_pos < INT_MAX);
337 +
338 + if (in_len - start_pos > 0) {
339 + *out = &in[start_pos];
340 + *out_len = (in_len - start_pos);
341 + } else {
342 + *out = "/";
343 + *out_len = 1;
344 + }
345 +}
346 +
347 +static char *joinpath(const char *dir, const char *base, int base_len)
348 +{
349 + char *out;
350 + int len;
351 +
352 + if (base_len == -1) {
353 + size_t bl = strlen(base);
354 +
355 + cl_assert(bl < INT_MAX);
356 + base_len = (int)bl;
357 + }
358 +
359 + len = strlen(dir) + base_len + 2;
360 + cl_assert(len > 0);
361 +
362 + cl_assert(out = malloc(len));
363 + cl_assert(snprintf(out, len, "%s/%.*s", dir, base_len, base) < len);
364 +
365 + return out;
366 +}
367 +
368 +static void
369 +fs_copydir_helper(const char *source, const char *dest, int dest_mode)
370 +{
371 + DIR *source_dir;
372 + struct dirent *d;
373 +
374 + mkdir(dest, dest_mode);
375 +
376 + cl_assert_(source_dir = opendir(source), "Could not open source dir");
377 + while ((d = (errno = 0, readdir(source_dir))) != NULL) {
378 + char *child;
379 +
380 + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
381 + continue;
382 +
383 + child = joinpath(source, d->d_name, -1);
384 + fs_copy(child, dest);
385 + free(child);
386 + }
387 +
388 + cl_assert_(errno == 0, "Failed to iterate source dir");
389 +
390 + closedir(source_dir);
391 +}
392 +
393 +static void
394 +fs_copyfile_helper(const char *source, size_t source_len, const char *dest, int dest_mode)
395 +{
396 + int in, out;
397 +
398 + cl_must_pass((in = open(source, O_RDONLY)));
399 + cl_must_pass((out = open(dest, O_WRONLY|O_CREAT|O_TRUNC, dest_mode)));
400 +
401 +#if USE_FCOPYFILE && defined(__APPLE__)
402 + ((void)(source_len)); /* unused */
403 + cl_must_pass(fcopyfile(in, out, 0, COPYFILE_DATA));
404 +#elif USE_SENDFILE && defined(__linux__)
405 + {
406 + ssize_t ret = 0;
407 +
408 + while (source_len && (ret = sendfile(out, in, NULL, source_len)) > 0) {
409 + source_len -= (size_t)ret;
410 + }
411 + cl_assert(ret >= 0);
412 + }
413 +#else
414 + {
415 + char buf[131072];
416 + ssize_t ret;
417 +
418 + ((void)(source_len)); /* unused */
419 +
420 + while ((ret = read(in, buf, sizeof(buf))) > 0) {
421 + size_t len = (size_t)ret;
422 +
423 + while (len && (ret = write(out, buf, len)) > 0) {
424 + cl_assert(ret <= (ssize_t)len);
425 + len -= ret;
426 + }
427 + cl_assert(ret >= 0);
428 + }
429 + cl_assert(ret == 0);
430 + }
431 +#endif
432 +
433 + close(in);
434 + close(out);
435 +}
436 +
437 +static void
438 +fs_copy(const char *source, const char *_dest)
439 +{
440 + char *dbuf = NULL;
441 + const char *dest = NULL;
442 + struct stat source_st, dest_st;
443 +
444 + cl_must_pass_(lstat(source, &source_st), "Failed to stat copy source");
445 +
446 + if (lstat(_dest, &dest_st) == 0) {
447 + const char *base;
448 + int base_len;
449 +
450 + /* Target exists and is directory; append basename */
451 + cl_assert(S_ISDIR(dest_st.st_mode));
452 +
453 + basename_r(&base, &base_len, source);
454 + cl_assert(base_len < INT_MAX);
455 +
456 + dbuf = joinpath(_dest, base, base_len);
457 + dest = dbuf;
458 + } else if (errno != ENOENT) {
459 + cl_fail("Cannot copy; cannot stat destination");
460 + } else {
461 + dest = _dest;
462 + }
463 +
464 + if (S_ISDIR(source_st.st_mode)) {
465 + fs_copydir_helper(source, dest, source_st.st_mode);
466 + } else {
467 + fs_copyfile_helper(source, source_st.st_size, dest, source_st.st_mode);
468 + }
469 +
470 + free(dbuf);
471 +}
472 +
473 +static void
474 +fs_rmdir_helper(const char *path)
475 +{
476 + DIR *dir;
477 + struct dirent *d;
478 +
479 + cl_assert_(dir = opendir(path), "Could not open dir");
480 + while ((d = (errno = 0, readdir(dir))) != NULL) {
481 + char *child;
482 +
483 + if (!strcmp(d->d_name, ".") || !strcmp(d->d_name, ".."))
484 + continue;
485 +
486 + child = joinpath(path, d->d_name, -1);
487 + fs_rm(child);
488 + free(child);
489 + }
490 +
491 + cl_assert_(errno == 0, "Failed to iterate source dir");
492 + closedir(dir);
493 +
494 + cl_must_pass_(rmdir(path), "Could not remove directory");
495 +}
496 +
497 +static void
498 +fs_rm(const char *path)
499 +{
500 + struct stat st;
501 +
502 + if (lstat(path, &st)) {
503 + if (errno == ENOENT)
504 + return;
505 +
506 + cl_fail("Cannot copy; cannot stat destination");
507 + }
508 +
509 + if (S_ISDIR(st.st_mode)) {
510 + fs_rmdir_helper(path);
511 + } else {
512 + cl_must_pass(unlink(path));
513 + }
514 +}
515 +
516 +void
517 +cl_fs_cleanup(void)
518 +{
519 + clar_unsandbox();
520 + clar_sandbox();
521 +}
522 +#endif
t/unit-tests/clar/clar/print.h new
+211
@@ -0,0 +1,211 @@
1 +/* clap: clar protocol, the traditional clar output format */
2 +
3 +static void clar_print_clap_init(int test_count, int suite_count, const char *suite_names)
4 +{
5 + (void)test_count;
6 + printf("Loaded %d suites: %s\n", (int)suite_count, suite_names);
7 + printf("Started (test status codes: OK='.' FAILURE='F' SKIPPED='S')\n");
8 +}
9 +
10 +static void clar_print_clap_shutdown(int test_count, int suite_count, int error_count)
11 +{
12 + (void)test_count;
13 + (void)suite_count;
14 + (void)error_count;
15 +
16 + printf("\n\n");
17 + clar_report_all();
18 +}
19 +
20 +static void clar_print_clap_error(int num, const struct clar_report *report, const struct clar_error *error)
21 +{
22 + printf(" %d) Failure:\n", num);
23 +
24 + printf("%s::%s [%s:%"PRIuZ"]\n",
25 + report->suite,
26 + report->test,
27 + error->file,
28 + error->line_number);
29 +
30 + printf(" %s\n", error->error_msg);
31 +
32 + if (error->description != NULL)
33 + printf(" %s\n", error->description);
34 +
35 + printf("\n");
36 + fflush(stdout);
37 +}
38 +
39 +static void clar_print_clap_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status)
40 +{
41 + (void)test_name;
42 + (void)test_number;
43 +
44 + if (_clar.verbosity > 1) {
45 + printf("%s::%s: ", suite_name, test_name);
46 +
47 + switch (status) {
48 + case CL_TEST_OK: printf("ok\n"); break;
49 + case CL_TEST_FAILURE: printf("fail\n"); break;
50 + case CL_TEST_SKIP: printf("skipped"); break;
51 + case CL_TEST_NOTRUN: printf("notrun"); break;
52 + }
53 + } else {
54 + switch (status) {
55 + case CL_TEST_OK: printf("."); break;
56 + case CL_TEST_FAILURE: printf("F"); break;
57 + case CL_TEST_SKIP: printf("S"); break;
58 + case CL_TEST_NOTRUN: printf("N"); break;
59 + }
60 +
61 + fflush(stdout);
62 + }
63 +}
64 +
65 +static void clar_print_clap_onsuite(const char *suite_name, int suite_index)
66 +{
67 + if (_clar.verbosity == 1)
68 + printf("\n%s", suite_name);
69 +
70 + (void)suite_index;
71 +}
72 +
73 +static void clar_print_clap_onabort(const char *fmt, va_list arg)
74 +{
75 + vfprintf(stderr, fmt, arg);
76 +}
77 +
78 +/* tap: test anywhere protocol format */
79 +
80 +static void clar_print_tap_init(int test_count, int suite_count, const char *suite_names)
81 +{
82 + (void)test_count;
83 + (void)suite_count;
84 + (void)suite_names;
85 + printf("TAP version 13\n");
86 +}
87 +
88 +static void clar_print_tap_shutdown(int test_count, int suite_count, int error_count)
89 +{
90 + (void)suite_count;
91 + (void)error_count;
92 +
93 + printf("1..%d\n", test_count);
94 +}
95 +
96 +static void clar_print_tap_error(int num, const struct clar_report *report, const struct clar_error *error)
97 +{
98 + (void)num;
99 + (void)report;
100 + (void)error;
101 +}
102 +
103 +static void print_escaped(const char *str)
104 +{
105 + char *c;
106 +
107 + while ((c = strchr(str, '\'')) != NULL) {
108 + printf("%.*s", (int)(c - str), str);
109 + printf("''");
110 + str = c + 1;
111 + }
112 +
113 + printf("%s", str);
114 +}
115 +
116 +static void clar_print_tap_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status)
117 +{
118 + const struct clar_error *error = _clar.last_report->errors;
119 +
120 + (void)test_name;
121 + (void)test_number;
122 +
123 + switch(status) {
124 + case CL_TEST_OK:
125 + printf("ok %d - %s::%s\n", test_number, suite_name, test_name);
126 + break;
127 + case CL_TEST_FAILURE:
128 + printf("not ok %d - %s::%s\n", test_number, suite_name, test_name);
129 +
130 + printf(" ---\n");
131 + printf(" reason: |\n");
132 + printf(" %s\n", error->error_msg);
133 +
134 + if (error->description)
135 + printf(" %s\n", error->description);
136 +
137 + printf(" at:\n");
138 + printf(" file: '"); print_escaped(error->file); printf("'\n");
139 + printf(" line: %" PRIuZ "\n", error->line_number);
140 + printf(" function: '%s'\n", error->function);
141 + printf(" ---\n");
142 +
143 + break;
144 + case CL_TEST_SKIP:
145 + case CL_TEST_NOTRUN:
146 + printf("ok %d - # SKIP %s::%s\n", test_number, suite_name, test_name);
147 + break;
148 + }
149 +
150 + fflush(stdout);
151 +}
152 +
153 +static void clar_print_tap_onsuite(const char *suite_name, int suite_index)
154 +{
155 + printf("# start of suite %d: %s\n", suite_index, suite_name);
156 +}
157 +
158 +static void clar_print_tap_onabort(const char *fmt, va_list arg)
159 +{
160 + printf("Bail out! ");
161 + vprintf(fmt, arg);
162 + fflush(stdout);
163 +}
164 +
165 +/* indirection between protocol output selection */
166 +
167 +#define PRINT(FN, ...) do { \
168 + switch (_clar.output_format) { \
169 + case CL_OUTPUT_CLAP: \
170 + clar_print_clap_##FN (__VA_ARGS__); \
171 + break; \
172 + case CL_OUTPUT_TAP: \
173 + clar_print_tap_##FN (__VA_ARGS__); \
174 + break; \
175 + default: \
176 + abort(); \
177 + } \
178 + } while (0)
179 +
180 +static void clar_print_init(int test_count, int suite_count, const char *suite_names)
181 +{
182 + PRINT(init, test_count, suite_count, suite_names);
183 +}
184 +
185 +static void clar_print_shutdown(int test_count, int suite_count, int error_count)
186 +{
187 + PRINT(shutdown, test_count, suite_count, error_count);
188 +}
189 +
190 +static void clar_print_error(int num, const struct clar_report *report, const struct clar_error *error)
191 +{
192 + PRINT(error, num, report, error);
193 +}
194 +
195 +static void clar_print_ontest(const char *suite_name, const char *test_name, int test_number, enum cl_test_status status)
196 +{
197 + PRINT(ontest, suite_name, test_name, test_number, status);
198 +}
199 +
200 +static void clar_print_onsuite(const char *suite_name, int suite_index)
201 +{
202 + PRINT(onsuite, suite_name, suite_index);
203 +}
204 +
205 +static void clar_print_onabort(const char *msg, ...)
206 +{
207 + va_list argp;
208 + va_start(argp, msg);
209 + PRINT(onabort, msg, argp);
210 + va_end(argp);
211 +}
t/unit-tests/clar/clar/sandbox.h new
+153
@@ -0,0 +1,153 @@
1 +#ifdef __APPLE__
2 +#include <sys/syslimits.h>
3 +#endif
4 +
5 +static char _clar_path[4096 + 1];
6 +
7 +static int
8 +is_valid_tmp_path(const char *path)
9 +{
10 + STAT_T st;
11 +
12 + if (stat(path, &st) != 0)
13 + return 0;
14 +
15 + if (!S_ISDIR(st.st_mode))
16 + return 0;
17 +
18 + return (access(path, W_OK) == 0);
19 +}
20 +
21 +static int
22 +find_tmp_path(char *buffer, size_t length)
23 +{
24 +#ifndef _WIN32
25 + static const size_t var_count = 5;
26 + static const char *env_vars[] = {
27 + "CLAR_TMP", "TMPDIR", "TMP", "TEMP", "USERPROFILE"
28 + };
29 +
30 + size_t i;
31 +
32 + for (i = 0; i < var_count; ++i) {
33 + const char *env = getenv(env_vars[i]);
34 + if (!env)
35 + continue;
36 +
37 + if (is_valid_tmp_path(env)) {
38 +#ifdef __APPLE__
39 + if (length >= PATH_MAX && realpath(env, buffer) != NULL)
40 + return 0;
41 +#endif
42 + strncpy(buffer, env, length - 1);
43 + buffer[length - 1] = '\0';
44 + return 0;
45 + }
46 + }
47 +
48 + /* If the environment doesn't say anything, try to use /tmp */
49 + if (is_valid_tmp_path("/tmp")) {
50 +#ifdef __APPLE__
51 + if (length >= PATH_MAX && realpath("/tmp", buffer) != NULL)
52 + return 0;
53 +#endif
54 + strncpy(buffer, "/tmp", length - 1);
55 + buffer[length - 1] = '\0';
56 + return 0;
57 + }
58 +
59 +#else
60 + DWORD env_len = GetEnvironmentVariable("CLAR_TMP", buffer, (DWORD)length);
61 + if (env_len > 0 && env_len < (DWORD)length)
62 + return 0;
63 +
64 + if (GetTempPath((DWORD)length, buffer))
65 + return 0;
66 +#endif
67 +
68 + /* This system doesn't like us, try to use the current directory */
69 + if (is_valid_tmp_path(".")) {
70 + strncpy(buffer, ".", length - 1);
71 + buffer[length - 1] = '\0';
72 + return 0;
73 + }
74 +
75 + return -1;
76 +}
77 +
78 +static void clar_unsandbox(void)
79 +{
80 + if (_clar_path[0] == '\0')
81 + return;
82 +
83 + cl_must_pass(chdir(".."));
84 +
85 + fs_rm(_clar_path);
86 +}
87 +
88 +static int build_sandbox_path(void)
89 +{
90 +#ifdef CLAR_TMPDIR
91 + const char path_tail[] = CLAR_TMPDIR "_XXXXXX";
92 +#else
93 + const char path_tail[] = "clar_tmp_XXXXXX";
94 +#endif
95 +
96 + size_t len;
97 +
98 + if (find_tmp_path(_clar_path, sizeof(_clar_path)) < 0)
99 + return -1;
100 +
101 + len = strlen(_clar_path);
102 +
103 +#ifdef _WIN32
104 + { /* normalize path to POSIX forward slashes */
105 + size_t i;
106 + for (i = 0; i < len; ++i) {
107 + if (_clar_path[i] == '\\')
108 + _clar_path[i] = '/';
109 + }
110 + }
111 +#endif
112 +
113 + if (_clar_path[len - 1] != '/') {
114 + _clar_path[len++] = '/';
115 + }
116 +
117 + strncpy(_clar_path + len, path_tail, sizeof(_clar_path) - len);
118 +
119 +#if defined(__MINGW32__)
120 + if (_mktemp(_clar_path) == NULL)
121 + return -1;
122 +
123 + if (mkdir(_clar_path, 0700) != 0)
124 + return -1;
125 +#elif defined(_WIN32)
126 + if (_mktemp_s(_clar_path, sizeof(_clar_path)) != 0)
127 + return -1;
128 +
129 + if (mkdir(_clar_path, 0700) != 0)
130 + return -1;
131 +#else
132 + if (mkdtemp(_clar_path) == NULL)
133 + return -1;
134 +#endif
135 +
136 + return 0;
137 +}
138 +
139 +static int clar_sandbox(void)
140 +{
141 + if (_clar_path[0] == '\0' && build_sandbox_path() < 0)
142 + return -1;
143 +
144 + if (chdir(_clar_path) != 0)
145 + return -1;
146 +
147 + return 0;
148 +}
149 +
150 +const char *clar_sandbox_path(void)
151 +{
152 + return _clar_path;
153 +}
t/unit-tests/clar/clar/summary.h new
+143
@@ -0,0 +1,143 @@
1 +
2 +#include <stdio.h>
3 +#include <time.h>
4 +
5 +static int clar_summary_close_tag(
6 + struct clar_summary *summary, const char *tag, int indent)
7 +{
8 + const char *indt;
9 +
10 + if (indent == 0) indt = "";
11 + else if (indent == 1) indt = "\t";
12 + else indt = "\t\t";
13 +
14 + return fprintf(summary->fp, "%s</%s>\n", indt, tag);
15 +}
16 +
17 +static int clar_summary_testsuites(struct clar_summary *summary)
18 +{
19 + return fprintf(summary->fp, "<testsuites>\n");
20 +}
21 +
22 +static int clar_summary_testsuite(struct clar_summary *summary,
23 + int idn, const char *name, time_t timestamp,
24 + int test_count, int fail_count, int error_count)
25 +{
26 + struct tm *tm = localtime(&timestamp);
27 + char iso_dt[20];
28 +
29 + if (strftime(iso_dt, sizeof(iso_dt), "%Y-%m-%dT%H:%M:%S", tm) == 0)
30 + return -1;
31 +
32 + return fprintf(summary->fp, "\t<testsuite"
33 + " id=\"%d\""
34 + " name=\"%s\""
35 + " hostname=\"localhost\""
36 + " timestamp=\"%s\""
37 + " tests=\"%d\""
38 + " failures=\"%d\""
39 + " errors=\"%d\">\n",
40 + idn, name, iso_dt, test_count, fail_count, error_count);
41 +}
42 +
43 +static int clar_summary_testcase(struct clar_summary *summary,
44 + const char *name, const char *classname, double elapsed)
45 +{
46 + return fprintf(summary->fp,
47 + "\t\t<testcase name=\"%s\" classname=\"%s\" time=\"%.2f\">\n",
48 + name, classname, elapsed);
49 +}
50 +
51 +static int clar_summary_failure(struct clar_summary *summary,
52 + const char *type, const char *message, const char *desc)
53 +{
54 + return fprintf(summary->fp,
55 + "\t\t\t<failure type=\"%s\"><![CDATA[%s\n%s]]></failure>\n",
56 + type, message, desc);
57 +}
58 +
59 +static int clar_summary_skipped(struct clar_summary *summary)
60 +{
61 + return fprintf(summary->fp, "\t\t\t<skipped />\n");
62 +}
63 +
64 +struct clar_summary *clar_summary_init(const char *filename)
65 +{
66 + struct clar_summary *summary;
67 + FILE *fp;
68 +
69 + if ((fp = fopen(filename, "w")) == NULL) {
70 + perror("fopen");
71 + return NULL;
72 + }
73 +
74 + if ((summary = malloc(sizeof(struct clar_summary))) == NULL) {
75 + perror("malloc");
76 + fclose(fp);
77 + return NULL;
78 + }
79 +
80 + summary->filename = filename;
81 + summary->fp = fp;
82 +
83 + return summary;
84 +}
85 +
86 +int clar_summary_shutdown(struct clar_summary *summary)
87 +{
88 + struct clar_report *report;
89 + const char *last_suite = NULL;
90 +
91 + if (clar_summary_testsuites(summary) < 0)
92 + goto on_error;
93 +
94 + report = _clar.reports;
95 + while (report != NULL) {
96 + struct clar_error *error = report->errors;
97 +
98 + if (last_suite == NULL || strcmp(last_suite, report->suite) != 0) {
99 + if (clar_summary_testsuite(summary, 0, report->suite,
100 + report->start, _clar.tests_ran, _clar.total_errors, 0) < 0)
101 + goto on_error;
102 + }
103 +
104 + last_suite = report->suite;
105 +
106 + clar_summary_testcase(summary, report->test, report->suite, report->elapsed);
107 +
108 + while (error != NULL) {
109 + if (clar_summary_failure(summary, "assert",
110 + error->error_msg, error->description) < 0)
111 + goto on_error;
112 +
113 + error = error->next;
114 + }
115 +
116 + if (report->status == CL_TEST_SKIP)
117 + clar_summary_skipped(summary);
118 +
119 + if (clar_summary_close_tag(summary, "testcase", 2) < 0)
120 + goto on_error;
121 +
122 + report = report->next;
123 +
124 + if (!report || strcmp(last_suite, report->suite) != 0) {
125 + if (clar_summary_close_tag(summary, "testsuite", 1) < 0)
126 + goto on_error;
127 + }
128 + }
129 +
130 + if (clar_summary_close_tag(summary, "testsuites", 0) < 0 ||
131 + fclose(summary->fp) != 0)
132 + goto on_error;
133 +
134 + printf("written summary file to %s\n", summary->filename);
135 +
136 + free(summary);
137 + return 0;
138 +
139 +on_error:
140 + fclose(summary->fp);
141 + free(summary);
142 + return -1;
143 +}
t/unit-tests/clar/generate.py new
+266
@@ -0,0 +1,266 @@
1 +#!/usr/bin/env python
2 +#
3 +# Copyright (c) Vicent Marti. All rights reserved.
4 +#
5 +# This file is part of clar, distributed under the ISC license.
6 +# For full terms see the included COPYING file.
7 +#
8 +
9 +from __future__ import with_statement
10 +from string import Template
11 +import re, fnmatch, os, sys, codecs, pickle
12 +
13 +class Module(object):
14 + class Template(object):
15 + def __init__(self, module):
16 + self.module = module
17 +
18 + def _render_callback(self, cb):
19 + if not cb:
20 + return ' { NULL, NULL }'
21 + return ' { "%s", &%s }' % (cb['short_name'], cb['symbol'])
22 +
23 + class DeclarationTemplate(Template):
24 + def render(self):
25 + out = "\n".join("extern %s;" % cb['declaration'] for cb in self.module.callbacks) + "\n"
26 +
27 + for initializer in self.module.initializers:
28 + out += "extern %s;\n" % initializer['declaration']
29 +
30 + if self.module.cleanup:
31 + out += "extern %s;\n" % self.module.cleanup['declaration']
32 +
33 + return out
34 +
35 + class CallbacksTemplate(Template):
36 + def render(self):
37 + out = "static const struct clar_func _clar_cb_%s[] = {\n" % self.module.name
38 + out += ",\n".join(self._render_callback(cb) for cb in self.module.callbacks)
39 + out += "\n};\n"
40 + return out
41 +
42 + class InfoTemplate(Template):
43 + def render(self):
44 + templates = []
45 +
46 + initializers = self.module.initializers
47 + if len(initializers) == 0:
48 + initializers = [ None ]
49 +
50 + for initializer in initializers:
51 + name = self.module.clean_name()
52 + if initializer and initializer['short_name'].startswith('initialize_'):
53 + variant = initializer['short_name'][len('initialize_'):]
54 + name += " (%s)" % variant.replace('_', ' ')
55 +
56 + template = Template(
57 + r"""
58 + {
59 + "${clean_name}",
60 + ${initialize},
61 + ${cleanup},
62 + ${cb_ptr}, ${cb_count}, ${enabled}
63 + }"""
64 + ).substitute(
65 + clean_name = name,
66 + initialize = self._render_callback(initializer),
67 + cleanup = self._render_callback(self.module.cleanup),
68 + cb_ptr = "_clar_cb_%s" % self.module.name,
69 + cb_count = len(self.module.callbacks),
70 + enabled = int(self.module.enabled)
71 + )
72 + templates.append(template)
73 +
74 + return ','.join(templates)
75 +
76 + def __init__(self, name):
77 + self.name = name
78 +
79 + self.mtime = None
80 + self.enabled = True
81 + self.modified = False
82 +
83 + def clean_name(self):
84 + return self.name.replace("_", "::")
85 +
86 + def _skip_comments(self, text):
87 + SKIP_COMMENTS_REGEX = re.compile(
88 + r'//.*?$|/\*.*?\*/|\'(?:\\.|[^\\\'])*\'|"(?:\\.|[^\\"])*"',
89 + re.DOTALL | re.MULTILINE)
90 +
91 + def _replacer(match):
92 + s = match.group(0)
93 + return "" if s.startswith('/') else s
94 +
95 + return re.sub(SKIP_COMMENTS_REGEX, _replacer, text)
96 +
97 + def parse(self, contents):
98 + TEST_FUNC_REGEX = r"^(void\s+(test_%s__(\w+))\s*\(\s*void\s*\))\s*\{"
99 +
100 + contents = self._skip_comments(contents)
101 + regex = re.compile(TEST_FUNC_REGEX % self.name, re.MULTILINE)
102 +
103 + self.callbacks = []
104 + self.initializers = []
105 + self.cleanup = None
106 +
107 + for (declaration, symbol, short_name) in regex.findall(contents):
108 + data = {
109 + "short_name" : short_name,
110 + "declaration" : declaration,
111 + "symbol" : symbol
112 + }
113 +
114 + if short_name.startswith('initialize'):
115 + self.initializers.append(data)
116 + elif short_name == 'cleanup':
117 + self.cleanup = data
118 + else:
119 + self.callbacks.append(data)
120 +
121 + return self.callbacks != []
122 +
123 + def refresh(self, path):
124 + self.modified = False
125 +
126 + try:
127 + st = os.stat(path)
128 +
129 + # Not modified
130 + if st.st_mtime == self.mtime:
131 + return True
132 +
133 + self.modified = True
134 + self.mtime = st.st_mtime
135 +
136 + with codecs.open(path, encoding='utf-8') as fp:
137 + raw_content = fp.read()
138 +
139 + except IOError:
140 + return False
141 +
142 + return self.parse(raw_content)
143 +
144 +class TestSuite(object):
145 +
146 + def __init__(self, path, output):
147 + self.path = path
148 + self.output = output
149 +
150 + def should_generate(self, path):
151 + if not os.path.isfile(path):
152 + return True
153 +
154 + if any(module.modified for module in self.modules.values()):
155 + return True
156 +
157 + return False
158 +
159 + def find_modules(self):
160 + modules = []
161 + for root, _, files in os.walk(self.path):
162 + module_root = root[len(self.path):]
163 + module_root = [c for c in module_root.split(os.sep) if c]
164 +
165 + tests_in_module = fnmatch.filter(files, "*.c")
166 +
167 + for test_file in tests_in_module:
168 + full_path = os.path.join(root, test_file)
169 + module_name = "_".join(module_root + [test_file[:-2]]).replace("-", "_")
170 +
171 + modules.append((full_path, module_name))
172 +
173 + return modules
174 +
175 + def load_cache(self):
176 + path = os.path.join(self.output, '.clarcache')
177 + cache = {}
178 +
179 + try:
180 + fp = open(path, 'rb')
181 + cache = pickle.load(fp)
182 + fp.close()
183 + except (IOError, ValueError):
184 + pass
185 +
186 + return cache
187 +
188 + def save_cache(self):
189 + path = os.path.join(self.output, '.clarcache')
190 + with open(path, 'wb') as cache:
191 + pickle.dump(self.modules, cache)
192 +
193 + def load(self, force = False):
194 + module_data = self.find_modules()
195 + self.modules = {} if force else self.load_cache()
196 +
197 + for path, name in module_data:
198 + if name not in self.modules:
199 + self.modules[name] = Module(name)
200 +
201 + if not self.modules[name].refresh(path):
202 + del self.modules[name]
203 +
204 + def disable(self, excluded):
205 + for exclude in excluded:
206 + for module in self.modules.values():
207 + name = module.clean_name()
208 + if name.startswith(exclude):
209 + module.enabled = False
210 + module.modified = True
211 +
212 + def suite_count(self):
213 + return sum(max(1, len(m.initializers)) for m in self.modules.values())
214 +
215 + def callback_count(self):
216 + return sum(len(module.callbacks) for module in self.modules.values())
217 +
218 + def write(self):
219 + output = os.path.join(self.output, 'clar.suite')
220 +
221 + if not self.should_generate(output):
222 + return False
223 +
224 + with open(output, 'w') as data:
225 + modules = sorted(self.modules.values(), key=lambda module: module.name)
226 +
227 + for module in modules:
228 + t = Module.DeclarationTemplate(module)
229 + data.write(t.render())
230 +
231 + for module in modules:
232 + t = Module.CallbacksTemplate(module)
233 + data.write(t.render())
234 +
235 + suites = "static struct clar_suite _clar_suites[] = {" + ','.join(
236 + Module.InfoTemplate(module).render() for module in modules
237 + ) + "\n};\n"
238 +
239 + data.write(suites)
240 +
241 + data.write("static const size_t _clar_suite_count = %d;\n" % self.suite_count())
242 + data.write("static const size_t _clar_callback_count = %d;\n" % self.callback_count())
243 +
244 + self.save_cache()
245 + return True
246 +
247 +if __name__ == '__main__':
248 + from optparse import OptionParser
249 +
250 + parser = OptionParser()
251 + parser.add_option('-f', '--force', action="store_true", dest='force', default=False)
252 + parser.add_option('-x', '--exclude', dest='excluded', action='append', default=[])
253 + parser.add_option('-o', '--output', dest='output')
254 +
255 + options, args = parser.parse_args()
256 + if len(args) > 1:
257 + print("More than one path given")
258 + sys.exit(1)
259 +
260 + path = args.pop() if args else '.'
261 + output = options.output or path
262 + suite = TestSuite(path, output)
263 + suite.load(options.force)
264 + suite.disable(options.excluded)
265 + if suite.write():
266 + print("Written `clar.suite` (%d tests in %d suites)" % (suite.callback_count(), suite.suite_count()))
t/unit-tests/clar/test/.gitignore new
+4
@@ -0,0 +1,4 @@
1 +clar.suite
2 +.clarcache
3 +clar_test
4 +*.o
t/unit-tests/clar/test/Makefile new
+39
@@ -0,0 +1,39 @@
1 +#
2 +# Copyright (c) Vicent Marti. All rights reserved.
3 +#
4 +# This file is part of clar, distributed under the ISC license.
5 +# For full terms see the included COPYING file.
6 +#
7 +
8 +#
9 +# Set up the path to the clar sources and to the fixtures directory
10 +#
11 +# The fixture path needs to be an absolute path so it can be used
12 +# even after we have chdir'ed into the test directory while testing.
13 +#
14 +CURRENT_MAKEFILE := $(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST))
15 +TEST_DIRECTORY := $(abspath $(dir $(CURRENT_MAKEFILE)))
16 +CLAR_PATH := $(dir $(TEST_DIRECTORY))
17 +CLAR_FIXTURE_PATH := $(TEST_DIRECTORY)/resources/
18 +
19 +CFLAGS=-g -I.. -I. -Wall -DCLAR_FIXTURE_PATH=\"$(CLAR_FIXTURE_PATH)\"
20 +
21 +.PHONY: clean
22 +
23 +# list the objects that go into our test
24 +objects = main.o sample.o
25 +
26 +# build the test executable itself
27 +clar_test: $(objects) clar_test.h clar.suite $(CLAR_PATH)clar.c
28 + $(CC) $(CFLAGS) -o $@ "$(CLAR_PATH)clar.c" $(objects)
29 +
30 +# test object files depend on clar macros
31 +$(objects) : $(CLAR_PATH)clar.h
32 +
33 +# build the clar.suite file of test metadata
34 +clar.suite:
35 + python "$(CLAR_PATH)generate.py" .
36 +
37 +# remove all generated files
38 +clean:
39 + $(RM) -rf *.o clar.suite .clarcache clar_test clar_test.dSYM
t/unit-tests/clar/test/clar_test.h new
+16
@@ -0,0 +1,16 @@
1 +/*
2 + * Copyright (c) Vicent Marti. All rights reserved.
3 + *
4 + * This file is part of clar, distributed under the ISC license.
5 + * For full terms see the included COPYING file.
6 + */
7 +#ifndef __CLAR_TEST__
8 +#define __CLAR_TEST__
9 +
10 +/* Import the standard clar helper functions */
11 +#include "clar.h"
12 +
13 +/* Your custom shared includes / defines here */
14 +extern int global_test_counter;
15 +
16 +#endif
t/unit-tests/clar/test/main.c new
+40
@@ -0,0 +1,40 @@
1 +/*
2 + * Copyright (c) Vicent Marti. All rights reserved.
3 + *
4 + * This file is part of clar, distributed under the ISC license.
5 + * For full terms see the included COPYING file.
6 + */
7 +
8 +#include "clar_test.h"
9 +
10 +/*
11 + * Sample main() for clar tests.
12 + *
13 + * You should write your own main routine for clar tests that does specific
14 + * setup and teardown as necessary for your application. The only required
15 + * line is the call to `clar_test(argc, argv)`, which will execute the test
16 + * suite. If you want to check the return value of the test application,
17 + * your main() should return the same value returned by clar_test().
18 + */
19 +
20 +int global_test_counter = 0;
21 +
22 +#ifdef _WIN32
23 +int __cdecl main(int argc, char *argv[])
24 +#else
25 +int main(int argc, char *argv[])
26 +#endif
27 +{
28 + int ret;
29 +
30 + /* Your custom initialization here */
31 + global_test_counter = 0;
32 +
33 + /* Run the test suite */
34 + ret = clar_test(argc, argv);
35 +
36 + /* Your custom cleanup here */
37 + cl_assert_equal_i(8, global_test_counter);
38 +
39 + return ret;
40 +}
t/unit-tests/clar/test/main.c.sample new
+27
@@ -0,0 +1,27 @@
1 +/*
2 + * Copyright (c) Vicent Marti. All rights reserved.
3 + *
4 + * This file is part of clar, distributed under the ISC license.
5 + * For full terms see the included COPYING file.
6 + */
7 +
8 +#include "clar_test.h"
9 +
10 +/*
11 + * Minimal main() for clar tests.
12 + *
13 + * Modify this with any application specific setup or teardown that you need.
14 + * The only required line is the call to `clar_test(argc, argv)`, which will
15 + * execute the test suite. If you want to check the return value of the test
16 + * application, main() should return the same value returned by clar_test().
17 + */
18 +
19 +#ifdef _WIN32
20 +int __cdecl main(int argc, char *argv[])
21 +#else
22 +int main(int argc, char *argv[])
23 +#endif
24 +{
25 + /* Run the test suite */
26 + return clar_test(argc, argv);
27 +}
t/unit-tests/clar/test/resources/test/file new
+1
@@ -0,0 +1 @@
1 +File
t/unit-tests/clar/test/sample.c new
+84
@@ -0,0 +1,84 @@
1 +#include "clar_test.h"
2 +#include <sys/stat.h>
3 +
4 +static int file_size(const char *filename)
5 +{
6 + struct stat st;
7 +
8 + if (stat(filename, &st) == 0)
9 + return (int)st.st_size;
10 + return -1;
11 +}
12 +
13 +void test_sample__initialize(void)
14 +{
15 + global_test_counter++;
16 +}
17 +
18 +void test_sample__cleanup(void)
19 +{
20 + cl_fixture_cleanup("test");
21 +
22 + cl_assert(file_size("test/file") == -1);
23 +}
24 +
25 +void test_sample__1(void)
26 +{
27 + cl_assert(1);
28 + cl_must_pass(0); /* 0 == success */
29 + cl_must_fail(-1); /* <0 == failure */
30 + cl_must_pass(-1); /* demonstrate a failing call */
31 +}
32 +
33 +void test_sample__2(void)
34 +{
35 + cl_fixture_sandbox("test");
36 +
37 + cl_assert(file_size("test/nonexistent") == -1);
38 + cl_assert(file_size("test/file") > 0);
39 + cl_assert(100 == 101);
40 +}
41 +
42 +void test_sample__strings(void)
43 +{
44 + const char *actual = "expected";
45 + cl_assert_equal_s("expected", actual);
46 + cl_assert_equal_s_("expected", actual, "second try with annotation");
47 + cl_assert_equal_s_("mismatched", actual, "this one fails");
48 +}
49 +
50 +void test_sample__strings_with_length(void)
51 +{
52 + const char *actual = "expected";
53 + cl_assert_equal_strn("expected_", actual, 8);
54 + cl_assert_equal_strn("exactly", actual, 2);
55 + cl_assert_equal_strn_("expected_", actual, 8, "with annotation");
56 + cl_assert_equal_strn_("exactly", actual, 3, "this one fails");
57 +}
58 +
59 +void test_sample__int(void)
60 +{
61 + int value = 100;
62 + cl_assert_equal_i(100, value);
63 + cl_assert_equal_i_(101, value, "extra note on failing test");
64 +}
65 +
66 +void test_sample__int_fmt(void)
67 +{
68 + int value = 100;
69 + cl_assert_equal_i_fmt(022, value, "%04o");
70 +}
71 +
72 +void test_sample__bool(void)
73 +{
74 + int value = 100;
75 + cl_assert_equal_b(1, value); /* test equality as booleans */
76 + cl_assert_equal_b(0, value);
77 +}
78 +
79 +void test_sample__ptr(void)
80 +{
81 + const char *actual = "expected";
82 + cl_assert_equal_p(actual, actual); /* pointers to same object */
83 + cl_assert_equal_p(&actual, actual);
84 +}