From 5ca2f1e10097a67c1b20cf366ad7eb28fe177c9e Mon Sep 17 00:00:00 2001 From: Dawid Pawliczek Date: Sun, 12 Apr 2026 19:43:18 +0200 Subject: [PATCH 1/5] Introduce unit tests infrastructure --- .github/workflows/build.yml | 17 ++ .gitignore | 1 + CMake/preset-mixins.json | 8 + CMakeLists.txt | 12 +- CMakePresets.json | 18 ++ include/stdbigos/types.h | 3 + tests/CMakeLists.txt | 11 + .../physical_memory/CMakeLists.txt | 19 ++ .../physical_memory/test_allocator.c | 199 ++++++++++++++++++ tests/stubs/hal_irq_stub.c | 4 + 10 files changed, 289 insertions(+), 3 deletions(-) create mode 100644 tests/CMakeLists.txt create mode 100644 tests/kernel/memory_management/physical_memory/CMakeLists.txt create mode 100644 tests/kernel/memory_management/physical_memory/test_allocator.c create mode 100644 tests/stubs/hal_irq_stub.c diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a0aba74d..d9689b36 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -197,3 +197,20 @@ jobs: with: name: build-artifacts-clang-macos-lto path: build/ + + unit-tests: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - uses: lukka/get-cmake@latest + with: + cmakeVersion: '~3.29.0' + + - name: Generate, build and run unit tests + uses: lukka/run-cmake@v10 + with: + configurePreset: 'tests-unit' + buildPreset: 'tests-unit' + testPreset: 'tests-unit' diff --git a/.gitignore b/.gitignore index 6c84768f..1b662601 100644 --- a/.gitignore +++ b/.gitignore @@ -58,6 +58,7 @@ dkms.conf # Build build/ +build-tests/ .cache/ # Disk images diff --git a/CMake/preset-mixins.json b/CMake/preset-mixins.json index bd72dc15..ed298976 100644 --- a/CMake/preset-mixins.json +++ b/CMake/preset-mixins.json @@ -104,6 +104,14 @@ "lhs": "${hostSystemName}", "rhs": "Darwin" } + }, + { + "name": "native-host-tests", + "hidden": true, + "binaryDir": "${sourceDir}/build-tests", + "cacheVariables": { + "BIGOS_BUILD_TESTS": "ON" + } } ] } diff --git a/CMakeLists.txt b/CMakeLists.txt index 463cca09..ad217f42 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,12 @@ set_property(GLOBAL PROPERTY USE_FOLDERS ON) include_directories(SYSTEM external/include) include_directories(include) -include(CMake/Linters.cmake) -include(CMake/CommonConfig.cmake) -add_subdirectory(src) +option(BIGOS_BUILD_TESTS "Build unit tests (native host only)" OFF) +if(BIGOS_BUILD_TESTS) + enable_testing() + add_subdirectory(tests) +else() + include(CMake/Linters.cmake) + include(CMake/CommonConfig.cmake) + add_subdirectory(src) +endif() diff --git a/CMakePresets.json b/CMakePresets.json index 81334e01..477ee820 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -226,6 +226,12 @@ ], "displayName": "CI MacOS Configuration with LTO", "description": "CI MacOS configuration with LTO using Ninja with warnings as errors" + }, + { + "name": "tests-unit", + "inherits": ["native-host-tests", "compile-commands", "debug-type"], + "displayName": "Unit Tests", + "description": "Native host build for unit tests" } ], "buildPresets": [ @@ -264,6 +270,12 @@ "configurePreset": "ci-macos-clang-lto", "displayName": "CI Ninja Build for MacOS - clang (LTO)", "description": "Build using CI Ninja configuration on MacOS (clang) (LTO)." + }, + { + "name": "tests-unit", + "configurePreset": "tests-unit", + "displayName": "Build Unit Tests", + "description": "Build unit tests on native platform." } ], "testPresets": [ @@ -272,6 +284,12 @@ "configurePreset": "ci-gcc", "displayName": "CI Ninja Test - GCC", "description": "Test using CI Ninja configuration (GCC)." + }, + { + "name": "tests-unit", + "configurePreset": "tests-unit", + "displayName": "Run Unit Tests", + "description": "Run unit tests via CTest on native platform." } ] } diff --git a/include/stdbigos/types.h b/include/stdbigos/types.h index ca0c2101..f83c9682 100644 --- a/include/stdbigos/types.h +++ b/include/stdbigos/types.h @@ -28,7 +28,10 @@ typedef int64_t i64; typedef unsigned long reg_t; typedef signed long ireg_t; + +#ifdef __riscv static_assert(sizeof(reg_t) * 8 == __riscv_xlen); +#endif typedef enum { ENDIAN_LITTLE = 0, diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 00000000..5de4f094 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,11 @@ +include(FetchContent) + +FetchContent_Declare( + unity + GIT_REPOSITORY https://github.com/ThrowTheSwitch/Unity.git + GIT_TAG v2.6.1 + GIT_SHALLOW TRUE +) +FetchContent_MakeAvailable(unity) + +add_subdirectory(kernel/memory_management/physical_memory) diff --git a/tests/kernel/memory_management/physical_memory/CMakeLists.txt b/tests/kernel/memory_management/physical_memory/CMakeLists.txt new file mode 100644 index 00000000..b7f959a6 --- /dev/null +++ b/tests/kernel/memory_management/physical_memory/CMakeLists.txt @@ -0,0 +1,19 @@ +add_executable(test_pmallocator + test_allocator.c + ${PROJECT_SOURCE_DIR}/src/kernel/memory_management/physical_memory/allocator.c + ${PROJECT_SOURCE_DIR}/tests/stubs/hal_irq_stub.c +) + +target_compile_features(test_pmallocator PUBLIC c_std_23) +target_compile_options(test_pmallocator PRIVATE -Wall -Wextra) + +target_include_directories(test_pmallocator PRIVATE + ${PROJECT_SOURCE_DIR}/include + ${PROJECT_SOURCE_DIR}/external/include + ${PROJECT_SOURCE_DIR}/src/kernel + ${PROJECT_SOURCE_DIR}/src/kernel/memory_management/physical_memory +) + +target_link_libraries(test_pmallocator PRIVATE unity) + +add_test(NAME pmallocator_tests COMMAND test_pmallocator) diff --git a/tests/kernel/memory_management/physical_memory/test_allocator.c b/tests/kernel/memory_management/physical_memory/test_allocator.c new file mode 100644 index 00000000..ea92ff10 --- /dev/null +++ b/tests/kernel/memory_management/physical_memory/test_allocator.c @@ -0,0 +1,199 @@ +#include +#include +#include "allocator.h" +#include "memory_management/include/physical_memory/manager.h" +#include "stdbigos/memory_types.h" + +#define TEST_PAGES 64 +#define TEST_SIZE (TEST_PAGES * PAGE_SIZE) + +#define RESERVED1_OFFSET 0 +#define RESERVED1_SIZE PAGE_SIZE + +#define RESERVED2_OFFSET (32 * PAGE_SIZE) +#define RESERVED2_SIZE (2 * PAGE_SIZE) + +#define TOTAL_RESERVED_PAGES 3 + +typedef struct { + memory_area_t* areas; + size_t count; + size_t index; +} reserved_ctx_t; + +static bool enumerator_stub(void* user, memory_area_t* out) { + reserved_ctx_t* ctx = user; + if (ctx->index >= ctx->count) { + ctx->index = 0; + return false; + } + *out = ctx->areas[ctx->index++]; + return true; +} + +static void* g_area_buf; +static memory_region_t g_header_region; +static memory_area_t g_area; +static size_t g_header_pages; + +void setUp(void) { + g_area_buf = calloc(1, TEST_SIZE); + + g_area = (memory_area_t){ + .addr = (uintptr_t)g_area_buf, + .size = TEST_SIZE, + }; + + memory_area_t reserved[] = { + {.addr = g_area.addr + RESERVED1_OFFSET, .size = RESERVED1_SIZE}, + {.addr = g_area.addr + RESERVED2_OFFSET, .size = RESERVED2_SIZE}, + }; + reserved_ctx_t ctx = {.areas = reserved, .count = 2, .index = 0}; + + memory_area_t header_area; + error_t err = pmallocator_get_header(g_area, enumerator_stub, &ctx, &header_area); + TEST_ASSERT_EQUAL(ERR_NONE, err); + TEST_ASSERT_EQUAL(g_area.addr + PAGE_SIZE, header_area.addr); + TEST_ASSERT_TRUE(header_area.size > 0); + TEST_ASSERT_EQUAL(0, header_area.size % PAGE_SIZE); + g_header_pages = header_area.size / PAGE_SIZE; + + g_header_region = (memory_region_t){ + .addr = (void*)header_area.addr, + .size = header_area.size, + }; + + ctx.index = 0; + error_t err2 = pmallocator_init_region(g_area, g_header_region, enumerator_stub, &ctx); + TEST_ASSERT_EQUAL(ERR_NONE, err2); +} + +void tearDown(void) { + free(g_area_buf); + g_area_buf = NULL; +} + + +static void test_allocate_single_page(void) { + memory_area_t result; + error_t err = pmallocator_allocate(FRAME_ORDER_4KiB, g_header_region, &result); + TEST_ASSERT_EQUAL(ERR_NONE, err); + TEST_ASSERT_EQUAL(PAGE_SIZE, result.size); + TEST_ASSERT_EQUAL(0, result.addr % PAGE_SIZE); + TEST_ASSERT_TRUE(result.addr >= g_area.addr); + TEST_ASSERT_TRUE(result.addr + result.size <= g_area.addr + TEST_SIZE); +} + +static void test_allocate_until_full(void) { + size_t expected = TEST_PAGES - TOTAL_RESERVED_PAGES - g_header_pages; + size_t count = 0; + memory_area_t result; + size_t prev_addr = -1; + + while (pmallocator_allocate(FRAME_ORDER_4KiB, g_header_region, &result) == ERR_NONE) { + TEST_ASSERT_FALSE( + do_memory_areas_overlap(result,(memory_area_t){ .addr = g_area.addr + RESERVED1_OFFSET, .size = RESERVED1_SIZE }) + ); + + TEST_ASSERT_FALSE( + do_memory_areas_overlap(result,(memory_area_t){ .addr = g_area.addr + RESERVED2_OFFSET, .size = RESERVED2_SIZE }) + ); + + TEST_ASSERT_FALSE( + prev_addr == result.addr + ); + + prev_addr = result.addr; + count++; + } + TEST_ASSERT_EQUAL(expected, count); +} + + +static void test_free_and_reallocate(void) { + memory_area_t first; + memory_area_t second; + error_t err = pmallocator_allocate(FRAME_ORDER_4KiB, g_header_region, &first); + TEST_ASSERT_EQUAL(ERR_NONE, err); + error_t err2 = pmallocator_free(first, g_header_region); + TEST_ASSERT_EQUAL(ERR_NONE, err2); + error_t err3 = pmallocator_allocate(FRAME_ORDER_4KiB, g_header_region, &second); + TEST_ASSERT_EQUAL(ERR_NONE, err3); + error_t err4 = pmallocator_free(second, g_header_region); + TEST_ASSERT_EQUAL(ERR_NONE, err4); + TEST_ASSERT_EQUAL(first.addr, second.addr); +} + +static void test_free_double_free(void) { + memory_area_t result; + (void)pmallocator_allocate(FRAME_ORDER_4KiB, g_header_region, &result); + error_t err = pmallocator_free(result, g_header_region); + TEST_ASSERT_EQUAL(ERR_NONE, err); + error_t err2 = pmallocator_free(result, g_header_region); + TEST_ASSERT_EQUAL(ERR_NOT_VALID, err2); +} + +static void test_free_out_of_bounds(void) { + memory_area_t bad = { .addr = g_area.addr - PAGE_SIZE, .size = PAGE_SIZE }; + error_t err = pmallocator_free(bad, g_header_region); + TEST_ASSERT_EQUAL(ERR_OUT_OF_BOUNDS, err); +} + +static void test_free_not_allocated(void) { + memory_area_t not_allocated = { .addr = g_area.addr + 2 * PAGE_SIZE, .size = PAGE_SIZE }; + error_t err = pmallocator_free(not_allocated, g_header_region); + TEST_ASSERT_EQUAL(ERR_NOT_VALID, err); +} + + +static void test_full_interleaved(void) { + memory_area_t a; + memory_area_t b; + memory_area_t c; + memory_area_t d; + (void)pmallocator_allocate(FRAME_ORDER_4KiB, g_header_region, &a); + (void)pmallocator_allocate(FRAME_ORDER_4KiB, g_header_region, &b); + (void)pmallocator_allocate(FRAME_ORDER_4KiB, g_header_region, &c); + error_t err = pmallocator_free(b, g_header_region); + TEST_ASSERT_EQUAL(ERR_NONE, err); + (void)pmallocator_allocate(FRAME_ORDER_4KiB, g_header_region, &d); + TEST_ASSERT_EQUAL(b.addr, d.addr); +} + +static void test_full_cycle(void) { + size_t expected = TEST_PAGES - TOTAL_RESERVED_PAGES - g_header_pages; + memory_area_t results[TEST_PAGES]; + size_t count = 0; + + while (pmallocator_allocate(FRAME_ORDER_4KiB, g_header_region, &results[count]) == ERR_NONE) { + count++; + } + + for (size_t i = 0; i < count; i++) { + error_t err = pmallocator_free(results[i], g_header_region); + TEST_ASSERT_EQUAL(ERR_NONE, err); + } + + size_t count2 = 0; + memory_area_t tmp; + while (pmallocator_allocate(FRAME_ORDER_4KiB, g_header_region, &tmp) == ERR_NONE) { + count2++; + } + + TEST_ASSERT_EQUAL(expected, count); + TEST_ASSERT_EQUAL(count, count2); +} + + +int main(void) { + UNITY_BEGIN(); + RUN_TEST(test_allocate_single_page); + RUN_TEST(test_allocate_until_full); + RUN_TEST(test_free_and_reallocate); + RUN_TEST(test_free_double_free); + RUN_TEST(test_free_out_of_bounds); + RUN_TEST(test_free_not_allocated); + RUN_TEST(test_full_interleaved); + RUN_TEST(test_full_cycle); + return UNITY_END(); +} diff --git a/tests/stubs/hal_irq_stub.c b/tests/stubs/hal_irq_stub.c new file mode 100644 index 00000000..e5328ce6 --- /dev/null +++ b/tests/stubs/hal_irq_stub.c @@ -0,0 +1,4 @@ +#include + +void hal_disable_irq(void) {} +void hal_enable_irq(void) {} From 2654a64683b2f9d6e21b775e845832e7140314b3 Mon Sep 17 00:00:00 2001 From: Dawid Pawliczek Date: Sun, 12 Apr 2026 19:47:27 +0200 Subject: [PATCH 2/5] Enhance unit test configuration to output results on failure --- CMakePresets.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CMakePresets.json b/CMakePresets.json index 477ee820..3bf11789 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -289,7 +289,10 @@ "name": "tests-unit", "configurePreset": "tests-unit", "displayName": "Run Unit Tests", - "description": "Run unit tests via CTest on native platform." + "description": "Run unit tests via CTest on native platform.", + "output": { + "outputOnFailure": true + } } ] } From 063ec8e2482671c83ca44f0fe9d909dcc8c4d27c Mon Sep 17 00:00:00 2001 From: Dawid Pawliczek Date: Sun, 12 Apr 2026 19:48:53 +0200 Subject: [PATCH 3/5] Enhance unit test run configuration with verbose output --- CMakePresets.json | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakePresets.json b/CMakePresets.json index 3bf11789..b40b647d 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -291,6 +291,7 @@ "displayName": "Run Unit Tests", "description": "Run unit tests via CTest on native platform.", "output": { + "verbosity": "verbose", "outputOnFailure": true } } From 5fce38fa27f6c9f79e7d3a92cf84b36c85f725a5 Mon Sep 17 00:00:00 2001 From: Dawid Pawliczek Date: Sun, 12 Apr 2026 19:52:45 +0200 Subject: [PATCH 4/5] use aligned_alloc --- tests/kernel/memory_management/physical_memory/test_allocator.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/kernel/memory_management/physical_memory/test_allocator.c b/tests/kernel/memory_management/physical_memory/test_allocator.c index ea92ff10..0f280a83 100644 --- a/tests/kernel/memory_management/physical_memory/test_allocator.c +++ b/tests/kernel/memory_management/physical_memory/test_allocator.c @@ -37,7 +37,7 @@ static memory_area_t g_area; static size_t g_header_pages; void setUp(void) { - g_area_buf = calloc(1, TEST_SIZE); + g_area_buf = aligned_alloc(PAGE_SIZE, TEST_SIZE); g_area = (memory_area_t){ .addr = (uintptr_t)g_area_buf, From 278ef248b3c061fa6d3572a78cd62009cebda30f Mon Sep 17 00:00:00 2001 From: Dawid Pawliczek Date: Tue, 21 Apr 2026 07:38:11 +0200 Subject: [PATCH 5/5] Remove hal_irq_stub from test_pmallocator executable --- tests/kernel/memory_management/physical_memory/CMakeLists.txt | 1 - tests/stubs/hal_irq_stub.c | 4 ---- 2 files changed, 5 deletions(-) delete mode 100644 tests/stubs/hal_irq_stub.c diff --git a/tests/kernel/memory_management/physical_memory/CMakeLists.txt b/tests/kernel/memory_management/physical_memory/CMakeLists.txt index b7f959a6..57725c22 100644 --- a/tests/kernel/memory_management/physical_memory/CMakeLists.txt +++ b/tests/kernel/memory_management/physical_memory/CMakeLists.txt @@ -1,7 +1,6 @@ add_executable(test_pmallocator test_allocator.c ${PROJECT_SOURCE_DIR}/src/kernel/memory_management/physical_memory/allocator.c - ${PROJECT_SOURCE_DIR}/tests/stubs/hal_irq_stub.c ) target_compile_features(test_pmallocator PUBLIC c_std_23) diff --git a/tests/stubs/hal_irq_stub.c b/tests/stubs/hal_irq_stub.c deleted file mode 100644 index e5328ce6..00000000 --- a/tests/stubs/hal_irq_stub.c +++ /dev/null @@ -1,4 +0,0 @@ -#include - -void hal_disable_irq(void) {} -void hal_enable_irq(void) {}