From cd7937ce2d313715f380eafd15b20d429b65c83c Mon Sep 17 00:00:00 2001 From: Ryan Ofsky Date: Wed, 20 Aug 2025 14:13:17 -0400 Subject: [PATCH] test: add tool_bitcoin to test bitcoin wrapper behavior Github-Pull: #33229 Rebased-From: 29e836fae660d9a89c54a094ae1a032e6a88c334 --- test/functional/test_runner.py | 1 + test/functional/tool_bitcoin.py | 102 ++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100755 test/functional/tool_bitcoin.py diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index fc86033800e..9d556d1cd7b 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -336,6 +336,7 @@ BASE_SCRIPTS = [ 'p2p_tx_privacy.py', 'rpc_getdescriptoractivity.py', 'rpc_scanblocks.py', + 'tool_bitcoin.py', 'p2p_sendtxrcncl.py', 'rpc_scantxoutset.py', 'feature_unsupported_utxo_db.py', diff --git a/test/functional/tool_bitcoin.py b/test/functional/tool_bitcoin.py new file mode 100755 index 00000000000..dcfdb2d774b --- /dev/null +++ b/test/functional/tool_bitcoin.py @@ -0,0 +1,102 @@ +#!/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. +"""Test the bitcoin wrapper tool.""" +from test_framework.test_framework import ( + BitcoinTestFramework, + SkipTest, +) +from test_framework.util import assert_equal + +import platform +import re + + +class ToolBitcoinTest(BitcoinTestFramework): + def set_test_params(self): + self.setup_clean_chain = True + self.num_nodes = 1 + + def skip_test_if_missing_module(self): + # Skip test on windows because currently when `bitcoin node -version` is + # run on windows, python doesn't capture output from the child + # `bitcoind` and `bitcoin-node` process started with _wexecvp, and + # stdout/stderr are always empty. See + # https://github.com/bitcoin/bitcoin/pull/33229#issuecomment-3265524908 + if platform.system() == "Windows": + raise SkipTest("Test does not currently work on windows") + + def setup_network(self): + """Set up nodes normally, but save a copy of their arguments before starting them.""" + self.add_nodes(self.num_nodes, self.extra_args) + node_argv = self.get_binaries().node_argv() + self.node_options = [node.args[len(node_argv):] for node in self.nodes] + assert all(node.args[:len(node_argv)] == node_argv for node in self.nodes) + + def set_cmd_args(self, node, args): + """Set up node so it will be started through bitcoin wrapper command with specified arguments.""" + node.args = [self.binary_paths.bitcoin_bin] + args + ["node"] + self.node_options[node.index] + + def test_args(self, cmd_args, node_args, expect_exe=None, expect_error=None): + node = self.nodes[0] + self.set_cmd_args(node, cmd_args) + extra_args = node_args + ["-version"] + if expect_error is not None: + node.assert_start_raises_init_error(expected_msg=expect_error, extra_args=extra_args) + else: + assert expect_exe + node.start(extra_args=extra_args) + ret, out, err = get_node_output(node) + try: + assert_equal(get_exe_name(out), expect_exe.encode()) + assert_equal(err, b"") + except Exception as e: + raise RuntimeError(f"Unexpected output from {node.args + extra_args}: {out=!r} {err=!r} {ret=!r}") from e + + def run_test(self): + self.log.info("Ensure bitcoin node command invokes bitcoind by default") + self.test_args([], [], expect_exe="bitcoind") + + self.log.info("Ensure bitcoin command does not accept -ipcbind by default") + self.test_args(["-M"], ["-ipcbind=unix"], expect_error='Error: Error parsing command line arguments: Invalid parameter -ipcbind=unix') + + self.log.info("Ensure bitcoin -M invokes bitcoind") + self.test_args(["-M"], [], expect_exe="bitcoind") + + self.log.info("Ensure bitcoin -M does not accept -ipcbind") + self.test_args(["-M"], ["-ipcbind=unix"], expect_error='Error: Error parsing command line arguments: Invalid parameter -ipcbind=unix') + + if self.is_ipc_compiled(): + self.log.info("Ensure bitcoin -m invokes bitcoin-node") + self.test_args(["-m"], [], expect_exe="bitcoin-node") + + self.log.info("Ensure bitcoin -m does accept -ipcbind") + self.test_args(["-m"], ["-ipcbind=unix"], expect_exe="bitcoin-node") + + +def get_node_output(node): + ret = node.process.wait(timeout=60) + node.stdout.seek(0) + node.stderr.seek(0) + out = node.stdout.read() + err = node.stderr.read() + node.stdout.close() + node.stderr.close() + + # Clean up TestNode state + node.running = False + node.process = None + node.rpc_connected = False + node.rpc = None + + return ret, out, err + + +def get_exe_name(version_str): + """Get exe name from last word of first line of version string.""" + return re.match(rb".*?(\S+)\s*?(?:\n|$)", version_str.strip()).group(1) + + +if __name__ == '__main__': + ToolBitcoinTest(__file__).main()