1 #!/usr/bin/env python3
2 # Copyright (c) 2014-present 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 """Base class for RPC testing."""
6 7 import configparser
8 from enum import Enum
9 import argparse
10 from datetime import datetime, timezone
11 from importlib.util import find_spec
12 import logging
13 import os
14 from pathlib import Path
15 import platform
16 import pdb
17 import random
18 import re
19 import shutil
20 import subprocess
21 import sys
22 import tempfile
23 import time
24 25 from .address import create_deterministic_address_bcrt1_p2tr_op_true
26 from . import coverage
27 from .messages import CAddress
28 from .p2p import NetworkThread
29 from .test_node import TestNode
30 from .util import (
31 Binaries,
32 MAX_NODES,
33 PortSeed,
34 assert_equal,
35 check_json_precision,
36 export_env_build_path,
37 find_vout_for_address,
38 get_binary_paths,
39 get_datadir_path,
40 initialize_datadir,
41 p2p_port,
42 wait_until_helper_internal,
43 wallet_importprivkey,
44 JSONRPCException,
45 )
46 47 48 class TestStatus(Enum):
49 PASSED = 1
50 FAILED = 2
51 SKIPPED = 3
52 53 TEST_EXIT_PASSED = 0
54 TEST_EXIT_FAILED = 1
55 TEST_EXIT_SKIPPED = 77
56 57 TMPDIR_PREFIX = "bitcoin_func_test_"
58 59 60 class SkipTest(Exception):
61 """This exception is raised to skip a test"""
62 63 def __init__(self, message):
64 self.message = message
65 66 67 class BitcoinTestMetaClass(type):
68 """Metaclass for BitcoinTestFramework.
69 70 Ensures that any attempt to register a subclass of `BitcoinTestFramework`
71 adheres to a standard whereby the subclass overrides `set_test_params` and
72 `run_test` but DOES NOT override either `__init__` or `main`. If any of
73 those standards are violated, a ``TypeError`` is raised."""
74 75 def __new__(cls, clsname, bases, dct):
76 if not clsname == 'BitcoinTestFramework':
77 if not ('run_test' in dct and 'set_test_params' in dct):
78 raise TypeError("BitcoinTestFramework subclasses must override "
79 "'run_test' and 'set_test_params'")
80 if '__init__' in dct or 'main' in dct:
81 raise TypeError("BitcoinTestFramework subclasses may not override "
82 "'__init__' or 'main'")
83 84 return super().__new__(cls, clsname, bases, dct)
85 86 87 class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
88 """Base class for a bitcoin test script.
89 90 Individual bitcoin test scripts should subclass this class and override the set_test_params() and run_test() methods.
91 92 Individual tests can also override the following methods to customize the test setup:
93 94 - add_options()
95 - setup_chain()
96 - setup_network()
97 - setup_nodes()
98 99 The __init__() and main() methods should not be overridden.
100 101 This class also contains various public and private helper methods."""
102 103 def __init__(self, test_file) -> None:
104 """Sets test framework defaults. Do not override this method. Instead, override the set_test_params() method"""
105 self.chain: str = 'regtest'
106 self.setup_clean_chain: bool = False
107 self.noban_tx_relay: bool = False
108 self.nodes: list[TestNode] = []
109 self.extra_args = None
110 self.extra_init = None
111 self.network_thread = None
112 self.rpc_timeout = 60 # Wait for up to 60 seconds for the RPC server to respond
113 self.supports_cli = True
114 self.bind_to_localhost_only = True
115 self.parse_args(test_file)
116 self.default_wallet_name = "default_wallet"
117 self.wallet_data_filename = "wallet.dat"
118 # Optional list of wallet names that can be set in set_test_params to
119 # create and import keys to. If unset, default is len(nodes) *
120 # [default_wallet_name]. If wallet names are None, wallet creation is
121 # skipped. If list is truncated, wallet creation is skipped and keys
122 # are not imported.
123 self.wallet_names = None
124 # By default the wallet is not required. Set to true by skip_if_no_wallet().
125 # Can also be set to None to indicate that the wallet will be used if available.
126 # When False or None, we ignore wallet_names in setup_nodes().
127 self.uses_wallet = False
128 # Disable ThreadOpenConnections by default, so that adding entries to
129 # addrman will not result in automatic connections to them.
130 self.disable_autoconnect = True
131 self.set_test_params()
132 assert self.wallet_names is None or len(self.wallet_names) <= self.num_nodes
133 self.rpc_timeout = int(self.rpc_timeout * self.options.timeout_factor) # optionally, increase timeout by a factor
134 135 def main(self):
136 """Main function. This should not be overridden by the subclass test scripts."""
137 138 assert hasattr(self, "num_nodes"), "Test must set self.num_nodes in set_test_params()"
139 140 try:
141 self.setup()
142 if self.options.test_methods:
143 self.run_test_methods()
144 else:
145 self.run_test()
146 147 except SkipTest as e:
148 self.log.warning("Test Skipped: %s" % e.message)
149 self.success = TestStatus.SKIPPED
150 except subprocess.CalledProcessError as e:
151 self.log.exception(f"Called Process failed with stdout='{e.stdout}'; stderr='{e.stderr}';")
152 self.success = TestStatus.FAILED
153 except BaseException:
154 # The `exception` log will add the exception info to the message.
155 # https://docs.python.org/3/library/logging.html#logging.exception
156 self.log.exception("Unexpected exception:")
157 self.success = TestStatus.FAILED
158 finally:
159 exit_code = self.shutdown()
160 sys.exit(exit_code)
161 162 def run_test_methods(self):
163 for method_name in self.options.test_methods:
164 self.log.info(f"Attempting to execute method: {method_name}")
165 method = getattr(self, method_name)
166 method()
167 self.log.info(f"Method '{method_name}' executed successfully.")
168 169 def parse_args(self, test_file):
170 previous_releases_path = os.getenv("PREVIOUS_RELEASES_DIR") or os.getcwd() + "/releases"
171 parser = argparse.ArgumentParser(usage="%(prog)s [options]")
172 parser.add_argument("--nocleanup", dest="nocleanup", default=False, action="store_true",
173 help="Leave bitcoinds and test.* datadir on exit or error")
174 parser.add_argument("--cachedir", dest="cachedir", default=os.path.abspath(os.path.dirname(test_file) + "/../cache"),
175 help="Directory for caching pregenerated datadirs (default: %(default)s)")
176 parser.add_argument("--tmpdir", dest="tmpdir", help="Root directory for datadirs (must not exist)")
177 parser.add_argument("-l", "--loglevel", dest="loglevel", default="INFO",
178 help="log events at this level and higher to the console. Can be set to DEBUG, INFO, WARNING, ERROR or CRITICAL. Passing --loglevel DEBUG will output all logs to console. Note that logs at all levels are always written to the test_framework.log file in the temporary test directory.")
179 parser.add_argument("--tracerpc", dest="trace_rpc", default=False, action="store_true",
180 help="Print out all RPC calls as they are made")
181 parser.add_argument("--portseed", dest="port_seed", default=os.getpid(), type=int,
182 help="The seed to use for assigning port numbers (default: current process id)")
183 parser.add_argument("--previous-releases", dest="prev_releases", action="store_true",
184 default=os.path.isdir(previous_releases_path) and bool(os.listdir(previous_releases_path)),
185 help="Force test of previous releases (default: %(default)s). Previous releases binaries can be downloaded via `test/get_previous_releases.py`.")
186 parser.add_argument("--coveragedir", dest="coveragedir",
187 help="Write tested RPC commands into this directory")
188 parser.add_argument("--configfile", dest="configfile",
189 default=os.path.abspath(os.path.dirname(test_file) + "/../config.ini"),
190 help="Location of the test framework config file (default: %(default)s)")
191 parser.add_argument("--pdbonfailure", dest="pdbonfailure", default=False, action="store_true",
192 help="Attach a python debugger if test fails")
193 parser.add_argument("--usecli", dest="usecli", default=False, action="store_true",
194 help="use bitcoin-cli instead of RPC for all commands")
195 parser.add_argument("--valgrind", dest="valgrind", default=False, action="store_true",
196 help="Run binaries under the valgrind memory error detector: Expect at least a ~10x slowdown. Does not apply to previous release binaries.")
197 parser.add_argument("--randomseed", type=int,
198 help="set a random seed for deterministically reproducing a previous test run")
199 parser.add_argument("--timeout-factor", dest="timeout_factor", type=float, help="adjust test timeouts by a factor. Setting it to 0 disables all timeouts")
200 parser.add_argument("--v2transport", dest="v2transport", default=False, action="store_true",
201 help="use BIP324 v2 connections between all nodes by default")
202 parser.add_argument("--v1transport", dest="v1transport", default=False, action="store_true",
203 help="Explicitly use v1 transport (can be used to overwrite global --v2transport option)")
204 parser.add_argument("--test_methods", dest="test_methods", nargs='*',
205 help="Run specified test methods sequentially instead of the full test. Use only for methods that do not depend on any context set up in run_test or other methods.")
206 207 self.add_options(parser)
208 # Running TestShell in a Jupyter notebook causes an additional -f argument
209 # To keep TestShell from failing with an "unrecognized argument" error, we add a dummy "-f" argument
210 # source: https://stackoverflow.com/questions/48796169/how-to-fix-ipykernel-launcher-py-error-unrecognized-arguments-in-jupyter/56349168#56349168
211 parser.add_argument("-f", "--fff", help="a dummy argument to fool ipython", default="1")
212 self.options = parser.parse_args()
213 if self.options.timeout_factor == 0:
214 self.options.timeout_factor = 999
215 self.options.timeout_factor = self.options.timeout_factor or (4 if self.options.valgrind else 1)
216 self.options.previous_releases_path = previous_releases_path
217 218 self.config = configparser.ConfigParser()
219 self.config.read_file(open(self.options.configfile))
220 self.binary_paths = get_binary_paths(self.config)
221 if self.options.v1transport:
222 self.options.v2transport=False
223 224 PortSeed.n = self.options.port_seed
225 226 def get_binaries(self, bin_dir=None):
227 return Binaries(self.binary_paths, bin_dir, use_valgrind=self.options.valgrind)
228 229 def setup(self):
230 """Call this method to start up the test framework object with options set."""
231 232 check_json_precision()
233 export_env_build_path(self.config)
234 235 self.options.cachedir = os.path.abspath(self.options.cachedir)
236 237 # Set up temp directory and start logging
238 if self.options.tmpdir:
239 self.options.tmpdir = os.path.abspath(self.options.tmpdir)
240 os.makedirs(self.options.tmpdir, exist_ok=False)
241 else:
242 self.options.tmpdir = tempfile.mkdtemp(prefix=TMPDIR_PREFIX)
243 self._start_logging()
244 245 # Seed the PRNG. Note that test runs are reproducible if and only if
246 # a single thread accesses the PRNG. For more information, see
247 # https://docs.python.org/3/library/random.html#notes-on-reproducibility.
248 # The network thread shouldn't access random. If we need to change the
249 # network thread to access randomness, it should instantiate its own
250 # random.Random object.
251 seed = self.options.randomseed
252 253 if seed is None:
254 seed = random.randrange(sys.maxsize)
255 else:
256 self.log.info("User supplied random seed {}".format(seed))
257 258 random.seed(seed)
259 self.log.info("PRNG seed is: {}".format(seed))
260 261 self.log.debug('Setting up network thread')
262 self.network_thread = NetworkThread()
263 self.network_thread.start()
264 self.wait_until(lambda: self.network_thread.network_event_loop is not None and self.network_thread.network_event_loop.is_running())
265 266 if self.options.usecli:
267 if not self.supports_cli:
268 raise SkipTest("--usecli specified but test does not support using CLI")
269 self.skip_if_no_cli()
270 self.skip_test_if_missing_module()
271 self.setup_chain()
272 self.setup_network()
273 274 self.success = TestStatus.PASSED
275 276 def shutdown(self):
277 """Call this method to shut down the test framework object."""
278 279 if self.success == TestStatus.FAILED and self.options.pdbonfailure:
280 print("Testcase failed. Attaching python debugger. Enter ? for help")
281 pdb.set_trace()
282 283 self.log.debug('Closing down network thread')
284 self.network_thread.close(timeout=self.options.timeout_factor * 10)
285 if self.success == TestStatus.FAILED:
286 self.log.info("Not stopping nodes as test failed. The dangling processes will be cleaned up later.")
287 else:
288 self.log.info("Stopping nodes")
289 if self.nodes:
290 self.stop_nodes()
291 292 should_clean_up = (
293 not self.options.nocleanup and
294 self.success != TestStatus.FAILED
295 )
296 if should_clean_up:
297 self.log.info("Cleaning up {} on exit".format(self.options.tmpdir))
298 cleanup_tree_on_exit = True
299 else:
300 self.log.warning("Not cleaning up dir {}".format(self.options.tmpdir))
301 cleanup_tree_on_exit = False
302 303 if self.success == TestStatus.PASSED:
304 self.log.info("Tests successful")
305 exit_code = TEST_EXIT_PASSED
306 elif self.success == TestStatus.SKIPPED:
307 self.log.info("Test skipped")
308 exit_code = TEST_EXIT_SKIPPED
309 else:
310 self.log.error("Test failed. Test logging available at %s/test_framework.log", self.options.tmpdir)
311 self.log.error("")
312 self.log.error("Hint: Call {} '{}' to consolidate all logs".format(os.path.normpath(os.path.dirname(os.path.realpath(__file__)) + "/../combine_logs.py"), self.options.tmpdir))
313 self.log.error("")
314 self.log.error("If this failure happened unexpectedly or intermittently, please file a bug and provide a link or upload of the combined log.")
315 self.log.error(self.config['environment']['CLIENT_BUGREPORT'])
316 self.log.error("")
317 exit_code = TEST_EXIT_FAILED
318 # Logging.shutdown will not remove stream- and filehandlers, so we must
319 # do it explicitly. Handlers are removed so the next test run can apply
320 # different log handler settings.
321 # See: https://docs.python.org/3/library/logging.html#logging.shutdown
322 for h in list(self.log.handlers):
323 h.flush()
324 h.close()
325 self.log.removeHandler(h)
326 rpc_logger = logging.getLogger("BitcoinRPC")
327 for h in list(rpc_logger.handlers):
328 h.flush()
329 rpc_logger.removeHandler(h)
330 if cleanup_tree_on_exit:
331 self.cleanup_folder(self.options.tmpdir)
332 333 self.nodes.clear()
334 return exit_code
335 336 # Methods to override in subclass test scripts.
337 def set_test_params(self):
338 """Tests must override this method to change default values for number of nodes, topology, etc"""
339 raise NotImplementedError
340 341 def add_options(self, parser):
342 """Override this method to add command-line options to the test"""
343 pass
344 345 def skip_test_if_missing_module(self):
346 """Override this method to skip a test if a module is not compiled"""
347 pass
348 349 def setup_chain(self):
350 """Override this method to customize blockchain setup"""
351 self.log.info("Initializing test directory " + self.options.tmpdir)
352 if self.setup_clean_chain:
353 self._initialize_chain_clean()
354 else:
355 self._initialize_chain()
356 357 def setup_network(self):
358 """Override this method to customize test network topology"""
359 self.setup_nodes()
360 361 # Connect the nodes as a "chain". This allows us
362 # to split the network between nodes 1 and 2 to get
363 # two halves that can work on competing chains.
364 #
365 # Topology looks like this:
366 # node0 <-- node1 <-- node2 <-- node3
367 #
368 # If all nodes are in IBD (clean chain from genesis), node0 is assumed to be the source of blocks (miner). To
369 # ensure block propagation, all nodes will establish outgoing connections toward node0.
370 # See fPreferredDownload in net_processing.
371 #
372 # If further outbound connections are needed, they can be added at the beginning of the test with e.g.
373 # self.connect_nodes(1, 2)
374 for i in range(self.num_nodes - 1):
375 self.connect_nodes(i + 1, i)
376 self.sync_all()
377 378 def setup_nodes(self):
379 """Override this method to customize test node setup"""
380 self.add_nodes(self.num_nodes, self.extra_args)
381 self.start_nodes()
382 if self.uses_wallet:
383 self.import_deterministic_coinbase_privkeys()
384 if not self.setup_clean_chain:
385 for n in self.nodes:
386 assert_equal(n.getblockchaininfo()["blocks"], 199)
387 # To ensure that all nodes are out of IBD, the most recent block
388 # must have a timestamp not too old (see IsInitialBlockDownload()).
389 self.log.debug('Generate a block with current time')
390 block_hash = self.generate(self.nodes[0], 1, sync_fun=self.no_op)[0]
391 block = self.nodes[0].getblock(blockhash=block_hash, verbosity=0)
392 for n in self.nodes:
393 n.submitblock(block)
394 chain_info = n.getblockchaininfo()
395 assert_equal(chain_info["blocks"], 200)
396 assert_equal(chain_info["initialblockdownload"], False)
397 398 def import_deterministic_coinbase_privkeys(self):
399 for i in range(self.num_nodes):
400 self.init_wallet(node=i)
401 402 def init_wallet(self, *, node):
403 wallet_name = self.default_wallet_name if self.wallet_names is None else self.wallet_names[node] if node < len(self.wallet_names) else False
404 if wallet_name is not False:
405 n = self.nodes[node]
406 if wallet_name is not None:
407 n.createwallet(wallet_name=wallet_name, load_on_startup=True)
408 wallet_importprivkey(n.get_wallet_rpc(wallet_name), n.get_deterministic_priv_key().key, 0, label="coinbase")
409 410 def run_test(self):
411 """Tests must override this method to define test logic"""
412 raise NotImplementedError
413 414 # Public helper methods. These can be accessed by the subclass test scripts.
415 416 def add_nodes(self, num_nodes: int, extra_args=None, *, rpchost=None, versions=None):
417 """Instantiate TestNode objects.
418 419 Should only be called once after the nodes have been specified in
420 set_test_params()."""
421 def bin_dir_from_version(version):
422 if not version:
423 return None
424 if version > 219999:
425 # Starting at client version 220000 the first two digits represent
426 # the major version, e.g. v22.0 instead of v0.22.0.
427 version *= 100
428 return os.path.join(
429 self.options.previous_releases_path,
430 re.sub(
431 r'\.0$' if version <= 219999 else r'(\.0){1,2}$',
432 '', # Remove trailing dot for point releases, after 22.0 also remove double trailing dot.
433 'v{}.{}.{}.{}'.format(
434 (version % 100000000) // 1000000,
435 (version % 1000000) // 10000,
436 (version % 10000) // 100,
437 (version % 100) // 1,
438 ),
439 ),
440 'bin',
441 )
442 443 if self.bind_to_localhost_only:
444 extra_confs = [["bind=127.0.0.1"]] * num_nodes
445 else:
446 extra_confs = [[]] * num_nodes
447 if extra_args is None:
448 extra_args = [[]] * num_nodes
449 # Whitelist peers to speed up tx relay / mempool sync. Don't use it if testing tx relay or timing.
450 if self.noban_tx_relay:
451 for i in range(len(extra_args)):
452 extra_args[i] = extra_args[i] + ["-whitelist=noban,in,out@127.0.0.1"]
453 if versions is None:
454 versions = [None] * num_nodes
455 bin_dirs = []
456 for v in versions:
457 bin_dir = bin_dir_from_version(v)
458 bin_dirs.append(bin_dir)
459 460 extra_init = [{}] * num_nodes if self.extra_init is None else self.extra_init # type: ignore[var-annotated]
461 assert_equal(len(extra_init), num_nodes)
462 assert_equal(len(extra_confs), num_nodes)
463 assert_equal(len(extra_args), num_nodes)
464 assert_equal(len(versions), num_nodes)
465 assert_equal(len(bin_dirs), num_nodes)
466 for i in range(num_nodes):
467 args = list(extra_args[i])
468 init = dict(
469 chain=self.chain,
470 rpchost=rpchost,
471 timewait=self.rpc_timeout,
472 timeout_factor=self.options.timeout_factor,
473 binaries=self.get_binaries(bin_dirs[i]),
474 version=versions[i],
475 coverage_dir=self.options.coveragedir,
476 cwd=self.options.tmpdir,
477 extra_conf=extra_confs[i],
478 extra_args=args,
479 use_cli=self.options.usecli,
480 v2transport=self.options.v2transport,
481 uses_wallet=self.uses_wallet,
482 )
483 init.update(extra_init[i])
484 test_node_i = TestNode(
485 i,
486 get_datadir_path(self.options.tmpdir, i),
487 **init)
488 self.nodes.append(test_node_i)
489 if not test_node_i.version_is_at_least(170000):
490 # adjust conf for pre 17
491 test_node_i.replace_in_config([('[regtest]', '')])
492 493 def start_node(self, i, *args, **kwargs):
494 """Start a bitcoind"""
495 496 node = self.nodes[i]
497 498 node.start(*args, **kwargs)
499 node.wait_for_rpc_connection()
500 501 if self.options.coveragedir is not None:
502 coverage.write_all_rpc_commands(self.options.coveragedir, node._rpc)
503 504 def start_nodes(self, extra_args=None, *args, **kwargs):
505 """Start multiple bitcoinds"""
506 507 if extra_args is None:
508 extra_args = [None] * self.num_nodes
509 assert_equal(len(extra_args), self.num_nodes)
510 for i, node in enumerate(self.nodes):
511 node.start(extra_args[i], *args, **kwargs)
512 for node in self.nodes:
513 node.wait_for_rpc_connection()
514 515 if self.options.coveragedir is not None:
516 for node in self.nodes:
517 coverage.write_all_rpc_commands(self.options.coveragedir, node._rpc)
518 519 def stop_node(self, i, expected_stderr='', wait=0):
520 """Stop a bitcoind test node"""
521 self.nodes[i].stop_node(expected_stderr, wait=wait)
522 523 def stop_nodes(self, wait=0):
524 """Stop multiple bitcoind test nodes"""
525 for node in self.nodes:
526 # Issue RPC to stop nodes
527 node.stop_node(wait=wait, wait_until_stopped=False)
528 529 for node in self.nodes:
530 # Wait for nodes to stop
531 node.wait_until_stopped()
532 533 def cleanup_partially_started_nodes(self):
534 """Tear down nodes left running after a failed start_nodes().
535 536 After start_nodes() raises (e.g. FailedToStartError), some nodes may be
537 RPC-connected, some may have a live process without RPC, and some may
538 already have exited. Stop the connected ones cleanly and force-kill the
539 rest so the framework's teardown can proceed.
540 """
541 for node in self.nodes:
542 if not node.running:
543 continue
544 if node.rpc_connected:
545 node.stop_node(wait=node.rpc_timeout)
546 else:
547 node.process.kill()
548 node.process.wait(timeout=node.rpc_timeout)
549 node.process = None
550 node.stdout.close()
551 node.stderr.close()
552 node.running = False
553 554 def restart_node(self, i, extra_args=None, clear_addrman=False, *, expected_stderr=''):
555 """Stop and start a test node"""
556 self.stop_node(i, expected_stderr=expected_stderr)
557 if clear_addrman:
558 peers_dat = self.nodes[i].chain_path / "peers.dat"
559 os.remove(peers_dat)
560 with self.nodes[i].assert_debug_log(expected_msgs=[f'Creating peers.dat because the file was not found ("{peers_dat}")']):
561 self.start_node(i, extra_args)
562 else:
563 self.start_node(i, extra_args)
564 565 def wait_for_node_exit(self, i, timeout):
566 self.nodes[i].process.wait(timeout)
567 568 def connect_nodes(self, a, b, *, peer_advertises_v2=None):
569 from_connection = self.nodes[a]
570 to_connection = self.nodes[b]
571 572 # Use subversion as peer id. Test nodes have their node number appended to the user agent string
573 from_connection_subver = from_connection.getnetworkinfo()['subversion']
574 to_connection_subver = to_connection.getnetworkinfo()['subversion']
575 576 def find_conn(node, peer_subversion, inbound):
577 return next(filter(lambda peer: peer['subver'] == peer_subversion and peer['inbound'] == inbound, node.getpeerinfo()), None)
578 579 self.wait_until(lambda: not find_conn(from_connection, to_connection_subver, inbound=False))
580 self.wait_until(lambda: not find_conn(to_connection, from_connection_subver, inbound=True))
581 582 ip_port = "127.0.0.1:" + str(p2p_port(b))
583 584 if peer_advertises_v2 is None:
585 peer_advertises_v2 = from_connection.use_v2transport
586 587 if peer_advertises_v2 != from_connection.use_v2transport:
588 from_connection.addnode(node=ip_port, command="onetry", v2transport=peer_advertises_v2)
589 else:
590 # skip the optional third argument if it matches the default, for
591 # compatibility with older clients
592 from_connection.addnode(ip_port, "onetry")
593 594 self.wait_until(lambda: find_conn(from_connection, to_connection_subver, inbound=False) is not None)
595 self.wait_until(lambda: find_conn(to_connection, from_connection_subver, inbound=True) is not None)
596 597 def check_bytesrecv(peer, msg_type, min_bytes_recv):
598 assert peer is not None, "Error: peer disconnected"
599 return peer['bytesrecv_per_msg'].pop(msg_type, 0) >= min_bytes_recv
600 601 # Poll until version handshake (fSuccessfullyConnected) is complete to
602 # avoid race conditions, because some message types are blocked from
603 # being sent or received before fSuccessfullyConnected.
604 #
605 # As the flag fSuccessfullyConnected is not exposed, check it by
606 # waiting for a pong, which can only happen after the flag was set.
607 self.wait_until(lambda: check_bytesrecv(find_conn(from_connection, to_connection_subver, inbound=False), 'pong', 29))
608 self.wait_until(lambda: check_bytesrecv(find_conn(to_connection, from_connection_subver, inbound=True), 'pong', 29))
609 610 def disconnect_nodes(self, a, b):
611 def disconnect_nodes_helper(node_a, node_b):
612 def get_peer_ids(from_connection, node_num):
613 result = []
614 for peer in from_connection.getpeerinfo():
615 if "testnode{}".format(node_num) in peer['subver']:
616 result.append(peer['id'])
617 return result
618 619 peer_ids = get_peer_ids(node_a, node_b.index)
620 if not peer_ids:
621 self.log.warning("disconnect_nodes: {} and {} were not connected".format(
622 node_a.index,
623 node_b.index,
624 ))
625 return
626 for peer_id in peer_ids:
627 try:
628 node_a.disconnectnode(nodeid=peer_id)
629 except JSONRPCException as e:
630 # If this node is disconnected between calculating the peer id
631 # and issuing the disconnect, don't worry about it.
632 # This avoids a race condition if we're mass-disconnecting peers.
633 if e.error['code'] != -29: # RPC_CLIENT_NODE_NOT_CONNECTED
634 raise
635 636 # wait to disconnect
637 self.wait_until(lambda: not get_peer_ids(node_a, node_b.index), timeout=5)
638 self.wait_until(lambda: not get_peer_ids(node_b, node_a.index), timeout=5)
639 640 disconnect_nodes_helper(self.nodes[a], self.nodes[b])
641 642 def split_network(self):
643 """
644 Split the network of four nodes into nodes 0/1 and 2/3.
645 """
646 self.disconnect_nodes(1, 2)
647 self.sync_all(self.nodes[:2])
648 self.sync_all(self.nodes[2:])
649 650 def join_network(self):
651 """
652 Join the (previously split) network halves together.
653 """
654 self.connect_nodes(1, 2)
655 self.sync_all()
656 657 def no_op(self):
658 pass
659 660 def generate(self, generator, *args, sync_fun=None, **kwargs):
661 blocks = generator.generate(*args, called_by_framework=True, **kwargs)
662 sync_fun() if sync_fun else self.sync_all()
663 return blocks
664 665 def generateblock(self, generator, *args, sync_fun=None, **kwargs):
666 blocks = generator.generateblock(*args, called_by_framework=True, **kwargs)
667 sync_fun() if sync_fun else self.sync_all()
668 return blocks
669 670 def generatetoaddress(self, generator, *args, sync_fun=None, **kwargs):
671 blocks = generator.generatetoaddress(*args, called_by_framework=True, **kwargs)
672 sync_fun() if sync_fun else self.sync_all()
673 return blocks
674 675 def generatetodescriptor(self, generator, *args, sync_fun=None, **kwargs):
676 blocks = generator.generatetodescriptor(*args, called_by_framework=True, **kwargs)
677 sync_fun() if sync_fun else self.sync_all()
678 return blocks
679 680 def create_outpoints(self, node, *, outputs):
681 """Send funds to a given list of `{address: amount}` targets using the bitcoind
682 wallet and return the corresponding outpoints as a list of dictionaries
683 `[{"txid": txid, "vout": vout1}, {"txid": txid, "vout": vout2}, ...]`.
684 The result can be used to specify inputs for RPCs like `createrawtransaction`,
685 `createpsbt`, `lockunspent` etc."""
686 for output in outputs:
687 assert_equal(len(output.keys()), 1)
688 send_res = node.send(outputs)
689 assert send_res["complete"]
690 utxos = []
691 for output in outputs:
692 address = list(output.keys())[0]
693 vout = find_vout_for_address(node, send_res["txid"], address)
694 utxos.append({"txid": send_res["txid"], "vout": vout})
695 return utxos
696 697 def sync_blocks(self, nodes=None, wait=1, timeout=60):
698 """
699 Wait until everybody has the same tip.
700 sync_blocks needs to be called with an rpc_connections set that has least
701 one node already synced to the latest, stable tip, otherwise there's a
702 chance it might return before all nodes are stably synced.
703 """
704 rpc_connections = nodes or self.nodes
705 timeout = int(timeout * self.options.timeout_factor)
706 stop_time = time.time() + timeout
707 while time.time() <= stop_time:
708 best_hash = [x.getbestblockhash() for x in rpc_connections]
709 if best_hash.count(best_hash[0]) == len(rpc_connections):
710 return
711 # Check that each peer has at least one connection
712 assert (all([len(x.getpeerinfo()) for x in rpc_connections]))
713 time.sleep(wait)
714 raise AssertionError("Block sync timed out after {}s:{}".format(
715 timeout,
716 "".join("\n {!r}".format(b) for b in best_hash),
717 ))
718 719 def sync_mempools(self, nodes=None, wait=1, timeout=60, flush_scheduler=True):
720 """
721 Wait until everybody has the same transactions in their memory
722 pools
723 """
724 rpc_connections = nodes or self.nodes
725 timeout = int(timeout * self.options.timeout_factor)
726 stop_time = time.time() + timeout
727 while time.time() <= stop_time:
728 pool = [set(r.getrawmempool()) for r in rpc_connections]
729 if pool.count(pool[0]) == len(rpc_connections):
730 if flush_scheduler:
731 for r in rpc_connections:
732 r.syncwithvalidationinterfacequeue()
733 return
734 # Check that each peer has at least one connection
735 assert (all([len(x.getpeerinfo()) for x in rpc_connections]))
736 time.sleep(wait)
737 raise AssertionError("Mempool sync timed out after {}s:{}".format(
738 timeout,
739 "".join("\n {!r}".format(m) for m in pool),
740 ))
741 742 def sync_all(self, nodes=None):
743 self.sync_blocks(nodes)
744 self.sync_mempools(nodes)
745 746 def wait_until(self, test_function, timeout=60, check_interval=0.05):
747 return wait_until_helper_internal(test_function, timeout=timeout, timeout_factor=self.options.timeout_factor, check_interval=check_interval)
748 749 def fill_node_addrman(self, *, node_index, address_types_to_add):
750 ADDRESSES = {
751 CAddress.NET_IPV4: [
752 "20.0.0.1",
753 "30.0.0.1",
754 "40.0.0.1",
755 "50.0.0.1",
756 "60.0.0.1",
757 "70.0.0.1",
758 "80.0.0.1",
759 "90.0.0.1",
760 "100.0.0.1",
761 "110.0.0.1",
762 "120.0.0.1",
763 "130.0.0.1",
764 "140.0.0.1",
765 "150.0.0.1",
766 "160.0.0.1",
767 "170.0.0.1",
768 "180.0.0.1",
769 "190.0.0.1",
770 "200.0.0.1",
771 "210.0.0.1",
772 ],
773 CAddress.NET_IPV6: [
774 "[20::1]",
775 "[30::1]",
776 "[40::1]",
777 "[50::1]",
778 "[60::1]",
779 "[70::1]",
780 "[80::1]",
781 "[90::1]",
782 "[100::1]",
783 "[110::1]",
784 "[120::1]",
785 "[130::1]",
786 "[140::1]",
787 "[150::1]",
788 "[160::1]",
789 "[170::1]",
790 "[180::1]",
791 "[190::1]",
792 "[200::1]",
793 "[210::1]",
794 ],
795 CAddress.NET_TORV3: [
796 "testonlyad777777777777777777777777777777777777777775b6qd.onion",
797 "testonlyah77777777777777777777777777777777777777777z7ayd.onion",
798 "testonlyal77777777777777777777777777777777777777777vp6qd.onion",
799 "testonlyap77777777777777777777777777777777777777777r5qad.onion",
800 "testonlyat77777777777777777777777777777777777777777udsid.onion",
801 "testonlyax77777777777777777777777777777777777777777yciid.onion",
802 "testonlya777777777777777777777777777777777777777777rhgyd.onion",
803 "testonlybd77777777777777777777777777777777777777777rs4ad.onion",
804 "testonlybp77777777777777777777777777777777777777777zs2ad.onion",
805 "testonlybt777777777777777777777777777777777777777777x6id.onion",
806 "testonlybx777777777777777777777777777777777777777775styd.onion",
807 "testonlyb3777777777777777777777777777777777777777774ckid.onion",
808 "testonlycd77777777777777777777777777777777777777777733id.onion",
809 "testonlych77777777777777777777777777777777777777777t6kid.onion",
810 "testonlycl77777777777777777777777777777777777777777tt3ad.onion",
811 "testonlyct77777777777777777777777777777777777777777wvhyd.onion",
812 "testonlycx7777777777777777777777777777777777777777774bad.onion",
813 "testonlyc377777777777777777777777777777777777777777u6aid.onion",
814 "testonlydd777777777777777777777777777777777777777777u5ad.onion",
815 "testonlydh77777777777777777777777777777777777777777wgnyd.onion",
816 ],
817 CAddress.NET_I2P: [
818 "testonlyad77777777777777777777777777777777777777777q.b32.i2p",
819 "testonlyah77777777777777777777777777777777777777777q.b32.i2p",
820 "testonlyap77777777777777777777777777777777777777777q.b32.i2p",
821 "testonlyat77777777777777777777777777777777777777777q.b32.i2p",
822 "testonlyax77777777777777777777777777777777777777777q.b32.i2p",
823 "testonlya377777777777777777777777777777777777777777q.b32.i2p",
824 "testonlya777777777777777777777777777777777777777777q.b32.i2p",
825 "testonlybd77777777777777777777777777777777777777777q.b32.i2p",
826 "testonlybh77777777777777777777777777777777777777777q.b32.i2p",
827 "testonlybl77777777777777777777777777777777777777777q.b32.i2p",
828 "testonlybp77777777777777777777777777777777777777777q.b32.i2p",
829 "testonlybt77777777777777777777777777777777777777777q.b32.i2p",
830 "testonlybx77777777777777777777777777777777777777777q.b32.i2p",
831 "testonlyb777777777777777777777777777777777777777777q.b32.i2p",
832 "testonlych77777777777777777777777777777777777777777q.b32.i2p",
833 "testonlycp77777777777777777777777777777777777777777q.b32.i2p",
834 "testonlyct77777777777777777777777777777777777777777q.b32.i2p",
835 "testonlycx77777777777777777777777777777777777777777q.b32.i2p",
836 "testonlyc377777777777777777777777777777777777777777q.b32.i2p",
837 "testonlyc777777777777777777777777777777777777777777q.b32.i2p",
838 ],
839 CAddress.NET_CJDNS: [
840 "[fc00::1]",
841 "[fc00::2]",
842 "[fc00::3]",
843 "[fc00::5]",
844 "[fc00::6]",
845 "[fc00::7]",
846 "[fc00::8]",
847 "[fc00::9]",
848 "[fc00::10]",
849 "[fc00::11]",
850 "[fc00::12]",
851 "[fc00::13]",
852 "[fc00::15]",
853 "[fc00::16]",
854 "[fc00::17]",
855 "[fc00::18]",
856 "[fc00::19]",
857 "[fc00::20]",
858 "[fc00::22]",
859 "[fc00::23]",
860 ],
861 }
862 for addr_type in address_types_to_add:
863 for addr in ADDRESSES[addr_type]:
864 res = self.nodes[node_index].addpeeraddress(address=addr, port=0 if addr.endswith(".i2p") else 8333, tried=False)
865 if not res["success"]:
866 self.log.debug(f"Could not add {addr} to nodes[{node_index}]'s addrman (collision?)")
867 868 # Private helper methods. These should not be accessed by the subclass test scripts.
869 870 def _start_logging(self):
871 # Add logger and logging handlers
872 self.log = logging.getLogger('TestFramework')
873 self.log.setLevel(logging.DEBUG)
874 # Create file handler to log all messages
875 fh = logging.FileHandler(self.options.tmpdir + '/test_framework.log')
876 fh.setLevel(logging.DEBUG)
877 # Create console handler to log messages to stderr. By default this logs only error messages, but can be configured with --loglevel.
878 ch = logging.StreamHandler(sys.stdout)
879 # User can provide log level as a number or string (eg DEBUG). loglevel was caught as a string, so try to convert it to an int
880 ll = int(self.options.loglevel) if self.options.loglevel.isdigit() else self.options.loglevel.upper()
881 ch.setLevel(ll)
882 883 # Format logs the same as bitcoind's debug.log with microprecision (so log files can be concatenated and sorted)
884 class MicrosecondFormatter(logging.Formatter):
885 def formatTime(self, record, _=None):
886 dt = datetime.fromtimestamp(record.created, timezone.utc)
887 return dt.strftime('%Y-%m-%dT%H:%M:%S.%f')
888 889 formatter = MicrosecondFormatter(
890 fmt='%(asctime)sZ %(name)s (%(levelname)s): %(message)s',
891 )
892 fh.setFormatter(formatter)
893 ch.setFormatter(formatter)
894 # add the handlers to the logger
895 self.log.addHandler(fh)
896 self.log.addHandler(ch)
897 898 if self.options.trace_rpc:
899 rpc_logger = logging.getLogger("BitcoinRPC")
900 rpc_logger.setLevel(logging.DEBUG)
901 rpc_handler = logging.StreamHandler(sys.stdout)
902 rpc_handler.setLevel(logging.DEBUG)
903 rpc_logger.addHandler(rpc_handler)
904 905 def _initialize_chain(self):
906 """Initialize a pre-mined blockchain for use by the test.
907 908 Create a cache of a 199-block-long chain
909 Afterward, create num_nodes copies from the cache."""
910 911 CACHE_NODE_ID = 0 # Use node 0 to create the cache for all other nodes
912 cache_node_dir = get_datadir_path(self.options.cachedir, CACHE_NODE_ID)
913 assert self.num_nodes <= MAX_NODES
914 915 if not os.path.isdir(cache_node_dir):
916 self.log.debug("Creating cache directory {}".format(cache_node_dir))
917 918 initialize_datadir(self.options.cachedir, CACHE_NODE_ID, self.chain, self.disable_autoconnect)
919 self.nodes.append(
920 TestNode(
921 CACHE_NODE_ID,
922 cache_node_dir,
923 chain=self.chain,
924 extra_conf=["bind=127.0.0.1"],
925 extra_args=[],
926 rpchost=None,
927 timewait=self.rpc_timeout,
928 timeout_factor=self.options.timeout_factor,
929 binaries=self.get_binaries(),
930 coverage_dir=None,
931 cwd=self.options.tmpdir,
932 uses_wallet=self.uses_wallet,
933 ))
934 self.start_node(CACHE_NODE_ID)
935 cache_node = self.nodes[CACHE_NODE_ID]
936 937 # Wait for RPC connections to be ready
938 cache_node.wait_for_rpc_connection()
939 940 # Set a time in the past, so that blocks don't end up in the future
941 cache_node.setmocktime(cache_node.getblockheader(cache_node.getbestblockhash())['time'])
942 943 # Create a 199-block-long chain; each of the 3 first nodes
944 # gets 25 mature blocks and 25 immature.
945 # The 4th address gets 25 mature and only 24 immature blocks so that the very last
946 # block in the cache does not age too much (have an old tip age).
947 # This is needed so that we are out of IBD when the test starts,
948 # see the tip age check in IsInitialBlockDownload().
949 gen_addresses = [k.address for k in TestNode.PRIV_KEYS][:3] + [create_deterministic_address_bcrt1_p2tr_op_true()[0]]
950 assert_equal(len(gen_addresses), 4)
951 for i in range(8):
952 self.generatetoaddress(
953 cache_node,
954 nblocks=25 if i != 7 else 24,
955 address=gen_addresses[i % len(gen_addresses)],
956 )
957 958 assert_equal(cache_node.getblockchaininfo()["blocks"], 199)
959 960 # Shut it down, and clean up cache directories:
961 self.stop_nodes()
962 self.nodes = []
963 964 def cache_path(*paths):
965 return os.path.join(cache_node_dir, self.chain, *paths)
966 967 os.rmdir(cache_path('wallets')) # Remove empty wallets dir
968 for entry in os.listdir(cache_path()):
969 if entry not in ['chainstate', 'blocks', 'indexes']: # Only indexes, chainstate and blocks folders
970 os.remove(cache_path(entry))
971 972 for i in range(self.num_nodes):
973 self.log.debug("Copy cache directory {} to node {}".format(cache_node_dir, i))
974 to_dir = get_datadir_path(self.options.tmpdir, i)
975 shutil.copytree(cache_node_dir, to_dir)
976 initialize_datadir(self.options.tmpdir, i, self.chain, self.disable_autoconnect) # Overwrite port/rpcport in bitcoin.conf
977 978 def _initialize_chain_clean(self):
979 """Initialize empty blockchain for use by the test.
980 981 Create an empty blockchain and num_nodes wallets.
982 Useful if a test case wants complete control over initialization."""
983 for i in range(self.num_nodes):
984 initialize_datadir(self.options.tmpdir, i, self.chain, self.disable_autoconnect)
985 986 def skip_if_no_py3_zmq(self):
987 """Attempt to import the zmq package and skip the test if the import fails."""
988 try:
989 import zmq # noqa
990 except ImportError:
991 raise SkipTest("python3-zmq module not available.")
992 993 def skip_if_no_py_sqlite3(self):
994 """Attempt to import the sqlite3 package and skip the test if the import fails."""
995 try:
996 import sqlite3 # noqa
997 except ImportError:
998 raise SkipTest("sqlite3 module not available.")
999 1000 def skip_if_no_py_capnp(self):
1001 """Attempt to import the capnp package and skip the test if the import fails."""
1002 try:
1003 import capnp # type: ignore[import] # noqa: F401
1004 except ImportError:
1005 raise SkipTest("capnp module not available.")
1006 1007 def skip_if_no_python_bcc(self):
1008 """Attempt to import the bcc package and skip the tests if the import fails."""
1009 try:
1010 import bcc # type: ignore[import] # noqa: F401
1011 except ImportError:
1012 raise SkipTest("bcc python module not available")
1013 1014 def skip_if_no_bitcoind_tracepoints(self):
1015 """Skip the running test if bitcoind has not been compiled with USDT tracepoint support."""
1016 if not self.is_usdt_compiled():
1017 raise SkipTest("bitcoind has not been built with USDT tracepoints enabled.")
1018 1019 def skip_if_no_bpf_permissions(self):
1020 """Skip the running test if we don't have permissions to do BPF syscalls and load BPF maps."""
1021 # check for 'root' permissions
1022 if os.geteuid() != 0:
1023 raise SkipTest("no permissions to use BPF (please review the tests carefully before running them with higher privileges)")
1024 1025 def skip_if_platform_not_linux(self):
1026 """Skip the running test if we are not on a Linux platform"""
1027 if platform.system() != "Linux":
1028 raise SkipTest("not on a Linux system")
1029 1030 def skip_if_platform_not_posix(self):
1031 """Skip the running test if we are not on a POSIX platform"""
1032 if os.name != 'posix':
1033 raise SkipTest("not on a POSIX system")
1034 1035 def skip_if_no_bitcoind_zmq(self):
1036 """Skip the running test if bitcoind has not been compiled with zmq support."""
1037 if not self.is_zmq_compiled():
1038 raise SkipTest("bitcoind has not been built with zmq enabled.")
1039 1040 def skip_if_no_wallet(self):
1041 """Skip the running test if wallet has not been compiled."""
1042 self.uses_wallet = True
1043 if not self.is_wallet_compiled():
1044 raise SkipTest("wallet has not been compiled.")
1045 1046 def skip_if_no_wallet_tool(self):
1047 """Skip the running test if bitcoin-wallet has not been compiled."""
1048 if not self.is_wallet_tool_compiled():
1049 raise SkipTest("bitcoin-wallet has not been compiled")
1050 1051 def skip_if_no_bitcoin_tx(self):
1052 """Skip the running test if bitcoin-tx has not been compiled."""
1053 if not self.is_bitcoin_tx_compiled():
1054 raise SkipTest("bitcoin-tx has not been compiled")
1055 1056 def skip_if_no_bitcoin_util(self):
1057 """Skip the running test if bitcoin-util has not been compiled."""
1058 if not self.is_bitcoin_util_compiled():
1059 raise SkipTest("bitcoin-util has not been compiled")
1060 1061 def skip_if_no_bitcoin_chainstate(self):
1062 """Skip the running test if bitcoin-chainstate has not been compiled."""
1063 if not self.is_bitcoin_chainstate_compiled():
1064 raise SkipTest("bitcoin-chainstate has not been compiled")
1065 1066 def skip_if_no_bitcoin_bench(self):
1067 """Skip the running test if bench_bitcoin has not been compiled."""
1068 if not self.is_bench_compiled():
1069 raise SkipTest("bench_bitcoin has not been compiled")
1070 1071 def skip_if_no_cli(self):
1072 """Skip the running test if bitcoin-cli has not been compiled."""
1073 if not self.is_cli_compiled():
1074 raise SkipTest("bitcoin-cli has not been compiled.")
1075 1076 def skip_if_no_ipc(self):
1077 """Skip the running test if ipc is not compiled."""
1078 if not self.is_ipc_compiled():
1079 raise SkipTest("ipc has not been compiled.")
1080 1081 def skip_if_no_previous_releases(self):
1082 """Skip the running test if previous releases are not available."""
1083 if not self.has_previous_releases():
1084 raise SkipTest("previous releases not available or disabled")
1085 1086 def has_resource_module(self):
1087 """Checks whether the resource module is available."""
1088 return find_spec('resource') is not None
1089 1090 @property
1091 def RLIM_INFINITY(self):
1092 if not self.has_resource_module():
1093 return None
1094 import resource
1095 return resource.RLIM_INFINITY
1096 1097 def has_previous_releases(self):
1098 """Checks whether previous releases are present and enabled."""
1099 if not os.path.isdir(self.options.previous_releases_path):
1100 if self.options.prev_releases:
1101 raise AssertionError(f"Force test of previous releases but releases missing: {self.options.previous_releases_path}\n"
1102 "Previous releases binaries can be downloaded via `test/get_previous_releases.py`.")
1103 return self.options.prev_releases
1104 1105 def skip_if_no_external_signer(self):
1106 """Skip the running test if external signer support has not been compiled."""
1107 if not self.is_external_signer_compiled():
1108 raise SkipTest("external signer support has not been compiled.")
1109 1110 def skip_if_running_under_valgrind(self):
1111 """Skip the running test if Valgrind is being used."""
1112 if self.options.valgrind:
1113 raise SkipTest("This test is not compatible with Valgrind.")
1114 1115 def is_bench_compiled(self):
1116 """Checks whether bench_bitcoin was compiled."""
1117 return self.config["components"].getboolean("BUILD_BENCH")
1118 1119 def is_cli_compiled(self):
1120 """Checks whether bitcoin-cli was compiled."""
1121 return self.config["components"].getboolean("ENABLE_CLI")
1122 1123 def is_external_signer_compiled(self):
1124 """Checks whether external signer support was compiled."""
1125 return self.config["components"].getboolean("ENABLE_EXTERNAL_SIGNER")
1126 1127 def is_wallet_compiled(self):
1128 """Checks whether the wallet module was compiled."""
1129 return self.config["components"].getboolean("ENABLE_WALLET")
1130 1131 def is_wallet_tool_compiled(self):
1132 """Checks whether bitcoin-wallet was compiled."""
1133 return self.config["components"].getboolean("ENABLE_WALLET_TOOL")
1134 1135 def is_bitcoin_tx_compiled(self):
1136 """Checks whether bitcoin-tx was compiled."""
1137 return self.config["components"].getboolean("BUILD_BITCOIN_TX")
1138 1139 def is_bitcoin_util_compiled(self):
1140 """Checks whether bitcoin-util was compiled."""
1141 return self.config["components"].getboolean("ENABLE_BITCOIN_UTIL")
1142 1143 def is_bitcoin_chainstate_compiled(self):
1144 """Checks whether bitcoin-chainstate was compiled."""
1145 return self.config["components"].getboolean("ENABLE_BITCOIN_CHAINSTATE")
1146 1147 def is_zmq_compiled(self):
1148 """Checks whether the zmq module was compiled."""
1149 return self.config["components"].getboolean("ENABLE_ZMQ")
1150 1151 def is_embedded_asmap_compiled(self):
1152 """Checks whether ASMap data was embedded during compilation."""
1153 return self.config["components"].getboolean("ENABLE_EMBEDDED_ASMAP")
1154 1155 def is_usdt_compiled(self):
1156 """Checks whether the USDT tracepoints were compiled."""
1157 return self.config["components"].getboolean("ENABLE_USDT_TRACEPOINTS")
1158 1159 def is_ipc_compiled(self):
1160 """Checks whether ipc was compiled."""
1161 return self.config["components"].getboolean("ENABLE_IPC")
1162 1163 def has_blockfile(self, node, filenum: str):
1164 return (node.blocks_path/ f"blk{filenum}.dat").is_file()
1165 1166 def inspect_sqlite_db(self, path, fn, *args, **kwargs):
1167 try:
1168 import sqlite3 # type: ignore[import]
1169 conn = sqlite3.connect(path)
1170 with conn:
1171 result = fn(conn, *args, **kwargs)
1172 conn.close()
1173 return result
1174 except ImportError:
1175 self.log.warning("sqlite3 module not available, skipping tests that inspect the database")
1176 1177 def cleanup_folder(self, _path):
1178 path = Path(_path)
1179 if not path.is_relative_to(self.options.tmpdir):
1180 raise AssertionError(f"Trying to delete #{path} outside of #{self.options.tmpdir}")
1181 shutil.rmtree(path)
1182