Bitcoin Core 31.99.0
P2P Digital Currency
unit_test.c
Go to the documentation of this file.
1/***********************************************************************
2 * Distributed under the MIT software license, see the accompanying *
3 * file COPYING or https://www.opensource.org/licenses/mit-license.php.*
4 ***********************************************************************/
5
6#include <stdio.h>
7#include <stdlib.h>
8#include <string.h>
9
10#if defined(SUPPORTS_CONCURRENCY)
11#include <sys/types.h>
12#include <sys/wait.h>
13#include <unistd.h>
14#endif
15
16#include "unit_test.h"
17#include "testrand.h"
18#include "tests_common.h"
19
20#if defined(__GNUC__)
21# pragma GCC diagnostic push
22# pragma GCC diagnostic warning "-Wunused-function"
23#endif
24
25#define UNUSED(x) (void)(x)
26
27/* Number of times certain tests will run */
28int COUNT = 16;
29
30static int parse_jobs_count(const char* key, const char* value, struct tf_framework* tf);
31static int parse_iterations(const char* key, const char* value, struct tf_framework* tf);
32static int parse_seed(const char* key, const char* value, struct tf_framework* tf);
33static int parse_target(const char* key, const char* value, struct tf_framework* tf);
34static int parse_logging(const char* key, const char* value, struct tf_framework* tf);
35
36/* Mapping table: key -> handler */
37typedef int (*ArgHandler)(const char* key, const char* value, struct tf_framework* tf);
38struct ArgMap {
39 const char* key;
41};
42
43/*
44 * Main entry point for handling command-line arguments.
45 *
46 * Developers should extend this map whenever new command-line
47 * options are introduced. Each new argument should be validated,
48 * converted to the appropriate type, and stored in 'tf->args' struct.
49 */
50static struct ArgMap arg_map[] = {
51 { "t", parse_target }, { "target", parse_target },
52 { "j", parse_jobs_count }, { "jobs", parse_jobs_count },
53 { "i", parse_iterations }, { "iterations", parse_iterations },
54 { "seed", parse_seed },
55 { "log", parse_logging },
56 { NULL, NULL } /* sentinel */
57};
58
59/* Display options that are not printed elsewhere */
60static void print_args(const struct tf_args* args) {
61 printf("iterations = %d\n", COUNT);
62 printf("jobs = %d. %s execution.\n", args->num_processes, args->num_processes > 1 ? "Parallel" : "Sequential");
63}
64
65/* Main entry point for reading environment variables */
66static int read_env(struct tf_framework* tf) {
67 const char* env_iter = getenv("SECP256K1_TEST_ITERS");
68 if (env_iter && strlen(env_iter) > 0) {
69 return parse_iterations("i", env_iter, tf);
70 }
71 return 0;
72}
73
74static int parse_arg(const char* key, const char* value, struct tf_framework* tf) {
75 int i;
76 for (i = 0; arg_map[i].key != NULL; i++) {
77 if (strcmp(key, arg_map[i].key) == 0) {
78 return arg_map[i].handler(key, value, tf);
79 }
80 }
81 /* Unknown key: report just so typos don't silently pass. */
82 fprintf(stderr, "Unknown argument '-%s=%s'\n", key, value);
83 return -1;
84}
85
86static void help(void) {
87 printf("Usage: ./tests [options]\n\n");
88 printf("Run the test suite for the project with optional configuration.\n\n");
89 printf("Options:\n");
90 printf(" --help, -h Show this help message\n");
91 printf(" --list_tests, -l Display list of all available tests and modules\n");
92 printf(" --jobs=<num>, -j=<num> Number of parallel worker processes (default: 0 = sequential)\n");
93 printf(" --iterations=<num>, -i=<num> Number of iterations for each test (default: 16)\n");
94 printf(" --seed=<hex> Set a specific RNG seed (default: random)\n");
95 printf(" --target=<test name>, -t=<name> Run a specific test (can be provided multiple times)\n");
96 printf(" --target=<module name>, -t=<module> Run all tests within a specific module (can be provided multiple times)\n");
97 printf(" --log=<0|1> Enable or disable test execution logging (default: 0 = disabled)\n");
98 printf("\n");
99 printf("Notes:\n");
100 printf(" - All arguments must be provided in the form '--key=value', '-key=value' or '-k=value'.\n");
101 printf(" - Single or double dashes are allowed for multi character options.\n");
102 printf(" - Unknown arguments are reported but ignored.\n");
103 printf(" - Sequential execution occurs if -jobs=0 or unspecified.\n");
104 printf(" - Iterations and seed can also be passed as positional arguments before any other argument for backward compatibility.\n");
105}
106
107/* Print all tests in registry */
108static void print_test_list(struct tf_framework* tf) {
109 int m, t, total = 0;
110 printf("\nAvailable tests (%d modules):\n", tf->num_modules);
111 printf("========================================\n");
112 for (m = 0; m < tf->num_modules; m++) {
113 const struct tf_test_module* mod = &tf->registry_modules[m];
114 printf("Module: %s (%d tests)\n", mod->name, mod->size);
115 for (t = 0; t < mod->size; t++) {
116 printf("\t[%3d] %s\n", total + 1, mod->data[t].name);
117 total++;
118 }
119 printf("----------------------------------------\n");
120 }
121 printf("\nRun specific module: ./tests -t=<module_name>\n");
122 printf("Run specific test: ./tests -t=<test_name>\n\n");
123}
124
125static int parse_jobs_count(const char* key, const char* value, struct tf_framework* tf) {
126 char* ptr_val;
127 long val = strtol(value, &ptr_val, 10); /* base 10 */
128 if (*ptr_val != '\0') {
129 fprintf(stderr, "Invalid number for -%s=%s\n", key, value);
130 return -1;
131 }
132 if (val < 0 || val > MAX_SUBPROCESSES) {
133 fprintf(stderr, "Arg '-%s' out of range: '%ld'. Range: 0..%d\n", key, val, MAX_SUBPROCESSES);
134 return -1;
135 }
136 tf->args.num_processes = (int) val;
137 return 0;
138}
139
140static int parse_iterations(const char* key, const char* value, struct tf_framework* tf) {
141 UNUSED(key); UNUSED(tf);
142 if (!value) return 0;
143 COUNT = (int) strtol(value, NULL, 0);
144 if (COUNT <= 0) {
145 fputs("An iteration count of 0 or less is not allowed.\n", stderr);
146 return -1;
147 }
148 return 0;
149}
150
151static int parse_seed(const char* key, const char* value, struct tf_framework* tf) {
152 UNUSED(key);
153 tf->args.custom_seed = (!value || strcmp(value, "NULL") == 0) ? NULL : value;
154 return 0;
155}
156
157static int parse_logging(const char* key, const char* value, struct tf_framework* tf) {
158 UNUSED(key);
159 tf->args.logging = value && strcmp(value, "1") == 0;
160 return 0;
161}
162
163/* Strip up to two leading dashes */
164static const char* normalize_key(const char* arg, const char** err_msg) {
165 const char* key;
166 if (!arg || arg[0] != '-') {
167 *err_msg = "missing initial dash";
168 return NULL;
169 }
170 /* single-dash short option */
171 if (arg[1] != '-') return arg + 1;
172
173 /* double-dash checks now */
174 if (arg[2] == '\0') {
175 *err_msg = "missing option name after double dash";
176 return NULL;
177 }
178
179 if (arg[2] == '-') {
180 *err_msg = "too many leading dashes";
181 return NULL;
182 }
183
184 key = arg + 2;
185 if (key[1] == '\0') {
186 *err_msg = "short option cannot use double dash";
187 return NULL;
188 }
189 return key;
190}
191
192static int parse_target(const char* key, const char* value, struct tf_framework* tf) {
193 int group, idx;
194 const struct tf_test_entry* entry;
195 UNUSED(key);
196 /* Find test index in the registry */
197 for (group = 0; group < tf->num_modules; group++) {
198 const struct tf_test_module* module = &tf->registry_modules[group];
199 int add_all = strcmp(value, module->name) == 0; /* select all from module */
200 for (idx = 0; idx < module->size; idx++) {
201 entry = &module->data[idx];
202 if (add_all || strcmp(value, entry->name) == 0) {
203 if (tf->args.targets.size >= MAX_ARGS) {
204 fprintf(stderr, "Too many -target args (max: %d)\n", MAX_ARGS);
205 return -1;
206 }
207 tf->args.targets.slots[tf->args.targets.size++] = entry;
208 /* Matched a single test, we're done */
209 if (!add_all) return 0;
210 }
211 }
212 /* If add_all was true, we added all tests in the module, so return */
213 if (add_all) return 0;
214 }
215 fprintf(stderr, "Error: target '%s' not found (missing or module disabled).\n"
216 "Run program with -list_tests option to display available tests and modules.\n", value);
217 return -1;
218}
219
220/* Read args: all must be in the form -key=value, --key=value or -key=value */
221static int read_args(int argc, char** argv, int start, struct tf_framework* tf) {
222 int i;
223 const char* key;
224 const char* value;
225 char* eq;
226 const char* err_msg = "unknown error";
227 for (i = start; i < argc; i++) {
228 char* raw_arg = argv[i];
229 if (!raw_arg || raw_arg[0] != '-') {
230 fprintf(stderr, "Invalid arg '%s': must start with '-'\n", raw_arg ? raw_arg : "(null)");
231 return -1;
232 }
233
234 key = normalize_key(raw_arg, &err_msg);
235 if (!key || *key == '\0') {
236 fprintf(stderr, "Invalid arg '%s': %s. Must be -k=value or --key=value\n", raw_arg, err_msg);
237 return -1;
238 }
239
240 eq = strchr(raw_arg, '=');
241 if (!eq || eq == raw_arg + 1) {
242 /* Allowed options without value */
243 if (strcmp(key, "h") == 0 || strcmp(key, "help") == 0) {
244 tf->args.help = 1;
245 return 0;
246 }
247 if (strcmp(key, "l") == 0 || strcmp(key, "list_tests") == 0) {
248 tf->args.list_tests = 1;
249 return 0;
250 }
251 fprintf(stderr, "Invalid arg '%s': must be -k=value or --key=value\n", raw_arg);
252 return -1;
253 }
254
255 *eq = '\0'; /* split key and value */
256 value = eq + 1;
257 if (!value || *value == '\0') { /* value is empty */
258 fprintf(stderr, "Invalid arg '%s': value cannot be empty\n", raw_arg);
259 return -1;
260 }
261
262 if (parse_arg(key, value, tf) != 0) return -1;
263 }
264 return 0;
265}
266
267static void run_test_log(const struct tf_test_entry* t) {
268 int64_t start_time = gettime_i64();
269 printf("Running %s..\n", t->name);
270 t->func();
271 printf("Test %s PASSED (%.3f sec)\n", t->name, (double)(gettime_i64() - start_time) / 1000000);
272}
273
274static void run_test(const struct tf_test_entry* t) { t->func(); }
275
276/* Process tests in sequential order */
277static int run_sequential(struct tf_framework* tf) {
278 int it;
279 for (it = 0; it < tf->args.targets.size; it++) {
280 tf->fn_run_test(tf->args.targets.slots[it]);
281 }
282 return EXIT_SUCCESS;
283}
284
285#if defined(SUPPORTS_CONCURRENCY)
286static const int MAX_TARGETS = 255;
287
288/* Process tests in parallel */
289static int run_concurrent(struct tf_framework* tf) {
290 /* Sub-processes info */
291 pid_t workers[MAX_SUBPROCESSES];
292 int pipefd[2];
293 int status = EXIT_SUCCESS;
294 int it; /* loop iterator */
295 unsigned char idx; /* test index */
296
297 if (tf->args.targets.size > MAX_TARGETS) {
298 fprintf(stderr, "Internal Error: the number of targets (%d) exceeds the maximum supported (%d). "
299 "If you need more, extend 'run_concurrent()' to handle additional targets.\n",
300 tf->args.targets.size, MAX_TARGETS);
301 exit(EXIT_FAILURE);
302 }
303
304
305 if (pipe(pipefd) != 0) {
306 perror("Error during pipe setup");
307 return EXIT_FAILURE;
308 }
309
310 /* Launch worker processes */
311 for (it = 0; it < tf->args.num_processes; it++) {
312 pid_t pid = fork();
313 if (pid < 0) {
314 perror("Error during process fork");
315 return EXIT_FAILURE;
316 }
317 if (pid == 0) {
318 /* Child worker: read jobs from the shared pipe */
319 close(pipefd[1]); /* children never write */
320 while (read(pipefd[0], &idx, sizeof(idx)) == sizeof(idx)) {
321 tf->fn_run_test(tf->args.targets.slots[(int)idx]);
322 }
323 _exit(EXIT_SUCCESS); /* finish child process */
324 } else {
325 /* Parent: save worker pid */
326 workers[it] = pid;
327 }
328 }
329
330 /* Parent: write all tasks into the pipe */
331 close(pipefd[0]); /* close read end */
332 for (it = 0; it < tf->args.targets.size; it++) {
333 idx = (unsigned char)it;
334 if (write(pipefd[1], &idx, sizeof(idx)) == -1) {
335 perror("Error during workload distribution");
336 close(pipefd[1]);
337 return EXIT_FAILURE;
338 }
339 }
340 /* Close write end to signal EOF */
341 close(pipefd[1]);
342 /* Wait for all workers */
343 for (it = 0; it < tf->args.num_processes; it++) {
344 int ret = 0;
345 if (waitpid(workers[it], &ret, 0) == -1 || ret != 0) {
346 status = EXIT_FAILURE;
347 }
348 }
349
350 return status;
351}
352#endif
353
354static int tf_init(struct tf_framework* tf, int argc, char** argv)
355{
356 /* Caller must set the registry and its size before calling tf_init */
357 if (tf->registry_modules == NULL || tf->num_modules <= 0) {
358 fprintf(stderr, "Error: tests registry not provided or empty\n");
359 return EXIT_FAILURE;
360 }
361
362 /* Initialize command-line options */
363 tf->args.num_processes = 0;
364 tf->args.custom_seed = NULL;
365 tf->args.help = 0;
366 tf->args.targets.size = 0;
367 tf->args.list_tests = 0;
368 tf->args.logging = 0;
369
370 /* Disable buffering for stdout to improve reliability of getting
371 * diagnostic information. Happens right at the start of main because
372 * setbuf must be used before any other operation on the stream. */
373 setbuf(stdout, NULL);
374 /* Also disable buffering for stderr because it's not guaranteed that it's
375 * unbuffered on all systems. */
376 setbuf(stderr, NULL);
377
378 /* Parse env args */
379 if (read_env(tf) != 0) return EXIT_FAILURE;
380
381 /* Parse command-line args */
382 if (argc > 1) {
383 int named_arg_start = 1; /* index to begin processing named arguments */
384 if (argc - 1 > MAX_ARGS) { /* first arg is always the binary path */
385 fprintf(stderr, "Too many command-line arguments (max: %d)\n", MAX_ARGS);
386 return EXIT_FAILURE;
387 }
388
389 /* Compatibility Note: The first two args were the number of iterations and the seed. */
390 /* If provided, parse them and adjust the starting index for named arguments accordingly. */
391 if (argv[1][0] != '-') {
392 int has_seed = argc > 2 && argv[2][0] != '-';
393 if (parse_iterations("i", argv[1], tf) != 0) return EXIT_FAILURE;
394 if (has_seed) parse_seed("seed", argv[2], tf);
395 named_arg_start = has_seed ? 3 : 2;
396 }
397 if (read_args(argc, argv, named_arg_start, tf) != 0) {
398 return EXIT_FAILURE;
399 }
400
401 if (tf->args.help) {
402 help();
403 exit(EXIT_SUCCESS);
404 }
405
406 if (tf->args.list_tests) {
407 print_test_list(tf);
408 exit(EXIT_SUCCESS);
409 }
410 }
411
413 return EXIT_SUCCESS;
414}
415
416static int tf_run(struct tf_framework* tf) {
417 /* Process exit status */
418 int status;
419 /* Whether to run all tests */
420 int run_all;
421 /* Loop iterator */
422 int it;
423 /* Initial test time */
424 int64_t start_time = gettime_i64();
425 /* Verify 'tf_init' has been called */
426 if (!tf->fn_run_test) {
427 fprintf(stderr, "Error: No test runner set. You must call 'tf_init' first to initialize the framework "
428 "or manually assign 'fn_run_test' before calling 'tf_run'.\n");
429 return EXIT_FAILURE;
430 }
431
432 /* Populate targets with all tests if none were explicitly specified */
433 run_all = tf->args.targets.size == 0;
434 if (run_all) {
435 int group, idx;
436 for (group = 0; group < tf->num_modules; group++) {
437 const struct tf_test_module* module = &tf->registry_modules[group];
438 for (idx = 0; idx < module->size; idx++) {
439 if (tf->args.targets.size >= MAX_ARGS) {
440 fprintf(stderr, "Internal Error: Number of tests (%d) exceeds MAX_ARGS (%d). "
441 "Increase MAX_ARGS to accommodate all tests.\n", tf->args.targets.size, MAX_ARGS);
442 return EXIT_FAILURE;
443 }
444 tf->args.targets.slots[tf->args.targets.size++] = &module->data[idx];
445 }
446 }
447 }
448
449 if (!tf->args.logging) printf("Tests running silently. Use '-log=1' to enable detailed logging\n");
450
451 /* Log configuration */
452 print_args(&tf->args);
453
454 /* Run test RNG tests (must run before we really initialize the test RNG) */
455 /* Note: currently, these tests are executed sequentially because there */
456 /* is really only one test. */
457 for (it = 0; tf->registry_no_rng && it < tf->registry_no_rng->size; it++) {
458 if (run_all) { /* future: support filtering */
459 tf->fn_run_test(&tf->registry_no_rng->data[it]);
460 }
461 }
462
463 /* Initialize test RNG and library contexts */
465 if (tf->fn_setup && tf->fn_setup() != 0) return EXIT_FAILURE;
466
467 /* Check whether to process tests sequentially or concurrently */
468 if (tf->args.num_processes <= 1) {
469 status = run_sequential(tf);
470 } else {
471#if defined(SUPPORTS_CONCURRENCY)
472 status = run_concurrent(tf);
473#else
474 fputs("Parallel execution not supported on your system. Running sequentially...\n", stderr);
475 status = run_sequential(tf);
476#endif
477 }
478
479 /* Print accumulated time */
480 printf("Total execution time: %.3f seconds\n", (double)(gettime_i64() - start_time) / 1000000);
481 if (tf->fn_teardown && tf->fn_teardown() != 0) return EXIT_FAILURE;
482
483 return status;
484}
485
486#if defined(__GNUC__)
487# pragma GCC diagnostic pop
488#endif
int ret
return EXIT_SUCCESS
ArgsManager & args
Definition: bitcoind.cpp:280
void printf(FormatStringCheck< sizeof...(Args)> fmt, const Args &... args)
Format list of arguments to std::cout, according to the given format string.
Definition: tinyformat.h:1096
ArgHandler handler
Definition: unit_test.c:40
const char * key
Definition: unit_test.c:39
int help
Definition: unit_test.h:82
int list_tests
Definition: unit_test.h:84
struct tf_targets targets
Definition: unit_test.h:86
int num_processes
Definition: unit_test.h:78
const char * custom_seed
Definition: unit_test.h:80
int logging
Definition: unit_test.h:88
teardown_fn fn_teardown
Definition: unit_test.h:106
run_test_fn fn_run_test
Definition: unit_test.h:108
setup_ctx_fn fn_setup
Definition: unit_test.h:105
const struct tf_test_module * registry_no_rng
Definition: unit_test.h:103
const struct tf_test_module * registry_modules
Definition: unit_test.h:99
struct tf_args args
Definition: unit_test.h:97
int num_modules
Definition: unit_test.h:101
int size
Definition: unit_test.h:72
const struct tf_test_entry * slots[MAX_ARGS]
Definition: unit_test.h:70
Definition: unit_test.h:53
const char * name
Definition: unit_test.h:54
const char * name
Definition: unit_test.h:59
const struct tf_test_entry * data
Definition: unit_test.h:60
static void testrand_init(const char *hexseed)
Initialize the test RNG using (hex encoded) array up to 16 bytes, or randomly if hexseed is NULL.
static int64_t gettime_i64(void)
Definition: tests_common.h:26
static struct ArgMap arg_map[]
Definition: unit_test.c:50
int COUNT
Definition: unit_test.c:28
static const char * normalize_key(const char *arg, const char **err_msg)
Definition: unit_test.c:164
static void run_test_log(const struct tf_test_entry *t)
Definition: unit_test.c:267
static int parse_arg(const char *key, const char *value, struct tf_framework *tf)
Definition: unit_test.c:74
static int parse_seed(const char *key, const char *value, struct tf_framework *tf)
Definition: unit_test.c:151
static void run_test(const struct tf_test_entry *t)
Definition: unit_test.c:274
#define UNUSED(x)
Definition: unit_test.c:25
static int tf_init(struct tf_framework *tf, int argc, char **argv)
Definition: unit_test.c:354
static int parse_iterations(const char *key, const char *value, struct tf_framework *tf)
Definition: unit_test.c:140
static int parse_logging(const char *key, const char *value, struct tf_framework *tf)
Definition: unit_test.c:157
int(* ArgHandler)(const char *key, const char *value, struct tf_framework *tf)
Definition: unit_test.c:37
static int run_sequential(struct tf_framework *tf)
Definition: unit_test.c:277
static int parse_target(const char *key, const char *value, struct tf_framework *tf)
Definition: unit_test.c:192
static void help(void)
Definition: unit_test.c:86
static int read_env(struct tf_framework *tf)
Definition: unit_test.c:66
static int read_args(int argc, char **argv, int start, struct tf_framework *tf)
Definition: unit_test.c:221
static void print_test_list(struct tf_framework *tf)
Definition: unit_test.c:108
static int tf_run(struct tf_framework *tf)
Definition: unit_test.c:416
static int parse_jobs_count(const char *key, const char *value, struct tf_framework *tf)
Definition: unit_test.c:125
static void print_args(const struct tf_args *args)
Definition: unit_test.c:60
#define MAX_ARGS
Definition: unit_test.h:18
#define MAX_SUBPROCESSES
Definition: unit_test.h:20