diff --git a/Makefile.am b/Makefile.am index d511853b05..dc798575e3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -45,7 +45,10 @@ noinst_HEADERS += src/precomputed_ecmult.h noinst_HEADERS += src/precomputed_ecmult_gen.h noinst_HEADERS += src/assumptions.h noinst_HEADERS += src/checkmem.h +noinst_HEADERS += src/tests_common.h noinst_HEADERS += src/testutil.h +noinst_HEADERS += src/unit_test.h +noinst_HEADERS += src/unit_test.c noinst_HEADERS += src/util.h noinst_HEADERS += src/util_local_visibility.h noinst_HEADERS += src/int128.h @@ -120,7 +123,7 @@ if USE_TESTS TESTS += noverify_tests noinst_PROGRAMS += noverify_tests noverify_tests_SOURCES = src/tests.c -noverify_tests_CPPFLAGS = $(SECP_CONFIG_DEFINES) +noverify_tests_CPPFLAGS = $(SECP_CONFIG_DEFINES) $(TEST_DEFINES) noverify_tests_LDADD = $(COMMON_LIB) $(PRECOMPUTED_LIB) noverify_tests_LDFLAGS = -static if !ENABLE_COVERAGE diff --git a/configure.ac b/configure.ac index 2f156ddc25..6028ee288d 100644 --- a/configure.ac +++ b/configure.ac @@ -443,6 +443,14 @@ if test x"$enable_experimental" = x"no"; then fi fi +# Check for concurrency support (tests only) +if test "x$enable_tests" != x"no"; then + AC_CHECK_HEADERS([sys/types.h sys/wait.h unistd.h]) + AS_IF([test "x$ac_cv_header_sys_types_h" = xyes && test "x$ac_cv_header_sys_wait_h" = xyes && + test "x$ac_cv_header_unistd_h" = xyes], [TEST_DEFINES="-DSUPPORTS_CONCURRENCY=1"], TEST_DEFINES="") + AC_SUBST(TEST_DEFINES) +fi + ### ### Generate output ### diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fa3b2903eb..ecbbbbe8e9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -134,15 +134,27 @@ if(SECP256K1_BUILD_BENCHMARK) endif() if(SECP256K1_BUILD_TESTS) + include(CheckIncludeFile) + check_include_file(sys/types.h HAVE_SYS_TYPES_H) + check_include_file(sys/wait.h HAVE_SYS_WAIT_H) + check_include_file(unistd.h HAVE_UNISTD_H) + + set(TEST_DEFINITIONS "") + if(HAVE_SYS_TYPES_H AND HAVE_SYS_WAIT_H AND HAVE_UNISTD_H) + list(APPEND TEST_DEFINITIONS SUPPORTS_CONCURRENCY=1) + endif() + add_executable(noverify_tests tests.c) target_link_libraries(noverify_tests secp256k1_precomputed secp256k1_asm) + target_compile_definitions(noverify_tests PRIVATE ${TEST_DEFINITIONS}) add_test(NAME secp256k1_noverify_tests COMMAND noverify_tests) if(NOT CMAKE_BUILD_TYPE STREQUAL "Coverage") add_executable(tests tests.c) - target_compile_definitions(tests PRIVATE VERIFY) + target_compile_definitions(tests PRIVATE VERIFY ${TEST_DEFINITIONS}) target_link_libraries(tests secp256k1_precomputed secp256k1_asm) add_test(NAME secp256k1_tests COMMAND tests) endif() + unset(TEST_DEFINITIONS) endif() if(SECP256K1_BUILD_EXHAUSTIVE_TESTS) diff --git a/src/bench.h b/src/bench.h index 232fb35fc0..4e8e961b67 100644 --- a/src/bench.h +++ b/src/bench.h @@ -12,27 +12,7 @@ #include #include -#if (defined(_MSC_VER) && _MSC_VER >= 1900) -# include -#else -# include -#endif - -static int64_t gettime_i64(void) { -#if (defined(_MSC_VER) && _MSC_VER >= 1900) - /* C11 way to get wallclock time */ - struct timespec tv; - if (!timespec_get(&tv, TIME_UTC)) { - fputs("timespec_get failed!", stderr); - exit(EXIT_FAILURE); - } - return (int64_t)tv.tv_nsec / 1000 + (int64_t)tv.tv_sec * 1000000LL; -#else - struct timeval tv; - gettimeofday(&tv, NULL); - return (int64_t)tv.tv_usec + (int64_t)tv.tv_sec * 1000000LL; -#endif -} +#include "tests_common.h" #define FP_EXP (6) #define FP_MULT (1000000LL) diff --git a/src/modules/ecdh/tests_impl.h b/src/modules/ecdh/tests_impl.h index 6888f18c64..c1a5e73c8a 100644 --- a/src/modules/ecdh/tests_impl.h +++ b/src/modules/ecdh/tests_impl.h @@ -7,6 +7,8 @@ #ifndef SECP256K1_MODULE_ECDH_TESTS_H #define SECP256K1_MODULE_ECDH_TESTS_H +#include "../../unit_test.h" + static int ecdh_hash_function_test_xpassthru(unsigned char *output, const unsigned char *x, const unsigned char *y, void *data) { (void)y; (void)data; @@ -182,12 +184,13 @@ static void test_ecdh_wycheproof(void) { } } -static void run_ecdh_tests(void) { - test_ecdh_api(); - test_ecdh_generator_basepoint(); - test_bad_scalar(); - test_result_basepoint(); - test_ecdh_wycheproof(); -} +/* --- Test registry --- */ +static const struct tf_test_entry tests_ecdh[] = { + CASE1(test_ecdh_api), + CASE1(test_ecdh_generator_basepoint), + CASE1(test_bad_scalar), + CASE1(test_result_basepoint), + CASE1(test_ecdh_wycheproof), +}; #endif /* SECP256K1_MODULE_ECDH_TESTS_H */ diff --git a/src/modules/ellswift/tests_impl.h b/src/modules/ellswift/tests_impl.h index b90fd0ab88..63c36e7ff1 100644 --- a/src/modules/ellswift/tests_impl.h +++ b/src/modules/ellswift/tests_impl.h @@ -7,6 +7,7 @@ #define SECP256K1_MODULE_ELLSWIFT_TESTS_H #include "../../../include/secp256k1_ellswift.h" +#include "../../unit_test.h" struct ellswift_xswiftec_inv_test { int enc_bitmap; @@ -433,4 +434,10 @@ void run_ellswift_tests(void) { } } +/* --- Test registry --- */ +/* TODO: subdivide test in cases */ +static const struct tf_test_entry tests_ellswift[] = { + CASE(ellswift_tests), +}; + #endif diff --git a/src/modules/extrakeys/tests_impl.h b/src/modules/extrakeys/tests_impl.h index ab4ef4a74b..abebd1106b 100644 --- a/src/modules/extrakeys/tests_impl.h +++ b/src/modules/extrakeys/tests_impl.h @@ -8,6 +8,7 @@ #define SECP256K1_MODULE_EXTRAKEYS_TESTS_H #include "../../../include/secp256k1_extrakeys.h" +#include "../../unit_test.h" static void test_xonly_pubkey(void) { secp256k1_pubkey pk; @@ -467,17 +468,17 @@ static void test_keypair_add(void) { } } -static void run_extrakeys_tests(void) { +/* --- Test registry --- */ +static const struct tf_test_entry tests_extrakeys[] = { /* xonly key test cases */ - test_xonly_pubkey(); - test_xonly_pubkey_tweak(); - test_xonly_pubkey_tweak_check(); - test_xonly_pubkey_tweak_recursive(); - test_xonly_pubkey_comparison(); - + CASE1(test_xonly_pubkey), + CASE1(test_xonly_pubkey_tweak), + CASE1(test_xonly_pubkey_tweak_check), + CASE1(test_xonly_pubkey_tweak_recursive), + CASE1(test_xonly_pubkey_comparison), /* keypair tests */ - test_keypair(); - test_keypair_add(); -} + CASE1(test_keypair), + CASE1(test_keypair_add), +}; #endif diff --git a/src/modules/musig/tests_impl.h b/src/modules/musig/tests_impl.h index b57b26264a..b4ba185494 100644 --- a/src/modules/musig/tests_impl.h +++ b/src/modules/musig/tests_impl.h @@ -20,6 +20,7 @@ #include "../../group.h" #include "../../hash.h" #include "../../util.h" +#include "../../unit_test.h" #include "vectors.h" @@ -36,7 +37,7 @@ static int create_keypair_and_pk(secp256k1_keypair *keypair, secp256k1_pubkey *p /* Just a simple (non-tweaked) 2-of-2 MuSig aggregate, sign, verify * test. */ -static void musig_simple_test(void) { +static void musig_simple_test_internal(void) { unsigned char sk[2][32]; secp256k1_keypair keypair[2]; secp256k1_musig_pubnonce pubnonce[2]; @@ -629,7 +630,7 @@ static void musig_tweak_test_helper(const secp256k1_xonly_pubkey* agg_pk, const /* Create aggregate public key P[0], tweak multiple times (using xonly and * plain tweaking) and test signing. */ -static void musig_tweak_test(void) { +static void musig_tweak_test_internal(void) { unsigned char sk[2][32]; secp256k1_pubkey pk[2]; const secp256k1_pubkey *pk_ptr[2]; @@ -1114,28 +1115,24 @@ static void musig_test_static_nonce_gen_counter(void) { CHECK(secp256k1_memcmp_var(pubnonce66, expected_pubnonce, sizeof(pubnonce66)) == 0); } -static void run_musig_tests(void) { - int i; - - for (i = 0; i < COUNT; i++) { - musig_simple_test(); - } - musig_api_tests(); - musig_nonce_test(); - for (i = 0; i < COUNT; i++) { - /* Run multiple times to ensure that pk and nonce have different y - * parities */ - musig_tweak_test(); - } - sha256_tag_test(); - musig_test_vectors_keyagg(); - musig_test_vectors_noncegen(); - musig_test_vectors_nonceagg(); - musig_test_vectors_signverify(); - musig_test_vectors_tweak(); - musig_test_vectors_sigagg(); - - musig_test_static_nonce_gen_counter(); -} +/* --- Test registry --- */ +REPEAT_TEST(musig_simple_test) +/* Run multiple times to ensure that pk and nonce have different y parities */ +REPEAT_TEST(musig_tweak_test) + +static const struct tf_test_entry tests_musig[] = { + CASE1(musig_simple_test), + CASE1(musig_api_tests), + CASE1(musig_nonce_test), + CASE1(musig_tweak_test), + CASE1(sha256_tag_test), + CASE1(musig_test_vectors_keyagg), + CASE1(musig_test_vectors_noncegen), + CASE1(musig_test_vectors_nonceagg), + CASE1(musig_test_vectors_signverify), + CASE1(musig_test_vectors_tweak), + CASE1(musig_test_vectors_sigagg), + CASE1(musig_test_static_nonce_gen_counter), +}; #endif diff --git a/src/modules/recovery/tests_impl.h b/src/modules/recovery/tests_impl.h index 7a28a3ce65..09554a242e 100644 --- a/src/modules/recovery/tests_impl.h +++ b/src/modules/recovery/tests_impl.h @@ -7,6 +7,8 @@ #ifndef SECP256K1_MODULE_RECOVERY_TESTS_H #define SECP256K1_MODULE_RECOVERY_TESTS_H +#include "../../unit_test.h" + static int recovery_test_nonce_function(unsigned char *nonce32, const unsigned char *msg32, const unsigned char *key32, const unsigned char *algo16, void *data, unsigned int counter) { (void) msg32; (void) key32; @@ -28,7 +30,7 @@ static int recovery_test_nonce_function(unsigned char *nonce32, const unsigned c return testrand_bits(1); } -static void test_ecdsa_recovery_api(void) { +static void test_ecdsa_recovery_api_internal(void) { /* Setup contexts that just count errors */ secp256k1_pubkey pubkey; secp256k1_pubkey recpubkey; @@ -92,7 +94,7 @@ static void test_ecdsa_recovery_api(void) { CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(CTX, &recsig, sig, recid) == 0); } -static void test_ecdsa_recovery_end_to_end(void) { +static void test_ecdsa_recovery_end_to_end_internal(void) { unsigned char extra[32] = {0x00}; unsigned char privkey[32]; unsigned char message[32]; @@ -324,15 +326,14 @@ static void test_ecdsa_recovery_edge_cases(void) { } } -static void run_recovery_tests(void) { - int i; - for (i = 0; i < COUNT; i++) { - test_ecdsa_recovery_api(); - } - for (i = 0; i < 64*COUNT; i++) { - test_ecdsa_recovery_end_to_end(); - } - test_ecdsa_recovery_edge_cases(); -} +/* --- Test registry --- */ +REPEAT_TEST(test_ecdsa_recovery_api) +REPEAT_TEST_MULT(test_ecdsa_recovery_end_to_end, 64) + +static const struct tf_test_entry tests_recovery[] = { + CASE1(test_ecdsa_recovery_api), + CASE1(test_ecdsa_recovery_end_to_end), + CASE1(test_ecdsa_recovery_edge_cases) +}; #endif /* SECP256K1_MODULE_RECOVERY_TESTS_H */ diff --git a/src/modules/schnorrsig/tests_impl.h b/src/modules/schnorrsig/tests_impl.h index 5abbeefe0b..9a1b15f0b2 100644 --- a/src/modules/schnorrsig/tests_impl.h +++ b/src/modules/schnorrsig/tests_impl.h @@ -8,6 +8,7 @@ #define SECP256K1_MODULE_SCHNORRSIG_TESTS_H #include "../../../include/secp256k1_schnorrsig.h" +#include "../../unit_test.h" /* Checks that a bit flip in the n_flip-th argument (that has n_bytes many * bytes) changes the hash function @@ -802,7 +803,7 @@ static int nonce_function_overflowing(unsigned char *nonce32, const unsigned cha return 1; } -static void test_schnorrsig_sign(void) { +static void test_schnorrsig_sign_internal(void) { unsigned char sk[32]; secp256k1_xonly_pubkey pk; secp256k1_keypair keypair; @@ -852,7 +853,7 @@ static void test_schnorrsig_sign(void) { /* Creates N_SIGS valid signatures and verifies them with verify and * verify_batch (TODO). Then flips some bits and checks that verification now * fails. */ -static void test_schnorrsig_sign_verify(void) { +static void test_schnorrsig_sign_verify_internal(void) { unsigned char sk[32]; unsigned char msg[N_SIGS][32]; unsigned char sig[N_SIGS][64]; @@ -965,18 +966,18 @@ static void test_schnorrsig_taproot(void) { CHECK(secp256k1_xonly_pubkey_tweak_add_check(CTX, output_pk_bytes, pk_parity, &internal_pk, tweak) == 1); } -static void run_schnorrsig_tests(void) { - int i; - run_nonce_function_bip340_tests(); - - test_schnorrsig_api(); - test_schnorrsig_sha256_tagged(); - test_schnorrsig_bip_vectors(); - for (i = 0; i < COUNT; i++) { - test_schnorrsig_sign(); - test_schnorrsig_sign_verify(); - } - test_schnorrsig_taproot(); -} +/* --- Test registry --- */ +REPEAT_TEST(test_schnorrsig_sign) +REPEAT_TEST(test_schnorrsig_sign_verify) + +static const struct tf_test_entry tests_schnorrsig[] = { + CASE(nonce_function_bip340_tests), + CASE1(test_schnorrsig_api), + CASE1(test_schnorrsig_sha256_tagged), + CASE1(test_schnorrsig_bip_vectors), + CASE1(test_schnorrsig_sign), + CASE1(test_schnorrsig_sign_verify), + CASE1(test_schnorrsig_taproot), +}; #endif diff --git a/src/tests.c b/src/tests.c index 28bec5904c..5f4f6b0f22 100644 --- a/src/tests.c +++ b/src/tests.c @@ -25,6 +25,8 @@ #include "checkmem.h" #include "testutil.h" #include "util.h" +#include "unit_test.h" +#include "unit_test.c" #include "../contrib/lax_der_parsing.c" #include "../contrib/lax_der_privatekey_parsing.c" @@ -37,7 +39,6 @@ #define CONDITIONAL_TEST(cnt, nam) if (COUNT < (cnt)) { printf("Skipping %s (iteration count too low)\n", nam); } else -static int COUNT = 16; static secp256k1_context *CTX = NULL; static secp256k1_context *STATIC_CTX = NULL; @@ -227,6 +228,12 @@ static void run_static_context_tests(int use_prealloc) { } } +static void run_all_static_context_tests(void) +{ + run_static_context_tests(0); + run_static_context_tests(1); +} + static void run_proper_context_tests(int use_prealloc) { int32_t dummy = 0; secp256k1_context *my_ctx, *my_ctx_fresh; @@ -349,6 +356,12 @@ static void run_proper_context_tests(int use_prealloc) { secp256k1_context_preallocated_destroy(NULL); } +static void run_all_proper_context_tests(void) +{ + run_proper_context_tests(0); + run_proper_context_tests(1); +} + static void run_scratch_tests(void) { const size_t adj_alloc = ((500 + ALIGNMENT - 1) / ALIGNMENT) * ALIGNMENT; @@ -7666,179 +7679,177 @@ static void run_cmov_tests(void) { ge_storage_cmov_test(); } -int main(int argc, char **argv) { - /* 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); - - /* find iteration count */ - if (argc > 1) { - COUNT = strtol(argv[1], NULL, 0); - } else { - const char* env = getenv("SECP256K1_TEST_ITERS"); - if (env && strlen(env) > 0) { - COUNT = strtol(env, NULL, 0); - } - } - if (COUNT <= 0) { - fputs("An iteration count of 0 or less is not allowed.\n", stderr); - return EXIT_FAILURE; - } - printf("test count = %i\n", COUNT); +/* --------------------------------------------------------- */ +/* Test Registry */ +/* --------------------------------------------------------- */ - /* run test RNG tests (must run before we really initialize the test RNG) */ - run_xoshiro256pp_tests(); +/* --- Special test cases that must run before RNG initialization --- */ +static const struct tf_test_entry tests_no_rng[] = { + CASE(xoshiro256pp_tests), +}; +static const struct tf_test_module registry_modules_no_rng = MAKE_TEST_MODULE(no_rng); + +/* --- Standard test cases start here --- */ +static const struct tf_test_entry tests_general[] = { + CASE(selftest_tests), + CASE(all_proper_context_tests), + CASE(all_static_context_tests), + CASE(deprecated_context_flags_test), + CASE(scratch_tests), +}; - /* find random seed */ - testrand_init(argc > 2 ? argv[2] : NULL); +static const struct tf_test_entry tests_integer[] = { +#ifdef SECP256K1_WIDEMUL_INT128 + CASE(int128_tests), +#endif + CASE(ctz_tests), + CASE(modinv_tests), + CASE(inverse_tests), +}; - /*** Setup test environment ***/ +static const struct tf_test_entry tests_hash[] = { + CASE(sha256_known_output_tests), + CASE(sha256_counter_tests), + CASE(hmac_sha256_tests), + CASE(rfc6979_hmac_sha256_tests), + CASE(tagged_sha256_tests), +}; - /* Create a global context available to all tests */ - CTX = secp256k1_context_create(SECP256K1_CONTEXT_NONE); - /* Randomize the context only with probability 15/16 - to make sure we test without context randomization from time to time. - TODO Reconsider this when recalibrating the tests. */ - if (testrand_bits(4)) { - unsigned char rand32[32]; - testrand256(rand32); - CHECK(secp256k1_context_randomize(CTX, rand32)); - } - /* Make a writable copy of secp256k1_context_static in order to test the effect of API functions - that write to the context. The API does not support cloning the static context, so we use - memcpy instead. The user is not supposed to copy a context but we should still ensure that - the API functions handle copies of the static context gracefully. */ - STATIC_CTX = malloc(sizeof(*secp256k1_context_static)); - CHECK(STATIC_CTX != NULL); - memcpy(STATIC_CTX, secp256k1_context_static, sizeof(secp256k1_context)); - CHECK(!secp256k1_context_is_proper(STATIC_CTX)); +static const struct tf_test_entry tests_scalar[] = { + CASE(scalar_tests), +}; + +static const struct tf_test_entry tests_field[] = { + CASE(field_half), + CASE(field_misc), + CASE(field_convert), + CASE(field_be32_overflow), + CASE(fe_mul), + CASE(sqr), + CASE(sqrt), +}; - /*** Run actual tests ***/ +static const struct tf_test_entry tests_group[] = { + CASE(ge), + CASE(gej), + CASE(group_decompress), +}; - /* selftest tests */ - run_selftest_tests(); +static const struct tf_test_entry tests_ecmult[] = { + CASE(ecmult_pre_g), + CASE(wnaf), + CASE(point_times_order), + CASE(ecmult_near_split_bound), + CASE(ecmult_chain), + CASE(ecmult_constants), + CASE(ecmult_gen_blind), + CASE(ecmult_const_tests), + CASE(ecmult_multi_tests), + CASE(ec_combine), +}; - /* context tests */ - run_proper_context_tests(0); run_proper_context_tests(1); - run_static_context_tests(0); run_static_context_tests(1); - run_deprecated_context_flags_test(); +static const struct tf_test_entry tests_ec[] = { + CASE(endomorphism_tests), + CASE(ec_pubkey_parse_test), + CASE(eckey_edge_case_test), + CASE(eckey_negate_test), +}; - /* scratch tests */ - run_scratch_tests(); +static const struct tf_test_entry tests_ecdsa[] = { + CASE(ec_illegal_argument_tests), + CASE(pubkey_comparison), + CASE(pubkey_sort), + CASE(random_pubkeys), + CASE(ecdsa_der_parse), + CASE(ecdsa_sign_verify), + CASE(ecdsa_end_to_end), + CASE(ecdsa_edge_cases), + CASE(ecdsa_wycheproof), +}; - /* integer arithmetic tests */ -#ifdef SECP256K1_WIDEMUL_INT128 - run_int128_tests(); -#endif - run_ctz_tests(); - run_modinv_tests(); - run_inverse_tests(); - - /* sorting tests */ - run_hsort_tests(); - - /* hash tests */ - run_sha256_known_output_tests(); - run_sha256_counter_tests(); - run_hmac_sha256_tests(); - run_rfc6979_hmac_sha256_tests(); - run_tagged_sha256_tests(); - - /* scalar tests */ - run_scalar_tests(); - - /* field tests */ - run_field_half(); - run_field_misc(); - run_field_convert(); - run_field_be32_overflow(); - run_fe_mul(); - run_sqr(); - run_sqrt(); - - /* group tests */ - run_ge(); - run_gej(); - run_group_decompress(); - - /* ecmult tests */ - run_ecmult_pre_g(); - run_wnaf(); - run_point_times_order(); - run_ecmult_near_split_bound(); - run_ecmult_chain(); - run_ecmult_constants(); - run_ecmult_gen_blind(); - run_ecmult_const_tests(); - run_ecmult_multi_tests(); - run_ec_combine(); - - /* endomorphism tests */ - run_endomorphism_tests(); - - /* EC point parser test */ - run_ec_pubkey_parse_test(); - - /* EC key edge cases */ - run_eckey_edge_case_test(); - - /* EC key arithmetic test */ - run_eckey_negate_test(); +static const struct tf_test_entry tests_utils[] = { + CASE(hsort_tests), + CASE(secp256k1_memczero_test), + CASE(secp256k1_is_zero_array_test), + CASE(secp256k1_byteorder_tests), + CASE(cmov_tests), +}; +/* Register test modules */ +static const struct tf_test_module registry_modules[] = { + MAKE_TEST_MODULE(general), + MAKE_TEST_MODULE(integer), + MAKE_TEST_MODULE(hash), + MAKE_TEST_MODULE(scalar), + MAKE_TEST_MODULE(field), + MAKE_TEST_MODULE(group), + MAKE_TEST_MODULE(ecmult), + MAKE_TEST_MODULE(ec), #ifdef ENABLE_MODULE_ECDH - /* ecdh tests */ - run_ecdh_tests(); + MAKE_TEST_MODULE(ecdh), #endif - - /* ecdsa tests */ - run_ec_illegal_argument_tests(); - run_pubkey_comparison(); - run_pubkey_sort(); - run_random_pubkeys(); - run_ecdsa_der_parse(); - run_ecdsa_sign_verify(); - run_ecdsa_end_to_end(); - run_ecdsa_edge_cases(); - run_ecdsa_wycheproof(); - + MAKE_TEST_MODULE(ecdsa), #ifdef ENABLE_MODULE_RECOVERY /* ECDSA pubkey recovery tests */ - run_recovery_tests(); + MAKE_TEST_MODULE(recovery), #endif - #ifdef ENABLE_MODULE_EXTRAKEYS - run_extrakeys_tests(); + MAKE_TEST_MODULE(extrakeys), #endif - #ifdef ENABLE_MODULE_SCHNORRSIG - run_schnorrsig_tests(); + MAKE_TEST_MODULE(schnorrsig), #endif - #ifdef ENABLE_MODULE_MUSIG - run_musig_tests(); + MAKE_TEST_MODULE(musig), #endif - #ifdef ENABLE_MODULE_ELLSWIFT - run_ellswift_tests(); + MAKE_TEST_MODULE(ellswift), #endif + MAKE_TEST_MODULE(utils), +}; - /* util tests */ - run_secp256k1_memczero_test(); - run_secp256k1_is_zero_array_test(); - run_secp256k1_byteorder_tests(); - - run_cmov_tests(); +/* Setup test environment */ +static int setup(void) { + /* Create a global context available to all tests */ + CTX = secp256k1_context_create(SECP256K1_CONTEXT_NONE); + /* Randomize the context only with probability 15/16 + to make sure we test without context randomization from time to time. + TODO Reconsider this when recalibrating the tests. */ + if (testrand_bits(4)) { + unsigned char rand32[32]; + testrand256(rand32); + CHECK(secp256k1_context_randomize(CTX, rand32)); + } + /* Make a writable copy of secp256k1_context_static in order to test the effect of API functions + that write to the context. The API does not support cloning the static context, so we use + memcpy instead. The user is not supposed to copy a context but we should still ensure that + the API functions handle copies of the static context gracefully. */ + STATIC_CTX = malloc(sizeof(*secp256k1_context_static)); + CHECK(STATIC_CTX != NULL); + memcpy(STATIC_CTX, secp256k1_context_static, sizeof(secp256k1_context)); + CHECK(!secp256k1_context_is_proper(STATIC_CTX)); + return 0; +} - /*** Tear down test environment ***/ +/* Shutdown test environment */ +static int teardown(void) { free(STATIC_CTX); secp256k1_context_destroy(CTX); + return 0; +} + +int main(int argc, char **argv) { + struct tf_framework tf = {0}; + tf.registry_modules = registry_modules; + tf.num_modules = sizeof(registry_modules) / sizeof(registry_modules[0]); + tf.registry_no_rng = ®istry_modules_no_rng; - testrand_finish(); + /* Add context creation/destruction functions */ + tf.fn_setup = setup; + tf.fn_teardown = teardown; - printf("no problems found\n"); - return EXIT_SUCCESS; + /* Init and run framework */ + if (tf_init(&tf, argc, argv) != 0) return EXIT_FAILURE; + return tf_run(&tf); } + diff --git a/src/tests_common.h b/src/tests_common.h new file mode 100644 index 0000000000..a341633bbc --- /dev/null +++ b/src/tests_common.h @@ -0,0 +1,42 @@ +/*********************************************************************** + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php.* + ***********************************************************************/ + +#ifndef SECP256K1_TESTS_COMMON_H +#define SECP256K1_TESTS_COMMON_H + +/*********************************************************************** + * Test Support Utilities + * + * This file provides general-purpose functions for tests and benchmark + * programs. Unlike testutil.h, this file is not linked to the library, + * allowing each program to choose whether to run against the production + * API or access library internals directly. + ***********************************************************************/ + +#include + +#if (defined(_MSC_VER) && _MSC_VER >= 1900) +# include +#else +# include +#endif + +static int64_t gettime_i64(void) { +#if (defined(_MSC_VER) && _MSC_VER >= 1900) + /* C11 way to get wallclock time */ + struct timespec tv; + if (!timespec_get(&tv, TIME_UTC)) { + fputs("timespec_get failed!", stderr); + exit(EXIT_FAILURE); + } + return (int64_t)tv.tv_nsec / 1000 + (int64_t)tv.tv_sec * 1000000LL; +#else + struct timeval tv; + gettimeofday(&tv, NULL); + return (int64_t)tv.tv_usec + (int64_t)tv.tv_sec * 1000000LL; +#endif +} + +#endif /* SECP256K1_TESTS_COMMON_H */ diff --git a/src/unit_test.c b/src/unit_test.c new file mode 100644 index 0000000000..a1858a117a --- /dev/null +++ b/src/unit_test.c @@ -0,0 +1,479 @@ +/*********************************************************************** + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php.* + ***********************************************************************/ + +#include +#include +#include + +#if defined(SUPPORTS_CONCURRENCY) +#include +#include +#include +#endif + +#include "unit_test.h" +#include "testrand.h" +#include "tests_common.h" + +#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); +static int parse_target(const char* key, const char* value, struct tf_framework* tf); +static int parse_logging(const char* key, const char* value, struct tf_framework* tf); + +/* 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[] = { + { "t", parse_target }, { "target", parse_target }, + { "j", parse_jobs_count }, { "jobs", parse_jobs_count }, + { "i", parse_iterations }, { "iterations", parse_iterations }, + { "seed", parse_seed }, + { "log", parse_logging }, + { 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; +} + +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"); + printf(" --list_tests, -l Display list of all available tests and modules\n"); + printf(" --jobs=, -j= Number of parallel worker processes (default: 0 = sequential)\n"); + printf(" --iterations=, -i= Number of iterations for each test (default: 16)\n"); + printf(" --seed= Set a specific RNG seed (default: random)\n"); + printf(" --target=, -t= Run a specific test (can be provided multiple times)\n"); + printf(" --target=, -t= Run all tests within a specific module (can be provided multiple times)\n"); + printf(" --log=<0|1> Enable or disable test execution logging (default: 0 = disabled)\n"); + 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"); +} + +/* 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=\n"); + printf("Run specific test: ./tests -t=\n\n"); +} + +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; +} + +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; +} + +/* 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; +} + +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" + "Run program with -list_tests option to display available tests and modules.\n", value); + return -1; +} + +/* 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) { + /* Allowed options without value */ + if (strcmp(key, "h") == 0 || strcmp(key, "help") == 0) { + tf->args.help = 1; + return 0; + } + if (strcmp(key, "l") == 0 || strcmp(key, "list_tests") == 0) { + tf->args.list_tests = 1; + return 0; + } + 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; +} + +static void run_test_log(const struct tf_test_entry* t) { + int64_t start_time = gettime_i64(); + printf("Running %s..\n", t->name); + t->func(); + printf("Test %s PASSED (%.3f sec)\n", t->name, (double)(gettime_i64() - start_time) / 1000000); +} + +static void run_test(const struct tf_test_entry* t) { t->func(); } + +/* Process tests in sequential order */ +static int run_sequential(struct tf_framework* tf) { + int it; + for (it = 0; it < tf->args.targets.size; it++) { + tf->fn_run_test(tf->args.targets.slots[it]); + } + return EXIT_SUCCESS; +} + +#if defined(SUPPORTS_CONCURRENCY) +static const int MAX_TARGETS = 255; + +/* 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 */ + 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); + } + + + 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 */ + while (read(pipefd[0], &idx, sizeof(idx)) == sizeof(idx)) { + tf->fn_run_test(tf->args.targets.slots[(int)idx]); + } + _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 */ + 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; + } + } + /* 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; + tf->args.help = 0; + tf->args.targets.size = 0; + tf->args.list_tests = 0; + tf->args.logging = 0; + + /* 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; + } + + if (tf->args.help) { + help(); + exit(EXIT_SUCCESS); + } + + if (tf->args.list_tests) { + print_test_list(tf); + exit(EXIT_SUCCESS); + } + } + + tf->fn_run_test = tf->args.logging ? run_test_log : run_test; + return EXIT_SUCCESS; +} + +static int tf_run(struct tf_framework* tf) { + /* Process exit status */ + int status; + /* Whether to run all tests */ + int run_all; + /* Loop iterator */ + int it; + /* Initial test time */ + int64_t start_time = gettime_i64(); + /* 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; + } + + /* 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]; + } + } + } + + if (!tf->args.logging) printf("Tests running silently. Use '-log=1' to enable detailed logging\n"); + + /* 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++) { + if (run_all) { /* future: support filtering */ + tf->fn_run_test(&tf->registry_no_rng->data[it]); + } + } + + /* 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; +} diff --git a/src/unit_test.h b/src/unit_test.h new file mode 100644 index 0000000000..bf301e5392 --- /dev/null +++ b/src/unit_test.h @@ -0,0 +1,145 @@ +/*********************************************************************** + * Distributed under the MIT software license, see the accompanying * + * file COPYING or https://www.opensource.org/licenses/mit-license.php.* + ***********************************************************************/ + +#ifndef SECP256K1_UNIT_TEST_H +#define SECP256K1_UNIT_TEST_H + +/* --------------------------------------------------------- */ +/* Configurable constants */ +/* --------------------------------------------------------- */ + +/* Maximum number of command-line arguments. + * Must be at least as large as the total number of tests + * to allow specifying all tests individually. */ +#define MAX_ARGS 150 +/* Maximum number of parallel jobs */ +#define MAX_SUBPROCESSES 16 + +/* --------------------------------------------------------- */ +/* Test Framework Registry Macros */ +/* --------------------------------------------------------- */ + +#define CASE(name) { #name, run_##name } +#define CASE1(name) { #name, name } + +#define MAKE_TEST_MODULE(name) { \ + #name, \ + tests_##name, \ + sizeof(tests_##name) / sizeof(tests_##name[0]) \ +} + +/* Macro to wrap a test internal function with a COUNT loop (iterations number) */ +#define REPEAT_TEST(fn) REPEAT_TEST_MULT(fn, 1) +#define REPEAT_TEST_MULT(fn, multiplier) \ + static void fn(void) { \ + int i; \ + int repeat = COUNT * (multiplier); \ + for (i = 0; i < repeat; i++) \ + fn##_internal(); \ + } + + + +/* --------------------------------------------------------- */ +/* Test Framework API */ +/* --------------------------------------------------------- */ + +typedef void (*test_fn)(void); + +struct tf_test_entry { + const char* name; + test_fn func; +}; + +struct tf_test_module { + const char* name; + const struct tf_test_entry* data; + int size; +}; + +typedef int (*setup_ctx_fn)(void); +typedef int (*teardown_fn)(void); +typedef void (*run_test_fn)(const struct tf_test_entry*); + +struct tf_targets { + /* Target tests indexes */ + const struct tf_test_entry* slots[MAX_ARGS]; + /* Next available slot */ + int size; +}; + +/* --- Command-line args --- */ +struct tf_args { + /* 0 => sequential; 1..MAX_SUBPROCESSES => parallel workers */ + int num_processes; + /* Specific RNG seed */ + const char* custom_seed; + /* Whether to print the help msg */ + int help; + /* Whether to print the tests list msg */ + int list_tests; + /* Target tests indexes */ + struct tf_targets targets; + /* Enable test execution logging */ + int logging; +}; + +/* --------------------------------------------------------- */ +/* Public API */ +/* --------------------------------------------------------- */ + +struct tf_framework { + /* Command-line args */ + struct tf_args args; + /* Test modules registry */ + const struct tf_test_module* registry_modules; + /* Num of modules */ + int num_modules; + /* Registry for tests that require no RNG init */ + const struct tf_test_module* registry_no_rng; + /* Specific context setup and teardown functions */ + setup_ctx_fn fn_setup; + teardown_fn fn_teardown; + /* Test runner function (can be customized) */ + run_test_fn fn_run_test; +}; + +/* + * Initialize the test framework. + * + * Must be called before tf_run() and before any output is performed to + * stdout or stderr, because this function disables buffering on both + * streams to ensure reliable diagnostic output. + * + * Parses command-line arguments and configures the framework context. + * The caller must initialize the following members of 'tf' before calling: + * - tf->registry_modules + * - tf->num_modules + * + * Side effects: + * - stdout and stderr are set to unbuffered mode via setbuf(). + * This allows immediate flushing of diagnostic messages but may + * affect performance for other output operations. + * + * Returns: + * EXIT_SUCCESS (0) on success, + * EXIT_FAILURE (non-zero) on error. + */ +static int tf_init(struct tf_framework* tf, int argc, char** argv); + +/* + * Run tests based on the provided test framework context. + * + * This function uses the configuration stored in the tf_framework + * (targets, number of processes, iteration count, etc.) to determine + * which tests to execute and how to execute them. + * + * Returns: + * EXIT_SUCCESS (0) if all tests passed, + * EXIT_FAILURE (non-zero) otherwise. + */ +static int tf_run(struct tf_framework* tf); + +#endif /* SECP256K1_UNIT_TEST_H */