diff options
-rw-r--r-- | README.md | 246 | ||||
-rw-r--r-- | doc/a_simple_example.c | 68 | ||||
-rw-r--r-- | doc/example.c | 103 | ||||
-rw-r--r-- | doc/run-01.png | bin | 2766 -> 0 bytes | |||
-rw-r--r-- | doc/run-02.png | bin | 1065 -> 0 bytes | |||
-rw-r--r-- | doc/run-03.png | bin | 1273 -> 0 bytes | |||
-rw-r--r-- | doc/run_01.png | bin | 0 -> 21120 bytes | |||
-rw-r--r-- | doc/run_02.png | bin | 0 -> 34615 bytes | |||
-rw-r--r-- | doc/run_03.png | bin | 0 -> 16912 bytes | |||
-rw-r--r-- | simple-test.h (renamed from src/simple-test.h) | 1 |
10 files changed, 247 insertions, 171 deletions
@@ -1,154 +1,194 @@ simple-test =========== -simple unit testing for C implemented as a single header file +[simple-test.h](simple-test.h) is a single header file which implements +quick-and-dirty unit testing for C. robust unit testing suites for C exist, but +require a degree of boilerplate which can be prohibitive for their use in +spare-time projects. if you're writing something for fun, lots of boilerplate +defeats the purpose of writing it. simple-test is made to be as easy to use as +possible: include the header, compile, and run. + +_note:_ although it has no dependencies outside the standard library, this +project makes heavy use of `_Generic`, so C11 support is required. + brief summary ------------- -Your tests should be written as a single .c file separate from -the body of text containing your functionality to be tested. -Write the tests, include simple-test.h, point your compiler at -the necessary files / libs, and you're done! +test suites are to be written as a single .c file separate from the body of +functionality to be tested. include the functionality needed (including headers +and linking against objects) to produce an executable file from the .c file, +then run the executable to test. simple-test internally defines a `main` +function, so none is required, and the `argc` and `argv` arguments are made +available to all `TEST`s. -an example ----------- -consider the following simple program: +example +------- -```c -/* keep this include at the very top of the file */ -#include "simple-test.h" +the following example file is available as [example.c](doc/example.c), which +you can compile and play with yourself. -/* any global variables, functions, other inclusions, etc. - * should be declared here */ -#include "header_with_stuff_to_be_tested.h" +```C +#include "../simple-test.h" -int add_here(int a, int b) +/* global variables, functions, and includes must come before BEGIN_TEST */ +char *global_s; + +/* a teardown function is called by TESTs which include USE_TEARDOWN. the call + * occurs either after an ASSERT fails or after the TEST is finished. + * + * defining one is optional, but, if used, REGISTER_TEARDOWN must appear + * between BEGIN_TEST and the first TEST + * + * here i'm using it to free memory that's alloced inside tests below */ +void teardown(void) { - /* ASSERT / ECHO statements within functions - * like this are perfectly valid. this is - * useful for writing initialisation functions - * called at the beginning of multiple TESTs */ - ECHO("this is the local add"); + free(global_s); +} - /* ensure a and b are non-0 using generic ASSERT */ - ASSERT(a); - ASSERT(b); +/* must appear before an (optional) REGISTER_TEARDOWN and all TESTs */ +BEGIN_TEST + +/* if used, must appear before first TEST and after BEGIN_TEST */ +REGISTER_TEARDOWN(teardown); - return a + b; +/* run a test. provided description must be a string literal */ +TEST("basic assertion") +{ + /* ASSERT fails if passed some sort of non-true value (0, false, NULL) */ + ASSERT(1); } -/* must come before all TESTs */ -BEGIN_TEST +TEST("basic not assertion") +{ + bool b = false; + + ASSERT_NOT(b); +} -/* the string is a description of the test being run */ -TEST("check add()'s return value") +TEST("boolean comparison") { - /* mixing different precisions is allowed */ - long var1 = 2; - int8_t var2 = 2; + bool a = false, b = true; - /* add is a function included from our hypothetical - * header_with_stuff_to_be_tested.h */ - ASSERT_INT_EQ(var1+var2, add(var1, var2)); + /* fail if parameters are not equal */ + ASSERT_EQ(a, b); +} - /* generic versions of ASSERTions are also valid, - * but only for number types (int / uint / float ) */ - ASSERT_EQ(var1+var2, add_here(var1, var2)); +TEST("type mismatch") +{ + char a = 'a'; + int i = 97; + char *b = NULL; + + /* for convenience's sake, when presented with unmatched types, assertions + * try to resolve them in a way that's most likely to match the + * programmer's intentions. here 'i' is interpreted as a char */ + ASSERT_EQ(a, i); + + /* if there isn't a straightforward comparison to make, though (as is the + * case here, with a 'char' and 'char *'), a type mismatch error occurs */ + ASSERT_EQ(a, b); } -TEST("compare two arrays of strings") +TEST("ECHO example") { int i; - char array1[][10] = { - "str1", - "str2", - "str3", - }; - char array2[][10] = { - "str1", - "str2", - /* matching will fail here */ - "different", - }; - - for(i = 0; i < sizeof(array1) / sizeof(char[10]); i++) { - /* ECHO can be used to print (with pretty - * formatting) the current state within the - * test */ - ECHO("checking strs at i == %i", i); - - ASSERT_STR_EQ(array1[i], array2[i]); + + /* ECHO can be used to neatly report information during a run */ + if (true) + ECHO("loop until i not less than 1"); + + for (i = 0; i < 2; i++) { + /* it takes printf format strings and variable args as well */ + ECHO("i == %d", i); + ASSERT_LT(i, 1); } } +TEST("string comparison") +{ + char *s = "test"; + global_s = strdup("test"); + + /* USE_TEARDOWN; tells this test to call the previously defined teardown + * function on exiting (successfully or otherwise) */ + USE_TEARDOWN; + + /* strings are compared by content, so this assertion succeeds */ + ASSERT_EQ(s, global_s); +} + +TEST("pointer comparison") +{ + char *s = "test"; + global_s = strdup("test"); + USE_TEARDOWN; + + /* you can cast parameters in order to use a different type of comparison. + * here i'm casting the 'char *' to 'void *' so assertion performs a + * pointer comparison, which fails */ + ASSERT_EQ((void*)s, (void*)global_s); +} + /* must come after all TESTs */ END_TEST ``` -running this test, the output might look something like this: +on compiling and running this file, the output will look something like this: + +![run 1](doc/run_01.png) -![run 1](doc/run-01.png) +an error, because the types passed to the second assertion in the "type +mismatch" test are... well, mismatched. simple-test does its best to +accommodate mismatched types when it makes sense to do so (e.g. comparing a +signed and unsigned integer), but there is no meaningful comparison to be made +between a `char` and a `char *`, so the error is returned. -if var1 in the first test were changed to 0, like so: +if that assertion is commented out: -```c - /* mixing different precisions is allowed */ - long var1 = 0; - int8_t var2 = 2; +```C + /* if there isn't a straightforward comparison to make, though (as is the + * case here, with a 'char' and 'char *'), a type mismatch error occurs */ + /* ASSERT_EQ(a, b); */ ``` -ASSERT(a); in add_here() would fail like this: +and the tests re-run, the output would look like this: -![run 2](doc/run-02.png) +![run 2](doc/run_02.png) -if array2 were modified instead so the third strings would match, -a successful output would look like this: +as you can see, there are multiple failing assertions, and their failures are +reported and a total count of failing tests printed. if all of these failing +asserts are commented out and the tests re-run, the output looks like this: -![run 3](doc/run-03.png) +![run 3](doc/run_03.png) +hooray! ^_^ -defined macros --------------- + +interface +--------- + +simple-test's interface consists of a series of preprocessor macros: | **NAME** | **DESCRIPTION** | |---------:|:----------------| -| **BEGIN_TEST** | must be included once, after includes and global declarations and before the first TEST statement | -| **END_TEST** | must be included once, after the final TEST statement | +| **BEGIN_TEST** | must be included once, after includes and global declarations and before the first `TEST` statement | +| **END_TEST** | must be included once, after the final `TEST` statement | | **TEST(<description>)**{} | declare a test, described by `description`, which consists of statements between the {} | +| **REGISTER_TEARDOWN(<func>)** | optionally included after `BEGIN_TEST` and before the first `TEST` statement. registers function `func`, which must be of type `void func(void)`, as a teardown function to be called after subscribing tests exit | +| **USE_TEARDOWN;** | if included within a `TEST` body (before any `ASSERT` statements), the designated `TEST` will call the function registered with `REGISTER_TEARDOWN` on terminating | | **ECHO(<...>)** | print a formatted description of the state within the current test | -| **ASSERT<_type_name><_condition>(<args>)** | assert, on either one or two `args` of type `_type_name`, that `_condition` is true | +| **ASSERT<_condition>(<args>)** | assert, on either one or two `args`, that `_condition` is true | -valid `_type_name` and `_condition` values for ASSERT statements are: - -| **_type_name** | **DESCRIPTION** | -|---------------:|:----------------| -| | if the _type_name is omitted, generic assertions which automatically determine type are used. valid only for number types, i.e. int, unsigned, double. | -| **_BOOL** | operate on args of type `bool` | -| **_INT** | operate on signed integer types | -| **_UINT** | operate on unsigned integer types | -| **_HEX** | operate on unsigned integer types. output with %X formatting | -| **_FLOAT** | operate on floating point types | -| **_PTR** | operate on generic pointer types. casts to void* | -| **_CHAR** | operate on args of type `char` | -| **_STR** | operate on strings pointed to by args of type `char*`. | -| **_WCHAR** | operate on wide character types | -| **_WSTR** | operate on wide strings pointed to by args of type `wchar_t*` | +valid values for `_condition` are: | **_condition** | **DESCRIPTION** | |---------------:|:----------------| -| | if the _condition is omitted, fail if the single arg is a "not" value, i.e. NULL, 0, '\0' etc. | -| **_EQ** | fail if the two args are not equal | -| **_NEQ** | fail if the two args are equal | -| **_G** | fail if the first arg is not greater than the second | -| **_GEQ** | fail if the first arg is not greater than or equal to the second | -| **_L** | fail if the first arg is not less than the second | -| **_LEQ** | fail if the first arg is not less than or equal to the second | - -note ----- - -this makes heavy use of _Generic, so C11 support is required - -tested successfully with both gcc and clang +| | if the _condition is omitted, fail if the single argument is a "not" value, i.e. NULL, 0, '\0' etc. | +| **_EQ** | fail if the two arguments are not equal | +| **_NEQ** | fail if the two arguments are equal | +| **_GT** | fail if the first argument is not greater than the second | +| **_GEQ** | fail if the first argument is not greater than or equal to the second | +| **_LT** | fail if the first argument is not less than the second | +| **_LEQ** | fail if the first argument is not less than or equal to the second | diff --git a/doc/a_simple_example.c b/doc/a_simple_example.c deleted file mode 100644 index 6784713..0000000 --- a/doc/a_simple_example.c +++ /dev/null @@ -1,68 +0,0 @@ -/* keep this include at the very top of the file */ -#include "../src/simple-test.h" - -/* any global variables, functions, other inclusions, etc. - * should be declared here */ -#include "header_with_stuff_to_be_tested.h" - -int add_here(int a, int b) -{ - /* ASSERT / ECHO statements within functions - * like this are perfectly valid. this is - * useful for writing initialisation functions - * called at the beginning of multiple TESTs */ - ECHO("this is the local add"); - - /* ensure a and b are non-0 using generic ASSERT */ - ASSERT(a); - ASSERT(b); - - return a + b; -} - -/* must come before all TESTs */ -BEGIN_TEST - -/* the string is a description of the test being run */ -TEST("check add()'s return value") -{ - /* mixing different precisions is allowed */ - long var1 = 2; - int8_t var2 = 2; - - /* add is a function included from our hypothetical - * header_with_stuff_to_be_tested.h */ - ASSERT_INT_EQ(var1+var2, add(var1, var2)); - - /* generic versions of ASSERTions are also valid, - * but only for number types (int / uint / float ) */ - ASSERT_EQ(var1+var2, add_here(var1, var2)); -} - -TEST("compare two arrays of strings") -{ - int i; - char array1[][10] = { - "str1", - "str2", - "str3", - }; - char array2[][10] = { - "str1", - "str2", - /* matching will fail here */ - "different", - }; - - for(i = 0; i < sizeof(array1) / sizeof(char[10]); i++) { - /* ECHO can be used to print (with pretty - * formatting) the current state within the - * test */ - ECHO("checking strs at i == %i", i); - - ASSERT_STR_EQ(array1[i], array2[i]); - } -} - -/* must come after all TESTs */ -END_TEST diff --git a/doc/example.c b/doc/example.c new file mode 100644 index 0000000..26d8f66 --- /dev/null +++ b/doc/example.c @@ -0,0 +1,103 @@ +#include "../simple-test.h" + +/* global variables, functions, and includes must come before BEGIN_TEST */ +char *global_s; + +/* a teardown function is called by TESTs which include USE_TEARDOWN. the call + * occurs either after an ASSERT fails or after the TEST is finished. + * + * defining one is optional, but, if used, REGISTER_TEARDOWN must appear + * between BEGIN_TEST and the first TEST + * + * here i'm using it to free memory that's alloced inside tests below */ +void teardown(void) +{ + free(global_s); +} + +/* must appear before an (optional) REGISTER_TEARDOWN and all TESTs */ +BEGIN_TEST + +/* if used, must appear before first TEST and after BEGIN_TEST */ +REGISTER_TEARDOWN(teardown); + +/* run a test. provided description must be a string literal */ +TEST("basic assertion") +{ + /* ASSERT fails if passed some sort of non-true value (0, false, NULL) */ + ASSERT(1); +} + +TEST("basic not assertion") +{ + bool b = false; + + ASSERT_NOT(b); +} + +TEST("boolean comparison") +{ + bool a = false, b = true; + + /* fail if parameters are not equal */ + ASSERT_EQ(a, b); +} + +TEST("type mismatch") +{ + char a = 'a'; + int i = 97; + char *b = NULL; + + /* for convenience's sake, when presented with unmatched types, assertions + * try to resolve them in a way that's most likely to match the + * programmer's intentions. here 'i' is interpreted as a char */ + ASSERT_EQ(a, i); + + /* if there isn't a straightforward comparison to make, though (as is the + * case here, with a 'char' and 'char *'), a type mismatch error occurs */ + ASSERT_EQ(a, b); +} + +TEST("ECHO example") +{ + int i; + + /* ECHO can be used to neatly report information during a run */ + if (true) + ECHO("loop until i not less than 1"); + + for (i = 0; i < 2; i++) { + /* it takes printf format strings and variable args as well */ + ECHO("i == %d", i); + ASSERT_LT(i, 1); + } +} + +TEST("string comparison") +{ + char *s = "test"; + global_s = strdup("test"); + + /* USE_TEARDOWN; tells this test to call the previously defined teardown + * function on exiting (successfully or otherwise) */ + USE_TEARDOWN; + + /* strings are compared by content, so this assertion succeeds */ + ASSERT_EQ(s, global_s); +} + +TEST("pointer comparison") +{ + char *s = "test"; + global_s = strdup("test"); + USE_TEARDOWN; + + /* you can cast parameters in order to use a different type of comparison. + * here i'm casting the 'char *' to 'void *' so assertion performs a + * pointer comparison, which fails */ + ASSERT_EQ((void*)s, (void*)global_s); +} + +/* must come after all TESTs */ +END_TEST diff --git a/doc/run-01.png b/doc/run-01.png Binary files differdeleted file mode 100644 index b7c6838..0000000 --- a/doc/run-01.png +++ /dev/null diff --git a/doc/run-02.png b/doc/run-02.png Binary files differdeleted file mode 100644 index aedd395..0000000 --- a/doc/run-02.png +++ /dev/null diff --git a/doc/run-03.png b/doc/run-03.png Binary files differdeleted file mode 100644 index f6843dc..0000000 --- a/doc/run-03.png +++ /dev/null diff --git a/doc/run_01.png b/doc/run_01.png Binary files differnew file mode 100644 index 0000000..df74574 --- /dev/null +++ b/doc/run_01.png diff --git a/doc/run_02.png b/doc/run_02.png Binary files differnew file mode 100644 index 0000000..b84d5e5 --- /dev/null +++ b/doc/run_02.png diff --git a/doc/run_03.png b/doc/run_03.png Binary files differnew file mode 100644 index 0000000..05fd99c --- /dev/null +++ b/doc/run_03.png diff --git a/src/simple-test.h b/simple-test.h index 63da668..fa05d36 100644 --- a/src/simple-test.h +++ b/simple-test.h @@ -300,6 +300,7 @@ static enum simple_test_type simple_test_type_resolve(enum simple_test_type t1, simple_test_pad_width, ' ', __LINE__); \ printf(__VA_ARGS__); \ printf("\n"); \ + printf("\e[1;31mtesting aborted\e[m\n"); \ exit(1); \ } while (0) |