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