From b2b1e42c5971d1199afd080d946a238093dc25b2 Mon Sep 17 00:00:00 2001
From: Angus Gratton <gus@projectgus.com>
Date: Wed, 17 Feb 2016 08:28:56 +1100
Subject: [PATCH] tests: Use heap memory instead of linker tricks for
 registering test cases

(Test case registration buffer gets freed before the test case runs.)
---
 tests/Makefile           |  1 -
 tests/include/testcase.h | 27 ++++++++--------
 tests/ld/tests.ld        | 11 -------
 tests/test_main.c        | 69 ++++++++++++++++++++++++----------------
 4 files changed, 54 insertions(+), 54 deletions(-)
 delete mode 100644 tests/ld/tests.ld

diff --git a/tests/Makefile b/tests/Makefile
index bd95ab3..476fc7b 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -1,6 +1,5 @@
 PROGRAM=tests
 
-EXTRA_LINKER_SCRIPTS = $(PROGRAM_DIR)ld/tests.ld
 PROGRAM_SRC_DIR = $(PROGRAM_DIR) $(PROGRAM_DIR)cases
 
 # Add unity test framework headers & core source file
diff --git a/tests/include/testcase.h b/tests/include/testcase.h
index 01c3198..c696596 100644
--- a/tests/include/testcase.h
+++ b/tests/include/testcase.h
@@ -20,15 +20,15 @@ typedef enum {
 typedef void (testcase_fn_t)(void);
 
 typedef struct {
-    char *name;
-    char *file;
-    uint8_t line;
+    const char *name;
+    const char *file;
+    int line;
     testcase_type_t type;
     testcase_fn_t *a_fn;
     testcase_fn_t *b_fn;
 } testcase_t;
 
-void testcase_register(const testcase_t *ignored);
+void testcase_register(const testcase_t *testcase);
 
 /* Register a test case using these macros. Use DEFINE_SOLO_TESTCASE for single-MCU tests,
    and DEFINE_TESTCASE for all other test types.
@@ -43,17 +43,16 @@ void testcase_register(const testcase_t *ignored);
     _DEFINE_TESTCASE_COMMON(NAME, TYPE, A_##NAME, B_##NAME)
 
 
-#define _DEFINE_TESTCASE_COMMON(NAME, TYPE, A_FN, B_FN)    \
-    const __attribute__((section(".testcases.text")))                   \
-    testcase_t testcase_##NAME = { .name = #NAME,                       \
-                                   .file = __FILE__,                    \
-                                   .line = __LINE__,                    \
-                                   .type = TYPE,                        \
-                                   .a_fn = A_FN,                        \
-                                   .b_fn = B_FN,                        \
-    };                                                                  \
+#define _DEFINE_TESTCASE_COMMON(NAME, TYPE, A_FN, B_FN)                 \
     void __attribute__((constructor)) testcase_ctor_##NAME() {          \
-        testcase_register(&testcase_##NAME);                            \
+        const testcase_t testcase = { .name = #NAME,                    \
+                                      .file = __FILE__,                 \
+                                      .line = __LINE__,                 \
+                                      .type = TYPE,                     \
+                                      .a_fn = A_FN,                     \
+                                      .b_fn = B_FN,                     \
+        };                                                              \
+        testcase_register(&testcase);                                   \
     }
 
 #endif
diff --git a/tests/ld/tests.ld b/tests/ld/tests.ld
deleted file mode 100644
index 2825c64..0000000
--- a/tests/ld/tests.ld
+++ /dev/null
@@ -1,11 +0,0 @@
-/* Extra linker script sections used only for test case registration */
-SECTIONS
-{
-  .irom0.text : ALIGN(4)
-  {
-    _testcases_start = ABSOLUTE(.);
-    *(.testcases.text)
-    _testcases_end = ABSOLUTE(.);
-  } > irom0_0_seg
-}
-
diff --git a/tests/test_main.c b/tests/test_main.c
index 48f2684..a93d6e1 100644
--- a/tests/test_main.c
+++ b/tests/test_main.c
@@ -1,10 +1,7 @@
 #include "testcase.h"
 #include <stdlib.h>
 #include <esp/uart.h>
-
-/* Linker sets up these pointers to the registered test cases */
-extern const testcase_t _testcases_start;
-extern const testcase_t _testcases_end;
+#include <string.h>
 
 /* Convert requirement enum to a string we can print */
 static const char *get_requirements_name(const testcase_type_t arg) {
@@ -20,16 +17,34 @@ static const char *get_requirements_name(const testcase_type_t arg) {
     }
 }
 
+static testcase_t *testcases;
+static uint32_t testcases_count;
+static uint32_t testcases_alloc;
+
+void testcase_register(const testcase_t *testcase)
+{
+    /* Grow the testcases buffer to fit the new test case,
+       this buffer will be freed before the test runs.
+     */
+    if(testcases_count == testcases_alloc) {
+        testcases_alloc += 1;
+        testcases = realloc(testcases, testcases_alloc * sizeof(testcase_t));
+        if(!testcases) {
+            printf("Failed to reallocate test case register length %d\n", testcases_alloc);
+            testcases_count = 0;
+            testcases_alloc = 0;
+        }
+    }
+    memcpy(&testcases[testcases_count++], testcase, sizeof(testcase_t));
+}
+
 void user_init(void)
 {
-    const testcase_t *cases_start = &_testcases_start;
-    const testcase_t *cases_end= &_testcases_end;
     uart_set_baud(0, 115200);
     printf("esp-open-rtos test runner.\n");
-    printf("testcases_start %p testcases_end %p\n", cases_start, cases_end);
-    printf("%d test cases are defined:\n\n", cases_end-cases_start);
-    for(const testcase_t *icase=cases_start; icase != cases_end; icase++) {
-        printf("CASE %d = %s %s\n", icase-cases_start, icase->name, get_requirements_name(icase->type));
+    printf("%d test cases are defined:\n\n", testcases_count);
+    for(int i = 0; i < testcases_count; i++) {
+        printf("CASE %d = %s %s\n", i, testcases[i].name, get_requirements_name(testcases[i].type));
     }
 
     printf("Enter A or B then number of test case to run, ie A0.\n");
@@ -65,10 +80,10 @@ void user_init(void)
         if(case_idx == -1) {
             printf("Invalid case index");
         }
-        else if(case_idx < 0 || case_idx >= cases_end-cases_start) {
+        else if(case_idx < 0 || case_idx >= testcases_count) {
             printf("Test case index out of range.\n");
         }
-        else if((type == 'b' || type =='B') && cases_start[case_idx].type == SOLO) {
+        else if((type == 'b' || type =='B') && testcases[case_idx].type == SOLO) {
             printf("No ESP type B for 'SOLO' test cases.\n");
         } else {
             break;
@@ -78,25 +93,23 @@ void user_init(void)
         type = 'A';
     else if (type == 'b')
         type = 'B';
-    const testcase_t *tcase = &cases_start[case_idx];
+    testcase_t testcase;
+    memcpy(&testcase, &testcases[case_idx], sizeof(testcase_t));
+    /* Free the register of test cases now we have the one we're running */
+    free(testcases);
+    testcases_alloc = 0;
+    testcases_count = 0;
+
     printf("\nRunning test case %d (%s %s) as instance %c \nDefinition at %s:%d\n***\n", case_idx,
-           tcase->name, get_requirements_name(tcase->type), type,
-           tcase->file, tcase->line);
-    Unity.CurrentTestName = tcase->name;
-    Unity.TestFile = tcase->file;
-    Unity.CurrentTestLineNumber = tcase->line;
+           testcase.name, get_requirements_name(testcase.type), type,
+           testcase.file, testcase.line);
+    Unity.CurrentTestName = testcase.name;
+    Unity.TestFile = testcase.file;
+    Unity.CurrentTestLineNumber = testcase.line;
     Unity.NumberOfTests = 1;
     if(type=='A')
-        cases_start[case_idx].a_fn();
+        testcase.a_fn();
     else
-        cases_start[case_idx].b_fn();
+        testcase.b_fn();
     TEST_FAIL_MESSAGE("\n\nTest initialisation routine returned without calling TEST_PASS. Buggy test?");
 }
-
-/* testcase_register is a no-op function, we just need it so the linker
-   knows to pull in the argument at link time.
- */
-void testcase_register(const testcase_t __attribute__((unused)) *ignored)
-{
-
-}