aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Phillips <david@yeah.nah.nz>2021-02-27 14:25:50 +1300
committerDavid Phillips <david@yeah.nah.nz>2021-02-27 14:25:50 +1300
commit4d9894c7578236aabf05b43ae16bdac2232da2c9 (patch)
tree4e519e1e92e4c8ba31f85811eb4d43058f5fd4ab
downloadunity-test-runner-4d9894c7578236aabf05b43ae16bdac2232da2c9.tar.xz
Add initial working code with readme
-rw-r--r--README.md63
-rw-r--r--linker_list.lds14
-rw-r--r--test_runner.c43
-rw-r--r--test_runner.h38
-rw-r--r--test_runner_list.h24
5 files changed, 182 insertions, 0 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..935b98d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,63 @@
+# Unity test runner
+## A mostly-auto-discovering unity unit test runner
+
+The excellent [unity][unity-homepage] unit testing library leaves the
+implementation of the test runner program to the user. This is a nice way to
+make the library more portable and generic, but it means that anyone as lazy
+as me is left running one of the bundled Ruby scripts to generate a test runner.
+I don't like the idea of mandating Ruby as a dependency for unit testing some
+embedded software, so I decided to make an equally "auto-discovering" method
+of running unit tests across multiple suites.
+
+So unity-test-runner was born.
+
+
+### Theory of operation
+
+In stock unity, without using the auto-generator ruby scripts, you need to keep
+updating a list of the tests you would like to run, often separate from the
+tests themselves. This is error prone and no doubt one day you will
+accidentally leave a test missing from this list.
+
+In order to use unity-test-runner, it is linked into the same executable as the
+tests in a suite. It provides the `int main`, which will iterate through a list
+of unit tests to run, and run each in sequence with unity.
+
+In order to let the magic discovery happen, you need to cast a spell. Instead
+of declaring tests as plain functions as you would under stock unity, we must
+"decorate" them with a macro:
+
+ #include "unity.h"
+ #inlcude "test_runner.h"
+
+ RUNNER_DECLARE_TEST(test_one_is_not_zero)
+ {
+ TEST_ASSERT_NOT_EQUAL(0, 1);
+ }
+
+...and use a custom linker script after compiling tests modules, unity, and
+this package into objects:
+
+ cc -o -Wl,-T,unity-test-runner/linker_list.ld test_runner test_runner.o unity.o test_foo.o ...
+
+Running the resulting executable should show `test_one_is_not_zero` run and
+pass:
+
+ $ ./test_runner
+ Suite has 1 tests
+ test_foo.c:4:test_one_is_not_zero:PASS
+
+ -----------------------
+ 1 Tests 0 Failures 0 Ignored
+ OK
+
+
+The magic trick is simply that `RUNNER_DECLARE_TEST` along with
+`linker_list.ld` declare a so-called *linker list* residing in the symbol table
+of the test runner executable. This list is picked up by the test runner `int
+main` which iterates over the whole list and runs each test in sequence.
+
+Notice that this requires no secondary list of test functions to be explicitly
+maintained, and no function prototypes for each of the unit tests to be
+declared in the test runner. If you look at `test_runner.c` you will see how
+it is very plain (naive?) and portable between different suites and projects.
diff --git a/linker_list.lds b/linker_list.lds
new file mode 100644
index 0000000..b00b7b3
--- /dev/null
+++ b/linker_list.lds
@@ -0,0 +1,14 @@
+/*
+ * This file is a derivative work of arch/mips/cpu/u-boot.lds from the U-Boot
+ * project <https://www.denx.de/wiki/U-Boot> and is licensed under GPL-2.0+.
+ *
+ * Copyright (C) 2003 Wolfgang Denk Engineering, <wd@denx.de>
+ */
+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..cb8f9eb
--- /dev/null
+++ b/test_runner.c
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2021 David Phillips <david@yeah.nah.nz>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#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);
+ }
+ return UNITY_END();
+}
diff --git a/test_runner.h b/test_runner.h
new file mode 100644
index 0000000..e4c551f
--- /dev/null
+++ b/test_runner.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2021 David Phillips <david@yeah.nah.nz>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#pragma once
+
+#include "test_runner_list.h"
+
+#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_runner_list.h b/test_runner_list.h
new file mode 100644
index 0000000..d84026b
--- /dev/null
+++ b/test_runner_list.h
@@ -0,0 +1,24 @@
+#pragma once
+
+/*
+ * This file is a derivative work of include/linker_lists.h from the U-Boot
+ * project <https://www.denx.de/wiki/U-Boot> and is licensed under GPL-2.0+.
+ *
+ * Copyright (C) 2012 Marek Vasut <marex@denx.de>
+ * Copyright (C) 2021 David Phillips <david@yeah.nah.nz>
+ */
+#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)))