test: add functional test for `TestShell` (matching doc example)
What changed, and why it matters
This commit adds a new automated test for Bitcoin Core's developer testing tool called TestShell. It does not change any production code, network rules, or wallet behavior. It only runs in the project's own test environment and is not a security fix or vulnerability.
No security action needed. Treat as routine test-coverage improvement.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit introduces test/functional/feature_framework_testshell.py, which exercises the TestShell interactive-test helper by reproducing the minimal documented example: setting up a 2-node regtest chain, verifying only one TestShell instance can run, optionally creating a wallet, mining 101 blocks, and checking balances. The test is registered in test_runner.py’s BASE_SCRIPTS list so it runs in CI. No consensus, P2P, RPC, or wallet implementation code is modified.
Changed components
test/functional/feature_framework_testshell.pytest/functional/test_runner.pyInspect captured patch +48 / −0
diff --git a/test/functional/feature_framework_testshell.py b/test/functional/feature_framework_testshell.py
new file mode 100755
index 00000000..cb39d9d0
--- /dev/null
+++ b/test/functional/feature_framework_testshell.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python3
+# Copyright (c) The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+"""Tests for the `TestShell` submodule."""
+
+from decimal import Decimal
+from pathlib import Path
+
+# Note that we need to import from functional test framework modules
+# *after* extending the Python path via sys.path.insert(0, ...) below,
+# in order to work with the full symlinked (unresolved) path within the
+# build directory (usually ./build/test/functional).
+
+
+# Test matching the minimal example from the documentation. Should be kept
+# in sync with the interactive shell instructions ('>>> ') in test-shell.md.
+def run_testshell_doc_example(functional_tests_dir):
+ import sys
+ sys.path.insert(0, functional_tests_dir)
+ from test_framework.test_shell import TestShell
+ from test_framework.util import assert_equal
+
+ test = TestShell().setup(num_nodes=2, setup_clean_chain=True)
+ try:
+ assert test is not None
+ test2 = TestShell().setup()
+ assert test2 is None # TODO: check for "TestShell is already running!" output to stdout
+ assert_equal(test.nodes[0].getblockchaininfo()["blocks"], 0)
+ if test.is_wallet_compiled():
+ res = test.nodes[0].createwallet('default')
+ assert_equal(res, {'name': 'default'})
+ address = test.nodes[0].getnewaddress()
+ res = test.generatetoaddress(test.nodes[0], 101, address)
+ assert_equal(len(res), 101)
+ test.sync_blocks()
+ assert_equal(test.nodes[1].getblockchaininfo()["blocks"], 101)
+ assert_equal(test.nodes[0].getbalance(), Decimal('50.0'))
+ test.nodes[0].log.info("Successfully mined regtest chain!")
+ finally:
+ test.shutdown()
+ test.reset()
+ assert test.num_nodes is None
+
+
+if __name__ == "__main__":
+ run_testshell_doc_example(str(Path(__file__).parent))
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index 5e820e43..008ec7b5 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -360,6 +360,7 @@ BASE_SCRIPTS = [
'rpc_getdescriptorinfo.py',
'rpc_mempool_info.py',
'rpc_help.py',
+ 'feature_framework_testshell.py',
'tool_rpcauth.py',
'p2p_handshake.py',
'p2p_handshake.py --v2transport',
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.