-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Introduce (mini) unit test framework #1734
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
16822f5
to
5fcb69c
Compare
Nice! I looked at existing unit test frameworks in the past, but nothing seemed appropriate for us. There are not that many for C, and they were either overkill or too simple (just handful of ifdefs) so they didn't add any functionality. I thought writing our own is too annoying (or I was just lazy). But the framework is ~300 lines, that seems fine to me. |
see also #1211 |
b1de641
to
7b184a1
Compare
9389623
to
f49e570
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @furszy. I played around with this a little bit. It reduces the execution time on my machine from 26 seconds to 10 seconds (-jobs=16
). Very nice! Some observations:
- It would be helpful if a helptext would be output when
tests
is run with-h
or--help
. - I think showing all the tests that have passed is a bit overkill. I'm already assuming that the tests pass if they do not show up in the output. I only need to see tests that don't pass.
- Maybe a future PR can autodetect the number of cores and set
-jobs
automatically by default? - There's an
-iter
command line flag, but the test output shows "test count
". It would be better if we were consistent.
|
af43348
to
0b3c74f
Compare
Thanks for the review jonasnick!
Awesome :). I think we can actually do even better, will do some changes.
The help message was actually already there, but for
Sure. Will hide the logging behind a
I'm not sure we want that. Sequential execution is usually "standard" on any system because we don’t know what else the user might be running. Picking a number of parallel tasks automatically (even if it is a low number) could hang the CPU or even make it run slower than sequential if the system is overloaded.
Sure 👍🏼. That was carried over from the previous code; will improve it. |
Yeah. Just reworked the framework to support registering and running groups of tests in a generic manner. This means we can now run specific tests and/or specific groups of tests via the On top of that, made the framework reusable across binaries and improved the overall API (we can now easily connect the Other than that, the A simple usage example: |
0b3c74f
to
4900aee
Compare
4900aee
to
aa5f041
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Concept ACK
Tested this quickly on an arm64 machine and observed a nice >2x speedup (~24.4s j=1
vs. ~11.1s j=6
), also played around running only tests of certain modules, which worked as expected. Left just a few first-look nits.
785c34e
to
5a50106
Compare
Updated per feedback, thanks theStack and hebasto! |
pid = fork(); | ||
if (pid < 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: It would be good to check return values of the pipe()
and fork()
calls consistently, for example ...==-1
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure these two functions are directly comparable. They have different return value ranges: pipe()
just indicates success or failure (0
or -1
), while fork()
has three cases: -1
for an error, 0
in the child process, and any value greater than 0
is the child’s PID in the parent. So it seemed slightly more accurate to me to describe the entire range for fork()
.
#define CASE(name) { #name, run_##name } | ||
#define CASE1(name) { #name, name } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's unfortunate to have such a pair of confusing macros. Perhaps their naming could be improved, although I don’t have a concrete suggestion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's unfortunate to have such a pair of confusing macros. Perhaps their naming could be improved, although I don’t have a concrete suggestion.
A small scripted-diff would improve the situation and let us use only one of them. We just need to rename the test functions that start with "run_*" so that is not included in the name. I just tried to avoid expanding the scope of the PR further.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Approach ACK 5a50106.
I've completed my first round of reviewing. The code looks good.
Relocate the clock time getter to tests_common.h to make it easily reusable across test programs. This will be useful for the upcoming unit test framework. Context - why not placing it inside testutil.h?: The bench program links against the production-compiled library, not its own compiled version. Therefore, `gettime_i64()` cannot be moved to testutil.h, because testutil.h calls `secp256k1_pubkey_save()`, which exists only in the internal secp256k1.c and not in the public API.
5a50106
to
726e70b
Compare
Updated per feedback, thanks Hebasto! |
I like this a lot. Approach ACK 726e70b |
726e70b
to
aaacb77
Compare
Updated per feedback. Thanks john-moffett! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice! useful to be able to run a subset of tests.
fputs("An iteration count of 0 or less is not allowed.\n", stderr); | ||
return -1; | ||
} | ||
printf("Iterations count = %i\n", COUNT); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
0423532: also don't see the
Iterations count =
log in the CI. I guess this is related to the environment variable not being set.
Yeah. We only print when the arg is provided by the user.
maybe useful to always print the log even if it's the default value being used/-iters isn't used ?
We should probably unify all args prints within a single place too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two more nits and one observation. Otherwise all looks good.
aaacb77
to
906b45a
Compare
Lightweight unit testing framework, providing a structured way to define, execute, and report tests. It includes a central test registry, a flexible command-line argument parser of the form "-key=value" (facilitating future framework extensions), ability to run tests in parallel and accumulated test time logging reports. So far the supported command-line args are: - "-jobs=<num>" to specify the number of parallel workers. - "-seed=<hex>" to specify the context seed (random if not set). - "-iterations=<value>" to specify the number of iterations. Compatibility Note: To stay compatible with previous versions, the framework also supports the two original positional arguments: the iterations count and the RNG seed (in that order).
This not only provides a structural improvement but also allows us to (1) specify individual tests to run and (2) execute each of them concurrently.
Add a help message for the test suite, documenting available options, defaults, and backward-compatible positional arguments.
Add support for specifying single tests or modules to run via the "-target" or "-t" command-line option. Multiple targets can be provided; only the specified tests or all tests in the specified module/s will run instead of the full suite. Examples: -t=<test name> runs an specific test. -t=<module name> runs all tests within the specified module. Both options can be provided multiple times.
Useful option to avoid opening the large tests.c file just to find the test case you want to run.
When enabled (-log=1), shows test start, completion, and execution time.
906b45a
to
7b51e53
Compare
ACK 7b51e53 |
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).-iter=<n>
: specify the number of iterations.-print_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)