Skip to content

Commit d543c0d

Browse files
Merge #1734: Introduce (mini) unit test framework
2f4546c test: add --log option to display tests execution (furszy) 95b9953 test: Add option to display all available tests (furszy) 953f7b0 test: support running specific tests/modules targets (furszy) 0302c1a test: add --help for command-line options (furszy) 9ec3bfe test: adapt modules to the new test infrastructure (furszy) 48789da test: introduce (mini) unit test framework (furszy) 9cce703 refactor: move 'gettime_i64()' to tests_common.h (furszy) Pull request description: Early Note: Don’t be scared by the PR’s line changes count — most of it’s just doc or part of the test framework API. Context: Currently, all tests run single-threaded sequentially and the library lacks the ability to specify which test (or group of tests) you would like to run. This is not only inconvenient as more tests are added but also time consuming during development and affects downstream projects that may want to parallelize the workload (such as Bitcoin-Core CI). PR Goal: Introduce a lightweight, extensible C89 unit test framework with no dynamic memory allocations, providing a structured way to register, execute, and report tests. The framework supports named command-line arguments in `-key=value` form, parallel test execution across multiple worker processes, granular test selection (selecting tests either by name or by module name), and time accumulation reports. The introduced framework supports: * `-help` or `-h`: display list of available commands along with their descriptions. * `-jobs=<num>`: distribute tests across multiple worker processes (default: sequential if 0). * `-target=<name>` or `-t=<name>`: run only specific tests by name; can be repeated to select multiple tests. * `-target=<module name>`, `-t=<module>` Run all tests within a specific module (can be provided multiple times) * `-seed=<hex>`: set a specific RNG seed (defaults to random if unspecified). * `-iterations=<n>`: specify the number of iterations. * `-list_tests`: display list of available tests and modules you can run. * `-log=<0|1>`: enable or disable test execution logging (default: 0 = disabled). Beyond these features, the idea is to also make future developments smoother, as adding new tests require only a single entry in the central test registry, and new command-line options can be introduced easily by extending the framework’s `parse_arg()` function. Compatibility Note: The framework continues accepting the two positional arguments previously supported (iterations and seed), ensuring existing workflows remain intact. Testing Notes: Have fun. You can quickly try it through `./tests -j=<workers_num>` for parallel execution or `./tests -t=<test_name>` to run a specific test (call `./tests -print_tests` to display all available tests and modules). Extra Note: I haven't checked the exhaustive tests file so far, but I will soon. For now, this only runs all tests declared in the `tests` binary. Testing Results: (Current master branch vs PR in seconds) * Raspberry Pi 5: master \~100 s → PR \~38 s (5 jobs) * MacBook Pro M1: master \~30 s → PR \~10 s (6 jobs) ACKs for top commit: theStack: re-ACK 2f4546c real-or-random: ACK 2f4546c hebasto: ACK 2f4546c. Tree-SHA512: 85ca2cbb620b84b35b353d5d4cf093c388fc3851ca405eeb0e458f8fa72b60534bccd357c7edabf8fc9aa93d9ad0a6fbac3dd5c4d5f9dfdf4d8701a9834755b9
2 parents f44c1eb + 2f4546c commit d543c0d

File tree

14 files changed

+929
-239
lines changed

14 files changed

+929
-239
lines changed

Makefile.am

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ noinst_HEADERS += src/precomputed_ecmult.h
4545
noinst_HEADERS += src/precomputed_ecmult_gen.h
4646
noinst_HEADERS += src/assumptions.h
4747
noinst_HEADERS += src/checkmem.h
48+
noinst_HEADERS += src/tests_common.h
4849
noinst_HEADERS += src/testutil.h
50+
noinst_HEADERS += src/unit_test.h
51+
noinst_HEADERS += src/unit_test.c
4952
noinst_HEADERS += src/util.h
5053
noinst_HEADERS += src/util_local_visibility.h
5154
noinst_HEADERS += src/int128.h
@@ -120,7 +123,7 @@ if USE_TESTS
120123
TESTS += noverify_tests
121124
noinst_PROGRAMS += noverify_tests
122125
noverify_tests_SOURCES = src/tests.c
123-
noverify_tests_CPPFLAGS = $(SECP_CONFIG_DEFINES)
126+
noverify_tests_CPPFLAGS = $(SECP_CONFIG_DEFINES) $(TEST_DEFINES)
124127
noverify_tests_LDADD = $(COMMON_LIB) $(PRECOMPUTED_LIB)
125128
noverify_tests_LDFLAGS = -static
126129
if !ENABLE_COVERAGE

