From 5430a6359097735edf900196d24d05baa2b7071a Mon Sep 17 00:00:00 2001 From: sanine Date: Fri, 11 Oct 2024 14:28:30 -0500 Subject: update README from documentation --- README.md | 239 ++++++++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 164 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index 0e6692a..ab05a60 100644 --- a/README.md +++ b/README.md @@ -1,96 +1,185 @@ lily ==== -**lily** is a testing library for C. It provides both basic assertions as well as mocking. +**lily** is a testing library for C. It provides assertions and +clean test registration. -To incorporate lily into a project, just copy `lily-test.h` and `lily-test.c` into your -source code. +To incorporate lily into a project, just copy `lily-test.h` into +your source code. usage ----- -Tests are any function with the signature `void (void)`, and are run with the macro `lily_run_test(test)`. -Tests are intended to be run inside of suite functions, which have the same signature as a test. -To add suite setup or teardown operations, just do them inside the suite function. -Suites are run with the `lily_run_suite(suite)` macro. - -Tests can contain assertions! You should not try to use these assertions outside of tests run with -`lily_run_test()` because you'll probably just crash your program. Tests, if they fail, generate -error messages that detail the mismatch between their expected and actual results and also indicate -the file and line of the failed assertion. On some compilers `__FILE__` is the complete path of the -file (i.e. all the way from root), so lily lets you trim N characters from the front of it by -defining a `SOURCE_PATH_SIZE` macro whose value is N. (I usually define this from within my build tool -so that it'll still work if other people download the software). - -The following assertions are available: - -* `lily_assert_true(bool)` -* `lily_assert_false(bool)` -* `lily_assert_not_null(void *)` -* `lily_assert_null(void *)` -* `lily_assert_ptr_equal(void *, void *)` -* `lily_assert_ptr_not_equal(void *, void *)` -* `lily_assert_int_equal(int, int)` -* `lily_assert_int_not_equal(int, int)` -* `lily_assert_float_equal(float, float, float epsilon)` -* `lily_assert_float_not_equal(float, float, float epsilon)` -* `lily_assert_string_equal(char *, char *)` -* `lily_assert_string_not_equal(char *, char *)` -* `lily_assert_memory_equal(void *, void *, size_t size)` -* `lily_assert_memory_not_equal(void *, void *, size_t size)` - -lily also provides some features to make mocking functions a little nicer. First, -there is the `lily_queue_t` object, which contains a FIFO queue for arbitrary data. -They support the following operations: - -* `lily_queue_t * lily_queue_create()` - create a new queue -* `lily_queue_destroy(lily_queue_t *)` - destroy a queue -* `lily_enqueue(lily_queue_t*, TYPE, value)` - enqueue some value. Any type can be queued; just be careful to dequeue the same data types in the same order! -* `lily_dequeue(lily_queue_t*, TYPE, ptr)` - pops a value from the queue into a pointer of **an appropriate type**. Be careful not to dequeue data types in the same order you queued them! - -This system is intended to allow you to control the behavior/return types of mock functions -by queueing things before a call that the mock will dequeue. - -lily also provides the `lily_mock_t` object, which uses these queues to easily store the arguments -of a call to a function. - -* `lily_mock_t * lily_mock_create()` - create a new mock object -* `lily_mock_destroy(lily_mock_t *)` - destroy a mock object -* `lily_mock_call(lily_mock_t*, struct lily_mock_arg_t*)` - store arguments (see below) -* `lily_get_call(lily_mock_t*, struct lily_mock_arg_t*, int)` - store arguments (see below) - -Each `struct lily_mock_arg_t` contains a size and void pointer, so storing a set of arguments -looks something like this: +A basic unit test file looks like this: + ``` -lily_mock_t *m; // initialized somewhere -void some_func(int a, const char *b) +#include "lily-test.h" + +LILY_FILE_BEGIN(example_file) + + +LILY_TEST("example test 1") +{ + CHECK_EQ(1+1, 2, "%d"); +} +#include LILY_PUSH_TEST() + + +LILY_TEST("example test 2") { - struct lily_mock_arg_t args[] = { - { sizeof(int), &a }, - { sizeof(const char *), &b }, - }; - lily_mock_call(m, args); - /* ... */ + CHECK_LT(3.0, 4.0, "%0.2f"); } +#include LILY_PUSH_TEST() + + +#define LILY_FILE_END +#include LILY_REGISTER_TESTS() ``` -Retrieving calls looks much the same, except the pointers in the array are to destinations -and not sources. (You also need to specify the zero-indexed number of the call -- that's the `int` argument). + +This will define a function pointer `example_file` with signature `void ()` +that, when called, will execute the two tests and display to stderr any errors +that they generate. While there is more boilerplate than in comparable C++ +libraries, this means that lily-test supports test auto-registration. To add a +new test, just use the LILY_TEST macro as shown, and then follow the function +body with `#include LILY_PUSH_TEST()` to register it. These macros will only +work correctly when used between a LILY_FILE_BEGIN and LILY_FILE_END block, +and only when used together; using LILY_TEST without a LILY_PUSH_TEST is +probably going to throw up a ton of compile errors. + + +### Using a single file + +For very small projects, you may be able to get away with using only a single +unit test file. In this case, you should set it up along the following lines: + + +``` +#define LILY_IMPLEMENTATION +#include "lily-test.h" + +/* any includes or code to get your tests working */ + +LILY_FILE_BEGIN(tests) + +/* test definitions... */ + +#define LILY_FILE_END +#include LILY_REGISTER_TESTS() + +int main() +{ + lily_begin(); + tests(); + lily_finish(); + return 0; +} +``` + + +However, in most cases you will want to have multiple test files. + + +### Multiple test files + +You will need, in addition to your unit test files, a header file that +contains extern declarations of the function pointers defined by your files +and a main C source file that contains a main function to run all of the +function pointers. A nice way to set this up is to use X-macros, like this: + + +``` +/* tests.h */ + +#define TESTS \ + X(suite_1) \ + X(suite_2) \ + X(suite_3) \ + + +#define X(suite) extern void (*suite)(); +TESTS +#undef X + + +/* tests_main.c */ + +#define LILY_IMPLEMENTATION +#include "lily-test.h" + +int main() +{ + lily_begin(); + #define X(suite) suite(); + TESTS + #undef X + lily_finish(); + return 0; +} +``` + + +This is convenient, because it means that when you add a new test file you +need only add a single line to the definition of the TESTS macro in tests.h +and all of the other relevant code is added for you automatically. + + +Note that exactly ONE file should define LILY_IMPLEMENTATION. I find it +reasonable to make this the same file with the implementation of main but YMMV. + + +assertions +---------- + +There are two different basic types of assertions: checks and requirements. +Both checks and requires will mark the overall test as failed if they are false, +but a require will also immediately stop executing the test in addition. + + +### check macros + +**CHECK(x)** +Marks the test as failed if x is false and prints stringified x. + +**CHECK_EQ(x, y, fmt)** +Marks the test as failed if x == y is false. Prints both the stringified names of x and y as well as their values, using the printf format code provided in fmt. + +**CHECK_NEQ(x, y, fmt)** +Marks the test as failed if x != y is false. Prints both the stringified names of x and y as well as their values, using the printf format code provided in fmt. + +**CHECK_LT(x, y, fmt)** +Marks the test as failed if x < y is false. Prints both the stringified names of x and y as well as their values, using the printf format code provided in fmt. + +**CHECK_LE(x, y, fmt)** +Marks the test as failed if x <= y is false. Prints both the stringified names of x and y as well as their values, using the printf format code provided in fmt. + +**CHECK_GT(x, y, fmt)** +Marks the test as failed if x > y is false. Prints both the stringified names of x and y as well as their values, using the printf format code provided in fmt. + +**CHECK_GE(x, y, fmt)** +Marks the test as failed if x >= y is false. Prints both the stringified names of x and y as well as their values, using the printf format code provided in fmt. + +### require macros + +**REQUIRE(x)** +Marks the test as failed and immediately ends test execution if x is false and prints stringified x. +**REQUIRE_EQ(x, y, fmt)** +Marks the test as failed and immediately ends test execution if x == y is false. Prints both the stringified names of x and y as well as their values, using the printf format code provided in fmt. +**REQUIRE_NEQ(x, y, fmt)** +Marks the test as failed and immediately ends test execution if x != y is false. Prints both the stringified names of x and y as well as their values, using the printf format code provided in fmt. -~ the future ~ --------------- +**REQUIRE_LT(x, y, fmt)** +Marks the test as failed and immediately ends test execution if x < y is false. Prints both the stringified names of x and y as well as their values, using the printf format code provided in fmt. -Some features I'd like to add: +**REQUIRE_LE(x, y, fmt)** +Marks the test as failed and immediately ends test execution if x <= y is false. Prints both the stringified names of x and y as well as their values, using the printf format code provided in fmt. -A `prepare-tests.sh` script that will automatically scan -the given source root for all files matching `*.test.c` and qenerate a header file, -main function, and Makefile to run all of the functions matching `void ()` in those -test files. (If you want a utility function or something, just make it `static`). +**REQUIRE_GT(x, y, fmt)** +Marks the test as failed and immediately ends test execution if x > y is false. Prints both the stringified names of x and y as well as their values, using the printf format code provided in fmt. -It might be nice to switch to an stb-style header-only library, where the contents of -`lily-test.c` are put inside of a macro gate, and the user just has to `#define LILY_IMPLEMENTATION` -in some file that includes `lily-test.h`. +**REQUIRE_GE(x, y, fmt)** +Marks the test as failed and immediately ends test execution if x >= y is false. Prints both the stringified names of x and y as well as their values, using the printf format code provided in fmt. -- cgit v1.2.1