[unittests] add RUN_UNITTESTS_AT_BOOT build option

This will cause the system to automatically run all of the unit tests
after a short pause on boot. Will be used by an automatic test script.

At the moment there aren't a lot of unit tests in the list, but this
should greatly increase the utility of them since they'll be
automatically run.
This commit is contained in:
Travis Geiselbrecht
2025-10-01 23:54:54 -07:00
parent be07027fe1
commit 976cd70f4f
4 changed files with 61 additions and 38 deletions

View File

@@ -14,60 +14,75 @@
#include <assert.h> #include <assert.h>
#include <lk/console_cmd.h> #include <lk/console_cmd.h>
#include <lk/err.h> #include <lk/err.h>
#include <lk/init.h>
static struct test_case_element *test_case_list = NULL; static struct list_node test_case_list = LIST_INITIAL_VALUE(test_case_list);
static struct test_case_element *failed_test_case_list = NULL;
#if WITH_LIB_CONSOLE
#include <lib/console.h>
#if RUN_UNITTESTS_AT_BOOT
static void unittest_run_at_boot(uint level) {
const char run_at_boot_script[] = "sleep 5; ut all";
console_t *con = console_create(false);
if (con) {
console_run_script(con, run_at_boot_script);
// TODO: destroy console afterwards
}
}
LK_INIT_HOOK(unittest_at_boot, unittest_run_at_boot, LK_INIT_LEVEL_LAST)
#endif
#endif
/* /*
* Registers a test case with the unit test framework. * Registers a test case with the unit test framework.
*/ */
void unittest_register_test_case(struct test_case_element *elem) { void unittest_register_test_case(struct test_case_element *elem) {
DEBUG_ASSERT(elem); DEBUG_ASSERT(elem);
DEBUG_ASSERT(elem->next == NULL); DEBUG_ASSERT(!list_in_list(&elem->node));
elem->next = test_case_list; list_add_tail(&test_case_list, &elem->node);
test_case_list = elem;
} }
/* /*
* Runs all registered test cases. * Runs all registered test cases.
*/ */
bool run_all_tests(void) { bool run_all_tests(void) {
unsigned int n_tests = 0; unsigned int n_tests = 0;
unsigned int n_success = 0; unsigned int n_success = 0;
unsigned int n_failed = 0; unsigned int n_failed = 0;
struct list_node failed_test_case_list = LIST_INITIAL_VALUE(failed_test_case_list);
bool all_success = true; bool all_success = true;
struct test_case_element *current = test_case_list; struct test_case_element *current;
while (current) { list_for_every_entry(&test_case_list, current, struct test_case_element, node) {
if (!current->test_case()) { if (!current->test_case()) {
current->failed_next = failed_test_case_list; list_add_tail(&failed_test_case_list, &current->failed_node);
failed_test_case_list = current;
all_success = false; all_success = false;
n_failed++;
} else {
n_success++;
} }
current = current->next;
n_tests++; n_tests++;
} }
if (all_success) { unittest_printf("\n====================================================\n");
n_success = n_tests; unittest_printf(" CASES: %d SUCCESS: %d FAILED: %d ",
unittest_printf("SUCCESS! All test cases passed!\n"); n_tests, n_success, n_failed);
} else { unittest_printf("\n====================================================\n");
struct test_case_element *failed = failed_test_case_list;
while (failed) {
struct test_case_element *failed_next =
failed->failed_next;
failed->failed_next = NULL;
failed = failed_next;
n_failed++;
}
n_success = n_tests - n_failed;
failed_test_case_list = NULL;
}
unittest_printf("\n====================================================\n"); if (all_success) {
unittest_printf (" CASES: %d SUCCESS: %d FAILED: %d ", unittest_printf("\nSUCCESS! All test cases passed\n");
n_tests, n_success, n_failed); } else {
unittest_printf("\n====================================================\n"); unittest_printf("\nFAILURE! Some test cases failed:\n");
struct test_case_element *failed;
struct test_case_element *temp;
list_for_every_entry_safe(&failed_test_case_list, failed, temp, struct test_case_element, failed_node) {
unittest_printf(" %s\n", failed->name);
list_delete(&failed->failed_node);
}
unittest_printf("\n");
}
return all_success; return all_success;
} }
@@ -87,13 +102,15 @@ usage:
bool result = run_all_tests(); bool result = run_all_tests();
printf("UNIT TEST: run_all_tests return %u\n", result); printf("UNIT TEST: run_all_tests return %u\n", result);
} else if (!strcmp(argv[1].str, "list")) { } else if (!strcmp(argv[1].str, "list")) {
for (struct test_case_element *current = test_case_list; current; current = current->next) { struct test_case_element *current;
list_for_every_entry(&test_case_list, current, struct test_case_element, node) {
puts(current->name); puts(current->name);
} }
} else { } else {
// look for unit test that matches the string name // look for unit test that matches the string name
bool found_test = false; bool found_test = false;
for (struct test_case_element *current = test_case_list; current; current = current->next) { struct test_case_element *current;
list_for_every_entry(&test_case_list, current, struct test_case_element, node) {
if (strcmp(argv[1].str, current->name) == 0) { if (strcmp(argv[1].str, current->name) == 0) {
found_test = true; found_test = true;
current->test_case(); current->test_case();
@@ -112,4 +129,3 @@ usage:
STATIC_COMMAND_START STATIC_COMMAND_START
STATIC_COMMAND("ut", "run some or all of the unit tests", do_unittests) STATIC_COMMAND("ut", "run some or all of the unit tests", do_unittests)
STATIC_COMMAND_END(unit_tests); STATIC_COMMAND_END(unit_tests);

View File

@@ -54,6 +54,7 @@
* lib/unittest \ * lib/unittest \
*/ */
#include <lk/compiler.h> #include <lk/compiler.h>
#include <lk/list.h>
#include <printf.h> #include <printf.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdio.h> #include <stdio.h>
@@ -116,8 +117,8 @@ __END_CDECLS
return all_ok; \ return all_ok; \
} \ } \
static struct test_case_element _##case_name##_element = { \ static struct test_case_element _##case_name##_element = { \
.next = NULL, \ .node = LIST_INITIAL_CLEARED_VALUE, \
.failed_next = NULL, \ .failed_node = LIST_INITIAL_CLEARED_VALUE, \
.name = #case_name, \ .name = #case_name, \
.test_case = case_name, \ .test_case = case_name, \
}; \ }; \
@@ -387,8 +388,8 @@ __BEGIN_CDECLS
* The list of test cases is made up of these elements. * The list of test cases is made up of these elements.
*/ */
struct test_case_element { struct test_case_element {
struct test_case_element *next; struct list_node node;
struct test_case_element *failed_next; struct list_node failed_node;
const char *name; const char *name;
bool (*test_case)(void); bool (*test_case)(void);
}; };

View File

@@ -6,4 +6,9 @@ MODULE_SRCS := \
$(LOCAL_DIR)/unittest.c \ $(LOCAL_DIR)/unittest.c \
$(LOCAL_DIR)/all_tests.c \ $(LOCAL_DIR)/all_tests.c \
ifeq (true,$(call TOBOOL,$(RUN_UNITTESTS_AT_BOOT)))
$(info Boot unit tests enabled)
MODULE_DEFINES += RUN_UNITTESTS_AT_BOOT=1
endif
include make/module.mk include make/module.mk

View File

@@ -27,6 +27,7 @@ export LKINC
export BUILDROOT export BUILDROOT
export DEFAULT_PROJECT export DEFAULT_PROJECT
export TOOLCHAIN_PREFIX export TOOLCHAIN_PREFIX
export RUN_UNITTESTS_AT_BOOT
# veneer makefile that calls into the engine with lk as the build root # veneer makefile that calls into the engine with lk as the build root
# if we're the top level invocation, call ourselves with additional args # if we're the top level invocation, call ourselves with additional args