1/* @title: Tests */
2#pragma once
3#include <cmdline.h>
4#include <compiler.h>
5#include <console/printf.h>
6#include <errno.h>
7#include <log.h>
8#include <math/fixed.h>
9#include <mem/alloc.h>
10#include <mem/pmm.h>
11#include <scaled_param.h>
12#include <stdbool.h>
13
14typedef void (*test_fn_t)(void);
15struct test_context;
16
17enum test_tier {
18 TEST_TIER_SMOKE,
19 TEST_TIER_UNIT,
20 TEST_TIER_INTEGRATION,
21 TEST_TIER_MAX,
22};
23
24enum test_options {
25 TEST_OPTION_NONE = 0, /* These are passed from cmdline */
26};
27
28/* These are immutable properties of the group */
29enum test_group_flags {
30 TEST_GROUP_FLAG_NONE = 0,
31 TEST_GROUP_FLAG_DEFAULT = 1, /* orphans get adopted by this group */
32};
33
34enum test_result {
35 TEST_RESULT_OK,
36 TEST_RESULT_FAILED,
37 TEST_RESULT_SKIPPED,
38 TEST_RESULT_MAX,
39};
40
41enum test_flags {
42 TEST_FLAG_NONE = 0,
43
44 /* Test flags will be structured in what they *enable* or honor/respect */
45 TEST_FLAG_HONORS_INTENSITY = 1,
46
47 /* If this is enabled, TEST_FLAG_HONORS_INTENSITY is auto-enabled */
48 TEST_FLAG_INHERITS_INTENSITY = 1 << 1,
49};
50
51enum test_skip_reason {
52 TEST_SKIP_NONE,
53 TEST_SKIP_RAM_LOW,
54 TEST_SKIP_UPSTREAM_FAILED, /* a prior tier in this group
55 failed */
56 TEST_SKIP_DISABLED, /* not the same as .enabled, just that the test
57 * won't be getting run for some reason */
58};
59
60enum test_state : uint8_t {
61 TEST_STATE_DISABLED = 0,
62 TEST_STATE_ENABLED = 1,
63 TEST_STATE_SENTINEL = 0xFF,
64};
65
66struct test_group {
67 const char *name;
68 const char *fname;
69 uint32_t line;
70 const enum test_group_flags flags;
71 struct test_verdict (*setup)(struct test_context *);
72 struct test_verdict (*teardown)(struct test_context *);
73
74 fx32_32_t default_intensity;
75
76 /* This is given to all children that TEST_FLAG_INHERITS_INTENSITY */
77 struct scaled_param intensity_desc;
78
79 enum test_state enabled;
80 union {
81 enum test_state tier_enabled[TEST_TIER_MAX];
82 struct {
83 enum test_state smoke_enabled;
84 enum test_state unit_enabled;
85 enum test_state integration_enabled;
86 };
87 };
88
89 bool incremental; /* First run smoke, then unit, then integration */
90 bool exit_on_fail; /* Different from incremental: exits after one failure,
91 * whereas incremental still completes the tier */
92
93 /* [tier][num] */
94 struct test **tests[TEST_TIER_MAX];
95 size_t num_tests[TEST_TIER_MAX];
96 size_t num_tests_enabled[TEST_TIER_MAX];
97};
98
99struct test {
100 const char *name;
101 const char *fname;
102 uint32_t line;
103
104 struct test_verdict (*func)(struct test_context *ctx);
105
106 const struct test_group *group;
107 enum test_tier tier;
108
109 size_t msg_cap;
110 enum test_flags flags;
111
112 time_ms_t duration_ms;
113
114 /* A lot of these have to be full bools for the cmdline parse */
115 bool print_logs; /* Prints logs *as* they are logged */
116 enum test_state enabled; /* If this is set to TEST_SENTINEL,
117 * it means we default */
118 bool keep_going; /* This basically means that with run_times,
119 * if it fails once, don't bother, and keep going
120 * until it runs run_times, however, it is overridden
121 * by exit_on_fail from the test_group */
122
123 fx32_32_t intensity; /* also [0, 1]. the point of this one is that
124 * it can be set by the command line, and the
125 * context reads and passes this into the test.
126 *
127 * the reason for this indirection is that the
128 * struct test_context that tests get might be
129 * changed by other subsystems that may not
130 * necessarily keep the same .intensity as the
131 * boot time command line option/default */
132
133 struct scaled_param intensity_desc;
134
135 uint64_t seed; /* Can be passed in through cmdline, NOTE:
136 * if a seed is set through the cmdline, and
137 * run_times > 1, this seed will be used in every
138 * run during run_times */
139
140 size_t run_times;
141
142 size_t inject_count;
143 struct inject_site *inject[];
144};
145
146/* The reason this exists is because the actual structures in struct
147 * test are not meant to be accessed from inside the test's func,
148 * and this is the way the test interacts with the variables.
149 *
150 * This might make one think "why not just interact with struct test?",
151 * and the answer is that struct test_context gives us extensibility,
152 * and allows a test to get run numerous different times with varied
153 * contexts, seeds, etc. without having to fiddle around with struct test */
154struct test_context {
155 struct log_site *site;
156 struct log_handle handle;
157
158 uint32_t soft_fails;
159
160 fx32_32_t intensity; /* Raw [0, 1] fixed-point intensity */
161 size_t intensity_val; /* Pre-computed scaled integer based on SCALE */
162 time_ms_t duration_ms;
163
164 uint64_t seed;
165};
166
167struct test_verdict {
168 enum test_result result;
169 enum test_skip_reason skip_reason;
170 const char *msg; /* optional, e.g. the failed assertion */
171};
172
173#define TEST_EXIT_OK 0
174#define TEST_EXIT_FAIL 1
175#define TEST_INTENSITY_SENTINEL ((fx32_32_t) - 1LL)
176#define TEST_INTENSITY_DEFAULT FX(0.5)
177
178/* Intended for use with TEST_FLAG_INHERITS_INTENSITY, so that tests can still
179 * set custom min, def, max outside of what they would inherit from group */
180#define TEST_INTENSITY(min, def, max) \
181 .flags = TEST_FLAG_INHERITS_INTENSITY, \
182 .intensity_desc = {.min_val = (min), .def_val = (def), .max_val = (max)}
183#define TEST_INTENSITY_DESC_SENTINEL \
184 (struct scaled_param) { \
185 .min_val = SIZE_MAX, .def_val = SIZE_MAX, .max_val = SIZE_MAX \
186 }
187
188struct test_globals {
189 fx32_32_t global_intensity;
190 size_t total_tests_enabled;
191 size_t results[TEST_TIER_MAX][TEST_RESULT_MAX];
192 size_t results_agg[TEST_RESULT_MAX];
193 struct test_context *current_test;
194 time_ms_t total_time;
195 bool show_output;
196 bool group_opt_in;
197 bool test_opt_in;
198 bool no_exit;
199 bool no_progress;
200};
201
202#define TEST_GROUP_NONE test_group_orphan_parent
203
204/* Goofy macros needed for the DECLARE macro */
205#define __test_group_TEST_GROUP_NONE test_group_orphan_parent
206#define __test_group_none test_group_orphan_parent
207#define __test_group_orphan test_group_orphan_parent
208#define __test_group_test_group_orphan_parent test_group_orphan_parent
209
210#define TEST_GROUP(name) &(__test_group_##name)
211#define TEST_GROUP_DEFINE(name) extern struct test_group __test_group_##name
212
213#define TEST(grp, id) __test_##grp##_##id
214#define TEST_DECLARE(grp, id, ...) \
215 static struct test_verdict __test_fn_##grp##_##id( \
216 struct test_context *ctx); \
217 extern struct test_group __test_group_##grp; \
218 extern struct test __test_##grp##_##id; \
219 LINKER_SECTION_OBJECT(struct test, tests) \
220 __test_##grp##_##id = {.name = #id, \
221 .fname = __RELFILE__, \
222 .line = __LINE__, \
223 .func = __test_fn_##grp##_##id, \
224 .run_times = 1, \
225 .group = TEST_GROUP(grp), \
226 .enabled = TEST_STATE_SENTINEL, \
227 .inject_count = 0, \
228 .msg_cap = 0, \
229 .seed = 0, \
230 .print_logs = false, \
231 .tier = TEST_TIER_UNIT, \
232 .intensity = TEST_INTENSITY_SENTINEL, \
233 .intensity_desc = TEST_INTENSITY_DESC_SENTINEL, \
234 ##__VA_ARGS__}; \
235 \
236 static struct test_verdict __test_fn_##grp##_##id( \
237 struct test_context *ctx __unused)
238
239#define TEST_DECLARE_SMOKE(grp, id, ...) \
240 TEST_DECLARE(grp, id, .tier = TEST_TIER_SMOKE, ##__VA_ARGS__)
241#define TEST_DECLARE_UNIT(grp, id, ...) \
242 TEST_DECLARE(grp, id, .tier = TEST_TIER_UNIT, ##__VA_ARGS__)
243#define TEST_DECLARE_INTEGRATION(grp, id, ...) \
244 TEST_DECLARE(grp, id, .tier = TEST_TIER_INTEGRATION, ##__VA_ARGS__)
245
246#define TEST_GROUP_DECLARE(n, ...) \
247 extern struct test_group __test_group_##n; \
248 LINKER_SECTION_OBJECT(struct test_group, test_groups) \
249 __test_group_##n = {.name = #n, \
250 .incremental = false, \
251 .exit_on_fail = false, \
252 .fname = __RELFILE__, \
253 .line = __LINE__, \
254 .enabled = TEST_STATE_SENTINEL, \
255 .smoke_enabled = TEST_STATE_SENTINEL, \
256 .unit_enabled = TEST_STATE_SENTINEL, \
257 .integration_enabled = TEST_STATE_SENTINEL, \
258 .default_intensity = TEST_INTENSITY_SENTINEL, \
259 .intensity_desc = TEST_INTENSITY_DESC_SENTINEL, \
260 ##__VA_ARGS__}
261
262/* 1D piecewise-log */
263#define TEST_INTENSITY_LOG(min, def, max, unit_str) \
264 .flags = TEST_FLAG_HONORS_INTENSITY, .intensity_desc = { \
265 .curve = SCALE_PIECEWISE_LOG, \
266 .min_val = (min), \
267 .def_val = (def), \
268 .max_val = (max), \
269 .unit = (unit_str), \
270 }
271
272/* Linear scaling */
273#define TEST_INTENSITY_LINEAR(min, def, max, unit_str) \
274 .flags = TEST_FLAG_HONORS_INTENSITY, .intensity_desc = { \
275 .curve = SCALE_PIECEWISE_LINEAR, \
276 .min_val = (min), \
277 .def_val = (def), \
278 .max_val = (max), \
279 .unit = (unit_str), \
280 }
281
282/* Core-scaled thread counts (threads = base * core_count) */
283#define TEST_INTENSITY_CORES(min_per_core, def_per_core, max_per_core, \
284 unit_str) \
285 .flags = TEST_FLAG_HONORS_INTENSITY, .intensity_desc = { \
286 .curve = SCALE_CORE_MULTIPLIER, \
287 .min_val = (min_per_core), \
288 .def_val = (def_per_core), \
289 .max_val = (max_per_core), \
290 .unit = (unit_str), \
291 }
292
293/* set scale with min and max */
294#define TEST_INTENSITY_CUSTOM_PRINT(scale, min, def, max, unit_str, print_fn) \
295 .flags = TEST_FLAG_HONORS_INTENSITY, .intensity_desc = { \
296 .curve = scale, \
297 .min_val = (min), \
298 .def_val = (def), \
299 .max_val = (max), \
300 .unit = (unit_str), \
301 .custom_print = (print_fn), \
302 }
303
304/* Entirely custom */
305#define TEST_INTENSITY_CUSTOM(scale_fn, print_fn) \
306 .flags = TEST_FLAG_HONORS_INTENSITY, .intensity_desc = { \
307 .curve = SCALE_CUSTOM, \
308 .custom_scale = (scale_fn), \
309 .custom_print = (print_fn), \
310 }
311
312#define TEST_SUCCESS ((struct test_verdict) {.result = TEST_RESULT_OK})
313#define TEST_FAIL(m) \
314 ((struct test_verdict) {.result = TEST_RESULT_FAILED, .msg = (m)})
315#define TEST_SKIP(r) \
316 ((struct test_verdict) {.result = TEST_RESULT_SKIPPED, .skip_reason = (r)})
317
318#define TEST_ARRAY_LEN(a) (sizeof(a) / sizeof((a)[0]))
319
320#define test_log(lvl, fmt, ...) \
321 log(test_global.current_test->site, &test_global.current_test->handle, \
322 lvl, fmt, ##__VA_ARGS__)
323
324#define test_err(fmt, ...) test_log(LOG_ERROR, fmt, ##__VA_ARGS__)
325#define test_warn(fmt, ...) test_log(LOG_WARN, fmt, ##__VA_ARGS__)
326#define test_info(fmt, ...) test_log(LOG_INFO, fmt, ##__VA_ARGS__)
327#define test_debug(fmt, ...) test_log(LOG_DEBUG, fmt, ##__VA_ARGS__)
328#define test_trace(fmt, ...) test_log(LOG_TRACE, fmt, ##__VA_ARGS__)
329
330#include <test/assert.h>
331
332#define ABORT_IF_RAM_LOW() \
333 if (pmm_get_usable_ram() < 1024 * 1024 * 8) { \
334 test_info("RAM too low for test to continue!\n"); \
335 return TEST_SKIP(TEST_SKIP_RAM_LOW); \
336 }
337
338void tests_run(void);
339CMDLINE_DEFINE(test_root);
340
341extern struct test_globals test_global;
342extern const char *large_test_string;
343extern struct test_group test_group_orphan_parent;
344
345static inline size_t test_current_message_count(void) {
346 return log_site_message_count(site: test_global.current_test->site);
347}
348
349static inline const char *test_tier_to_str(enum test_tier tier) {
350 switch (tier) {
351 case TEST_TIER_SMOKE: return "smoke";
352 case TEST_TIER_UNIT: return "unit";
353 case TEST_TIER_INTEGRATION: return "integration";
354 default: unreachable();
355 }
356}
357
358static inline const char *test_tier_to_str_color(enum test_tier tier) {
359 switch (tier) {
360 case TEST_TIER_SMOKE: return ANSI_GRAY "smoke";
361 case TEST_TIER_UNIT: return ANSI_MAGENTA "unit";
362 case TEST_TIER_INTEGRATION: return ANSI_YELLOW "integration";
363 default: unreachable();
364 }
365}
366
367static inline const char *test_result_to_str(enum test_result result) {
368 switch (result) {
369 case TEST_RESULT_OK: return ANSI_BLUE "ok" ANSI_RESET;
370 case TEST_RESULT_FAILED: return ANSI_RED "failed" ANSI_RESET;
371 case TEST_RESULT_SKIPPED: return ANSI_GRAY "skipped" ANSI_RESET;
372 default: unreachable();
373 }
374}
375
376static inline const char *test_result_plain(enum test_result result) {
377 switch (result) {
378 case TEST_RESULT_OK: return "ok";
379 case TEST_RESULT_FAILED: return "failed";
380 case TEST_RESULT_SKIPPED: return "skipped";
381 default: return "unknown";
382 }
383}
384
385static inline const char *
386test_skip_reason_to_str(enum test_skip_reason reason) {
387 switch (reason) {
388 case TEST_SKIP_NONE: return "none";
389 case TEST_SKIP_RAM_LOW: return "RAM low";
390 case TEST_SKIP_UPSTREAM_FAILED: return "upstream failed";
391 case TEST_SKIP_DISABLED: return "disabled";
392 default: unreachable();
393 }
394}
395