configure.ac

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,14 @@ if test x"$enable_experimental" = x"no"; then
443443
fi
444444
fi
445445

446+
# Check for concurrency support (tests only)
447+
if test "x$enable_tests" != x"no"; then
448+
AC_CHECK_HEADERS([sys/types.h sys/wait.h unistd.h])
449+
AS_IF([test "x$ac_cv_header_sys_types_h" = xyes && test "x$ac_cv_header_sys_wait_h" = xyes &&
450+
test "x$ac_cv_header_unistd_h" = xyes], [TEST_DEFINES="-DSUPPORTS_CONCURRENCY=1"], TEST_DEFINES="")
451+
AC_SUBST(TEST_DEFINES)
452+
fi
453+
446454
###
447455
### Generate output
448456
###

src/CMakeLists.txt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,15 +134,27 @@ if(SECP256K1_BUILD_BENCHMARK)
134134
endif()
135135

136136
if(SECP256K1_BUILD_TESTS)
137+
include(CheckIncludeFile)
138+
check_include_file(sys/types.h HAVE_SYS_TYPES_H)
139+
check_include_file(sys/wait.h HAVE_SYS_WAIT_H)
140+
check_include_file(unistd.h HAVE_UNISTD_H)
141+
142+
set(TEST_DEFINITIONS "")
143+
if(HAVE_SYS_TYPES_H AND HAVE_SYS_WAIT_H AND HAVE_UNISTD_H)
144+
list(APPEND TEST_DEFINITIONS SUPPORTS_CONCURRENCY=1)
145+
endif()
146+
137147
add_executable(noverify_tests tests.c)
138148
target_link_libraries(noverify_tests secp256k1_precomputed secp256k1_asm)
149+
target_compile_definitions(noverify_tests PRIVATE ${TEST_DEFINITIONS})
139150
add_test(NAME secp256k1_noverify_tests COMMAND noverify_tests)
140151
if(NOT CMAKE_BUILD_TYPE STREQUAL "Coverage")
141152
add_executable(tests tests.c)
142-
target_compile_definitions(tests PRIVATE VERIFY)
153+
target_compile_definitions(tests PRIVATE VERIFY ${TEST_DEFINITIONS})
143154
target_link_libraries(tests secp256k1_precomputed secp256k1_asm)
144155
add_test(NAME secp256k1_tests COMMAND tests)
145156
endif()
157+
unset(TEST_DEFINITIONS)
146158
endif()
147159

148160
if(SECP256K1_BUILD_EXHAUSTIVE_TESTS)

src/bench.h

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -12,27 +12,7 @@
1212
#include <stdio.h>
1313
#include <string.h>
1414

15-
#if (defined(_MSC_VER) && _MSC_VER >= 1900)
16-
# include <time.h>
17-
#else
18-
# include <sys/time.h>
19-
#endif
20-
21-
static int64_t gettime_i64(void) {
22-
#if (defined(_MSC_VER) && _MSC_VER >= 1900)
23-
/* C11 way to get wallclock time */
24-
struct timespec tv;
25-
if (!timespec_get(&tv, TIME_UTC)) {
26-
fputs("timespec_get failed!", stderr);
27-
exit(EXIT_FAILURE);
28-
}
29-
return (int64_t)tv.tv_nsec / 1000 + (int64_t)tv.tv_sec * 1000000LL;
30-
#else
31-
struct timeval tv;
32-
gettimeofday(&tv, NULL);
33-
return (int64_t)tv.tv_usec + (int64_t)tv.tv_sec * 1000000LL;
34-
#endif
35-
}
15+
#include "tests_common.h"
3616

