2 **ctest_harness** is a small and portable unit test framework for C.
\r
5 - **Easy to integrate**
\r
7 Having only one header file and source file, integrating it into your build system is trivial.
\r
8 - **Small, Portable**
\r
10 - Self-contained, no external dependencies
\r
11 - Easy to compile and uses POSIX make
\r
12 - Should compile without warnings using `-Wall -Wextra -Wpedantic`
\r
13 - No dynamic allocation.
\r
14 - **Timing information**
\r
16 Provides wall clock timing information to help diagnose performance regressions.
\r
19 Organize your tests into test suites.
\r
20 - **Handy assertion macros**
\r
22 Assertions are provided for testing equality and inequality of integral data types, strings and double floating point.
\r
23 - **Pseudo-random number generator**
\r
25 32-bit PRNG based on [xoroshiro64**](https://prng.di.unimi.it/) used for seed generation and to aid the creation of randomized tests.
\r
28 **ctest_harness** is designed to be easy to use. Just add harness.c to your sources, include harness.h, and you're good to go. Here's a basic example
\r
30 #include "harness.h"
\r
32 static int x_should_equal_1(void *user_data) {
\r
37 return HARNESS_PASS;
\r
41 struct harness_cfg cfg = {0};
\r
42 int ret = EXIT_SUCCESS;
\r
44 if (harness_init(&cfg) == HARNESS_FAIL) {
\r
45 return EXIT_FAILURE;
\r
48 (void)harness_add_test("my_suite", "x_should_equal_1",x_should_equal_1, NULL, NULL, NULL);
\r
49 ret = harness_run(NULL);
\r
50 harness_destroy(&cfg);
\r
57 Assertions are a fundamental part of testing. **ctest_harness** provides two levels of assertions: (1) fatal and (2) non-fatal. Fatal assertions stop
\r
58 the execution of the test resulting in a FAIL verdict, while non-fatal assertions as the name implies don't stop the execution.
\r
60 Let's say you want to test two values for equality:
\r
62 int your_test_function(void *user_data) {
\r
70 return HARNESS_PASS;
\r
74 ERROR> srcfile.c:6: assertion failed: a == b
\r
76 The non-fatal variants are prefixed with `EXPECT_` instead of `ASSERT_`.
\r
78 ## Pseudo-Random numbers
\r
79 Being able to randomize tests is a great way to increase coverage of your tests. The drawback of randomized tests is reproducibility: a test failed,
\r
80 but if it's randomized then how do I reproduce it? That's where seeding comes into play, by initializing the PRNG with the same seed you can
\r
81 reproduce the exact same scenario. Every time the test harness runs an unsigned 32-bit seed is generated and printed in hexadecimal notation so that
\r
82 you can later use it to re-run the tests with the same output. If you want to set a specific seed you can do that by setting the `seed` field in the
\r
83 `struct harness_cfg`.
\r
85 The API is the following:
\r
87 void random_init(void);
\r
89 Initialize the state with the value of time(0) is used.
\r
92 uint32_t random_next(void);
\r
94 Generate a 32-bit random value
\r
97 uint32_t random_bounded(uint32_t range);
\r
99 Generate a 32-bit random value in the interval [0, range)
\r
102 float random_float(void);
\r
104 Generate a random float in the interval [0, 1)
\r
107 ## Setting up the test harness
\r
108 Before adding and executing test cases you need to initilize the test harness. This is done by first defining a variable of type struct `harness_cfg`
\r
109 and calling `harness_init`:
\r
111 struct harness_cfg cfg = {
\r
113 .teardown = teardown,
\r
114 .iterations = 1, /* if not set or set to 0, it defaults to 1 */
\r
117 harness_init(&cfg);
\r
119 This is also the place where we have to define a global fixture setup and teardown, they must follow the following prototype (of course the name can
\r
122 int setup_function(void *user_data);
\r
123 void teardown_function(void *user_data);
\r
125 If they are not needed they can be set to `NULL`.
\r
127 Once we are done with our tests we have to destroy the test harness:
\r
129 harness_destroy(&cfg);
\r
132 ## Tests and Suites
\r
133 Let's look at how to structure testcases for **ctest_harness**. Each testcase should be a separate function with the following prototype:
\r
135 int my_test(void *user_data);
\r
137 Of course the name of the test does not matter it's only for your internal use and convenience.
\r
139 The possible return values of a test case are four:
\r
143 |`HARNESS_PASS`|The test passed|
\r
144 |`HARNESS_FAIL`|The test failed, if an assertion fails this is the result|
\r
145 |`HARNESS_SKIP`|The test was skipped for some reason, this can be used in situations where the test is not applicable.|
\r
146 |`HARNESS_ERROR`|There was an error during the test case execution. This type of error is used when the error does not have to do with the scope of
\r
147 the test. For example your test writes and reads data to and from SRAM via SPI but the SPI driver cannot be initialized|
\r
149 It is recommended that each test scenario should be implemented in a different function.
\r
151 ### Adding a test case
\r
152 Once you have defined a test case, or more than one, it's time to add them to a test suite. There are two ways you can do that: you can either add
\r
153 them one by one using the `harness_add_test` function:
\r
157 "my_suite", /* suite name */
\r
158 "my_test", /* test name */
\r
159 my_test, /* test function */
\r
160 NULL, /* fixture setup */
\r
161 NULL, /* fixture teardown */
\r
162 NULL /* user data */
\r
165 or you can add them by declaring an array of struct `harness_test` and then feeding that array to `harness_add_suite`:
\r
167 struct harness_test tests[] = {
\r
168 {"my_test", my_test, NULL, NULL, NULL},
\r
169 {NULL, NULL, NULL, NULL, NULL}
\r
172 harness_add_suite("my_suite", tests);
\r
174 Note that the NULL entry at the end is used to tell the test runner when the array is over.
\r
176 ### Fixture setup and teardown
\r
177 **ctest_harness** has two levels of fixtures: *global* and *test specific* fixtures. If global setup and teardown functions are provided to the
\r
178 `harness_cfg` structure, the setup function will be executed before all tests are run. Similarly if a global teardown function is provided it will be
\r
179 executed at the end of all the test cases. Test specific fixtures will be executed before (setup) and after (teardown) each test case.
\r
181 These functions are commonly used to initialize and destroy resources used by all the test cases or by a specific test. For example:
\r
183 static int setup(void *user_data) {
\r
184 char *buf = (char *)user_data;
\r
186 buf = malloc(1024);
\r
188 return (HARNESS_ERROR);
\r
191 return (HARNESS_PASS);
\r
194 static void teardown(void *user_data) {
\r
195 char *buf = (char *)user_data;
\r
199 static int test_setup(void *user_data) {
\r
200 char *buf = (char *)user_data;
\r
201 FILE *fp = fopen("file.txt", "r");
\r
203 return (HARNESS_ERROR);
\r
206 fgets(buf, 1024, fp);
\r
209 return (HARNESS_SUCCESS);
\r
212 static void test_teardown(void *user_data) {
\r
216 static int my_test(void *user_data) {
\r
217 char* buf = (char *)user_data;
\r
218 ASSERT_STREQ(buf, "Hello, world!");
\r
220 return HARNESS_PASS;
\r
224 struct harness_cfg cfg = {
\r
226 .teardown = teardown,
\r
230 harness_init(&cfg);
\r
231 harness_add_test("my_suite", "my_test", my_test, test_setup, test_teardown, buf);
\r
235 The return value of the test specific setup function will be checked before executing the test case itself, if a value different from `HARNESS_SUCCESS`
\r
236 is returned then the execution will stop without calling the actual test function.
\r
239 ### Running the tests
\r
240 Once you've added your tests and suites, it's time to call `harness_run`, its prototype is
\r
242 int harness_run(void *user_data);
\r
244 The return value will be `EXIT_SUCCESS` if all tests pass,or `EXIT_FAILURE` if any test has failed. This makes it particularly suitable for returning
\r
245 directly from your main() function.
\r
248 Up until now we've skipped talking about the `user_data` parameters, but it's time to address it.
\r
250 If a global setup and/or teardown function has been provided, the `user_data` parameter passed to `harness_run` is passed to the setup and/or teardown
\r
253 If a test specific setup and/or teardown function has been provided, the `user_data` field of the `struct harness_test` is passed to the setup and/or
\r
254 teardown fixtures as well as the test function.
\r
257 Static analysis on the code base is done by using clang's static analyzer run through `scan-build.sh` which wraps the
\r
258 `scan-build` utility. The checkers used are part of the
\r
259 [Experimental Checkers](https://releases.llvm.org/12.0.0/tools/clang/docs/analyzer/checkers.html#alpha-checkers)
\r
260 (aka *alpha* checkers):
\r
263 * `alpha.core.CastSize`
\r
264 * `alpha.core.CastToStruct`
\r
265 * `alpha.core.IdenticalExpr`
\r
266 * `alpha.core.PointerArithm`
\r
267 * `alpha.core.PointerSub`
\r
268 * `alpha.core.SizeofPtr`
\r
269 * `alpha.core.TestAfterDivZero`
\r
273 BSD 2-Clause FreeBSD License, see LICENSE.
\r