1#include <colors.h>
2#include <console/panic.h>
3#include <console/printf.h>
4#include <crypto/prng.h>
5#include <global.h>
6#include <irq/irq.h>
7#include <math/div.h>
8#include <mem/alloc_or_die.h>
9#include <mem/vas.h>
10#include <smp/core.h>
11#include <stdbool.h>
12#include <stddef.h>
13#include <stdint.h>
14#include <test.h>
15#include <time/spin_sleep.h>
16#include <time/time.h>
17
18#include "mem/slab/internal.h"
19
20/* Basically, every test and test setting wires back here */
21CMDLINE_ENTRY_DECLARE(test_root, .name = "test");
22LINKER_SECTION_OBJECT(struct test_group, test_groups)
23test_group_orphan_parent = {.name = "test_group_orphan_parent",
24 .enabled = true,
25 .exit_on_fail = false,
26 .ent = CMDLINE_ENTRY(test_root)};
27
28LOG_SITE_DECLARE_DEFAULT(test_harness);
29LOG_HANDLE_DECLARE_DEFAULT(test_harness, .flags = LOG_PRINT | LOG_NO_NEWLINE);
30
31#define test_harness_log(lvl, fmt, ...) \
32 log(LOG_SITE(test_harness), LOG_HANDLE(test_harness), lvl, fmt, \
33 ##__VA_ARGS__)
34
35#define test_harness_err(fmt, ...) \
36 test_harness_log(LOG_ERROR, fmt, ##__VA_ARGS__)
37#define test_harness_warn(fmt, ...) \
38 test_harness_log(LOG_WARN, fmt, ##__VA_ARGS__)
39#define test_harness_info(fmt, ...) \
40 test_harness_log(LOG_INFO, fmt, ##__VA_ARGS__)
41#define test_harness_debug(fmt, ...) \
42 test_harness_log(LOG_DEBUG, fmt, ##__VA_ARGS__)
43#define test_harness_trace(fmt, ...) \
44 test_harness_log(LOG_TRACE, fmt, ##__VA_ARGS__)
45
46LINKER_SECTION_DEFINE(struct test, tests);
47LINKER_SECTION_DEFINE(struct test_group, test_groups);
48/* no need to clean up allocations in these tests, we are supposed to
49 * reboot/poweroff after all tests complete, and the userland should
50 * not be in a state where we can boot it when running tests */
51struct test_globals test_global;
52
53static bool is_keyword(const char *check, const char **against,
54 size_t num_keywords) {
55 for (size_t i = 0; i < num_keywords; i++) {
56 if (strcmp(str1: check, str2: against[i]) == 0)
57 return true;
58 }
59
60 return false;
61}
62
63void tests_hook_boot() {
64#ifdef TEST_ENABLED
65 /* In here, we go through all tests, and assign their command line parents
66 */
67 for (struct test *t = __skernel_tests; t < __ekernel_tests; t++) {
68 kassert(t->group);
69 t->base_entry->parent = t->group->ent;
70 }
71#endif
72}
73
74static void tests_setup_groups() {
75 for (struct test_group *tg = __skernel_test_groups;
76 tg < __ekernel_test_groups; tg++) {
77 size_t num_tests[TEST_TIER_MAX] = {0};
78 for (struct test *t = __skernel_tests; t < __ekernel_tests; t++) {
79 if (t->group == tg) {
80 num_tests[t->tier]++;
81 }
82 }
83
84 for (int i = 0; i < TEST_TIER_MAX; i++) {
85 if (num_tests[i]) {
86 tg->tests[i] =
87 kmalloc_or_die(sizeof(struct test *) * num_tests[i]);
88 }
89 tg->num_tests[i] = num_tests[i];
90 }
91
92 for (struct test *t = __skernel_tests; t < __ekernel_tests; t++) {
93 if (t->group == tg) {
94 tg->tests[t->tier][num_tests[t->tier] - 1] = t;
95 num_tests[t->tier]--;
96 }
97 }
98 }
99}
100
101struct test_group_result {
102 size_t totals[TEST_TIER_MAX][TEST_RESULT_MAX];
103};
104
105static void test_group_run(struct test_group *tg) {
106 if (!tg->enabled)
107 return;
108
109 size_t total_tests = 0;
110 time_t total_time = 0;
111 for (int i = 0; i < TEST_TIER_MAX; i++)
112 total_tests += tg->num_tests[i];
113
114 test_harness_info("Running test group '%s' (%zu tests):\n", tg->name,
115 total_tests);
116 test_harness_info(" incremental: %s\n",
117 tg->incremental ? "true" : "false");
118 test_harness_info(" exit_on_fail: %s\n",
119 tg->exit_on_fail ? "true" : "false");
120 test_harness_info(" default intensity: %F\n", tg->default_intensity);
121
122 bool stop_outer = false;
123
124 struct test_group_result result_totals = {0};
125 for (int i = 0; i < TEST_TIER_MAX; i++) {
126 if (stop_outer)
127 break;
128
129 test_harness_info("%zu %s tests:\n", tg->num_tests[i],
130 test_tier_to_str(i));
131 if (!tg->num_tests[i])
132 continue;
133
134 for (size_t test_num = 0; test_num < tg->num_tests[i]; test_num++) {
135 struct test *t = tg->tests[i][test_num];
136 struct test_context tctx = {0};
137
138 /* Modifiable by the test */
139 struct log_dump_options dopts = {
140 .min_level = LOG_TRACE,
141 .show_args = true,
142 };
143
144 enum log_site_flags flags = LOG_SITE_NONE;
145 if (t->print_logs)
146 flags |= LOG_SITE_PRINT;
147
148 struct log_site_options opts = {
149 .capacity =
150 t->msg_cap == 0 ? LOG_SITE_CAPACITY_DEFAULT : t->msg_cap,
151 .name = "test",
152 .enabled_mask = LOG_SITE_ALL,
153 .dump_opts = dopts,
154 .flags = flags,
155 };
156 alloc_or_die(tctx.site = log_site_create(opts));
157 test_global.current_test = &tctx;
158 tctx.handle.msg = "test_handle";
159 tctx.intensity = t->intensity;
160 tctx.seed = !t->seed ? prng_next() : t->seed;
161
162 size_t result_times[TEST_RESULT_MAX] = {0}, run_times = 0;
163 struct test_verdict verdicts[t->run_times];
164
165 enum test_result singular_result = TEST_RESULT_MAX;
166
167 test_harness_info("'%s'", t->name);
168
169 time_t start_ms = time_get_ms();
170 for (; run_times < t->run_times; run_times++) {
171 struct test_verdict verdict = t->func(&tctx);
172 singular_result = verdict.result;
173 verdicts[run_times] = verdict;
174
175 if (verdict.result == TEST_RESULT_SKIPPED) {
176 result_times[TEST_RESULT_SKIPPED]++;
177 } else if (verdict.result == TEST_RESULT_FAILED) {
178 result_times[TEST_RESULT_FAILED]++;
179 } else {
180 result_times[TEST_RESULT_OK]++;
181 }
182
183 if (result_times[TEST_RESULT_FAILED] && !t->keep_going)
184 break;
185
186 tctx.seed = !t->seed ? prng_next() : t->seed;
187 }
188 time_t end_ms = time_get_ms();
189 time_t took = end_ms - start_ms;
190 total_time += took;
191
192 size_t total_run = run_times - result_times[TEST_RESULT_SKIPPED];
193
194 if (t->run_times > 1) {
195 char *color = run_times < total_run ? ANSI_RED : ANSI_BLUE;
196 printf(format: " ran (%s%zu" ANSI_RESET "/" ANSI_BLUE "%zu" ANSI_RESET
197 ") times in " ANSI_GREEN "%zu" ANSI_RESET " ms,",
198 color, total_run, t->run_times, took);
199 if (result_times[TEST_RESULT_OK] == run_times) {
200 printf(ANSI_BLUE " all successful" ANSI_RESET "\n");
201 } else if (result_times[TEST_RESULT_SKIPPED]) {
202 printf(ANSI_GRAY " %zu skipped" ANSI_RESET,
203 result_times[TEST_RESULT_SKIPPED]);
204 }
205
206 if (result_times[TEST_RESULT_FAILED]) {
207 printf(ANSI_RED " %zu failed" ANSI_RESET "\n",
208 result_times[TEST_RESULT_FAILED]);
209 }
210
211 for (size_t i = 0; i < run_times; i++) {
212 struct test_verdict v = verdicts[i];
213 if (v.result != TEST_RESULT_OK) {
214 test_harness_info(" |-> run %zu %s", i, v.result);
215 if (v.result == TEST_RESULT_SKIPPED) {
216 printf(ANSI_GRAY " (%s)" ANSI_RESET, v.skip_reason);
217 }
218 printf(format: "\n");
219 }
220 }
221 } else {
222 char *status;
223 char *color;
224 switch (singular_result) {
225 case TEST_RESULT_OK:
226 color = ANSI_BLUE;
227 status = "successful";
228 break;
229 case TEST_RESULT_SKIPPED:
230 color = ANSI_GRAY;
231 status = "skipped";
232 break;
233 case TEST_RESULT_FAILED:
234 color = ANSI_RED;
235 status = "error";
236 break;
237 default: kassert_unreachable();
238 }
239 printf(format: " %s%s" ANSI_RESET " in " ANSI_GREEN "%zu" ANSI_RESET
240 " ms\n",
241 color, status, took);
242 }
243
244 if (log_site_message_count(site: tctx.site) && !t->print_logs) {
245 test_harness_info("messages:\n");
246 log_dump_site(site: tctx.site);
247 }
248
249 for (int j = 0; j < TEST_RESULT_MAX; j++)
250 result_totals.totals[i][j] += result_times[j];
251
252 if (result_times[TEST_RESULT_FAILED] && tg->incremental) {
253 stop_outer = true;
254 } else if (result_times[TEST_RESULT_FAILED] && tg->exit_on_fail) {
255 stop_outer = true;
256 break;
257 }
258 }
259 }
260
261 for (int i = 0; i < TEST_TIER_MAX; i++) {
262 for (int j = 0; j < TEST_RESULT_MAX; j++) {
263 test_global.results[i][j] += result_totals.totals[i][j];
264 }
265 }
266
267 test_harness_info("Test group '%s' complete in %zu ms: \n", tg->name,
268 total_time);
269 if (stop_outer) {
270 for (int i = 0; i < TEST_TIER_MAX; i++) {
271 size_t fails = result_totals.totals[i][TEST_RESULT_FAILED];
272 size_t skips = result_totals.totals[i][TEST_RESULT_SKIPPED];
273 if (skips && !fails) {
274 test_harness_info(" Tier '%s' had %zu skips\n",
275 test_tier_to_str(i), skips);
276 } else if (fails && !skips) {
277 test_harness_info(" Tier '%s' had %zu fails\n",
278 test_tier_to_str(i), fails);
279 } else {
280 test_harness_info(" Tier '%s' had %zu skips, %zu fails\n",
281 test_tier_to_str(i), skips, fails);
282 }
283 }
284 } else {
285 test_harness_info(" All successful\n");
286 }
287}
288
289static void test_global_aggregate_results() {
290 for (int i = 0; i < TEST_TIER_MAX; i++) {
291 for (int j = 0; j < TEST_RESULT_MAX; j++) {
292 test_global.results_agg[j] += test_global.results[i][j];
293 }
294 }
295}
296
297void tests_run(void) {
298#ifdef TEST_ENABLED
299 tests_setup_groups();
300
301 bool all_ok = true;
302 char *msg = all_ok ? "all tests pass 🎉!" : "some errors occurred";
303 char *color = all_ok ? ANSI_GREEN : ANSI_RED;
304
305 test_harness_info("Running %zu tests:\n",
306 __ekernel_tests - __skernel_tests);
307
308 for (struct test_group *tg = __skernel_test_groups;
309 tg < __ekernel_test_groups; tg++) {
310 test_group_run(tg);
311 }
312 test_global_aggregate_results();
313
314 size_t fail_count = test_global.results_agg[TEST_RESULT_FAILED];
315 size_t skip_count = test_global.results_agg[TEST_RESULT_SKIPPED];
316 size_t pass_count = test_global.results_agg[TEST_RESULT_OK];
317 time_t total_time = test_global.total_time;
318 all_ok = fail_count == 0;
319 color = all_ok ? ANSI_GREEN : ANSI_RED;
320 msg = all_ok ? "all tests pass 🎉!" : "some errors occurred";
321 char *fail_color = all_ok ? ANSI_GREEN : ANSI_RED;
322 char *skip_color = all_ok ? ANSI_GREEN : ANSI_GRAY;
323
324 test_harness_info("%llu " ANSI_CYAN "total" ANSI_RESET
325 " tests, %llu " ANSI_GREEN "passed" ANSI_RESET
326 ", %llu %sfailed" ANSI_RESET ", %llu %sskipped" ANSI_RESET
327 "\n",
328 __ekernel_tests - __skernel_tests, pass_count, fail_count,
329 fail_color, skip_count, skip_color);
330
331 test_harness_info("%s%s" ANSI_RESET " (%llu ms)\n", color, msg, total_time);
332
333 vas_space_dump(vas: slab_global.vas);
334
335#endif
336}
337