forked from git/git
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
unit-tests: proof of concept test framework
This patch contains a proof of concept 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); if (check(buf.buf == strbuf_slopbuf)) return; /* avoid SIGSEV */ 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 can be built with "make unit-tests" (this works but the Makefile changes need some further work). Once they have been built they can be run manually (e.g t/unit-tests/t-strbuf) or with prove. Signed-off-by: Phillip Wood <[email protected]>
- Loading branch information
1 parent
69c7866
commit 2d29258
Showing
7 changed files
with
716 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
#!/bin/sh | ||
|
||
test_description='Test the output of the unit test framework' | ||
|
||
. ./test-lib.sh | ||
|
||
test_expect_success 'TAP output from unit tests' ' | ||
cat >expect <<-EOF && | ||
ok 1 - passing test | ||
ok 2 - passing test and assertion return 0 | ||
# check "1 == 2" failed at t/unit-tests/t-basic.c:68 | ||
# left: 1 | ||
# right: 2 | ||
not ok 3 - failing test | ||
ok 4 - failing test and assertion return -1 | ||
not ok 5 - passing TEST_TODO() # TODO | ||
ok 6 - passing TEST_TODO() returns 0 | ||
# todo check ${SQ}check(x)${SQ} succeeded at t/unit-tests/t-basic.c:17 | ||
not ok 7 - failing TEST_TODO() | ||
ok 8 - failing TEST_TODO() returns -1 | ||
# check "0" failed at t/unit-tests/t-basic.c:22 | ||
# skipping test - missing prerequisite | ||
# skipping check ${SQ}1${SQ} at t/unit-tests/t-basic.c:24 | ||
ok 9 - test_skip() # SKIP | ||
ok 10 - skipped test returns 0 | ||
# skipping test - missing prerequisite | ||
ok 11 - test_skip() inside TEST_TODO() # SKIP | ||
ok 12 - test_skip() inside TEST_TODO() returns 0 | ||
# check "0" failed at t/unit-tests/t-basic.c:40 | ||
not ok 13 - TEST_TODO() after failing check | ||
ok 14 - TEST_TODO() after failing check returns -1 | ||
# check "0" failed at t/unit-tests/t-basic.c:48 | ||
not ok 15 - failing check after TEST_TODO() | ||
ok 16 - failing check after TEST_TODO() returns -1 | ||
# check "!strcmp("\thello\\\\", "there\"\n")" failed at t/unit-tests/t-basic.c:53 | ||
# left: "\011hello\\\\" | ||
# right: "there\"\012" | ||
# check "!strcmp("NULL", NULL)" failed at t/unit-tests/t-basic.c:54 | ||
# left: "NULL" | ||
# right: NULL | ||
# check "${SQ}a${SQ} == ${SQ}\n${SQ}" failed at t/unit-tests/t-basic.c:55 | ||
# left: ${SQ}a${SQ} | ||
# right: ${SQ}\012${SQ} | ||
# check "${SQ}\\\\${SQ} == ${SQ}\\${SQ}${SQ}" failed at t/unit-tests/t-basic.c:56 | ||
# left: ${SQ}\\\\${SQ} | ||
# right: ${SQ}\\${SQ}${SQ} | ||
not ok 17 - messages from failing string and char comparison | ||
# BUG: test has no checks at t/unit-tests/t-basic.c:83 | ||
not ok 18 - test with no checks | ||
ok 19 - test with no checks returns -1 | ||
1..19 | ||
EOF | ||
! "$GIT_BUILD_DIR"/t/unit-tests/t-basic >actual && | ||
test_cmp expect actual | ||
' | ||
|
||
test_done |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
/t-basic | ||
/t-strbuf |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
#include "test-lib.h" | ||
|
||
/* Used to store the return value of check_int(). */ | ||
static int check_res; | ||
|
||
/* Used to store the return value of TEST(). */ | ||
static int test_res; | ||
|
||
static void t_res(int expect) | ||
{ | ||
check_int(check_res, ==, expect); | ||
check_int(test_res, ==, expect); | ||
} | ||
|
||
static void t_todo(int x) | ||
{ | ||
check_res = TEST_TODO(check(x)); | ||
} | ||
|
||
static void t_skip(void) | ||
{ | ||
check(0); | ||
test_skip("missing prerequisite"); | ||
check(1); | ||
} | ||
|
||
static int do_skip(void) | ||
{ | ||
test_skip("missing prerequisite"); | ||
return 0; | ||
} | ||
|
||
static void t_skip_todo(void) | ||
{ | ||
check_res = TEST_TODO(do_skip()); | ||
} | ||
|
||
static void t_todo_after_fail(void) | ||
{ | ||
check(0); | ||
TEST_TODO(check(0)); | ||
} | ||
|
||
static void t_fail_after_todo(void) | ||
{ | ||
check(1); | ||
TEST_TODO(check(0)); | ||
check(0); | ||
} | ||
|
||
static void t_messages(void) | ||
{ | ||
check_str("\thello\\", "there\"\n"); | ||
check_str("NULL", NULL); | ||
check_char('a', ==, '\n'); | ||
check_char('\\', ==, '\''); | ||
} | ||
|
||
static void t_empty(void) | ||
{ | ||
; /* empty */ | ||
} | ||
|
||
int cmd_main(int argc, const char **argv) | ||
{ | ||
test_res = TEST(check_res = check_int(1, ==, 1), "passing test"); | ||
TEST(t_res(0), "passing test and assertion return 0"); | ||
test_res = TEST(check_res = check_int(1, ==, 2), "failing test"); | ||
TEST(t_res(-1), "failing test and assertion return -1"); | ||
test_res = TEST(t_todo(0), "passing TEST_TODO()"); | ||
TEST(t_res(0), "passing TEST_TODO() returns 0"); | ||
test_res = TEST(t_todo(1), "failing TEST_TODO()"); | ||
TEST(t_res(-1), "failing TEST_TODO() returns -1"); | ||
test_res = TEST(t_skip(), "test_skip()"); | ||
TEST(check_int(test_res, ==, 0), "skipped test returns 0"); | ||
test_res = TEST(t_skip_todo(), "test_skip() inside TEST_TODO()"); | ||
TEST(t_res(0), "test_skip() inside TEST_TODO() returns 0"); | ||
test_res = TEST(t_todo_after_fail(), "TEST_TODO() after failing check"); | ||
TEST(check_int(test_res, ==, -1), "TEST_TODO() after failing check returns -1"); | ||
test_res = TEST(t_fail_after_todo(), "failing check after TEST_TODO()"); | ||
TEST(check_int(test_res, ==, -1), "failing check after TEST_TODO() returns -1"); | ||
TEST(t_messages(), "messages from failing string and char comparison"); | ||
test_res = TEST(t_empty(), "test with no checks"); | ||
TEST(check_int(test_res, ==, -1), "test with no checks returns -1"); | ||
|
||
return test_done(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
#include "test-lib.h" | ||
#include "strbuf.h" | ||
|
||
/* wrapper that supplies tests with an initialized strbuf */ | ||
static void setup(void (*f)(struct strbuf*, void*), void *data) | ||
{ | ||
struct strbuf buf = STRBUF_INIT; | ||
|
||
f(&buf, data); | ||
strbuf_release(&buf); | ||
check_uint(buf.len, ==, 0); | ||
check_uint(buf.alloc, ==, 0); | ||
check(buf.buf == strbuf_slopbuf); | ||
check_char(buf.buf[0], ==, '\0'); | ||
} | ||
|
||
static void t_static_init(void) | ||
{ | ||
struct strbuf buf = STRBUF_INIT; | ||
|
||
check_uint(buf.len, ==, 0); | ||
check_uint(buf.alloc, ==, 0); | ||
if (check(buf.buf == strbuf_slopbuf)) | ||
return; /* avoid de-referencing buf.buf */ | ||
check_char(buf.buf[0], ==, '\0'); | ||
} | ||
|
||
static void t_dynamic_init(void) | ||
{ | ||
struct strbuf buf; | ||
|
||
strbuf_init(&buf, 1024); | ||
check_uint(buf.len, ==, 0); | ||
check_uint(buf.alloc, >=, 1024); | ||
if (!check(buf.buf != NULL)) | ||
check_char(buf.buf[0], ==, '\0'); | ||
strbuf_release(&buf); | ||
} | ||
|
||
static void t_addch(struct strbuf *buf, void *data) | ||
{ | ||
const char *p_ch = data; | ||
const char ch = *p_ch; | ||
|
||
strbuf_addch(buf, ch); | ||
if (check_uint(buf->len, ==, 1) || | ||
check_uint(buf->alloc, >, 1)) | ||
return; /* avoid de-referencing buf->buf */ | ||
check_char(buf->buf[0], ==, ch); | ||
check_char(buf->buf[1], ==, '\0'); | ||
} | ||
|
||
static void t_addstr(struct strbuf *buf, void *data) | ||
{ | ||
const char *text = data; | ||
size_t len = strlen(text); | ||
|
||
strbuf_addstr(buf, text); | ||
if (check_uint(buf->len, ==, len) || | ||
check_uint(buf->alloc, >, len) || | ||
check_char(buf->buf[len], ==, '\0')) | ||
return; | ||
check_str(buf->buf, text); | ||
} | ||
|
||
int cmd_main(int argc, const char **argv) | ||
{ | ||
if (TEST(t_static_init(), "static initialization works")) | ||
test_skip_all("STRBUF_INIT is broken"); | ||
TEST(t_dynamic_init(), "dynamic initialization works"); | ||
TEST(setup(t_addch, "a"), "strbuf_addch adds char"); | ||
TEST(setup(t_addch, ""), "strbuf_addch adds NUL char"); | ||
TEST(setup(t_addstr, "hello there"), "strbuf_addstr adds string"); | ||
|
||
return test_done(); | ||
} |
Oops, something went wrong.