From 7e0c088cf0b1a1550616133c1580d424ef4a3eae Mon Sep 17 00:00:00 2001 From: jlamothe Date: Wed, 22 Nov 2023 20:55:03 +0000 Subject: [PATCH] completed documentation in README.md (hopefully) --- README.md | 249 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 226 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 0dbf97e..d19accd 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ License along with this program. If not, see A simple unit testing framework for C programs in Plan9 This provides the library file `9unit.a` and the header `9unit.h`. -The header file is relatively well commented and can provide a fairly +The header is relatively well commented and can provide a fairly comprehensive breakdown of the API. This document will however provide a basic overview below. @@ -35,23 +35,23 @@ structure. As its name would imply, it contains the current state of the tests in progress, however it should almost never be necessary to interact with it directly. With the exception of `run_tests()` (described below), all functions provided by the library will take -take a pointer to a `TestState` value as their first parameter. +take a pointer to the current `TestState` value as their first +parameter. ## `run_tests()` -This will typically be the first function you call in your tests. It -sets up the testing framework, creates an initial `TestState` value, -runs the tests provided to it and displays a log and summary at the -end. If any of the provided tests failed, it will cause the test -program to exit with status of `"test(s) failed"`. Its prototype -follows: +This will typically be the first function you call. It sets up the +testing framework, creates an initial `TestState` value, runs the +tests provided to it, and displays a log and summary at the end. If +any of the provided tests fail, it will cause the test program to exit +with a status of `"test(s) failed"`. Its prototype follows: ```C -void run_test(void (*tests)(TestState *)) +void run_tests(void (*)(TestState *)); ``` -The `tests` parameter is a pointer to a function which then is -responsible for running the actual tests. A pointer to the newly +Its only argument is a pointer to a function which is then +responsible for acuually running the tests. A pointer to the newly created `TestState` value will be passed to this function. ## Simple Tests @@ -71,20 +71,40 @@ unsurprisingly) the result of the test. The options are as follows: - `test_success`: the test was completed successfully - `test_failure`: the test failed -- `test_pending`: the test is pending and should be ignored for now +- `test_pending`: the test is pending, and should be ignored for now -Tests of this type can be run by passing them to the `run_test()` -function, which has the following prototype: +Tests of this type can be run by passing a pointer to them to the +`run_test()` function, which has the following prototype: ```C void run_test( - TestState *s, - TestResult (*test)(TestState *) + TestState *, + TestResult (*)(TestState *) ) ``` -This function will run the provided test and update the provided -`TestState` to reflect the result of the test. +This function will run the provided test and update the state to +reflect the result of the test. Thus, the above hypothetical test +could by run as follows: + +```C +void +tests(TestState *s) +{ + run_test(s, my_test); +} + +void +main() +{ + run_tests(tests); + exits(0); +} +``` + +Passing a null `TestState` pointer will cause nothing to happen. This +is true of all functions in this library. Passing a null function +pointer to `run_test()` will be interpreted as a pending test. ## Passing Values to Tests @@ -96,8 +116,191 @@ into a generic test so that it can be reused multiple times. ### The `ptr` Value -The `TestState` struct has a value called `ptr` which is a void -pointer that can be set prior to calling `run_test()`. This value can -then be read by the test function, giving you the ability to -essentially pass in *any* type of data you may need. While not ideal, -it's *a* solution. \ No newline at end of file +The `TestState` struct has a value called `ptr` which is a `void` +pointer that can be set prior to calling `run_test()` (or any other +function, really). This value can then be read by the test function, +giving you the ability to essentially pass in *any* type of data you +may need. While not ideal, it's *a* solution. + +The library does not perform any kind of validation or automatic +memory management on the `ptr` value (this is C after all), so the +responsibility for this falls to the programmer implementing the +tests. + +### Convenience Functions + +As the tests become more and more complex, managing a single `ptr` +value can become increasingly burdensome. For this reason, there are +a few convenience functions that provide an alternate mechanism of +passing data into a function, without altering the `ptr` value. (They +actually do internally, but they restore the original value before +passing the state on.) Two such functions are `run_test_with()` and +`run_test_compare()`. + +`run_test_with()` has the following prototype: + +```C +void run_test_with( + TestState *, + TestResult (*)(TestState *, void *), + void * +); +``` + +The first argument points to the current test state. The second +points to a test function much like the simple test function described +above, but that takes a void pointer as a second argument. Finally, +the third argument is the pointer that gets passed into the test +function. + +`run_test_compare()` is similar, but it allows two pointers to be +passed into the test. This is useful for comparing an actual value to +an expected one, for instance comparing the actual output from a +function to an expected value. + +The prototype for `run_test_compare()` follows: + +```C +void run_test_compare( + TestState *, + TestResult (*)(TestState *, void *, void *), + void *, + void * +); +``` + +## Test Contexts + +It is useful to document what your tests are doing. This can be +achieved using contexts. These are essentially just string values +describing what the provided test(s) are for. Contexts can be nested +into hierarchies. This is useful both for organization purposes as +well as creating reusable test code. The following functions allow +for the creation of test contexts as well as displaying information +about the tests as they're run. + +### `test_context()` + +```C +void test_context( + TestState *, + const char *, + void (*)(TestState *) +); +``` + +This function takes a pointer to the current `TestState`, a string +describing the context, and a function pointer that is used the same +way as the funciton pointer passed to `run_tests()`. + +### `test_context_with()` + +```C +void test_context_with( + TestState *, + const char *, + void (*)(TestState *, void *), + void * +); +``` + +This funciton allows for the passing of a `void` pointer into the test +function, in much the same way as the `run_test_with()` function. Its +arguments are (in order), a pointer to the current state, the context +description, a pointer to the test function, and the pointer being +passed into that function. + +### `test_context_compare()` + +```C +void test_context_compare( + TestState *, + const char *, + void (*)(TestState *, void *, void *), + void *, + void * +); +``` + +This funciton allows the passing to two `void` pointers into a context +in a manner similar to `run_test_compare()`. + +### `single_test_context()` + +```C +void single_test_context( + TestState *, + const char *, + TestState (*)(TestState *) +); +``` + +This function applies the context label to a *single* test. The +function passed in is expected to operate in the same way as a +function passed to `run_test()`. + +### `single_test_context_with()` + +```C +void single_test_context_with( + TestState *, + const char *, + TestState (*)(TestState *, void *), + void * +); +``` + +This is similar to `single_test_context()` but allows a `void` pointer +to be passed as in `run_test_with()`. + +### `single_test_context_compare()` + +```C +void single_test_context_compare( + TestState *, + const char *, + TestResult (*)(TestState *, void *, void *), + void *, + void * +); +``` + +I assume you get the idea at this point. + +## Logging + +When `run_tests()` finishes running the tests, it displays a log and +summary. The summary is simply a count of the number of tests run, +passed, failed, and pending. While this is useful (and probably all +you need to know when all the tests pass) you probably want more +detail when something goes wrong. To facilitate this, tests can +append to the test log, which is automatically displayed just before +the summary. There are two functions for doing this. + +### `append_test_log()` + +```C +void append_test_log( + TestState *, + const char * +); +``` + +This appends an arbitrary string to the end of the test log. The +string is copied into the log, so the value pointed to by the second +argument does not need to persist in memory beyond the end of the call +to the function. Log entries are expected to be single lines. No +trailing newline should be present (but the trailing NUL character +should (obviously)). + +### `log_test_context()` + +```C +void log_test_context(TestState *); +``` + +This function appends an entry to the log indicating the test's +current *full* context. If no context is defined, the log entry will +be `""`. If the test is inside of a context labeled +`"foo"` which is inside of another context labeled `"bar"`, the +resulting log entry will read `"bar: foo"`.