From 4710921284adbf5005421515fbab5dd1aa38e9aa Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sat, 13 Feb 2021 18:21:36 +1300 Subject: Add test runner This patch adds a "magic" test runner which discovers all test cases in the executable into which it is linked. This is achieved through the use of a special macro used to declare tests, so that they are added onto a linker- generated list. The linker list logic is largely taken from U-Boot source code, with minor some tweaks made. This linker-generated list is then used at runtime in order to pick out every test case which can be run, resulting in a cross-file "suite" of unity unit tests, rather than separate test executables per module. A sample test module, test_test.c is provided to illustrate the test runner's ability to correctly discover tests across multiple translation units. --- Makefile | 4 +++- linker_list.lds | 8 ++++++++ test_runner.c | 21 +++++++++++++++++++++ test_runner.h | 32 ++++++++++++++++++++++++++++++++ test_test.c | 7 +++++++ test_util.c | 21 +++++---------------- 6 files changed, 76 insertions(+), 17 deletions(-) create mode 100644 linker_list.lds create mode 100644 test_runner.c create mode 100644 test_runner.h create mode 100644 test_test.c diff --git a/Makefile b/Makefile index e05f490..73d957d 100644 --- a/Makefile +++ b/Makefile @@ -51,9 +51,11 @@ clean: test: \ $(UNITY_O) \ + build/native/test_runner.o \ build/native/test_util.o \ + build/native/test_test.o \ build/native/util.o - $(CC) $(LDFLAGS) -o $@ $^ + $(CC) $(LDFLAGS) -Wl,-T,linker_list.lds -o $@ $^ ./$@ # ELF for real hardware. No mock/sim hardware included (except barometer, diff --git a/linker_list.lds b/linker_list.lds new file mode 100644 index 0000000..bb70bd8 --- /dev/null +++ b/linker_list.lds @@ -0,0 +1,8 @@ +SECTIONS { + __test_runner_list_start = .; + .test_runner_list : { + KEEP(*(SORT(.test_runner_list*))); + } + __test_runner_list_end = .; +} +INSERT BEFORE .bss; diff --git a/test_runner.c b/test_runner.c new file mode 100644 index 0000000..9d3ad65 --- /dev/null +++ b/test_runner.c @@ -0,0 +1,21 @@ +#include + +#include "test_runner.h" +#include "unity.h" + +/* unity requires these, even if empty */ +void setUp(){}; +void tearDown(){}; + +int main(void) +{ + struct test_fn *tests = list_head(struct test_fn, test_list); + size_t count = list_entry_count(struct test_fn, test_list); + printf("Suite has %zd tests\n", count); + UNITY_BEGIN(); + for (size_t i = 0; i < count; i++) { + UnitySetTestFile(tests[i].file); + UnityDefaultTestRun(tests[i].fn, tests[i].name, tests[i].line); + } + UNITY_END(); +} diff --git a/test_runner.h b/test_runner.h new file mode 100644 index 0000000..5b891b0 --- /dev/null +++ b/test_runner.h @@ -0,0 +1,32 @@ +#pragma once + +/* Linker-generated lists derived from U-Boot source `include/linker_lists.h` + * with minor modification to store function pointers */ +#define list_head(entry_type, list_name) \ + ({ static char head[0] __attribute__((__aligned__(4), unused, section(".test_runner_list_2_"#list_name"_1")));(entry_type*)&head; }) + +#define list_tail(entry_type, list_name) \ + ({ static char tail[0] __attribute__((__aligned__(4), unused, section(".test_runner_list_2_"#list_name"_3")));(entry_type*)&tail; }) + +#define list_entry_count(entry_type, list_name) \ + ({ \ + entry_type* head = list_head(entry_type, list_name); \ + entry_type* tail = list_tail(entry_type, list_name); \ + (size_t)(tail - head); \ + }) + +#define list_entry(entry_type, list_name, entry_name) \ + entry_type _test_runner_list_2_##list_name_2_##entry_name __attribute__((__aligned__(4), unused, section(".test_runner_list_2_"#list_name"_2_"#entry_name))) + +#define RUNNER_DECLARE_TEST(test_name) \ + void test_name(void);\ + list_entry(struct test_fn, test_list, test_name) = { .name = #test_name, .file = __FILE__, .line = __LINE__, .fn = test_name}; \ + void test_name(void) + +struct test_fn { + const char *name; + const char *file; + int line; + void (*fn)(); +}; + diff --git a/test_test.c b/test_test.c new file mode 100644 index 0000000..9a78c3e --- /dev/null +++ b/test_test.c @@ -0,0 +1,7 @@ +#include "test_runner.h" +#include "unity.h" + +RUNNER_DECLARE_TEST(test_test) +{ + TEST_ASSERT_EQUAL(0,0); +} diff --git a/test_util.c b/test_util.c index 81c3db7..c6b9b83 100644 --- a/test_util.c +++ b/test_util.c @@ -1,14 +1,13 @@ #include +#include "test_runner.h" #include "unity.h" #include "util.h" -void setUp(){}; -void tearDown(){}; /* blank_to_eol should pad a buffer with spaces as far as it can, after an * existing string therein, without overrunning, while still terminating */ -void test_blank_to_eol_happy(void) +RUNNER_DECLARE_TEST(test_blank_to_eol_happy) { char buf[11] = "hello"; blank_to_eol(buf, sizeof(buf)); @@ -17,7 +16,7 @@ void test_blank_to_eol_happy(void) /* blanking an empty string to end of buffer should fill entire buffer with * spaces until the last byte, which should be null terminator */ -void test_blank_to_eol_empty(void) +RUNNER_DECLARE_TEST(test_blank_to_eol_empty) { char buf[5] = ""; blank_to_eol(buf, sizeof(buf)); @@ -25,7 +24,7 @@ void test_blank_to_eol_empty(void) } /* blanking should not overrun the specified buffer size at all */ -void test_blank_to_eol_no_overrun(void) +RUNNER_DECLARE_TEST(test_blank_to_eol_no_overrun) { char buf[20] = { 'h', 'e', 'l', 'l', 'o', '\0', @@ -41,7 +40,7 @@ void test_blank_to_eol_no_overrun(void) /* blanking a buffer which already has no '\0' within it should result in the * buffer being terminated, truncating contents minimally as necessary to fit * a '\0' */ -void test_blank_to_eol_overrun(void) +RUNNER_DECLARE_TEST(test_blank_to_eol_overrun) { char buf[20] = "hello, world!"; blank_to_eol(buf, 6); @@ -50,13 +49,3 @@ void test_blank_to_eol_overrun(void) * doesn't test OOB reads, only writes */ TEST_ASSERT_EQUAL_STRING(" world!", &buf[6]); } - -int main(void) -{ - UNITY_BEGIN(); - RUN_TEST(test_blank_to_eol_happy); - RUN_TEST(test_blank_to_eol_empty); - RUN_TEST(test_blank_to_eol_no_overrun); - RUN_TEST(test_blank_to_eol_overrun); - return UNITY_END(); -} -- cgit v1.1