unit tests: add TAP unit test framework
This patch contains an implementation for writing unit tests with TAP
output. Each test is a function that contains one or more checks. The
test is run with the TEST() macro and if any of the checks fail then the
test will fail. A complete program that tests STRBUF_INIT would look
like
#include "test-lib.h"
#include "strbuf.h"
static void t_static_init(void)
{
struct strbuf buf = STRBUF_INIT;
check_uint(buf.len, ==, 0);
check_uint(buf.alloc, ==, 0);
check_char(buf.buf[0], ==, '\0');
}
int main(void)
{
TEST(t_static_init(), "static initialization works);
return test_done();
}
The output of this program would be
ok 1 - static initialization works
1..1
If any of the checks in a test fail then they print a diagnostic message
to aid debugging and the test will be reported as failing. For example a
failing integer check would look like
# check "x >= 3" failed at my-test.c:102
# left: 2
# right: 3
not ok 1 - x is greater than or equal to three
There are a number of check functions implemented so far. check() checks
a boolean condition, check_int(), check_uint() and check_char() take two
values to compare and a comparison operator. check_str() will check if
two strings are equal. Custom checks are simple to implement as shown in
the comments above test_assert() in test-lib.h.
Tests can be skipped with test_skip() which can be supplied with a
reason for skipping which it will print. Tests can print diagnostic
messages with test_msg(). Checks that are known to fail can be wrapped
in TEST_TODO().
There are a couple of example test programs included in this
patch. t-basic.c implements some self-tests and demonstrates the
diagnostic output for failing test. The output of this program is
checked by t0080-unit-test-output.sh. t-strbuf.c shows some example
unit tests for strbuf.c
The unit tests will be built as part of the default "make all" target,
to avoid bitrot. If you wish to build just the unit tests, you can run
"make build-unit-tests". To run the tests, you can use "make unit-tests"
or run the test binaries directly, as in "./t/unit-tests/bin/t-strbuf".
Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>
Signed-off-by: Josh Steadmon <steadmon@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-11-09 18:50:43 +00:00
|
|
|
#ifndef TEST_LIB_H
|
|
|
|
#define TEST_LIB_H
|
|
|
|
|
|
|
|
#include "git-compat-util.h"
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Run a test function, returns 1 if the test succeeds, 0 if it
|
|
|
|
* fails. If test_skip_all() has been called then the test will not be
|
|
|
|
* run. The description for each test should be unique. For example:
|
|
|
|
*
|
|
|
|
* TEST(test_something(arg1, arg2), "something %d %d", arg1, arg2)
|
|
|
|
*/
|
|
|
|
#define TEST(t, ...) \
|
|
|
|
test__run_end(test__run_begin() ? 0 : (t, 1), \
|
|
|
|
TEST_LOCATION(), __VA_ARGS__)
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Print a test plan, should be called before any tests. If the number
|
|
|
|
* of tests is not known in advance test_done() will automatically
|
|
|
|
* print a plan at the end of the test program.
|
|
|
|
*/
|
|
|
|
void test_plan(int count);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* test_done() must be called at the end of main(). It will print the
|
|
|
|
* plan if plan() was not called at the beginning of the test program
|
|
|
|
* and returns the exit code for the test program.
|
|
|
|
*/
|
|
|
|
int test_done(void);
|
|
|
|
|
|
|
|
/* Skip the current test. */
|
|
|
|
__attribute__((format (printf, 1, 2)))
|
|
|
|
void test_skip(const char *format, ...);
|
|
|
|
|
|
|
|
/* Skip all remaining tests. */
|
|
|
|
__attribute__((format (printf, 1, 2)))
|
|
|
|
void test_skip_all(const char *format, ...);
|
|
|
|
|
|
|
|
/* Print a diagnostic message to stdout. */
|
|
|
|
__attribute__((format (printf, 1, 2)))
|
|
|
|
void test_msg(const char *format, ...);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Test checks are built around test_assert(). checks return 1 on
|
|
|
|
* success, 0 on failure. If any check fails then the test will fail. To
|
|
|
|
* create a custom check define a function that wraps test_assert() and
|
|
|
|
* a macro to wrap that function to provide a source location and
|
|
|
|
* stringified arguments. Custom checks that take pointer arguments
|
|
|
|
* should be careful to check that they are non-NULL before
|
|
|
|
* dereferencing them. For example:
|
|
|
|
*
|
|
|
|
* static int check_oid_loc(const char *loc, const char *check,
|
|
|
|
* struct object_id *a, struct object_id *b)
|
|
|
|
* {
|
|
|
|
* int res = test_assert(loc, check, a && b && oideq(a, b));
|
|
|
|
*
|
|
|
|
* if (!res) {
|
|
|
|
* test_msg(" left: %s", a ? oid_to_hex(a) : "NULL";
|
|
|
|
* test_msg(" right: %s", b ? oid_to_hex(a) : "NULL";
|
|
|
|
*
|
|
|
|
* }
|
|
|
|
* return res;
|
|
|
|
* }
|
|
|
|
*
|
|
|
|
* #define check_oid(a, b) \
|
|
|
|
* check_oid_loc(TEST_LOCATION(), "oideq("#a", "#b")", a, b)
|
|
|
|
*/
|
|
|
|
int test_assert(const char *location, const char *check, int ok);
|
|
|
|
|
|
|
|
/* Helper macro to pass the location to checks */
|
|
|
|
#define TEST_LOCATION() TEST__MAKE_LOCATION(__LINE__)
|
|
|
|
|
|
|
|
/* Check a boolean condition. */
|
|
|
|
#define check(x) \
|
|
|
|
check_bool_loc(TEST_LOCATION(), #x, x)
|
|
|
|
int check_bool_loc(const char *loc, const char *check, int ok);
|
|
|
|
|
2024-05-27 11:47:04 +00:00
|
|
|
/*
|
|
|
|
* Compare two integers. Prints a message with the two values if the
|
|
|
|
* comparison fails. NB this is not thread safe.
|
|
|
|
*/
|
|
|
|
#define check_pointer_eq(a, b) \
|
|
|
|
(test__tmp[0].p = (a), test__tmp[1].p = (b), \
|
|
|
|
check_pointer_eq_loc(TEST_LOCATION(), #a" == "#b, \
|
|
|
|
test__tmp[0].p == test__tmp[1].p, \
|
|
|
|
test__tmp[0].p, test__tmp[1].p))
|
|
|
|
int check_pointer_eq_loc(const char *loc, const char *check, int ok,
|
|
|
|
const void *a, const void *b);
|
|
|
|
|
unit tests: add TAP unit test framework
This patch contains an implementation for writing unit tests with TAP
output. Each test is a function that contains one or more checks. The
test is run with the TEST() macro and if any of the checks fail then the
test will fail. A complete program that tests STRBUF_INIT would look
like
#include "test-lib.h"
#include "strbuf.h"
static void t_static_init(void)
{
struct strbuf buf = STRBUF_INIT;
check_uint(buf.len, ==, 0);
check_uint(buf.alloc, ==, 0);
check_char(buf.buf[0], ==, '\0');
}
int main(void)
{
TEST(t_static_init(), "static initialization works);
return test_done();
}
The output of this program would be
ok 1 - static initialization works
1..1
If any of the checks in a test fail then they print a diagnostic message
to aid debugging and the test will be reported as failing. For example a
failing integer check would look like
# check "x >= 3" failed at my-test.c:102
# left: 2
# right: 3
not ok 1 - x is greater than or equal to three
There are a number of check functions implemented so far. check() checks
a boolean condition, check_int(), check_uint() and check_char() take two
values to compare and a comparison operator. check_str() will check if
two strings are equal. Custom checks are simple to implement as shown in
the comments above test_assert() in test-lib.h.
Tests can be skipped with test_skip() which can be supplied with a
reason for skipping which it will print. Tests can print diagnostic
messages with test_msg(). Checks that are known to fail can be wrapped
in TEST_TODO().
There are a couple of example test programs included in this
patch. t-basic.c implements some self-tests and demonstrates the
diagnostic output for failing test. The output of this program is
checked by t0080-unit-test-output.sh. t-strbuf.c shows some example
unit tests for strbuf.c
The unit tests will be built as part of the default "make all" target,
to avoid bitrot. If you wish to build just the unit tests, you can run
"make build-unit-tests". To run the tests, you can use "make unit-tests"
or run the test binaries directly, as in "./t/unit-tests/bin/t-strbuf".
Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>
Signed-off-by: Josh Steadmon <steadmon@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-11-09 18:50:43 +00:00
|
|
|
/*
|
|
|
|
* Compare two integers. Prints a message with the two values if the
|
|
|
|
* comparison fails. NB this is not thread safe.
|
|
|
|
*/
|
|
|
|
#define check_int(a, op, b) \
|
|
|
|
(test__tmp[0].i = (a), test__tmp[1].i = (b), \
|
|
|
|
check_int_loc(TEST_LOCATION(), #a" "#op" "#b, \
|
|
|
|
test__tmp[0].i op test__tmp[1].i, \
|
|
|
|
test__tmp[0].i, test__tmp[1].i))
|
|
|
|
int check_int_loc(const char *loc, const char *check, int ok,
|
|
|
|
intmax_t a, intmax_t b);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Compare two unsigned integers. Prints a message with the two values
|
|
|
|
* if the comparison fails. NB this is not thread safe.
|
|
|
|
*/
|
|
|
|
#define check_uint(a, op, b) \
|
|
|
|
(test__tmp[0].u = (a), test__tmp[1].u = (b), \
|
|
|
|
check_uint_loc(TEST_LOCATION(), #a" "#op" "#b, \
|
|
|
|
test__tmp[0].u op test__tmp[1].u, \
|
|
|
|
test__tmp[0].u, test__tmp[1].u))
|
|
|
|
int check_uint_loc(const char *loc, const char *check, int ok,
|
|
|
|
uintmax_t a, uintmax_t b);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Compare two chars. Prints a message with the two values if the
|
|
|
|
* comparison fails. NB this is not thread safe.
|
|
|
|
*/
|
|
|
|
#define check_char(a, op, b) \
|
|
|
|
(test__tmp[0].c = (a), test__tmp[1].c = (b), \
|
|
|
|
check_char_loc(TEST_LOCATION(), #a" "#op" "#b, \
|
|
|
|
test__tmp[0].c op test__tmp[1].c, \
|
|
|
|
test__tmp[0].c, test__tmp[1].c))
|
|
|
|
int check_char_loc(const char *loc, const char *check, int ok,
|
|
|
|
char a, char b);
|
|
|
|
|
|
|
|
/* Check whether two strings are equal. */
|
|
|
|
#define check_str(a, b) \
|
|
|
|
check_str_loc(TEST_LOCATION(), "!strcmp("#a", "#b")", a, b)
|
|
|
|
int check_str_loc(const char *loc, const char *check,
|
|
|
|
const char *a, const char *b);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Wrap a check that is known to fail. If the check succeeds then the
|
|
|
|
* test will fail. Returns 1 if the check fails, 0 if it
|
|
|
|
* succeeds. For example:
|
|
|
|
*
|
|
|
|
* TEST_TODO(check(0));
|
|
|
|
*/
|
|
|
|
#define TEST_TODO(check) \
|
|
|
|
(test__todo_begin(), test__todo_end(TEST_LOCATION(), #check, check))
|
|
|
|
|
|
|
|
/* Private helpers */
|
|
|
|
|
|
|
|
#define TEST__STR(x) #x
|
|
|
|
#define TEST__MAKE_LOCATION(line) __FILE__ ":" TEST__STR(line)
|
|
|
|
|
|
|
|
union test__tmp {
|
|
|
|
intmax_t i;
|
|
|
|
uintmax_t u;
|
|
|
|
char c;
|
2024-05-27 11:47:04 +00:00
|
|
|
const void *p;
|
unit tests: add TAP unit test framework
This patch contains an implementation for writing unit tests with TAP
output. Each test is a function that contains one or more checks. The
test is run with the TEST() macro and if any of the checks fail then the
test will fail. A complete program that tests STRBUF_INIT would look
like
#include "test-lib.h"
#include "strbuf.h"
static void t_static_init(void)
{
struct strbuf buf = STRBUF_INIT;
check_uint(buf.len, ==, 0);
check_uint(buf.alloc, ==, 0);
check_char(buf.buf[0], ==, '\0');
}
int main(void)
{
TEST(t_static_init(), "static initialization works);
return test_done();
}
The output of this program would be
ok 1 - static initialization works
1..1
If any of the checks in a test fail then they print a diagnostic message
to aid debugging and the test will be reported as failing. For example a
failing integer check would look like
# check "x >= 3" failed at my-test.c:102
# left: 2
# right: 3
not ok 1 - x is greater than or equal to three
There are a number of check functions implemented so far. check() checks
a boolean condition, check_int(), check_uint() and check_char() take two
values to compare and a comparison operator. check_str() will check if
two strings are equal. Custom checks are simple to implement as shown in
the comments above test_assert() in test-lib.h.
Tests can be skipped with test_skip() which can be supplied with a
reason for skipping which it will print. Tests can print diagnostic
messages with test_msg(). Checks that are known to fail can be wrapped
in TEST_TODO().
There are a couple of example test programs included in this
patch. t-basic.c implements some self-tests and demonstrates the
diagnostic output for failing test. The output of this program is
checked by t0080-unit-test-output.sh. t-strbuf.c shows some example
unit tests for strbuf.c
The unit tests will be built as part of the default "make all" target,
to avoid bitrot. If you wish to build just the unit tests, you can run
"make build-unit-tests". To run the tests, you can use "make unit-tests"
or run the test binaries directly, as in "./t/unit-tests/bin/t-strbuf".
Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>
Signed-off-by: Josh Steadmon <steadmon@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2023-11-09 18:50:43 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
extern union test__tmp test__tmp[2];
|
|
|
|
|
|
|
|
int test__run_begin(void);
|
|
|
|
__attribute__((format (printf, 3, 4)))
|
|
|
|
int test__run_end(int, const char *, const char *, ...);
|
|
|
|
void test__todo_begin(void);
|
|
|
|
int test__todo_end(const char *, const char *, int);
|
|
|
|
|
|
|
|
#endif /* TEST_LIB_H */
|