Skip to content

Commit 1ffbd96

Browse files
committed
Merge bitcoin/bitcoin#29771: test: Run framework unit tests in parallel
f19f0a2 test: Run framework unit tests in parallel (tdb3) Pull request description: Functional test framework unit tests are currently run prior to all other functional tests. This PR enables execution of the test framework unit tests in parallel with the functional tests, rather than before the functional tests, saving runtime and more efficiently using available cores. This is a follow up to bitcoin/bitcoin#29470 (comment) ### New behavior: 1) When running all tests, the framework unit tests are run in parallel with the other tests (unless explicitly skipped with `--exclude`). This parallelization introduces marginal time savings when running all tests, depending on the machine used. As an example, a 2-3% time savings (9 seconds) was observed on a machine using `--jobs=18` (with 18 available cores). 2) When running specific functional tests, framework unit tests are now skipped by default. Framework unit tests can be added by including `feature_framework_unit_tests.py` in the list of specific tests being executed. The rationale for skipping by default is that if the tester is running specific functional tests, there is a conscious decision to focus testing, and choosing to run all tests (where unit tests are run by default) would be a next step. 3) The `--skipunit` option is now removed since unit tests are parallelized (they no longer delay other tests). Unit tests are treated equally as functional tests. ### Implementation notes: Since `TextTestRunner` can be noisy (even with verbosity=0, and therefore trigger job failure through the presence of non-failure stderr output), the approach taken was to send output to stdout, and forward test result (as determined by `TestResult` returned). This aligns with the previous check for unit test failure (`if not result.wasSuccessful():`). This approach was tested by inserting `self.assertEquals(True, False)` into test_framework/address.py and seeing specifics of the failure reported. ``` 135/302 - feature_framework_unit_tests.py failed, Duration: 0 s stdout: .F ====================================================================== FAIL: test_bech32_decode (test_framework.address.TestFrameworkScript.test_bech32_decode) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/dev/myrepos/bitcoin/test/functional/test_framework/address.py", line 228, in test_bech32_decode self.assertEqual(True, False) AssertionError: True != False ---------------------------------------------------------------------- Ran 2 tests in 0.003s FAILED (failures=1) stderr: ``` There was an initial thought to parallelize the execution of the unit tests themselves (i.e. run the 12 unit test files in parallel), however, this is not anticipated to further reduce runtime meaningfully and is anticipated to add unnecessary complexity. ACKs for top commit: maflcko: ACK f19f0a2 🌽 achow101: ACK f19f0a2 kevkevinpal: Approach ACK f19f0a2 Tree-SHA512: ab9f82c30371b2242bc7a263ea0e25d35e68e2ddf223d2a55498ad940d1e5b73bba76cce8b264d71e2ed31b753430d8ef8d57efc1e4fd9ced7fb845e27f4f47e
2 parents 7973a67 + f19f0a2 commit 1ffbd96

File tree

2 files changed

+53
-30
lines changed

2 files changed

+53
-30
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) 2017-2024 The Bitcoin Core developers
3+
# Distributed under the MIT software license, see the accompanying
4+
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
5+
"""Framework unit tests
6+
7+
Unit tests for the test framework.
8+
"""
9+
10+
import sys
11+
import unittest
12+
13+
from test_framework.test_framework import TEST_EXIT_PASSED, TEST_EXIT_FAILED
14+
15+
# List of framework modules containing unit tests. Should be kept in sync with
16+
# the output of `git grep unittest.TestCase ./test/functional/test_framework`
17+
TEST_FRAMEWORK_MODULES = [
18+
"address",
19+
"crypto.bip324_cipher",
20+
"blocktools",
21+
"crypto.chacha20",
22+
"crypto.ellswift",
23+
"key",
24+
"messages",
25+
"crypto.muhash",
26+
"crypto.poly1305",
27+
"crypto.ripemd160",
28+
"script",
29+
"segwit_addr",
30+
"wallet_util",
31+
]
32+
33+
34+
def run_unit_tests():
35+
test_framework_tests = unittest.TestSuite()
36+
for module in TEST_FRAMEWORK_MODULES:
37+
test_framework_tests.addTest(
38+
unittest.TestLoader().loadTestsFromName(f"test_framework.{module}")
39+
)
40+
result = unittest.TextTestRunner(stream=sys.stdout, verbosity=1, failfast=True).run(
41+
test_framework_tests
42+
)
43+
if not result.wasSuccessful():
44+
sys.exit(TEST_EXIT_FAILED)
45+
sys.exit(TEST_EXIT_PASSED)
46+
47+
48+
if __name__ == "__main__":
49+
run_unit_tests()
50+