3717
#define FP_EXP (6)
3818
#define FP_MULT (1000000LL)

src/modules/ecdh/tests_impl.h

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
#ifndef SECP256K1_MODULE_ECDH_TESTS_H
88
#define SECP256K1_MODULE_ECDH_TESTS_H
99

10+
#include "../../unit_test.h"
11+
1012
static int ecdh_hash_function_test_xpassthru(unsigned char *output, const unsigned char *x, const unsigned char *y, void *data) {
1113
(void)y;
1214
(void)data;
@@ -178,12 +180,13 @@ static void test_ecdh_wycheproof(void) {
178180
}
179181
}
180182

181-
static void run_ecdh_tests(void) {
182-
test_ecdh_api();
183-
test_ecdh_generator_basepoint();
184-
test_bad_scalar();
185-
test_result_basepoint();
186-
test_ecdh_wycheproof();
187-
}
183+
/* --- Test registry --- */
184+
static const struct tf_test_entry tests_ecdh[] = {
185+
CASE1(test_ecdh_api),
186+
CASE1(test_ecdh_generator_basepoint),
187+
CASE1(test_bad_scalar),
188+
CASE1(test_result_basepoint),
189+
CASE1(test_ecdh_wycheproof),
190+
};
188191

189192
#endif /* SECP256K1_MODULE_ECDH_TESTS_H */

src/modules/ellswift/tests_impl.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#define SECP256K1_MODULE_ELLSWIFT_TESTS_H
88

99
#include "../../../include/secp256k1_ellswift.h"
10+
#include "../../unit_test.h"
1011

1112
struct ellswift_xswiftec_inv_test {
1213
int enc_bitmap;
@@ -433,4 +434,10 @@ void run_ellswift_tests(void) {
433434
}
434435
}
435436

437+
/* --- Test registry --- */
438+
/* TODO: subdivide test in cases */
439+
static const struct tf_test_entry tests_ellswift[] = {
440+
CASE(ellswift_tests),
441+
};
442+
436443
#endif

src/modules/extrakeys/tests_impl.h

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#define SECP256K1_MODULE_EXTRAKEYS_TESTS_H
99

1010
#include "../../../include/secp256k1_extrakeys.h"
11+
#include "../../unit_test.h"
1112

1213
static void test_xonly_pubkey(void) {
1314
secp256k1_pubkey pk;
@@ -467,17 +468,17 @@ static void test_keypair_add(void) {
467468
}
468469
}
469470

470-
static void run_extrakeys_tests(void) {
471+
/* --- Test registry --- */
472+
static const struct tf_test_entry tests_extrakeys[] = {
471473
/* xonly key test cases */
472-
test_xonly_pubkey();
473-
test_xonly_pubkey_tweak();
474-
test_xonly_pubkey_tweak_check();
475-
test_xonly_pubkey_tweak_recursive();
476-
test_xonly_pubkey_comparison();
477-
474+
CASE1(test_xonly_pubkey),
475+
CASE1(test_xonly_pubkey_tweak),
476+
CASE1(test_xonly_pubkey_tweak_check),
477+
CASE1(test_xonly_pubkey_tweak_recursive),
478+
CASE1(test_xonly_pubkey_comparison),
478479
/* keypair tests */
479-
test_keypair();
480-
test_keypair_add();
481-
}
480+
CASE1(test_keypair),
481+
CASE1(test_keypair_add),
482+
};
482483

483484
#endif

src/modules/musig/tests_impl.h

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "../../group.h"
2121
#include "../../hash.h"
2222
#include "../../util.h"
23+
#include "../../unit_test.h"
2324

2425
#include "vectors.h"
2526

