├── demonstration.gif ├── Makefile ├── testsuite.c ├── LICENSE ├── test.h └── README.md /demonstration.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rubenvannieuwpoort/c_unit_tests/HEAD/demonstration.gif -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | testsuite: testsuite.c test.h 2 | gcc testsuite.c -ggdb -O3 -o testsuite 3 | 4 | .PHONY: clean test 5 | 6 | test: testsuite 7 | ./testsuite 8 | 9 | clean: 10 | rm -f testsuite 11 | -------------------------------------------------------------------------------- /testsuite.c: -------------------------------------------------------------------------------- 1 | #include "test.h" 2 | #include 3 | #include 4 | 5 | UNIT_TEST(hello) { 6 | test_assert(true); 7 | } 8 | 9 | UNIT_TEST(goodbye) { 10 | test_assert(true); 11 | } 12 | 13 | UNIT_TEST(loopy) { 14 | sleep(1); 15 | test_assert(true); 16 | } 17 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Ruben van Nieuwpoort 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | typedef struct test_node test_node; 4 | 5 | struct test_node { 6 | test_node *next; 7 | void (*func)(void); 8 | }; 9 | 10 | extern void add_test(test_node *tn); 11 | 12 | #define test_assert(b) do { if (b) { printf("SUCCESS\n"); tests_passed++; } else { printf("FAILED\n"); tests_failed++; } } while(0) 13 | 14 | #define UNIT_TEST(f) static void f(void);\ 15 | static void __attribute__((constructor)) __construct_##f(void) { static test_node tn; tn.next = NULL; tn.func = f; add_test(&tn); }\ 16 | static void __real_##f(void);\ 17 | static void f(void) { printf("%s... ", __func__); fflush(stdout); __real_##f(); }\ 18 | static void __real_##f(void) 19 | 20 | 21 | int tests_passed = 0, tests_failed = 0; 22 | test_node *start = 0; 23 | 24 | void add_test(test_node *tn) { 25 | test_node **current = &start; 26 | while (*current) current = &((*current)->next); 27 | *current = tn; 28 | } 29 | 30 | 31 | int main() { 32 | test_node *current = start; 33 | while (current) { 34 | current->func(); 35 | current = current->next; 36 | } 37 | int total_tests = tests_passed + tests_failed; 38 | if (total_tests == 0) 39 | printf("No tests found\n"); 40 | else 41 | printf("%i tests passed, %i tests failed\n", tests_passed, tests_failed); 42 | return tests_failed > 0; 43 | } 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # C unit tests 2 | 3 | Minimalistic unit tests in C. Uses the `__attribute__((constructor))` which, as far as I know, is supported by `GCC` and `clang`. So this probably only works for those compilers. 4 | 5 | ![](demonstration.gif) 6 | 7 | ## Example 8 | 9 | To see the unit tests run: 10 | 11 | ``` 12 | $ git clone git@github.com:rubenvannieuwpoort/c_unit_tests.git 13 | $ cd c_unit_tests 14 | $ make test 15 | ``` 16 | 17 | 18 | ## Usage 19 | 20 | Make a separate C source file for your tests that includes `test.h`. This file should not contain a `main` -- it is provided by the `test.h` header file. A unit test now looks like this: 21 | ``` 22 | UNIT_TEST(test_name) { 23 | // do something useful here 24 | test_assert(...); 25 | } 26 | ``` 27 | 28 | This file can be compiled and run as usual. When running, it displays something like 29 | ``` 30 | test_name... SUCCESS 31 | 1 tests passed, 0 tests failed 32 | ``` 33 | 34 | And the program will exit with code 0 if all tests passed and 1 if there are failing tests. 35 | 36 | I like to make a target `test` in the `Makefile` that runs the test binary, so that you can do `make test` to run the tests. See this repo for example: 37 | ``` 38 | $ make test 39 | gcc testsuite.c -ggdb -O3 -o testsuite 40 | ./testsuite 41 | hello... SUCCESS 42 | goodbye... SUCCESS 43 | loopy... SUCCESS 44 | 3 tests passed, 0 tests failed 45 | ``` 46 | 47 | 48 | ## How does it work? 49 | 50 | Short story, it uses some macro magic. 51 | 52 | Long story: 53 | `UNIT_TEST` is a macro. If you write `UNIT_TEST(my_test)`, this line expands to 54 | ``` 55 | static void my_test(void); 56 | 57 | static void __attribute__((constructor)) __construct_my_test(void) { 58 | add_test(my_test); 59 | } 60 | 61 | static void __real_my_test(void); 62 | 63 | static void my_test(void) { 64 | printf("%s... ", __func__); 65 | fflush(stdout); 66 | __real_my_test(); 67 | } 68 | 69 | static void __real_my_test(void) 70 | ``` 71 | 72 | Note that there is intentionally no semicolon on the last line: `UNIT_TEST(my_test)` should be followed by a block that makes up the body of the test. 73 | This code: 74 | 1. Declares `my_test` but doesn't define it (a so-called [forward declaration](https://en.wikipedia.org/wiki/Forward_declaration)). This is necessary so that in the next step, we can use `my_test` without having defined it. 75 | 2. Defines a `__construct_my_test` function. This has the `__attribute__((constructor))` attribute, which means the function is executed *even before `main` is executed* (this is GCC-specific, see [this page](https://gcc.gnu.org/onlinedocs/gcc-4.7.0/gcc/Function-Attributes.html) for more information). This code then runs `add_test(my_test)`, which adds a function pointer to `my_test` to a global linked list (defined in `test.h`). 76 | 3. We have another forward declaration, now for `__real_my_test`, the function that will contain the actual unit test code. 77 | 4. It then defines a function `my_test` which prints `my_test... ` and then runs `__real_my_test`. 78 | 5. `__real_my_test` now contains the code block that was placed after `UNIT_TEST(my_test)` as its body. 79 | 80 | The `main` method provided by `test.h` traverses the linked list with function pointers that was initialized by the `__construct_my_test` method (and other such methods if you have more testcases), and runs the functions in the linked list. 81 | 82 | Then, the `__real_my_test` function is the actual tests which calls `test_assert(condition)`, which, again, is a macro. It expands to: 83 | ``` 84 | do { 85 | if (condition) { 86 | printf("SUCCESS\n"); 87 | tests_passed++; 88 | } 89 | else { 90 | printf("FAILED\n"); 91 | tests_failed++; 92 | } 93 | } while(0) 94 | ``` 95 | 96 | This `do ... while` is there to make sure the behavior is right when the macro is used in any context (see [this page](http://www.bruceblinn.com/linuxinfo/DoWhile.html) for a more in-depth explanation). The other code is straightforward. It prints `SUCCESS` or `FAILED` (which comes after the `mytest... ` which was printed in `my_test`, assuming that no other stuff is printed before `test_assert` is called) and keeps track of the number of passed and failed tests. 97 | 98 | Before terminating, `main` prints the number of tests passed and failed (or `No tests are found` if no tests have run at all). 99 | --------------------------------------------------------------------------------