Merge #16509: test: Adapt test framework for chains other than "regtest"
faf36838bd
test: Avoid hardcoding the chain name in combine_logs (MarcoFalke)fa8a1d7ba3
test: Adapt test framework for chains other than "regtest" (MarcoFalke)68f546635d
test: Fix “local variable 'e' is assigned to but never used” (Ben Woosley) Pull request description: This is required for various work in progress: * testchains #8994 * signet #16411 * some of my locally written tests While it will be unused in the master branch as of now, it will make all of those pull requests shorter. Thus review for non-regtest tests can focus on the actual changes and not some test framework changes. ACKs for top commit: jonatack: ACKfaf36838bd
, ran tests and reviewed the code. Tree-SHA512: 35add66c12cab68f2fac8f7c7d47c604d3f24eae9336ff78f83e2c92b3dc08a25e7f4217199bac5393dd3fb72f945bba9c001d6fbb8efd298c88858075fcb3d6
This commit is contained in:
commit
d5ea8f4bf3
8 changed files with 59 additions and 45 deletions
|
@ -8,6 +8,7 @@ If no argument is provided, the most recent test directory will be used."""
|
|||
|
||||
import argparse
|
||||
from collections import defaultdict, namedtuple
|
||||
import glob
|
||||
import heapq
|
||||
import itertools
|
||||
import os
|
||||
|
@ -76,9 +77,17 @@ def read_logs(tmp_dir):
|
|||
Delegates to generator function get_log_events() to provide individual log events
|
||||
for each of the input log files."""
|
||||
|
||||
# Find out what the folder is called that holds the debug.log file
|
||||
chain = glob.glob("{}/node0/*/debug.log".format(tmp_dir))
|
||||
if chain:
|
||||
chain = chain[0] # pick the first one if more than one chain was found (should never happen)
|
||||
chain = re.search('node0/(.+?)/debug\.log$', chain).group(1) # extract the chain name
|
||||
else:
|
||||
chain = 'regtest' # fallback to regtest (should only happen when none exists)
|
||||
|
||||
files = [("test", "%s/test_framework.log" % tmp_dir)]
|
||||
for i in itertools.count():
|
||||
logfile = "{}/node{}/regtest/debug.log".format(tmp_dir, i)
|
||||
logfile = "{}/node{}/{}/debug.log".format(tmp_dir, i, chain)
|
||||
if not os.path.isfile(logfile):
|
||||
break
|
||||
files.append(("node%d" % i, logfile))
|
||||
|
@ -164,25 +173,26 @@ def get_log_events(source, logfile):
|
|||
|
||||
|
||||
def print_logs_plain(log_events, colors):
|
||||
"""Renders the iterator of log events into text."""
|
||||
for event in log_events:
|
||||
lines = event.event.splitlines()
|
||||
print("{0} {1: <5} {2} {3}".format(colors[event.source.rstrip()], event.source, lines[0], colors["reset"]))
|
||||
if len(lines) > 1:
|
||||
for line in lines[1:]:
|
||||
print("{0}{1}{2}".format(colors[event.source.rstrip()], line, colors["reset"]))
|
||||
"""Renders the iterator of log events into text."""
|
||||
for event in log_events:
|
||||
lines = event.event.splitlines()
|
||||
print("{0} {1: <5} {2} {3}".format(colors[event.source.rstrip()], event.source, lines[0], colors["reset"]))
|
||||
if len(lines) > 1:
|
||||
for line in lines[1:]:
|
||||
print("{0}{1}{2}".format(colors[event.source.rstrip()], line, colors["reset"]))
|
||||
|
||||
|
||||
def print_logs_html(log_events):
|
||||
"""Renders the iterator of log events into html."""
|
||||
try:
|
||||
import jinja2
|
||||
except ImportError:
|
||||
print("jinja2 not found. Try `pip install jinja2`")
|
||||
sys.exit(1)
|
||||
print(jinja2.Environment(loader=jinja2.FileSystemLoader('./'))
|
||||
"""Renders the iterator of log events into html."""
|
||||
try:
|
||||
import jinja2
|
||||
except ImportError:
|
||||
print("jinja2 not found. Try `pip install jinja2`")
|
||||
sys.exit(1)
|
||||
print(jinja2.Environment(loader=jinja2.FileSystemLoader('./'))
|
||||
.get_template('combined_log_template.html')
|
||||
.render(title="Combined Logs from testcase", log_events=[event._asdict() for event in log_events]))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
|
@ -72,7 +72,7 @@ class AssumeValidTest(BitcoinTestFramework):
|
|||
break
|
||||
try:
|
||||
p2p_conn.send_message(msg_block(self.blocks[i]))
|
||||
except IOError as e:
|
||||
except IOError:
|
||||
assert not p2p_conn.is_connected
|
||||
break
|
||||
|
||||
|
|
|
@ -18,10 +18,10 @@ class BlocksdirTest(BitcoinTestFramework):
|
|||
|
||||
def run_test(self):
|
||||
self.stop_node(0)
|
||||
assert os.path.isdir(os.path.join(self.nodes[0].datadir, "regtest", "blocks"))
|
||||
assert os.path.isdir(os.path.join(self.nodes[0].datadir, self.chain, "blocks"))
|
||||
assert not os.path.isdir(os.path.join(self.nodes[0].datadir, "blocks"))
|
||||
shutil.rmtree(self.nodes[0].datadir)
|
||||
initialize_datadir(self.options.tmpdir, 0)
|
||||
initialize_datadir(self.options.tmpdir, 0, self.chain)
|
||||
self.log.info("Starting with nonexistent blocksdir ...")
|
||||
blocksdir_path = os.path.join(self.options.tmpdir, 'blocksdir')
|
||||
self.nodes[0].assert_start_raises_init_error(["-blocksdir=" + blocksdir_path], 'Error: Specified blocks directory "{}" does not exist.'.format(blocksdir_path))
|
||||
|
@ -30,8 +30,8 @@ class BlocksdirTest(BitcoinTestFramework):
|
|||
self.start_node(0, ["-blocksdir=" + blocksdir_path])
|
||||
self.log.info("mining blocks..")
|
||||
self.nodes[0].generatetoaddress(10, self.nodes[0].get_deterministic_priv_key().address)
|
||||
assert os.path.isfile(os.path.join(blocksdir_path, "regtest", "blocks", "blk00000.dat"))
|
||||
assert os.path.isdir(os.path.join(self.nodes[0].datadir, "regtest", "blocks", "index"))
|
||||
assert os.path.isfile(os.path.join(blocksdir_path, self.chain, "blocks", "blk00000.dat"))
|
||||
assert os.path.isdir(os.path.join(self.nodes[0].datadir, self.chain, "blocks", "index"))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -29,7 +29,7 @@ class TestBitcoinCli(BitcoinTestFramework):
|
|||
rpc_response = self.nodes[0].getblockchaininfo()
|
||||
assert_equal(cli_response, rpc_response)
|
||||
|
||||
user, password = get_auth_cookie(self.nodes[0].datadir)
|
||||
user, password = get_auth_cookie(self.nodes[0].datadir, self.chain)
|
||||
|
||||
self.log.info("Test -stdinrpcpass option")
|
||||
assert_equal(0, self.nodes[0].cli('-rpcuser=%s' % user, '-stdinrpcpass', input=password).getblockcount())
|
||||
|
|
|
@ -55,7 +55,7 @@ class RPCBindTest(BitcoinTestFramework):
|
|||
self.nodes[0].rpchost = None
|
||||
self.start_nodes([node_args])
|
||||
# connect to node through non-loopback interface
|
||||
node = get_rpc_proxy(rpc_url(self.nodes[0].datadir, 0, "%s:%d" % (rpchost, rpcport)), 0, coveragedir=self.options.coveragedir)
|
||||
node = get_rpc_proxy(rpc_url(self.nodes[0].datadir, 0, self.chain, "%s:%d" % (rpchost, rpcport)), 0, coveragedir=self.options.coveragedir)
|
||||
node.getnetworkinfo()
|
||||
self.stop_nodes()
|
||||
|
||||
|
|
|
@ -91,6 +91,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
|
|||
|
||||
def __init__(self):
|
||||
"""Sets test framework defaults. Do not override this method. Instead, override the set_test_params() method"""
|
||||
self.chain = 'regtest'
|
||||
self.setup_clean_chain = False
|
||||
self.nodes = []
|
||||
self.network_thread = None
|
||||
|
@ -192,18 +193,18 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
|
|||
self.setup_network()
|
||||
self.run_test()
|
||||
success = TestStatus.PASSED
|
||||
except JSONRPCException as e:
|
||||
except JSONRPCException:
|
||||
self.log.exception("JSONRPC error")
|
||||
except SkipTest as e:
|
||||
self.log.warning("Test Skipped: %s" % e.message)
|
||||
success = TestStatus.SKIPPED
|
||||
except AssertionError as e:
|
||||
except AssertionError:
|
||||
self.log.exception("Assertion failed")
|
||||
except KeyError as e:
|
||||
except KeyError:
|
||||
self.log.exception("Key error")
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
self.log.exception("Unexpected exception caught during testing")
|
||||
except KeyboardInterrupt as e:
|
||||
except KeyboardInterrupt:
|
||||
self.log.warning("Exiting after keyboard interrupt")
|
||||
|
||||
if success == TestStatus.FAILED and self.options.pdbonfailure:
|
||||
|
@ -342,6 +343,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
|
|||
self.nodes.append(TestNode(
|
||||
i,
|
||||
get_datadir_path(self.options.tmpdir, i),
|
||||
chain=self.chain,
|
||||
rpchost=rpchost,
|
||||
timewait=self.rpc_timeout,
|
||||
bitcoind=binary[i],
|
||||
|
@ -477,11 +479,12 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
|
|||
if not os.path.isdir(cache_node_dir):
|
||||
self.log.debug("Creating cache directory {}".format(cache_node_dir))
|
||||
|
||||
initialize_datadir(self.options.cachedir, CACHE_NODE_ID)
|
||||
initialize_datadir(self.options.cachedir, CACHE_NODE_ID, self.chain)
|
||||
self.nodes.append(
|
||||
TestNode(
|
||||
CACHE_NODE_ID,
|
||||
cache_node_dir,
|
||||
chain=self.chain,
|
||||
extra_conf=["bind=127.0.0.1"],
|
||||
extra_args=['-disablewallet'],
|
||||
rpchost=None,
|
||||
|
@ -515,7 +518,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
|
|||
self.nodes = []
|
||||
|
||||
def cache_path(*paths):
|
||||
return os.path.join(cache_node_dir, "regtest", *paths)
|
||||
return os.path.join(cache_node_dir, self.chain, *paths)
|
||||
|
||||
os.rmdir(cache_path('wallets')) # Remove empty wallets dir
|
||||
for entry in os.listdir(cache_path()):
|
||||
|
@ -526,7 +529,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
|
|||
self.log.debug("Copy cache directory {} to node {}".format(cache_node_dir, i))
|
||||
to_dir = get_datadir_path(self.options.tmpdir, i)
|
||||
shutil.copytree(cache_node_dir, to_dir)
|
||||
initialize_datadir(self.options.tmpdir, i) # Overwrite port/rpcport in bitcoin.conf
|
||||
initialize_datadir(self.options.tmpdir, i, self.chain) # Overwrite port/rpcport in bitcoin.conf
|
||||
|
||||
def _initialize_chain_clean(self):
|
||||
"""Initialize empty blockchain for use by the test.
|
||||
|
@ -534,7 +537,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
|
|||
Create an empty blockchain and num_nodes wallets.
|
||||
Useful if a test case wants complete control over initialization."""
|
||||
for i in range(self.num_nodes):
|
||||
initialize_datadir(self.options.tmpdir, i)
|
||||
initialize_datadir(self.options.tmpdir, i, self.chain)
|
||||
|
||||
def skip_if_no_py3_zmq(self):
|
||||
"""Attempt to import the zmq package and skip the test if the import fails."""
|
||||
|
|
|
@ -59,7 +59,7 @@ class TestNode():
|
|||
To make things easier for the test writer, any unrecognised messages will
|
||||
be dispatched to the RPC connection."""
|
||||
|
||||
def __init__(self, i, datadir, *, rpchost, timewait, bitcoind, bitcoin_cli, coverage_dir, cwd, extra_conf=None, extra_args=None, use_cli=False, start_perf=False):
|
||||
def __init__(self, i, datadir, *, chain, rpchost, timewait, bitcoind, bitcoin_cli, coverage_dir, cwd, extra_conf=None, extra_args=None, use_cli=False, start_perf=False):
|
||||
"""
|
||||
Kwargs:
|
||||
start_perf (bool): If True, begin profiling the node with `perf` as soon as
|
||||
|
@ -70,6 +70,7 @@ class TestNode():
|
|||
self.datadir = datadir
|
||||
self.stdout_dir = os.path.join(self.datadir, "stdout")
|
||||
self.stderr_dir = os.path.join(self.datadir, "stderr")
|
||||
self.chain = chain
|
||||
self.rpchost = rpchost
|
||||
self.rpc_timeout = timewait
|
||||
self.binary = bitcoind
|
||||
|
@ -197,7 +198,7 @@ class TestNode():
|
|||
# Delete any existing cookie file -- if such a file exists (eg due to
|
||||
# unclean shutdown), it will get overwritten anyway by bitcoind, and
|
||||
# potentially interfere with our attempt to authenticate
|
||||
delete_cookie_file(self.datadir)
|
||||
delete_cookie_file(self.datadir, self.chain)
|
||||
|
||||
# add environment variable LIBC_FATAL_STDERR_=1 so that libc errors are written to stderr and not the terminal
|
||||
subp_env = dict(os.environ, LIBC_FATAL_STDERR_="1")
|
||||
|
@ -219,7 +220,7 @@ class TestNode():
|
|||
raise FailedToStartError(self._node_msg(
|
||||
'bitcoind exited with status {} during initialization'.format(self.process.returncode)))
|
||||
try:
|
||||
rpc = get_rpc_proxy(rpc_url(self.datadir, self.index, self.rpchost), self.index, timeout=self.rpc_timeout, coveragedir=self.coverage_dir)
|
||||
rpc = get_rpc_proxy(rpc_url(self.datadir, self.index, self.chain, self.rpchost), self.index, timeout=self.rpc_timeout, coveragedir=self.coverage_dir)
|
||||
rpc.getblockcount()
|
||||
# If the call to getblockcount() succeeds then the RPC connection is up
|
||||
self.log.debug("RPC successfully started")
|
||||
|
@ -306,7 +307,7 @@ class TestNode():
|
|||
|
||||
@contextlib.contextmanager
|
||||
def assert_debug_log(self, expected_msgs):
|
||||
debug_log = os.path.join(self.datadir, 'regtest', 'debug.log')
|
||||
debug_log = os.path.join(self.datadir, self.chain, 'debug.log')
|
||||
with open(debug_log, encoding='utf-8') as dl:
|
||||
dl.seek(0, 2)
|
||||
prev_size = dl.tell()
|
||||
|
|
|
@ -271,8 +271,8 @@ def p2p_port(n):
|
|||
def rpc_port(n):
|
||||
return PORT_MIN + PORT_RANGE + n + (MAX_NODES * PortSeed.n) % (PORT_RANGE - 1 - MAX_NODES)
|
||||
|
||||
def rpc_url(datadir, i, rpchost=None):
|
||||
rpc_u, rpc_p = get_auth_cookie(datadir)
|
||||
def rpc_url(datadir, i, chain, rpchost):
|
||||
rpc_u, rpc_p = get_auth_cookie(datadir, chain)
|
||||
host = '127.0.0.1'
|
||||
port = rpc_port(i)
|
||||
if rpchost:
|
||||
|
@ -286,13 +286,13 @@ def rpc_url(datadir, i, rpchost=None):
|
|||
# Node functions
|
||||
################
|
||||
|
||||
def initialize_datadir(dirname, n):
|
||||
def initialize_datadir(dirname, n, chain):
|
||||
datadir = get_datadir_path(dirname, n)
|
||||
if not os.path.isdir(datadir):
|
||||
os.makedirs(datadir)
|
||||
with open(os.path.join(datadir, "bitcoin.conf"), 'w', encoding='utf8') as f:
|
||||
f.write("regtest=1\n")
|
||||
f.write("[regtest]\n")
|
||||
f.write("{}=1\n".format(chain))
|
||||
f.write("[{}]\n".format(chain))
|
||||
f.write("port=" + str(p2p_port(n)) + "\n")
|
||||
f.write("rpcport=" + str(rpc_port(n)) + "\n")
|
||||
f.write("server=1\n")
|
||||
|
@ -312,7 +312,7 @@ def append_config(datadir, options):
|
|||
for option in options:
|
||||
f.write(option + "\n")
|
||||
|
||||
def get_auth_cookie(datadir):
|
||||
def get_auth_cookie(datadir, chain):
|
||||
user = None
|
||||
password = None
|
||||
if os.path.isfile(os.path.join(datadir, "bitcoin.conf")):
|
||||
|
@ -325,7 +325,7 @@ def get_auth_cookie(datadir):
|
|||
assert password is None # Ensure that there is only one rpcpassword line
|
||||
password = line.split("=")[1].strip("\n")
|
||||
try:
|
||||
with open(os.path.join(datadir, "regtest", ".cookie"), 'r', encoding="ascii") as f:
|
||||
with open(os.path.join(datadir, chain, ".cookie"), 'r', encoding="ascii") as f:
|
||||
userpass = f.read()
|
||||
split_userpass = userpass.split(':')
|
||||
user = split_userpass[0]
|
||||
|
@ -337,10 +337,10 @@ def get_auth_cookie(datadir):
|
|||
return user, password
|
||||
|
||||
# If a cookie file exists in the given datadir, delete it.
|
||||
def delete_cookie_file(datadir):
|
||||
if os.path.isfile(os.path.join(datadir, "regtest", ".cookie")):
|
||||
def delete_cookie_file(datadir, chain):
|
||||
if os.path.isfile(os.path.join(datadir, chain, ".cookie")):
|
||||
logger.debug("Deleting leftover cookie file")
|
||||
os.remove(os.path.join(datadir, "regtest", ".cookie"))
|
||||
os.remove(os.path.join(datadir, chain, ".cookie"))
|
||||
|
||||
def get_bip9_status(node, key):
|
||||
info = node.getblockchaininfo()
|
||||
|
|
Loading…
Reference in a new issue