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: + + - +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: - + -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: - + +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.pngBinary files differ deleted file mode 100644 index b7c6838..0000000 --- a/doc/run-01.png +++ /dev/null diff --git a/doc/run-02.png b/doc/run-02.pngBinary files differ deleted file mode 100644 index aedd395..0000000 --- a/doc/run-02.png +++ /dev/null diff --git a/doc/run-03.png b/doc/run-03.pngBinary files differ deleted file mode 100644 index f6843dc..0000000 --- a/doc/run-03.png +++ /dev/null diff --git a/doc/run_01.png b/doc/run_01.pngBinary files differ new file mode 100644 index 0000000..df74574 --- /dev/null +++ b/doc/run_01.png diff --git a/doc/run_02.png b/doc/run_02.pngBinary files differ new file mode 100644 index 0000000..b84d5e5 --- /dev/null +++ b/doc/run_02.png diff --git a/doc/run_03.png b/doc/run_03.pngBinary files differ new 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) | 
