diff options
Diffstat (limited to 'test_harness')
-rw-r--r-- | test_harness/Makefile | 9 | ||||
-rw-r--r-- | test_harness/fake_env.c | 63 | ||||
-rw-r--r-- | test_harness/fake_env.h | 20 | ||||
-rw-r--r-- | test_harness/test_harness.c | 181 | ||||
-rw-r--r-- | test_harness/test_harness.h | 101 |
5 files changed, 374 insertions, 0 deletions
diff --git a/test_harness/Makefile b/test_harness/Makefile new file mode 100644 index 0000000..443292b --- /dev/null +++ b/test_harness/Makefile @@ -0,0 +1,9 @@ +CC=gcc +CFLAGS=-g3 -ggdb -Wall + +test_harness.a: test_harness.c test_harness.h fake_env.o + gcc -o test_harness.o -c test_harness.c $(CFLAGS) + ar r test_harness.a test_harness.o fake_env.o + +fake_env.o: fake_env.c fake_env.h + gcc -o fake_env.o -c fake_env.c $(CFLAGS) diff --git a/test_harness/fake_env.c b/test_harness/fake_env.c new file mode 100644 index 0000000..6a32c99 --- /dev/null +++ b/test_harness/fake_env.c @@ -0,0 +1,63 @@ +#include "fake_env.h" + +#include <stdlib.h> +#include <assert.h> + +struct fakeenv_memseg { + const char* name; + void* segment; +}; + +#define DEFINE_MEMORY_SEGMENT(seg, start_addr, end_addr) \ + static __attribute((__section__("fakeenv"))) \ + struct fakeenv_memseg fake_##seg = { \ + .name = #seg, \ + .segment = NULL, \ + }; \ + void* load_fake_##seg##__ () \ + { \ + if (fake_##seg .segment == NULL) { \ + fake_##seg .segment = malloc((end_addr) - (start_addr)); \ + assert(fake_##seg .segment != NULL); \ + } \ + return fake_##seg.segment; \ + } + +extern struct fakeenv_memseg __start_fakeenv; +extern struct fakeenv_memseg __stop_fakeenv; + +void wipeout_fake_env() +{ + for (struct fakeenv_memseg* iter = &__start_fakeenv; + iter < &__stop_fakeenv; + ++ iter) { + free(iter->segment); + iter->segment = NULL; + } +} + +/* Reset and clock control. */ +DEFINE_MEMORY_SEGMENT(rcc, 0x40021000, 0x400210A0) + +/* Peripheral buses */ +DEFINE_MEMORY_SEGMENT(apb1, 0x40000000, 0x40010000) +DEFINE_MEMORY_SEGMENT(apb2, 0x40010000, 0x40020000) +DEFINE_MEMORY_SEGMENT(ahb1, 0x40020000, 0x40024400) +DEFINE_MEMORY_SEGMENT(ahb2, 0x48000000, 0x50060C00) + +/* System Control Block */ +DEFINE_MEMORY_SEGMENT(scb, 0xE000E008, 0xE000EF04) + +/* Nested Vector Interrupt Controller (NVIC) */ +/* Note that this memory space acutally overlaps with the SCB, but + * they are functionally distinct entitites and such are modeled as + * separate structures in memeory. */ +DEFINE_MEMORY_SEGMENT(nvic, 0xE000E004, 0xE000E4F0) + +/* SRAM */ +DEFINE_MEMORY_SEGMENT(sram1, 0x20000000, 0x2000C000) +DEFINE_MEMORY_SEGMENT(sram2, 0x2000C000, 0x20018000) + +/* Serial Peripheral Interface */ +DEFINE_MEMORY_SEGMENT(spi1, 0x40013000, 0x400133FF) +DEFINE_MEMORY_SEGMENT(spi3, 0x40003C00, 0x40003FFF) diff --git a/test_harness/fake_env.h b/test_harness/fake_env.h new file mode 100644 index 0000000..34056f4 --- /dev/null +++ b/test_harness/fake_env.h @@ -0,0 +1,20 @@ +#ifndef FAKE_ENV_H_ +#define FAKE_ENV_H_ + +/* Functions which wil lazily load fake chunks of memory + * corresponding to the*/ +void* load_fake_ahb1__(); +void* load_fake_ahb2__(); +void* load_fake_apb1__(); +void* load_fake_apb2__(); +void* load_fake_sram1__(); +void* load_fake_sram2__(); +void* load_fake_scb__(); +void* load_fake_nvic__(); +void* load_fake_rcc__(); +void* load_fake_spi1__(); +void* load_fake_spi3__(); + +void wipeout_fake_env(); + +#endif /* FAKE_ENV_H_ */ diff --git a/test_harness/test_harness.c b/test_harness/test_harness.c new file mode 100644 index 0000000..bf9249c --- /dev/null +++ b/test_harness/test_harness.c @@ -0,0 +1,181 @@ +#include "test_harness.h" +#include "fake_env.h" + +#include <assert.h> +#include <stdio.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> +#include <stdlib.h> +#include <setjmp.h> + + +static jmp_buf jmpbuf; + +volatile test_t dummy + __attribute((__section__("tests"))) + __attribute((__used__)) = { + "dummy", + "dummy", + NULL +}; + +extern unsigned char __data_start; +extern unsigned char _end; +extern test_t __start_tests; +extern test_t __stop_tests; + +test_t* iter = &__start_tests; + +static int execute_test(test_t* test); + +void test_printll(size_t sz, long long v1, long long v2) +{ + fprintf(stderr, "%lld == %lld\n", v1, v2); +} +void test_printul(size_t sz, unsigned long v1, unsigned long v2) +{ + fprintf(stderr, "%lu == %lu\n", v1, v2); +} +void test_printd(size_t sz, int v1, int v2) +{ + fprintf(stderr, "%d == %d\n", v1, v2); +} +void test_printl(size_t sz, long v1, long v2) +{ + fprintf(stderr, "%lu == %lu\n", v1, v2); +} +void test_printui(size_t sz, unsigned int v1, unsigned int v2) +{ + fprintf(stderr, "%u == %u\n", v1, v2); +} +void test_prints(size_t sz, short v1, short v2) +{ + fprintf(stderr, "%hu == %hu\n", v1, v2); +} +void test_printus(size_t sz, unsigned short v1, unsigned short v2) +{ + fprintf(stderr, "%hu == %hu\n", v1, v2); +} +void test_printc(size_t sz, char v1, char v2) +{ + fprintf(stderr, "'%c' == '%c'\n", v1, v2); +} +void test_printf(size_t sz, double v1, double v2) +{ + fprintf(stderr, "%f == %f\n", v1, v2); +} +void test_printp(size_t sz, void* v1, void* v2) +{ + fprintf(stderr, "%p == %p\n", v1, v2); +} +void test_printuc( + size_t sz, unsigned char v1, unsigned char v2) +{ + fprintf(stderr, "%02x == %02x\n", (int) v1, (int) v2); +} + +static int do_fork = 1; +static size_t saved_data_size; +static unsigned char* saved_data = NULL; + +int main(int argc, char** argv) { + /* Save all initialized data. */ + saved_data_size = &_end - &__data_start; + saved_data = malloc(saved_data_size); + memcpy(saved_data, &__data_start, saved_data_size); + + + if (argc > 1 && strcmp(argv[1], "--nofork") == 0) { + do_fork = 0; + } + + for( ; iter < &__stop_tests; ++ iter) { + if (iter->fn_ptr != NULL) { + execute_test(iter); + } + } +} + +void test_harness_abort(int ec) +{ + longjmp(jmpbuf, ec); + assert("Long jump failed.\n"); +} + +/* + * When nofork is true, this function will be called after each + * test to try and make each test hermetic. + * + * It does this by reseting the data segment to what it was when + * the program was first initialized. + * + * Of course, without forking, there's no way to guarantee hermetic + * testing in C. In fact a simple segementation fault will break + * the hermetic testing, but this does a pretty good job of at least + * reseting the environment so tests don't directly depend on eachother. + */ +static void nofork_reset() +{ + wipeout_fake_env(); + + /* Reset the data segment to what it was before. */ + memcpy(&__data_start, saved_data, saved_data_size); +} + +static int execute_test(test_t* test) +{ + char fullname[512]; + int status; + int ec = 0; + pid_t pid; + + snprintf( + fullname, sizeof(fullname), "%s::%s", iter->test_suite, iter->test_name); + + if (!do_fork) { + if ((ec = setjmp(jmpbuf)) == 0) { + test->fn_ptr(); + printf(GREEN "[PASS]" RESET " %s\n", fullname); + nofork_reset(); + return 0; + } else { + printf(RED "[FAIL] (%d)" RESET " %s\n", ec, fullname); + nofork_reset(); + return ec; + } + } + + if (!(pid = fork())) { + // child + + if ((ec = setjmp(jmpbuf))) { + exit(ec); + } else { + test->fn_ptr(); + exit(0); + } + } else { + if (waitpid(pid, &status, 0) == -1) { + fprintf(stderr, "waitpid() failed\n"); + return 1; + } + + if (WIFEXITED(status)) { + switch ((ec = WEXITSTATUS(status))) { + case 0: + printf(GREEN "[PASS]" RESET " %s\n", fullname); + return 0; + default: + printf(RED "[FAIL] (%d)" RESET " %s\n", ec, fullname); + return ec; + } + } else if (WIFSIGNALED(status)) { + int ec = WTERMSIG(status); + printf("%s " RED "[FAIL] signaled %d" RESET "\n", fullname, ec); + return ec; + } + + return ec; + } +} diff --git a/test_harness/test_harness.h b/test_harness/test_harness.h new file mode 100644 index 0000000..698e2da --- /dev/null +++ b/test_harness/test_harness.h @@ -0,0 +1,101 @@ +#ifndef TEST_HARNESS_H_ +#define TEST_HARNESS_H_ + +#include <stdio.h> +#include <string.h> + +#define YELLOW "\x1b[00;33m" +#define GREEN "\x1b[01;32m" +#define RED "\x1b[01;31m" +#define RESET "\x1b[0m" + +typedef struct { + const char* test_suite; + const char* test_name; + int (*fn_ptr)(); + void* alignment; +} test_t; + +#define GENPR(fmt, v1, v2) \ + fprintf(stderr, fmt "\n", v1, v2) + +void test_printll(size_t sz, long long v1, long long v2); +void test_printul(size_t sz, unsigned long v1, unsigned long v2); +void test_printd(size_t sz, int v1, int v2); +void test_printl(size_t sz, long v1, long v2); +void test_printui(size_t sz, unsigned int v1, unsigned int v2); +void test_prints(size_t sz, short v1, short v2); +void test_printus(size_t sz, unsigned short v1, unsigned short v2); +void test_printc(size_t sz, char v1, char v2); +void test_printf(size_t sz, double v1, double v2); +void test_printp(size_t sz, void* v1, void* v2); +void test_printuc(size_t sz, unsigned char v1, unsigned char v2); + +#define FORMAT_STRING(v1, v2) \ + _Generic((v1), \ + long long: test_printll, \ + unsigned long: test_printul, \ + int: test_printd, \ + long: test_printl, \ + unsigned int: test_printui, \ + short: test_prints, \ + unsigned short: test_printus, \ + char: test_printc, \ + unsigned char: test_printuc, \ + double: test_printf, \ + default: test_printp)(sizeof(v1), v1, v2) \ + +#define TRY_PRINT_TYPE(v1, v2, type, fmt) \ + else if (__builtin_types_compatible_p(typeof (v1), type)) { \ + fprintf(stderr, fmt " == " fmt "\n", v1, v2); \ + } + +#define TYPE_STR(t) #t + +#define ASSERT_TRUE(x) \ + do { \ + if (!(x)) { \ + fprintf(stderr, RED "ASSERT_TRUE FAILED!\n" RESET); \ + fprintf(stderr, " - " YELLOW "In expression ASSERT_TRUE(" #x ")\n"); \ + fprintf(stderr, RESET " - " YELLOW "At " __FILE__ ":%d\n" RESET, __LINE__); \ + test_harness_abort(1); \ + } \ + } while (0) + +#define ASSERT_EQ(x, y) \ + do { \ + if ((x) != (y)) { \ + fprintf(stderr, RED "ASSERT_EQ FAILED! " RESET "Not true that "); \ + FORMAT_STRING((x), (y)); \ + fprintf(stderr, " - " YELLOW "In expression ASSERT_EQ(" #x ", " #y ")\n"); \ + fprintf(stderr, RESET " - " YELLOW "At " __FILE__ ":%d\n" RESET, __LINE__); \ + test_harness_abort(1); \ + } \ + } while (0) + +#define ASSERT_EQ_STR(x, y) \ + do { \ + if (strcmp(x, y)) { \ + fprintf(stderr, \ + RED "ASSSERT_EQ_STR FAILED! " RESET "Not true that \"%s\" equals \"%s\"", \ + x, y); \ + fprintf(stderr, " - " YELLOW "In expression ASSERT_EQ_STR(" #x ", " #y ")\n"); \ + fprintf(stderr, RESET " - " YELLOW "At " __FILE__":%d\n" RESET, __LINE__); \ + test_harness_abort(1); \ + } \ + } while (0) + + +#define TEST(test_suite, test_name) \ + int test_suite ## _ ## test_name ## _fn (void); \ + volatile test_t test_suite ## _ ## test_name ## _testing_struct__ \ + __attribute((__section__("tests"))) __attribute((__used__)) = \ + {#test_suite, #test_name, test_suite ## _ ## test_name ## _fn}; \ + int test_suite ## _ ## test_name ## _fn (void) + +void test_harness_abort(int ec); + + +void wipeout_fake_env(); + +#endif |