2025-09-03 10:59:37 -04:00
|
|
|
/***********************************************************************
|
|
|
|
|
* Distributed under the MIT software license, see the accompanying *
|
|
|
|
|
* file COPYING or https://www.opensource.org/licenses/mit-license.php.*
|
|
|
|
|
***********************************************************************/
|
|
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
|
|
#if defined(SUPPORTS_CONCURRENCY)
|
|
|
|
|
#include <sys/types.h>
|
|
|
|
|
#include <sys/wait.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
#include "unit_test.h"
|
|
|
|
|
#include "testrand.h"
|
|
|
|
|
#include "tests_common.h"
|
|
|
|
|
|
2026-06-12 19:10:33 +05:30
|
|
|
#if defined(__GNUC__)
|
|
|
|
|
# pragma GCC diagnostic push
|
|
|
|
|
# pragma GCC diagnostic warning "-Wunused-function"
|
|
|
|
|
#endif
|
|
|
|
|
|
2025-09-03 10:59:37 -04:00
|
|
|
#define UNUSED(x) (void)(x)
|
|
|
|
|
|
|
|
|
|
/* Number of times certain tests will run */
|
|
|
|
|
int COUNT = 16;
|
|
|
|
|
|
|
|
|
|
static int parse_jobs_count(const char* key, const char* value, struct tf_framework* tf);
|
|
|
|
|
static int parse_iterations(const char* key, const char* value, struct tf_framework* tf);
|
|
|
|
|
static int parse_seed(const char* key, const char* value, struct tf_framework* tf);
|
2025-09-03 17:18:23 -04:00
|
|
|
static int parse_target(const char* key, const char* value, struct tf_framework* tf);
|
2025-09-09 11:02:05 -04:00
|
|
|
static int parse_logging(const char* key, const char* value, struct tf_framework* tf);
|
2025-09-03 10:59:37 -04:00
|
|
|
|
|
|
|
|
/* Mapping table: key -> handler */
|
|
|
|
|
typedef int (*ArgHandler)(const char* key, const char* value, struct tf_framework* tf);
|
|
|
|
|
struct ArgMap {
|
|
|
|
|
const char* key;
|
|
|
|
|
ArgHandler handler;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Main entry point for handling command-line arguments.
|
|
|
|
|
*
|
|
|
|
|
* Developers should extend this map whenever new command-line
|
|
|
|
|
* options are introduced. Each new argument should be validated,
|
|
|
|
|
* converted to the appropriate type, and stored in 'tf->args' struct.
|
|
|
|
|
*/
|
|
|
|
|
static struct ArgMap arg_map[] = {
|
2025-09-03 17:18:23 -04:00
|
|
|
{ "t", parse_target }, { "target", parse_target },
|
2025-09-03 10:59:37 -04:00
|
|
|
{ "j", parse_jobs_count }, { "jobs", parse_jobs_count },
|
|
|
|
|
{ "i", parse_iterations }, { "iterations", parse_iterations },
|
|
|
|
|
{ "seed", parse_seed },
|
2025-09-09 11:02:05 -04:00
|
|
|
{ "log", parse_logging },
|
2025-09-03 10:59:37 -04:00
|
|
|
{ NULL, NULL } /* sentinel */
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* Display options that are not printed elsewhere */
|
|
|
|
|
static void print_args(const struct tf_args* args) {
|
|
|
|
|
printf("iterations = %d\n", COUNT);
|
|
|
|
|
printf("jobs = %d. %s execution.\n", args->num_processes, args->num_processes > 1 ? "Parallel" : "Sequential");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Main entry point for reading environment variables */
|
|
|
|
|
static int read_env(struct tf_framework* tf) {
|
|
|
|
|
const char* env_iter = getenv("SECP256K1_TEST_ITERS");
|
|
|
|
|
if (env_iter && strlen(env_iter) > 0) {
|
|
|
|
|
return parse_iterations("i", env_iter, tf);
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int parse_arg(const char* key, const char* value, struct tf_framework* tf) {
|
|
|
|
|
int i;
|
|
|
|
|
for (i = 0; arg_map[i].key != NULL; i++) {
|
|
|
|
|
if (strcmp(key, arg_map[i].key) == 0) {
|
|
|
|
|
return arg_map[i].handler(key, value, tf);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/* Unknown key: report just so typos don't silently pass. */
|
|
|
|
|
fprintf(stderr, "Unknown argument '-%s=%s'\n", key, value);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-03 15:27:23 -04:00
|
|
|
static void help(void) {
|
|
|
|
|
printf("Usage: ./tests [options]\n\n");
|
|
|
|
|
printf("Run the test suite for the project with optional configuration.\n\n");
|
|
|
|
|
printf("Options:\n");
|
|
|
|
|
printf(" --help, -h Show this help message\n");
|
2025-09-08 14:17:12 -04:00
|
|
|
printf(" --list_tests, -l Display list of all available tests and modules\n");
|
2025-09-03 15:27:23 -04:00
|
|
|
printf(" --jobs=<num>, -j=<num> Number of parallel worker processes (default: 0 = sequential)\n");
|
|
|
|
|
printf(" --iterations=<num>, -i=<num> Number of iterations for each test (default: 16)\n");
|
|
|
|
|
printf(" --seed=<hex> Set a specific RNG seed (default: random)\n");
|
2025-09-03 17:18:23 -04:00
|
|
|
printf(" --target=<test name>, -t=<name> Run a specific test (can be provided multiple times)\n");
|
|
|
|
|
printf(" --target=<module name>, -t=<module> Run all tests within a specific module (can be provided multiple times)\n");
|
2025-09-09 11:02:05 -04:00
|
|
|
printf(" --log=<0|1> Enable or disable test execution logging (default: 0 = disabled)\n");
|
2025-09-03 15:27:23 -04:00
|
|
|
printf("\n");
|
|
|
|
|
printf("Notes:\n");
|
|
|
|
|
printf(" - All arguments must be provided in the form '--key=value', '-key=value' or '-k=value'.\n");
|
|
|
|
|
printf(" - Single or double dashes are allowed for multi character options.\n");
|
|
|
|
|
printf(" - Unknown arguments are reported but ignored.\n");
|
|
|
|
|
printf(" - Sequential execution occurs if -jobs=0 or unspecified.\n");
|
|
|
|
|
printf(" - Iterations and seed can also be passed as positional arguments before any other argument for backward compatibility.\n");
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-08 14:17:12 -04:00
|
|
|
/* Print all tests in registry */
|
|
|
|
|
static void print_test_list(struct tf_framework* tf) {
|
|
|
|
|
int m, t, total = 0;
|
|
|
|
|
printf("\nAvailable tests (%d modules):\n", tf->num_modules);
|
|
|
|
|
printf("========================================\n");
|
|
|
|
|
for (m = 0; m < tf->num_modules; m++) {
|
|
|
|
|
const struct tf_test_module* mod = &tf->registry_modules[m];
|
|
|
|
|
printf("Module: %s (%d tests)\n", mod->name, mod->size);
|
|
|
|
|
for (t = 0; t < mod->size; t++) {
|
|
|
|
|
printf("\t[%3d] %s\n", total + 1, mod->data[t].name);
|
|
|
|
|
total++;
|
|
|
|
|
}
|
|
|
|
|
printf("----------------------------------------\n");
|
|
|
|
|
}
|
|
|
|
|
printf("\nRun specific module: ./tests -t=<module_name>\n");
|
|
|
|
|
printf("Run specific test: ./tests -t=<test_name>\n\n");
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-03 10:59:37 -04:00
|
|
|
static int parse_jobs_count(const char* key, const char* value, struct tf_framework* tf) {
|
|
|
|
|
char* ptr_val;
|
|
|
|
|
long val = strtol(value, &ptr_val, 10); /* base 10 */
|
|
|
|
|
if (*ptr_val != '\0') {
|
|
|
|
|
fprintf(stderr, "Invalid number for -%s=%s\n", key, value);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
if (val < 0 || val > MAX_SUBPROCESSES) {
|
|
|
|
|
fprintf(stderr, "Arg '-%s' out of range: '%ld'. Range: 0..%d\n", key, val, MAX_SUBPROCESSES);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
tf->args.num_processes = (int) val;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int parse_iterations(const char* key, const char* value, struct tf_framework* tf) {
|
|
|
|
|
UNUSED(key); UNUSED(tf);
|
|
|
|
|
if (!value) return 0;
|
|
|
|
|
COUNT = (int) strtol(value, NULL, 0);
|
|
|
|
|
if (COUNT <= 0) {
|
|
|
|
|
fputs("An iteration count of 0 or less is not allowed.\n", stderr);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int parse_seed(const char* key, const char* value, struct tf_framework* tf) {
|
|
|
|
|
UNUSED(key);
|
|
|
|
|
tf->args.custom_seed = (!value || strcmp(value, "NULL") == 0) ? NULL : value;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-09 11:02:05 -04:00
|
|
|
static int parse_logging(const char* key, const char* value, struct tf_framework* tf) {
|
|
|
|
|
UNUSED(key);
|
|
|
|
|
tf->args.logging = value && strcmp(value, "1") == 0;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-03 10:59:37 -04:00
|
|
|
/* Strip up to two leading dashes */
|
|
|
|
|
static const char* normalize_key(const char* arg, const char** err_msg) {
|
|
|
|
|
const char* key;
|
|
|
|
|
if (!arg || arg[0] != '-') {
|
|
|
|
|
*err_msg = "missing initial dash";
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
/* single-dash short option */
|
|
|
|
|
if (arg[1] != '-') return arg + 1;
|
|
|
|
|
|
|
|
|
|
/* double-dash checks now */
|
|
|
|
|
if (arg[2] == '\0') {
|
|
|
|
|
*err_msg = "missing option name after double dash";
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (arg[2] == '-') {
|
|
|
|
|
*err_msg = "too many leading dashes";
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
key = arg + 2;
|
|
|
|
|
if (key[1] == '\0') {
|
|
|
|
|
*err_msg = "short option cannot use double dash";
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
return key;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-03 17:18:23 -04:00
|
|
|
static int parse_target(const char* key, const char* value, struct tf_framework* tf) {
|
|
|
|
|
int group, idx;
|
|
|
|
|
const struct tf_test_entry* entry;
|
|
|
|
|
UNUSED(key);
|
|
|
|
|
/* Find test index in the registry */
|
|
|
|
|
for (group = 0; group < tf->num_modules; group++) {
|
|
|
|
|
const struct tf_test_module* module = &tf->registry_modules[group];
|
|
|
|
|
int add_all = strcmp(value, module->name) == 0; /* select all from module */
|
|
|
|
|
for (idx = 0; idx < module->size; idx++) {
|
|
|
|
|
entry = &module->data[idx];
|
|
|
|
|
if (add_all || strcmp(value, entry->name) == 0) {
|
|
|
|
|
if (tf->args.targets.size >= MAX_ARGS) {
|
|
|
|
|
fprintf(stderr, "Too many -target args (max: %d)\n", MAX_ARGS);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
tf->args.targets.slots[tf->args.targets.size++] = entry;
|
|
|
|
|
/* Matched a single test, we're done */
|
|
|
|
|
if (!add_all) return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/* If add_all was true, we added all tests in the module, so return */
|
|
|
|
|
if (add_all) return 0;
|
|
|
|
|
}
|
|
|
|
|
fprintf(stderr, "Error: target '%s' not found (missing or module disabled).\n"
|
2025-09-08 14:17:12 -04:00
|
|
|
"Run program with -list_tests option to display available tests and modules.\n", value);
|
2025-09-03 17:18:23 -04:00
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-03 10:59:37 -04:00
|
|
|
/* Read args: all must be in the form -key=value, --key=value or -key=value */
|
|
|
|
|
static int read_args(int argc, char** argv, int start, struct tf_framework* tf) {
|
|
|
|
|
int i;
|
|
|
|
|
const char* key;
|
|
|
|
|
const char* value;
|
|
|
|
|
char* eq;
|
|
|
|
|
const char* err_msg = "unknown error";
|
|
|
|
|
for (i = start; i < argc; i++) {
|
|
|
|
|
char* raw_arg = argv[i];
|
|
|
|
|
if (!raw_arg || raw_arg[0] != '-') {
|
|
|
|
|
fprintf(stderr, "Invalid arg '%s': must start with '-'\n", raw_arg ? raw_arg : "(null)");
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
key = normalize_key(raw_arg, &err_msg);
|
|
|
|
|
if (!key || *key == '\0') {
|
|
|
|
|
fprintf(stderr, "Invalid arg '%s': %s. Must be -k=value or --key=value\n", raw_arg, err_msg);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
eq = strchr(raw_arg, '=');
|
|
|
|
|
if (!eq || eq == raw_arg + 1) {
|
2025-09-03 15:27:23 -04:00
|
|
|
/* Allowed options without value */
|
|
|
|
|
if (strcmp(key, "h") == 0 || strcmp(key, "help") == 0) {
|
|
|
|
|
tf->args.help = 1;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2025-09-08 14:17:12 -04:00
|
|
|
if (strcmp(key, "l") == 0 || strcmp(key, "list_tests") == 0) {
|
|
|
|
|
tf->args.list_tests = 1;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2025-09-03 10:59:37 -04:00
|
|
|
fprintf(stderr, "Invalid arg '%s': must be -k=value or --key=value\n", raw_arg);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*eq = '\0'; /* split key and value */
|
|
|
|
|
value = eq + 1;
|
|
|
|
|
if (!value || *value == '\0') { /* value is empty */
|
|
|
|
|
fprintf(stderr, "Invalid arg '%s': value cannot be empty\n", raw_arg);
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (parse_arg(key, value, tf) != 0) return -1;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-09 11:02:05 -04:00
|
|
|
static void run_test_log(const struct tf_test_entry* t) {
|
|
|
|
|
int64_t start_time = gettime_i64();
|
2025-09-03 10:59:37 -04:00
|
|
|
printf("Running %s..\n", t->name);
|
|
|
|
|
t->func();
|
2025-09-09 11:02:05 -04:00
|
|
|
printf("Test %s PASSED (%.3f sec)\n", t->name, (double)(gettime_i64() - start_time) / 1000000);
|
2025-09-03 10:59:37 -04:00
|
|
|
}
|
|
|
|
|
|
2025-09-09 11:02:05 -04:00
|
|
|
static void run_test(const struct tf_test_entry* t) { t->func(); }
|
|
|
|
|
|
2025-09-03 10:59:37 -04:00
|
|
|
/* Process tests in sequential order */
|
|
|
|
|
static int run_sequential(struct tf_framework* tf) {
|
2025-09-03 17:18:23 -04:00
|
|
|
int it;
|
|
|
|
|
for (it = 0; it < tf->args.targets.size; it++) {
|
2025-09-09 11:02:05 -04:00
|
|
|
tf->fn_run_test(tf->args.targets.slots[it]);
|
2025-09-03 10:59:37 -04:00
|
|
|
}
|
|
|
|
|
return EXIT_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#if defined(SUPPORTS_CONCURRENCY)
|
2025-09-03 17:18:23 -04:00
|
|
|
static const int MAX_TARGETS = 255;
|
|
|
|
|
|
2025-09-03 10:59:37 -04:00
|
|
|
/* Process tests in parallel */
|
|
|
|
|
static int run_concurrent(struct tf_framework* tf) {
|
|
|
|
|
/* Sub-processes info */
|
|
|
|
|
pid_t workers[MAX_SUBPROCESSES];
|
|
|
|
|
int pipefd[2];
|
|
|
|
|
int status = EXIT_SUCCESS;
|
|
|
|
|
int it; /* loop iterator */
|
2025-09-03 17:18:23 -04:00
|
|
|
unsigned char idx; /* test index */
|
|
|
|
|
|
|
|
|
|
if (tf->args.targets.size > MAX_TARGETS) {
|
|
|
|
|
fprintf(stderr, "Internal Error: the number of targets (%d) exceeds the maximum supported (%d). "
|
|
|
|
|
"If you need more, extend 'run_concurrent()' to handle additional targets.\n",
|
|
|
|
|
tf->args.targets.size, MAX_TARGETS);
|
|
|
|
|
exit(EXIT_FAILURE);
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-03 10:59:37 -04:00
|
|
|
|
|
|
|
|
if (pipe(pipefd) != 0) {
|
|
|
|
|
perror("Error during pipe setup");
|
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Launch worker processes */
|
|
|
|
|
for (it = 0; it < tf->args.num_processes; it++) {
|
|
|
|
|
pid_t pid = fork();
|
|
|
|
|
if (pid < 0) {
|
|
|
|
|
perror("Error during process fork");
|
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
|
}
|
|
|
|
|
if (pid == 0) {
|
|
|
|
|
/* Child worker: read jobs from the shared pipe */
|
|
|
|
|
close(pipefd[1]); /* children never write */
|
2025-09-03 17:18:23 -04:00
|
|
|
while (read(pipefd[0], &idx, sizeof(idx)) == sizeof(idx)) {
|
2025-09-09 11:02:05 -04:00
|
|
|
tf->fn_run_test(tf->args.targets.slots[(int)idx]);
|
2025-09-03 10:59:37 -04:00
|
|
|
}
|
|
|
|
|
_exit(EXIT_SUCCESS); /* finish child process */
|
|
|
|
|
} else {
|
|
|
|
|
/* Parent: save worker pid */
|
|
|
|
|
workers[it] = pid;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Parent: write all tasks into the pipe */
|
|
|
|
|
close(pipefd[0]); /* close read end */
|
2025-09-03 17:18:23 -04:00
|
|
|
for (it = 0; it < tf->args.targets.size; it++) {
|
|
|
|
|
idx = (unsigned char)it;
|
|
|
|
|
if (write(pipefd[1], &idx, sizeof(idx)) == -1) {
|
|
|
|
|
perror("Error during workload distribution");
|
|
|
|
|
close(pipefd[1]);
|
|
|
|
|
return EXIT_FAILURE;
|
2025-09-03 10:59:37 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/* Close write end to signal EOF */
|
|
|
|
|
close(pipefd[1]);
|
|
|
|
|
/* Wait for all workers */
|
|
|
|
|
for (it = 0; it < tf->args.num_processes; it++) {
|
|
|
|
|
int ret = 0;
|
|
|
|
|
if (waitpid(workers[it], &ret, 0) == -1 || ret != 0) {
|
|
|
|
|
status = EXIT_FAILURE;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return status;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
static int tf_init(struct tf_framework* tf, int argc, char** argv)
|
|
|
|
|
{
|
|
|
|
|
/* Caller must set the registry and its size before calling tf_init */
|
|
|
|
|
if (tf->registry_modules == NULL || tf->num_modules <= 0) {
|
|
|
|
|
fprintf(stderr, "Error: tests registry not provided or empty\n");
|
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Initialize command-line options */
|
|
|
|
|
tf->args.num_processes = 0;
|
|
|
|
|
tf->args.custom_seed = NULL;
|
2025-09-03 15:27:23 -04:00
|
|
|
tf->args.help = 0;
|
2025-09-03 17:18:23 -04:00
|
|
|
tf->args.targets.size = 0;
|
2025-09-08 14:17:12 -04:00
|
|
|
tf->args.list_tests = 0;
|
2025-09-09 11:02:05 -04:00
|
|
|
tf->args.logging = 0;
|
2025-09-03 10:59:37 -04:00
|
|
|
|
|
|
|
|
/* Disable buffering for stdout to improve reliability of getting
|
|
|
|
|
* diagnostic information. Happens right at the start of main because
|
|
|
|
|
* setbuf must be used before any other operation on the stream. */
|
|
|
|
|
setbuf(stdout, NULL);
|
|
|
|
|
/* Also disable buffering for stderr because it's not guaranteed that it's
|
|
|
|
|
* unbuffered on all systems. */
|
|
|
|
|
setbuf(stderr, NULL);
|
|
|
|
|
|
|
|
|
|
/* Parse env args */
|
|
|
|
|
if (read_env(tf) != 0) return EXIT_FAILURE;
|
|
|
|
|
|
|
|
|
|
/* Parse command-line args */
|
|
|
|
|
if (argc > 1) {
|
|
|
|
|
int named_arg_start = 1; /* index to begin processing named arguments */
|
|
|
|
|
if (argc - 1 > MAX_ARGS) { /* first arg is always the binary path */
|
|
|
|
|
fprintf(stderr, "Too many command-line arguments (max: %d)\n", MAX_ARGS);
|
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Compatibility Note: The first two args were the number of iterations and the seed. */
|
|
|
|
|
/* If provided, parse them and adjust the starting index for named arguments accordingly. */
|
|
|
|
|
if (argv[1][0] != '-') {
|
|
|
|
|
int has_seed = argc > 2 && argv[2][0] != '-';
|
|
|
|
|
if (parse_iterations("i", argv[1], tf) != 0) return EXIT_FAILURE;
|
|
|
|
|
if (has_seed) parse_seed("seed", argv[2], tf);
|
|
|
|
|
named_arg_start = has_seed ? 3 : 2;
|
|
|
|
|
}
|
|
|
|
|
if (read_args(argc, argv, named_arg_start, tf) != 0) {
|
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
|
}
|
2025-09-03 15:27:23 -04:00
|
|
|
|
|
|
|
|
if (tf->args.help) {
|
|
|
|
|
help();
|
|
|
|
|
exit(EXIT_SUCCESS);
|
|
|
|
|
}
|
2025-09-08 14:17:12 -04:00
|
|
|
|
|
|
|
|
if (tf->args.list_tests) {
|
|
|
|
|
print_test_list(tf);
|
|
|
|
|
exit(EXIT_SUCCESS);
|
|
|
|
|
}
|
2025-09-03 10:59:37 -04:00
|
|
|
}
|
|
|
|
|
|
2025-09-09 11:02:05 -04:00
|
|
|
tf->fn_run_test = tf->args.logging ? run_test_log : run_test;
|
2025-09-03 10:59:37 -04:00
|
|
|
return EXIT_SUCCESS;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int tf_run(struct tf_framework* tf) {
|
|
|
|
|
/* Process exit status */
|
|
|
|
|
int status;
|
2025-09-03 17:18:23 -04:00
|
|
|
/* Whether to run all tests */
|
|
|
|
|
int run_all;
|
2025-09-03 10:59:37 -04:00
|
|
|
/* Loop iterator */
|
|
|
|
|
int it;
|
|
|
|
|
/* Initial test time */
|
|
|
|
|
int64_t start_time = gettime_i64();
|
2025-09-09 11:02:05 -04:00
|
|
|
/* Verify 'tf_init' has been called */
|
|
|
|
|
if (!tf->fn_run_test) {
|
|
|
|
|
fprintf(stderr, "Error: No test runner set. You must call 'tf_init' first to initialize the framework "
|
|
|
|
|
"or manually assign 'fn_run_test' before calling 'tf_run'.\n");
|
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
|
}
|
2025-09-03 10:59:37 -04:00
|
|
|
|
2025-09-03 17:18:23 -04:00
|
|
|
/* Populate targets with all tests if none were explicitly specified */
|
|
|
|
|
run_all = tf->args.targets.size == 0;
|
|
|
|
|
if (run_all) {
|
|
|
|
|
int group, idx;
|
|
|
|
|
for (group = 0; group < tf->num_modules; group++) {
|
|
|
|
|
const struct tf_test_module* module = &tf->registry_modules[group];
|
|
|
|
|
for (idx = 0; idx < module->size; idx++) {
|
|
|
|
|
if (tf->args.targets.size >= MAX_ARGS) {
|
|
|
|
|
fprintf(stderr, "Internal Error: Number of tests (%d) exceeds MAX_ARGS (%d). "
|
|
|
|
|
"Increase MAX_ARGS to accommodate all tests.\n", tf->args.targets.size, MAX_ARGS);
|
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
|
}
|
|
|
|
|
tf->args.targets.slots[tf->args.targets.size++] = &module->data[idx];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-09 11:02:05 -04:00
|
|
|
if (!tf->args.logging) printf("Tests running silently. Use '-log=1' to enable detailed logging\n");
|
|
|
|
|
|
2025-09-03 10:59:37 -04:00
|
|
|
/* Log configuration */
|
|
|
|
|
print_args(&tf->args);
|
|
|
|
|
|
|
|
|
|
/* Run test RNG tests (must run before we really initialize the test RNG) */
|
|
|
|
|
/* Note: currently, these tests are executed sequentially because there */
|
|
|
|
|
/* is really only one test. */
|
|
|
|
|
for (it = 0; tf->registry_no_rng && it < tf->registry_no_rng->size; it++) {
|
2025-09-03 17:18:23 -04:00
|
|
|
if (run_all) { /* future: support filtering */
|
2025-09-09 11:02:05 -04:00
|
|
|
tf->fn_run_test(&tf->registry_no_rng->data[it]);
|
2025-09-03 17:18:23 -04:00
|
|
|
}
|
2025-09-03 10:59:37 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Initialize test RNG and library contexts */
|
|
|
|
|
testrand_init(tf->args.custom_seed);
|
|
|
|
|
if (tf->fn_setup && tf->fn_setup() != 0) return EXIT_FAILURE;
|
|
|
|
|
|
|
|
|
|
/* Check whether to process tests sequentially or concurrently */
|
|
|
|
|
if (tf->args.num_processes <= 1) {
|
|
|
|
|
status = run_sequential(tf);
|
|
|
|
|
} else {
|
|
|
|
|
#if defined(SUPPORTS_CONCURRENCY)
|
|
|
|
|
status = run_concurrent(tf);
|
|
|
|
|
#else
|
|
|
|
|
fputs("Parallel execution not supported on your system. Running sequentially...\n", stderr);
|
|
|
|
|
status = run_sequential(tf);
|
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Print accumulated time */
|
|
|
|
|
printf("Total execution time: %.3f seconds\n", (double)(gettime_i64() - start_time) / 1000000);
|
|
|
|
|
if (tf->fn_teardown && tf->fn_teardown() != 0) return EXIT_FAILURE;
|
|
|
|
|
|
|
|
|
|
return status;
|
|
|
|
|
}
|
2026-06-12 19:10:33 +05:30
|
|
|
|
|
|
|
|
#if defined(__GNUC__)
|
|
|
|
|
# pragma GCC diagnostic pop
|
|
|
|
|
#endif
|