@@ -36,7 +37,7 @@ static int create_keypair_and_pk(secp256k1_keypair *keypair, secp256k1_pubkey *p
3637

3738
/* Just a simple (non-tweaked) 2-of-2 MuSig aggregate, sign, verify
3839
* test. */
39-
static void musig_simple_test(void) {
40+
static void musig_simple_test_internal(void) {
4041
unsigned char sk[2][32];
4142
secp256k1_keypair keypair[2];
4243
secp256k1_musig_pubnonce pubnonce[2];
@@ -629,7 +630,7 @@ static void musig_tweak_test_helper(const secp256k1_xonly_pubkey* agg_pk, const
629630

630631
/* Create aggregate public key P[0], tweak multiple times (using xonly and
631632
* plain tweaking) and test signing. */
632-
static void musig_tweak_test(void) {
633+
static void musig_tweak_test_internal(void) {
633634
unsigned char sk[2][32];
634635
secp256k1_pubkey pk[2];
635636
const secp256k1_pubkey *pk_ptr[2];
@@ -1114,28 +1115,24 @@ static void musig_test_static_nonce_gen_counter(void) {
11141115
CHECK(secp256k1_memcmp_var(pubnonce66, expected_pubnonce, sizeof(pubnonce66)) == 0);
11151116
}
11161117

1117-
static void run_musig_tests(void) {
1118-
int i;
1119-
1120-
for (i = 0; i < COUNT; i++) {
1121-
musig_simple_test();
1122-
}
1123-
musig_api_tests();
1124-
musig_nonce_test();
1125-
for (i = 0; i < COUNT; i++) {
1126-
/* Run multiple times to ensure that pk and nonce have different y
1127-
* parities */
1128-
musig_tweak_test();
1129-
}
1130-
sha256_tag_test();
1131-
musig_test_vectors_keyagg();
1132-
musig_test_vectors_noncegen();
1133-
musig_test_vectors_nonceagg();
1134-
musig_test_vectors_signverify();
1135-
musig_test_vectors_tweak();
1136-
musig_test_vectors_sigagg();
1137-
1138-
musig_test_static_nonce_gen_counter();
1139-
}
1118+
/* --- Test registry --- */
1119+
REPEAT_TEST(musig_simple_test)
1120+
/* Run multiple times to ensure that pk and nonce have different y parities */
1121+
REPEAT_TEST(musig_tweak_test)
1122+
1123+
static const struct tf_test_entry tests_musig[] = {
1124+
CASE1(musig_simple_test),
1125+
CASE1(musig_api_tests),
1126+
CASE1(musig_nonce_test),
1127+
CASE1(musig_tweak_test),
1128+
CASE1(sha256_tag_test),
1129+
CASE1(musig_test_vectors_keyagg),
1130+
CASE1(musig_test_vectors_noncegen),
1131+
CASE1(musig_test_vectors_nonceagg),
1132+
CASE1(musig_test_vectors_signverify),
1133+
CASE1(musig_test_vectors_tweak),
1134+
CASE1(musig_test_vectors_sigagg),
1135+
CASE1(musig_test_static_nonce_gen_counter),
1136+
};
11401137

11411138
#endif

src/modules/recovery/tests_impl.h

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
#ifndef SECP256K1_MODULE_RECOVERY_TESTS_H
88
#define SECP256K1_MODULE_RECOVERY_TESTS_H
99

10+
#include "../../unit_test.h"
11+
1012
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) {
1113
(void) msg32;
1214
(void) key32;
@@ -28,7 +30,7 @@ static int recovery_test_nonce_function(unsigned char *nonce32, const unsigned c
2830
return testrand_bits(1);
2931
}
3032

31-
static void test_ecdsa_recovery_api(void) {
33+
static void test_ecdsa_recovery_api_internal(void) {
3234
/* Setup contexts that just count errors */
3335
secp256k1_pubkey pubkey;
3436
secp256k1_pubkey recpubkey;
@@ -92,7 +94,7 @@ static void test_ecdsa_recovery_api(void) {
9294
CHECK(secp256k1_ecdsa_recoverable_signature_parse_compact(CTX, &recsig, sig, recid) == 0);
9395
}
9496

95-
static void test_ecdsa_recovery_end_to_end(void) {
97+
static void test_ecdsa_recovery_end_to_end_internal(void) {
9698
unsigned char extra[32] = {0x00};
9799
unsigned char privkey[32];
98100
unsigned char message[32];
@@ -324,15 +326,14 @@ static void test_ecdsa_recovery_edge_cases(void) {
324326
}
325327
}
326328

327-
static void run_recovery_tests(void) {
328-
int i;
329-
for (i = 0; i < COUNT; i++) {
330-
test_ecdsa_recovery_api();
331-
}
332-
for (i = 0; i < 64*COUNT; i++) {
333-
test_ecdsa_recovery_end_to_end();
334-
}
335-
test_ecdsa_recovery_edge_cases();
336-
}
329+
/* --- Test registry --- */
330+
REPEAT_TEST(test_ecdsa_recovery_api)
331+
REPEAT_TEST_MULT(test_ecdsa_recovery_end_to_end, 64)
332+
333+
static const struct tf_test_entry tests_recovery[] = {
334+
CASE1(test_ecdsa_recovery_api),
335+
CASE1(test_ecdsa_recovery_end_to_end),
336+
CASE1(test_ecdsa_recovery_edge_cases)
337+
};
337338

338339
#endif /* SECP256K1_MODULE_RECOVERY_TESTS_H */

src/modules/schnorrsig/tests_impl.h

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#define SECP256K1_MODULE_SCHNORRSIG_TESTS_H
99

1010
#include "../../../include/secp256k1_schnorrsig.h"
11+
#include "../../unit_test.h"
1112

1213
/* Checks that a bit flip in the n_flip-th argument (that has n_bytes many
1314
* bytes) changes the hash function
@@ -802,7 +803,7 @@ static int nonce_function_overflowing(unsigned char *nonce32, const unsigned cha
802803
return 1;
803804
}
804805

805-
static void test_schnorrsig_sign(void) {
806+
static void test_schnorrsig_sign_internal(void) {
806807
unsigned char sk[32];
807808
secp256k1_xonly_pubkey pk;
808809
secp256k1_keypair keypair;
@@ -852,7 +853,7 @@ static void test_schnorrsig_sign(void) {
852853
/* Creates N_SIGS valid signatures and verifies them with verify and
853854
* verify_batch (TODO). Then flips some bits and checks that verification now
854855
* fails. */
855-
static void test_schnorrsig_sign_verify(void) {
856+
static void test_schnorrsig_sign_verify_internal(void) {
856857
unsigned char sk[32];
857858
unsigned char msg[N_SIGS][32];
858859
unsigned char sig[N_SIGS][64];
@@ -965,18 +966,18 @@ static void test_schnorrsig_taproot(void) {
965966
CHECK(secp256k1_xonly_pubkey_tweak_add_check(CTX, output_pk_bytes, pk_parity, &internal_pk, tweak) == 1);
966967
}
967968

968-
static void run_schnorrsig_tests(void) {
969-
int i;
970-
run_nonce_function_bip340_tests();
971-
972-
test_schnorrsig_api();
973-
test_schnorrsig_sha256_tagged();
974-
test_schnorrsig_bip_vectors();
975-
for (i = 0; i < COUNT; i++) {
976-
test_schnorrsig_sign();
977-
test_schnorrsig_sign_verify();
978-
}
979-
test_schnorrsig_taproot();
980-
}
969+
/* --- Test registry --- */
970+
REPEAT_TEST(test_schnorrsig_sign)
971+
REPEAT_TEST(test_schnorrsig_sign_verify)
972+
973+
static const struct tf_test_entry tests_schnorrsig[] = {
974+
CASE(nonce_function_bip340_tests),
975+
CASE1(test_schnorrsig_api),
976+
CASE1(test_schnorrsig_sha256_tagged),
977+
CASE1(test_schnorrsig_bip_vectors),
978+
CASE1(test_schnorrsig_sign),
979+
CASE1(test_schnorrsig_sign_verify),
980+
CASE1(test_schnorrsig_taproot),
981+
};
981982

982983
#endif

0 commit comments

Comments
 (0)