The Pedigree Project 0.1
modules/system/module-runtime-tests/main.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "pedigree/kernel/Log.h"
3#include "pedigree/kernel/linker/KernelElf.h"
4#include "pedigree/kernel/linker/ModuleImage.h"
5#include "pedigree/kernel/process/Scheduler.h"
6#include "pedigree/kernel/process/Thread.h"
7#include "pedigree/kernel/time/Time.h"
8
9#include "modules/Module.h"
10
11#if !HOSTED || !PEDIGREE_HOSTED_SMOKE_TESTS
12#error "The runtime-module lifecycle fixture requires the hosted smoke test configuration"
13#endif
14
15extern void system_reset();
16extern "C" {
17extern const uint8_t runtime_provider_image[];
18extern const size_t runtime_provider_image_size;
19extern const uint8_t runtime_consumer_image[];
20extern const size_t runtime_consumer_image_size;
21extern const uint8_t runtime_undeclared_image[];
22extern const size_t runtime_undeclared_image_size;
23}
24
25namespace {
26using Load = KernelElf::RuntimeLoadResult;
27using Unload = KernelElf::RuntimeUnloadResult;
28size_t constructors = 0, entries = 0, exits = 0, destructors = 0;
29bool entryFails = false, visibleDuringEntry = false, expectHidden = true;
30ModuleImage inspection;
31
32bool check(bool value, const char* detail) {
33 if (!value)
34 ERROR("HOSTED-RUNTIME-MODULE-TEST: FAIL " << detail);
35 return value;
36}
37
38Load load(const uint8_t* image, size_t length, char suffix = 0) {
39 auto& kernel = KernelElf::instance();
40 KernelElf::RuntimeLoad transaction;
41 const auto admitted = kernel.beginRuntimeModuleLoad(length, transaction);
42 if (admitted != Load::Ready)
43 return admitted;
44 MemoryCopy(transaction.data(), image, length);
45 if (suffix) {
47 uintptr_t address = 0;
48 size_t offset = 0;
49 if (inspection.preflight(transaction.data(), length) != ModuleImage::Result::Valid ||
50 !inspection.findSymbol("g_pModuleName", name) ||
51 !inspection.localPointer(name.value, address) ||
52 !inspection.fileOffset(address, 17, offset))
53 return Load::InvalidImage;
54 // Rename only the fixed trailing digit in the benign provider fixture.
55 transaction.data()[offset + StringLength("runtime-fixture-")] = suffix;
56 }
57 return kernel.loadModuleRuntime(transaction);
58}
59
60bool run() {
61 auto& kernel = KernelElf::instance();
62 const auto start = Time::getTicks();
63 bool ready = false;
64 for (size_t attempt = 0;
65 attempt < 100000 && Time::getTicks() - start < 10 * Time::Multiplier::Second; ++attempt) {
67 const auto admitted = kernel.beginRuntimeModuleLoad(runtime_provider_image_size, probe);
68 if (admitted == Load::Ready) {
69 ready = true;
70 break;
71 }
72 if (admitted != Load::Busy)
73 return check(false, "arena admission");
75 }
76 if (!check(ready, "bounded wait for Active boot provider"))
77 return false;
78 NOTICE("HOSTED-RUNTIME-MODULE-TEST: PASS active-admission");
79
80 if (!check(inspection.preflight(runtime_provider_image, runtime_provider_image_size) ==
81 ModuleImage::Result::Valid,
82 "native fixture preflight") ||
83 !check(load(runtime_provider_image, 16) == Load::InvalidImage, "truncated header") ||
84 !check(load(runtime_consumer_image, runtime_consumer_image_size) == Load::MissingDependency,
85 "missing provider"))
86 return false;
87 NOTICE("HOSTED-RUNTIME-MODULE-TEST: PASS preflight-missing-provider");
88
89 {
90 KernelElf::RuntimeLoad first, second;
91 if (!check(kernel.beginRuntimeModuleLoad(runtime_provider_image_size, first) == Load::Ready &&
92 kernel.beginRuntimeModuleLoad(runtime_provider_image_size, second) == Load::Busy,
93 "single transition admission"))
94 return false;
95 }
96 KernelElf::failRuntimeProtectionForTest(2);
97 if (!check(load(runtime_provider_image, runtime_provider_image_size) == Load::ProtectionFailed,
98 "recoverable protection failure") ||
99 !check(!constructors && !entries && !exits && !destructors, "protection failure ran code"))
100 return false;
101 NOTICE("HOSTED-RUNTIME-MODULE-TEST: PASS protection-rollback");
102
103 entryFails = true;
104 if (!check(load(runtime_provider_image, runtime_provider_image_size) == Load::EntryFailed,
105 "entry failure") ||
106 !check(constructors == 1 && entries == 1 && exits == 1 && destructors == 1,
107 "entry failure lifecycle cleanup") ||
108 !check(kernel.unloadModuleRuntime("runtime-fixture-0") == Unload::NotFound,
109 "failed entry remained published"))
110 return false;
111 entryFails = false;
112 if (!check(load(runtime_provider_image, runtime_provider_image_size) == Load::Loaded,
113 "retry after entry failure") ||
114 !check(!visibleDuringEntry, "incomplete exports became visible") ||
115 !check(kernel.globalLookupSymbol("module_runtime_fixture_value") != 0, "Active export") ||
116 !check(load(runtime_provider_image, runtime_provider_image_size) == Load::Duplicate,
117 "duplicate live name"))
118 return false;
119 NOTICE("HOSTED-RUNTIME-MODULE-TEST: PASS entry-rollback-retry-publish");
120
121 expectHidden = false;
122 if (!check(
123 load(runtime_undeclared_image, runtime_undeclared_image_size) == Load::MissingDependency,
124 "undeclared import provider") ||
125 !check(load(runtime_consumer_image, runtime_consumer_image_size) == Load::Loaded,
126 "declared provider import") ||
127 !check(kernel.unloadModuleRuntime("runtime-fixture-0") == Unload::DependedOn,
128 "live dependency did not pin provider") ||
129 !check(kernel.unloadModuleRuntime("runtime-consumer") == Unload::Unloaded,
130 "consumer unload") ||
131 !check(kernel.unloadModuleRuntime("runtime-fixture-0") == Unload::Unloaded,
132 "provider unload") ||
133 !check(kernel.unloadModuleRuntime("runtime-fixture-0") == Unload::NotFound,
134 "repeated unload") ||
135 !check(!kernel.globalLookupSymbol("module_runtime_fixture_value"), "retired export visible"))
136 return false;
137 NOTICE("HOSTED-RUNTIME-MODULE-TEST: PASS dependency-unload");
138
139 for (char suffix = '0'; suffix <= '3'; ++suffix) {
140 if (!check(load(runtime_provider_image, runtime_provider_image_size, suffix) == Load::Loaded,
141 "fill all four slots"))
142 return false;
143 }
144 if (!check(load(runtime_provider_image, runtime_provider_image_size, '4') == Load::NoMemory,
145 "bounded arena exhaustion"))
146 return false;
147 for (char suffix = '0'; suffix <= '3'; ++suffix) {
148 char name[] = "runtime-fixture-0";
149 name[sizeof(name) - 2] = suffix;
150 if (!check(kernel.unloadModuleRuntime(name) == Unload::Unloaded, "slot reclaim"))
151 return false;
152 }
153 expectHidden = true;
154 if (!check(load(runtime_provider_image, runtime_provider_image_size) == Load::Loaded,
155 "reload after slot exhaustion") ||
156 !check(kernel.unloadModuleRuntime("runtime-fixture-0") == Unload::Unloaded, "final unload") ||
157 !check(constructors == entries && entries == exits && exits == destructors,
158 "exactly-once lifecycle totals"))
159 return false;
160 NOTICE("HOSTED-RUNTIME-MODULE-TEST: PASS exhaustion-reload");
161 return true;
162}
163
164int worker(void*) {
165 const bool passed = run();
166 NOTICE("HOSTED-RUNTIME-MODULE-TEST: END " << (passed ? "PASS" : "FAIL"));
167 system_reset();
168 return passed ? 0 : 1;
169}
170
171bool entry() {
172 if (!KernelElf::instance().prepareRuntimeModules()) {
173 ERROR("HOSTED-RUNTIME-MODULE-TEST: FAIL boot preparation");
174 NOTICE("HOSTED-RUNTIME-MODULE-TEST: END FAIL");
175 system_reset();
176 return false;
177 }
178 Thread* thread = new Thread(Scheduler::instance().getKernelProcess(), worker, nullptr);
179 thread->setName("Runtime module lifecycle tests");
180 thread->detach();
181 return true;
182}
183void exit() {}
184} // namespace
185
186extern "C" EXPORTED_PUBLIC void module_runtime_test_event(unsigned int event) {
187 switch (event) {
188 case 1:
189 ++constructors;
190 break;
191 case 2:
192 ++entries;
193 if (expectHidden && KernelElf::instance().globalLookupSymbol("module_runtime_fixture_value"))
194 visibleDuringEntry = true;
195 break;
196 case 3:
197 ++exits;
198 break;
199 case 4:
200 ++destructors;
201 break;
202 }
203}
204extern "C" EXPORTED_PUBLIC bool module_runtime_test_entry() {
205 return !entryFails;
206}
207MODULE_INFO_NON_UNLOADABLE("module-runtime-tests", &entry, &exit);
static KernelElf & instance()
Definition KernelElf.h:135
static Scheduler & instance()
Definition Scheduler.h:96
void yield()
Definition Scheduler.cc:236
bool detach()
Definition Thread.cc:2987