test/functional/test_runner.py

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import tempfile
2727
import re
2828
import logging
29-
import unittest
3029

3130
os.environ["REQUIRE_WALLET_TYPE_SET"] = "1"
3231

@@ -70,23 +69,7 @@
7069
TEST_EXIT_PASSED = 0
7170
TEST_EXIT_SKIPPED = 77
7271

73-
# List of framework modules containing unit tests. Should be kept in sync with
74-
# the output of `git grep unittest.TestCase ./test/functional/test_framework`
75-
TEST_FRAMEWORK_MODULES = [
76-
"address",
77-
"crypto.bip324_cipher",
78-
"blocktools",
79-
"crypto.chacha20",
80-
"crypto.ellswift",
81-
"key",
82-
"messages",
83-
"crypto.muhash",
84-
"crypto.poly1305",
85-
"crypto.ripemd160",
86-
"script",
87-
"segwit_addr",
88-
"wallet_util",
89-
]
72+
TEST_FRAMEWORK_UNIT_TESTS = 'feature_framework_unit_tests.py'
9073

9174
EXTENDED_SCRIPTS = [
9275
# These tests are not run by default.
@@ -255,6 +238,7 @@
255238
'wallet_keypool.py --descriptors',
256239
'wallet_descriptor.py --descriptors',
257240
'p2p_nobloomfilter_messages.py',
241+
TEST_FRAMEWORK_UNIT_TESTS,
258242
'p2p_filter.py',
259243
'rpc_setban.py --v1transport',
260244
'rpc_setban.py --v2transport',
@@ -440,7 +424,6 @@ def main():
440424
parser.add_argument('--tmpdirprefix', '-t', default=tempfile.gettempdir(), help="Root directory for datadirs")
441425
parser.add_argument('--failfast', '-F', action='store_true', help='stop execution after the first test failure')
442426
parser.add_argument('--filter', help='filter scripts to run by regular expression')
443-
parser.add_argument('--skipunit', '-u', action='store_true', help='skip unit tests for the test framework')
444427

445428

446429
args, unknown_args = parser.parse_known_args()
@@ -552,10 +535,9 @@ def main():
552535
combined_logs_len=args.combinedlogslen,
553536
failfast=args.failfast,
554537
use_term_control=args.ansi,
555-
skipunit=args.skipunit,
556538
)
557539

558-
def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=False, args=None, combined_logs_len=0, failfast=False, use_term_control, skipunit=False):
540+
def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=False, args=None, combined_logs_len=0, failfast=False, use_term_control):
559541
args = args or []
560542

561543
# Warn if bitcoind is already running
@@ -578,15 +560,6 @@ def run_tests(*, test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=
578560
# a hard link or a copy on any platform. See https://github.com/bitcoin/bitcoin/pull/27561.
579561
sys.path.append(tests_dir)
580562

581-
if not skipunit:
582-
print("Running Unit Tests for Test Framework Modules")
583-
test_framework_tests = unittest.TestSuite()
584-
for module in TEST_FRAMEWORK_MODULES:
585-
test_framework_tests.addTest(unittest.TestLoader().loadTestsFromName("test_framework.{}".format(module)))
586-
result = unittest.TextTestRunner(verbosity=1, failfast=True).run(test_framework_tests)
587-
if not result.wasSuccessful():
588-
sys.exit("Early exiting after failure in TestFramework unit tests")
589-
590563
flags = ['--cachedir={}'.format(cache_dir)] + args
591564

592565
if enable_coverage:

0 commit comments

Comments
 (0)