1/* @title: Tests */
2#pragma once
3#include <cmdline.h>
4#include <compiler/core.h>
5#include <compiler/diagnostic.h>
6#include <compiler/wrapper.h>
7#include <console/printf.h>
8#include <err.h>
9#include <fs/detect.h>
10#include <log.h>
11#include <math/fixed.h>
12#include <math/units.h>
13#include <mem/alloc.h>
14#include <mem/pmm.h>
15#include <scaled_param.h>
16#include <stdbool.h>
17#include <stringify.h>
18#include <sync/lock_general.h>
19#include <test/test_api_internal.h>
20
21typedef void (*test_fn_t)(void);
22struct test_context;
23struct test_fleet;
24struct thread;
25struct thread_create_params;
26
27#define TEST_MAX_HELPERS 8
28
29struct test_alloc_node {
30 struct test_alloc_node *next;
31};
32
33enum test_tier {
34 TEST_TIER_SMOKE,
35 TEST_TIER_UNIT,
36 TEST_TIER_INTEGRATION,
37 TEST_TIER_MAX,
38};
39
40enum test_options {
41 TEST_OPTION_NONE = 0, /* These are passed from cmdline */
42};
43
44/* These are immutable properties of the group */
45enum test_group_flags {
46 TEST_GROUP_FLAG_NONE = 0,
47 TEST_GROUP_FLAG_DEFAULT = 1, /* orphans get adopted by this group */
48};
49
50enum test_result {
51 TEST_RESULT_OK,
52 TEST_RESULT_FAILED,
53 TEST_RESULT_SKIPPED,
54 TEST_RESULT_MAX,
55};
56
57enum test_flags {
58 TEST_FLAG_NONE = 0,
59
60 /* Test flags will be structured in what they *enable* or honor/respect */
61 TEST_FLAG_HONORS_INTENSITY = 1,
62
63 /* If this is enabled, TEST_FLAG_HONORS_INTENSITY is auto-enabled */
64 TEST_FLAG_INHERITS_INTENSITY = 1 << 1,
65};
66
67enum test_skip_reason {
68 TEST_SKIP_NONE,
69 TEST_SKIP_RAM_LOW,
70 TEST_SKIP_UPSTREAM_FAILED, /* a prior tier in this group
71 failed */
72 TEST_SKIP_DISABLED, /* not the same as .enabled, just that the test
73 * won't be getting run for some reason */
74 TEST_SKIP_INSUFFICIENT_CORES, /* fewer online cores than .min_cores */
75 TEST_SKIP_UNSUPPORTED_FS, /* mounted root is not .required_fs */
76};
77
78enum test_state : uint8_t {
79 TEST_STATE_DISABLED = 0,
80 TEST_STATE_ENABLED = 1,
81 TEST_STATE_SENTINEL = 0xFF,
82};
83
84struct test_group {
85 const char *name;
86 const char *fname;
87 uint32_t line;
88 const enum test_group_flags flags;
89 struct test_verdict (*setup)(struct test_context *);
90 struct test_verdict (*teardown)(struct test_context *);
91
92 fx32_32_t default_intensity;
93
94 /* This is given to all children that TEST_FLAG_INHERITS_INTENSITY */
95 struct scaled_param intensity_desc;
96
97 enum test_state enabled;
98 union {
99 enum test_state tier_enabled[TEST_TIER_MAX];
100 struct {
101 enum test_state smoke_enabled;
102 enum test_state unit_enabled;
103 enum test_state integration_enabled;
104 };
105 };
106
107 bool incremental; /* First run smoke, then unit, then integration */
108 bool exit_on_fail; /* Different from incremental: exits after one failure,
109 * whereas incremental still completes the tier */
110
111 /* [tier][num] */
112 struct test **tests[TEST_TIER_MAX];
113 size_t num_tests[TEST_TIER_MAX];
114 size_t num_tests_enabled[TEST_TIER_MAX];
115};
116
117/*
118 * The idea with (line|msg|stmt)_hint is that crash codes
119 * get reused in various different places, and it's sometimes not enough
120 * to merely check that the code matches, because it could still
121 * be tripping the wrong assertion.
122 *
123 * So, we have these hints instead to fuzzy match and note/warn
124 * when a hint does(n't) match. The expected crash code matching
125 * will pass regardless, and hints just give a bit more data.
126 *
127 * msg_hint fuzzy matches against the message, if any, and
128 * stmt_hint fuzzy matches against the statement, if any, e.g.
129 *
130 * enforce(my_var) would have the 'stmt' of my_var
131 */
132struct test_death {
133 const struct crash_code *expect;
134 uint32_t line_hint;
135 const char *msg_hint;
136 const char *stmt_hint;
137};
138
139struct test {
140 const char *name;
141 const char *fname;
142 uint32_t line;
143
144 struct test_verdict (*func)(struct test_context *ctx);
145
146 const struct test_group *group;
147 enum test_tier tier;
148
149 size_t msg_cap;
150 enum test_flags flags;
151
152 time_ms_t duration_ms;
153
154 /* A lot of these have to be full bools for the cmdline parse */
155 struct test_death *death;
156 bool print_logs; /* Prints logs *as* they are logged */
157 enum test_state enabled; /* If this is set to TEST_SENTINEL,
158 * it means we default */
159
160 bool keep_going; /* This basically means that with run_times,
161 * if it fails once, don't bother, and keep going
162 * until it runs run_times, however, it is overridden
163 * by exit_on_fail from the test_group */
164
165 fx32_32_t intensity; /* also [0, 1]. the point of this one is that
166 * it can be set by the command line, and the
167 * context reads and passes this into the test.
168 *
169 * the reason for this indirection is that the
170 * struct test_context that tests get might be
171 * changed by other subsystems that may not
172 * necessarily keep the same .intensity as the
173 * boot time command line option/default */
174
175 struct scaled_param intensity_desc;
176
177 uint64_t seed; /* Can be passed in through cmdline, NOTE:
178 * if a seed is set through the cmdline, and
179 * run_times > 1, this seed will be used in every
180 * run during run_times */
181
182 size_t run_times;
183
184 /* TODO: typed core-count strong int */
185 size_t min_cores;
186 sz_mib_t min_ram_mib;
187 enum fs_type required_fs;
188
189 size_t inject_count;
190 struct inject_site *inject[] cc_counted_by(inject_count);
191};
192
193/* The reason this exists is because the actual structures in struct
194 * test are not meant to be accessed from inside the test's func,
195 * and this is the way the test interacts with the variables.
196 *
197 * This might make one think "why not just interact with struct test?",
198 * and the answer is that struct test_context gives us extensibility,
199 * and allows a test to get run numerous different times with varied
200 * contexts, seeds, etc. without having to fiddle around with struct test */
201#define TEST_FAIL_MSG_MAX 512
202
203struct test_context {
204 struct log_site *site;
205 struct log_handle handle;
206
207 uint32_t soft_fails;
208
209 fx32_32_t intensity; /* Raw [0, 1] fixed-point intensity */
210 size_t intensity_val; /* Pre-computed scaled integer based on SCALE */
211 time_ms_t duration_ms;
212
213 uint64_t seed;
214
215 /* Workers put their data into here */
216 char fail_msg[TEST_FAIL_MSG_MAX];
217
218 /* Allocated by the runner */
219 struct test_fleet *fleet;
220
221 /* Framework-owned allocations, freed at test teardown */
222 struct test_alloc_node *allocs;
223
224 /* Runner-managed auxiliary threads, joined at test teardown */
225 struct thread *helpers[TEST_MAX_HELPERS];
226 size_t helper_count;
227};
228
229void *test_alloc(struct test_context *ctx, size_t size) cw_alloc(2);
230
231/* int *x = test_new(x); */
232#define TEST_NEW_CHECK(p) \
233 static_assert( \
234 __builtin_types_compatible_p(__typeof_unqual__(p), typeof(&*(p))), \
235 "test_new: target must be a pointer lvalue, not an array"); \
236 static_assert( \
237 !cc_generic((p), void * : 1, const void * : 1, volatile void * : 1, \
238 const volatile void * : 1, default : 0), \
239 "test_new: void pointer target, use test_alloc() with a size")
240
241#define test_new(p) \
242 ({ \
243 TEST_NEW_CHECK(p); \
244 (VNEW_PTR(p)) test_alloc(ctx, sizeof(*(p))); \
245 })
246
247struct thread *test_spawn_internal(struct test_context *ctx,
248 void (*entry)(void *),
249 const struct thread_create_params *params,
250 const char *name);
251
252#define test_spawn(ctx, name, entry, ...) \
253 test_spawn_internal((ctx), (entry), \
254 &(struct thread_create_params) \
255 thread_params_with_defaults(__VA_ARGS__), \
256 PP_UNPAREN(name))
257
258cpu_id_t test_core(size_t index);
259
260bool test_context_teardown(struct test_context *ctx);
261
262struct test_verdict {
263 enum test_result result;
264 enum test_skip_reason skip_reason;
265 const char *msg; /* optional, e.g. the failed assertion */
266};
267
268struct test_globals {
269 fx32_32_t global_intensity;
270 size_t total_tests_enabled;
271 size_t results[TEST_TIER_MAX][TEST_RESULT_MAX];
272 size_t results_agg[TEST_RESULT_MAX];
273 struct test_context *current_test;
274 time_ms_t total_time;
275 bool show_output;
276 bool group_opt_in;
277 bool test_opt_in;
278 bool no_exit;
279 bool no_progress;
280};
281
282#define TEST_EXIT_OK 0
283#define TEST_EXIT_FAIL 1
284#define TEST_INTENSITY_SENTINEL ((fx32_32_t) - 1LL)
285#define TEST_INTENSITY_DEFAULT FX(0.5)
286
287/* Intended for use with TEST_FLAG_INHERITS_INTENSITY, so that tests can still
288 * set custom min, def, max outside of what they would inherit from group */
289#define TEST_INTENSITY(min, def, max) \
290 .flags = TEST_FLAG_INHERITS_INTENSITY, \
291 .intensity_desc = {.min_val = (min), .def_val = (def), .max_val = (max)}
292#define TEST_INTENSITY_DESC_SENTINEL \
293 (struct scaled_param) { \
294 .min_val = SIZE_MAX, .def_val = SIZE_MAX, .max_val = SIZE_MAX \
295 }
296
297#define TEST_GROUP_NONE test_group_orphan_parent
298#define TEST_GROUP(name) &(__test_group_##name)
299#define TEST_GROUP_EXTERN(name) extern struct test_group __test_group_##name
300
301#define TEST(grp, id) __test_##grp##_##id
302
303#define TEST_DEFINE(grp, id, ...) \
304 cc_wno_override_init_start static struct test_verdict \
305 __test_fn_##grp##_##id(struct test_context *ctx); \
306 extern struct test_group __test_group_##grp; \
307 extern struct test __test_##grp##_##id; \
308 LINKER_SECTION_OBJECT(struct test, tests) \
309 __test_##grp##_##id = {.name = #id, \
310 .fname = __RELFILE__, \
311 .line = __LINE__, \
312 .func = __test_fn_##grp##_##id, \
313 .run_times = 1, \
314 .group = TEST_GROUP(grp), \
315 .enabled = TEST_STATE_SENTINEL, \
316 .inject_count = 0, \
317 .msg_cap = 0, \
318 .seed = 0, \
319 .print_logs = false, \
320 .tier = TEST_TIER_UNIT, \
321 .intensity = TEST_INTENSITY_SENTINEL, \
322 .intensity_desc = TEST_INTENSITY_DESC_SENTINEL, \
323 .min_cores = 0, \
324 .min_ram_mib = 0, \
325 .required_fs = FS_UNKNOWN, \
326 ##__VA_ARGS__}; \
327 cc_wno_override_init_end \
328 \
329 static struct test_verdict \
330 __test_fn_##grp##_##id(struct test_context *ctx cc_unused)
331
332#define TEST_GROUP_DEFINE(n, ...) \
333 cc_wno_override_init_start extern struct test_group __test_group_##n; \
334 LINKER_SECTION_OBJECT(struct test_group, test_groups) \
335 __test_group_##n = {.name = #n, \
336 .incremental = false, \
337 .exit_on_fail = false, \
338 .fname = __RELFILE__, \
339 .line = __LINE__, \
340 .enabled = TEST_STATE_SENTINEL, \
341 .smoke_enabled = TEST_STATE_SENTINEL, \
342 .unit_enabled = TEST_STATE_SENTINEL, \
343 .integration_enabled = TEST_STATE_SENTINEL, \
344 .default_intensity = TEST_INTENSITY_SENTINEL, \
345 .intensity_desc = TEST_INTENSITY_DESC_SENTINEL, \
346 ##__VA_ARGS__}; \
347 cc_wno_override_init_end
348
349#define TEST_SUCCESS ((struct test_verdict) {.result = TEST_RESULT_OK})
350#define TEST_FAIL(m) \
351 ((struct test_verdict) {.result = TEST_RESULT_FAILED, .msg = (m)})
352#define TEST_SKIP(r) \
353 ((struct test_verdict) {.result = TEST_RESULT_SKIPPED, .skip_reason = (r)})
354
355#define TEST_ARRAY_LEN(a) ct_array_size(a)
356
357#define test_log(lvl, fmt, ...) \
358 log(test_global.current_test->site, &test_global.current_test->handle, \
359 lvl, fmt, ##__VA_ARGS__)
360
361#define test_err(fmt, ...) test_log(LOG_ERROR, fmt, ##__VA_ARGS__)
362#define test_warn(fmt, ...) test_log(LOG_WARN, fmt, ##__VA_ARGS__)
363#define test_info(fmt, ...) test_log(LOG_INFO, fmt, ##__VA_ARGS__)
364#define test_debug(fmt, ...) test_log(LOG_DEBUG, fmt, ##__VA_ARGS__)
365#define test_trace(fmt, ...) test_log(LOG_TRACE, fmt, ##__VA_ARGS__)
366
367#include <test/assert.h>
368
369#define ABORT_IF_RAM_LOW() \
370 if (pmm_get_usable_ram() < MIB(8)) { \
371 test_info("RAM too low for test to continue!\n"); \
372 return TEST_SKIP(TEST_SKIP_RAM_LOW); \
373 }
374
375void tests_run(void);
376CMDLINE_EXTERN(test_root);
377
378extern struct test_globals test_global;
379extern const char *large_test_string;
380extern struct test_group test_group_orphan_parent;
381
382static inline size_t test_current_message_count(void) {
383 return log_site_message_count(site: test_global.current_test->site);
384}
385
386STRINGIFY_DECLARE_ENUM(test_tier) {
387 switch (stringify_in) {
388 case TEST_TIER_SMOKE: return "smoke";
389 case TEST_TIER_UNIT: return "unit";
390 case TEST_TIER_INTEGRATION: return "integration";
391 default: cc_unreachable();
392 }
393}
394
395STRINGIFY_DECLARE(test_tier_to_str_color, enum test_tier) {
396 switch (stringify_in) {
397 case TEST_TIER_SMOKE: return ANSI_GRAY "smoke";
398 case TEST_TIER_UNIT: return ANSI_MAGENTA "unit";
399 case TEST_TIER_INTEGRATION: return ANSI_YELLOW "integration";
400 default: cc_unreachable();
401 }
402}
403
404STRINGIFY_DECLARE_ENUM(test_result) {
405 switch (stringify_in) {
406 case TEST_RESULT_OK: return ANSI_BLUE "ok" ANSI_RESET;
407 case TEST_RESULT_FAILED: return ANSI_RED "failed" ANSI_RESET;
408 case TEST_RESULT_SKIPPED: return ANSI_GRAY "skipped" ANSI_RESET;
409 default: cc_unreachable();
410 }
411}
412
413STRINGIFY_DECLARE(test_result_to_str_plain, enum test_result) {
414 switch (stringify_in) {
415 case TEST_RESULT_OK: return "ok";
416 case TEST_RESULT_FAILED: return "failed";
417 case TEST_RESULT_SKIPPED: return "skipped";
418 default: return "unknown";
419 }
420}
421
422STRINGIFY_DECLARE_ENUM(test_skip_reason) {
423 switch (stringify_in) {
424 case TEST_SKIP_NONE: return "none";
425 case TEST_SKIP_RAM_LOW: return "RAM low";
426 case TEST_SKIP_UPSTREAM_FAILED: return "upstream failed";
427 case TEST_SKIP_DISABLED: return "disabled";
428 case TEST_SKIP_INSUFFICIENT_CORES: return "insufficient cores";
429 case TEST_SKIP_UNSUPPORTED_FS: return "unsupported fs";
430 default: cc_unreachable();
431 }
432}
433
434void test_verify_signatures(void);
435