Changeset View
Changeset View
Standalone View
Standalone View
test/functional/test_framework/test_node.py
Show First 20 Lines • Show All 57 Lines • ▼ Show 20 Lines | class TestNode(): | ||||
- a Python subprocess.Popen object representing the running process | - a Python subprocess.Popen object representing the running process | ||||
- an RPC connection to the node | - an RPC connection to the node | ||||
- one or more P2P connections to the node | - one or more P2P connections to the node | ||||
To make things easier for the test writer, any unrecognised messages will | To make things easier for the test writer, any unrecognised messages will | ||||
be dispatched to the RPC connection.""" | be dispatched to the RPC connection.""" | ||||
def __init__(self, i, datadir, *, chain, host, rpc_port, p2p_port, timewait, timeout_factor, bitcoind, bitcoin_cli, | def __init__(self, i, datadir, *, chain, host, rpc_port, p2p_port, timewait, timeout_factor, bitcoind, bitcoin_cli, | ||||
coverage_dir, cwd, extra_conf=None, extra_args=None, use_cli=False, emulator=None, start_perf=False, use_valgrind=False): | coverage_dir, cwd, extra_conf=None, extra_args=None, use_cli=False, emulator=None, start_perf=False, use_valgrind=False, descriptors=False): | ||||
""" | """ | ||||
Kwargs: | Kwargs: | ||||
start_perf (bool): If True, begin profiling the node with `perf` as soon as | start_perf (bool): If True, begin profiling the node with `perf` as soon as | ||||
the node starts. | the node starts. | ||||
""" | """ | ||||
self.index = i | self.index = i | ||||
self.datadir = datadir | self.datadir = datadir | ||||
self.bitcoinconf = os.path.join(self.datadir, "bitcoin.conf") | self.bitcoinconf = os.path.join(self.datadir, "bitcoin.conf") | ||||
self.stdout_dir = os.path.join(self.datadir, "stdout") | self.stdout_dir = os.path.join(self.datadir, "stdout") | ||||
self.stderr_dir = os.path.join(self.datadir, "stderr") | self.stderr_dir = os.path.join(self.datadir, "stderr") | ||||
self.chain = chain | self.chain = chain | ||||
self.host = host | self.host = host | ||||
self.rpc_port = rpc_port | self.rpc_port = rpc_port | ||||
self.p2p_port = p2p_port | self.p2p_port = p2p_port | ||||
self.name = "testnode-{}".format(i) | self.name = "testnode-{}".format(i) | ||||
self.rpc_timeout = timewait | self.rpc_timeout = timewait | ||||
self.binary = bitcoind | self.binary = bitcoind | ||||
if not os.path.isfile(self.binary): | if not os.path.isfile(self.binary): | ||||
raise FileNotFoundError( | raise FileNotFoundError( | ||||
"Binary '{}' could not be found.\nTry setting it manually:\n\tBITCOIND=<path/to/bitcoind> {}".format(self.binary, sys.argv[0])) | "Binary '{}' could not be found.\nTry setting it manually:\n\tBITCOIND=<path/to/bitcoind> {}".format(self.binary, sys.argv[0])) | ||||
self.coverage_dir = coverage_dir | self.coverage_dir = coverage_dir | ||||
self.cwd = cwd | self.cwd = cwd | ||||
self.descriptors = descriptors | |||||
if extra_conf is not None: | if extra_conf is not None: | ||||
append_config(datadir, extra_conf) | append_config(datadir, extra_conf) | ||||
# Most callers will just need to add extra args to the default list | # Most callers will just need to add extra args to the default list | ||||
# below. | # below. | ||||
# For those callers that need more flexibility, they can access the | # For those callers that need more flexibility, they can access the | ||||
# default args using the provided facilities. | # default args using the provided facilities. | ||||
# Note that common args are set in the config file (see | # Note that common args are set in the config file (see | ||||
# initialize_datadir) | # initialize_datadir) | ||||
▲ Show 20 Lines • Show All 114 Lines • ▼ Show 20 Lines | def __del__(self): | ||||
# Avoid using logger, as that may have already been shutdown when | # Avoid using logger, as that may have already been shutdown when | ||||
# this destructor is called. | # this destructor is called. | ||||
print(self._node_msg("Cleaning up leftover process")) | print(self._node_msg("Cleaning up leftover process")) | ||||
self.process.kill() | self.process.kill() | ||||
def __getattr__(self, name): | def __getattr__(self, name): | ||||
"""Dispatches any unrecognised messages to the RPC connection or a CLI instance.""" | """Dispatches any unrecognised messages to the RPC connection or a CLI instance.""" | ||||
if self.use_cli: | if self.use_cli: | ||||
return getattr(RPCOverloadWrapper(self.cli, True), name) | return getattr(RPCOverloadWrapper( | ||||
self.cli, True, self.descriptors), name) | |||||
else: | else: | ||||
assert self.rpc is not None, self._node_msg( | assert self.rpc is not None, self._node_msg( | ||||
"Error: RPC not initialized") | "Error: RPC not initialized") | ||||
assert self.rpc_connected, self._node_msg( | assert self.rpc_connected and self.rpc is not None, self._node_msg( | ||||
"Error: No RPC connection") | "Error: no RPC connection") | ||||
return getattr(RPCOverloadWrapper(self.rpc), name) | return getattr(RPCOverloadWrapper( | ||||
self.rpc, descriptors=self.descriptors), name) | |||||
def clear_default_args(self): | def clear_default_args(self): | ||||
self.default_args.clear() | self.default_args.clear() | ||||
def extend_default_args(self, args): | def extend_default_args(self, args): | ||||
self.default_args.extend(args) | self.default_args.extend(args) | ||||
def remove_default_args(self, args): | def remove_default_args(self, args): | ||||
▲ Show 20 Lines • Show All 155 Lines • ▼ Show 20 Lines | def generate(self, nblocks, maxtries=1000000): | ||||
self.log.debug( | self.log.debug( | ||||
"TestNode.generate() dispatches `generate` call to `generatetoaddress`") | "TestNode.generate() dispatches `generate` call to `generatetoaddress`") | ||||
return self.generatetoaddress( | return self.generatetoaddress( | ||||
nblocks=nblocks, address=self.get_deterministic_priv_key().address, maxtries=maxtries) | nblocks=nblocks, address=self.get_deterministic_priv_key().address, maxtries=maxtries) | ||||
def get_wallet_rpc(self, wallet_name): | def get_wallet_rpc(self, wallet_name): | ||||
if self.use_cli: | if self.use_cli: | ||||
return RPCOverloadWrapper( | return RPCOverloadWrapper( | ||||
self.cli("-rpcwallet={}".format(wallet_name)), True) | self.cli("-rpcwallet={}".format(wallet_name)), True, self.descriptors) | ||||
else: | else: | ||||
assert self.rpc is not None, self._node_msg( | assert self.rpc is not None, self._node_msg( | ||||
"Error: RPC not initialized") | "Error: RPC not initialized") | ||||
assert self.rpc_connected, self._node_msg( | assert self.rpc_connected, self._node_msg( | ||||
"Error: RPC not connected") | "Error: RPC not connected") | ||||
wallet_path = "wallet/{}".format(urllib.parse.quote(wallet_name)) | wallet_path = "wallet/{}".format(urllib.parse.quote(wallet_name)) | ||||
return RPCOverloadWrapper(self.rpc / wallet_path) | return RPCOverloadWrapper( | ||||
self.rpc / wallet_path, descriptors=self.descriptors) | |||||
def stop_node(self, expected_stderr='', *, wait=0, | def stop_node(self, expected_stderr='', *, wait=0, | ||||
wait_until_stopped=True): | wait_until_stopped=True): | ||||
"""Stop the node.""" | """Stop the node.""" | ||||
if not self.running: | if not self.running: | ||||
return | return | ||||
self.log.debug("Stopping node") | self.log.debug("Stopping node") | ||||
try: | try: | ||||
▲ Show 20 Lines • Show All 390 Lines • ▼ Show 20 Lines | def send_cli(self, command=None, *args, **kwargs): | ||||
returncode, self.binary, output=cli_stderr) | returncode, self.binary, output=cli_stderr) | ||||
try: | try: | ||||
return json.loads(cli_stdout, parse_float=decimal.Decimal) | return json.loads(cli_stdout, parse_float=decimal.Decimal) | ||||
except (json.JSONDecodeError, decimal.InvalidOperation): | except (json.JSONDecodeError, decimal.InvalidOperation): | ||||
return cli_stdout.rstrip("\n") | return cli_stdout.rstrip("\n") | ||||
class RPCOverloadWrapper(): | class RPCOverloadWrapper(): | ||||
def __init__(self, rpc, cli=False): | def __init__(self, rpc, cli=False, descriptors=False): | ||||
self.rpc = rpc | self.rpc = rpc | ||||
self.is_cli = cli | self.is_cli = cli | ||||
self.descriptors = descriptors | |||||
def __getattr__(self, name): | def __getattr__(self, name): | ||||
return getattr(self.rpc, name) | return getattr(self.rpc, name) | ||||
def createwallet(self, wallet_name, disable_private_keys=None, | |||||
blank=None, passphrase=None, avoid_reuse=None, descriptors=None): | |||||
if self.is_cli: | |||||
if disable_private_keys is None: | |||||
disable_private_keys = 'null' | |||||
if blank is None: | |||||
blank = 'null' | |||||
if passphrase is None: | |||||
passphrase = '' | |||||
if avoid_reuse is None: | |||||
avoid_reuse = 'null' | |||||
if descriptors is None: | |||||
descriptors = self.descriptors | |||||
return self.__getattr__('createwallet')( | |||||
wallet_name, disable_private_keys, blank, passphrase, avoid_reuse, descriptors) | |||||
def importprivkey(self, privkey, label=None, rescan=None): | def importprivkey(self, privkey, label=None, rescan=None): | ||||
wallet_info = self.getwalletinfo() | wallet_info = self.getwalletinfo() | ||||
if self.is_cli: | if self.is_cli: | ||||
if label is None: | if label is None: | ||||
label = 'null' | label = 'null' | ||||
if rescan is None: | if rescan is None: | ||||
rescan = 'null' | rescan = 'null' | ||||
if 'descriptors' not in wallet_info or ( | if 'descriptors' not in wallet_info or ( | ||||
▲ Show 20 Lines • Show All 88 Lines • Show Last 20 Lines |