From 4d9894c7578236aabf05b43ae16bdac2232da2c9 Mon Sep 17 00:00:00 2001 From: David Phillips Date: Sat, 27 Feb 2021 14:25:50 +1300 Subject: Add initial working code with readme --- README.md | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ linker_list.lds | 14 ++++++++++++ test_runner.c | 43 +++++++++++++++++++++++++++++++++++++ test_runner.h | 38 ++++++++++++++++++++++++++++++++ test_runner_list.h | 24 +++++++++++++++++++++ 5 files changed, 182 insertions(+) create mode 100644 README.md create mode 100644 linker_list.lds create mode 100644 test_runner.c create mode 100644 test_runner.h create mode 100644 test_runner_list.h 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 and is licensed under GPL-2.0+. + * + * Copyright (C) 2003 Wolfgang Denk Engineering, + */ +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 + * + * 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 + +#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 + * + * 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 and is licensed under GPL-2.0+. + * + * Copyright (C) 2012 Marek Vasut + * Copyright (C) 2021 David Phillips + */ +#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))) -- cgit v1.1