aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Phillips <david@yeah.nah.nz>2021-02-13 18:21:36 +1300
committerDavid Phillips <david@yeah.nah.nz>2021-02-13 18:25:28 +1300
commit4710921284adbf5005421515fbab5dd1aa38e9aa (patch)
tree18d1850a58c58f0095c8752a76ef8584a2628dbc
parent8ab11293cdd2a47e7950fa9c488cbc504325cad3 (diff)
downloadaltimeter-4710921284adbf5005421515fbab5dd1aa38e9aa.tar.xz
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.
-rw-r--r--Makefile4
-rw-r--r--linker_list.lds8
-rw-r--r--test_runner.c21
-rw-r--r--test_runner.h32
-rw-r--r--test_test.c7
-rw-r--r--test_util.c21
6 files changed, 76 insertions, 17 deletions
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 <stdio.h>
+
+#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 <string.h>
+#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();
-}