diff --git a/test/functional/abc-cmdline.py b/test/functional/abc-cmdline.py --- a/test/functional/abc-cmdline.py +++ b/test/functional/abc-cmdline.py @@ -16,7 +16,7 @@ from test_framework.util import assert_equal MAX_GENERATED_BLOCK_SIZE_ERROR = ( - 'Max generated block size (blockmaxsize) cannot exceed the excessive block size (excessiveblocksize)') + 'Max generated block size \(blockmaxsize\) cannot exceed the excessive block size \(excessiveblocksize\)') class ABC_CmdLine_Test (BitcoinTestFramework): @@ -53,12 +53,12 @@ self.log.info(" Attempt to set below legacy limit of 1MB - try {} bytes".format( LEGACY_MAX_BLOCK_SIZE)) self.stop_node(0) - self.assert_start_raises_init_error( - 0, ["-excessiveblocksize={}".format(LEGACY_MAX_BLOCK_SIZE)], - 'Error: Excessive block size must be > 1,000,000 bytes (1MB)') + self.nodes[0].assert_start_raises_init_error( + ["-excessiveblocksize={}".format(LEGACY_MAX_BLOCK_SIZE)], + 'Error: Excessive block size must be > 1,000,000 bytes \(1MB\)') self.log.info(" Attempt to set below blockmaxsize (mining limit)") - self.assert_start_raises_init_error( - 0, ['-blockmaxsize=1500000', '-excessiveblocksize=1300000'], 'Error: ' + MAX_GENERATED_BLOCK_SIZE_ERROR) + self.nodes[0].assert_start_raises_init_error( + ['-blockmaxsize=1500000', '-excessiveblocksize=1300000'], 'Error: ' + MAX_GENERATED_BLOCK_SIZE_ERROR) # Make sure we leave the test with a node running as this is what thee # framework expects. diff --git a/test/functional/feature_blocksdir.py b/test/functional/feature_blocksdir.py --- a/test/functional/feature_blocksdir.py +++ b/test/functional/feature_blocksdir.py @@ -5,10 +5,11 @@ """Test the blocksdir option. """ -from test_framework.test_framework import BitcoinTestFramework, initialize_datadir - -import shutil import os +import re +import shutil + +from test_framework.test_framework import BitcoinTestFramework, initialize_datadir class BlocksdirTest(BitcoinTestFramework): @@ -18,21 +19,21 @@ def run_test(self): self.stop_node(0) - node0path = os.path.join(self.options.tmpdir, "node0") - shutil.rmtree(node0path) + shutil.rmtree(self.nodes[0].datadir) initialize_datadir(self.options.tmpdir, 0) self.log.info("Starting with non exiting blocksdir ...") - self.assert_start_raises_init_error( - 0, ["-blocksdir="+self.options.tmpdir + "/blocksdir"], "Specified blocks director") - os.mkdir(self.options.tmpdir + "/blocksdir") + blocksdir_path = os.path.join(self.options.tmpdir, 'blocksdir') + self.nodes[0].assert_start_raises_init_error(["-blocksdir=" + blocksdir_path], re.escape( + 'Error: Specified blocks directory "{}" does not exist.'.format(blocksdir_path))) + os.mkdir(blocksdir_path) self.log.info("Starting with exiting blocksdir ...") - self.start_node(0, ["-blocksdir="+self.options.tmpdir + "/blocksdir"]) + self.start_node(0, ["-blocksdir=" + blocksdir_path]) self.log.info("mining blocks..") self.nodes[0].generate(10) - assert(os.path.isfile(os.path.join(self.options.tmpdir, - "blocksdir", "regtest", "blocks", "blk00000.dat"))) - assert(os.path.isdir(os.path.join(self.options.tmpdir, - "node0", "regtest", "blocks", "index"))) + 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")) if __name__ == '__main__': diff --git a/test/functional/feature_config_args.py b/test/functional/feature_config_args.py --- a/test/functional/feature_config_args.py +++ b/test/functional/feature_config_args.py @@ -4,6 +4,7 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test various command line arguments and configuration file parameters.""" import os +import re from test_framework.test_framework import BitcoinTestFramework @@ -24,9 +25,9 @@ # Check that using -datadir argument on non-existent directory fails self.nodes[0].datadir = new_data_dir - self.assert_start_raises_init_error( - 0, ['-datadir='+new_data_dir], - 'Error: Specified data directory "' + new_data_dir + '" does not exist.') + self.nodes[0].assert_start_raises_init_error( + ['-datadir=' + new_data_dir], + 'Error: Specified data directory "' + re.escape(new_data_dir) + '" does not exist.') # Check that using non-existent datadir in conf file fails conf_file = os.path.join(default_data_dir, "bitcoin.conf") @@ -37,9 +38,9 @@ f.write("datadir=" + new_data_dir + "\n") f.write(conf_file_contents) - self.assert_start_raises_init_error( - 0, ['-conf='+conf_file], - 'Error reading configuration file: specified data directory "' + new_data_dir + '" does not exist.') + self.nodes[0].assert_start_raises_init_error( + ['-conf=' + conf_file], + 'Error reading configuration file: specified data directory "' + re.escape(new_data_dir) + '" does not exist.') # Create the directory and ensure the config file now works os.mkdir(new_data_dir) diff --git a/test/functional/feature_logging.py b/test/functional/feature_logging.py --- a/test/functional/feature_logging.py +++ b/test/functional/feature_logging.py @@ -33,8 +33,9 @@ invdir = os.path.join(self.nodes[0].datadir, "regtest", "foo") invalidname = os.path.join("foo", "foo.log") self.stop_node(0) - self.assert_start_raises_init_error(0, ["-debuglogfile={}".format(invalidname)], - "Error: Could not open debug log file") + exp_stderr = "Error: Could not open debug log file \S+$" + self.nodes[0].assert_start_raises_init_error( + ["-debuglogfile={}".format(invalidname)], exp_stderr) assert not os.path.isfile(os.path.join(invdir, "foo.log")) # check that invalid log (relative) works after path exists @@ -47,8 +48,8 @@ self.stop_node(0) invdir = os.path.join(self.options.tmpdir, "foo") invalidname = os.path.join(invdir, "foo.log") - self.assert_start_raises_init_error(0, ["-debuglogfile={}".format(invalidname)], - "Error: Could not open debug log file") + self.nodes[0].assert_start_raises_init_error( + ["-debuglogfile={}".format(invalidname)], exp_stderr) assert not os.path.isfile(os.path.join(invdir, "foo.log")) # check that invalid log (absolute) works after path exists diff --git a/test/functional/feature_uacomment.py b/test/functional/feature_uacomment.py --- a/test/functional/feature_uacomment.py +++ b/test/functional/feature_uacomment.py @@ -4,6 +4,8 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test the -uacomment option.""" +import re + from test_framework.test_framework import BitcoinTestFramework from test_framework.util import assert_equal @@ -25,16 +27,16 @@ self.log.info("test -uacomment max length") self.stop_node(0) - expected = "Total length of network version string (290) exceeds maximum length (256). Reduce the number or size of uacomments." - self.assert_start_raises_init_error( - 0, ["-uacomment=" + 'a' * 256], expected) + expected = "Error: Total length of network version string \([0-9]+\) exceeds maximum length \(256\). Reduce the number or size of uacomments." + self.nodes[0].assert_start_raises_init_error( + ["-uacomment=" + 'a' * 256], expected) self.log.info("test -uacomment unsafe characters") for unsafe_char in ['/', ':', '(', ')']: - expected = "User Agent comment (" + \ - unsafe_char + ") contains unsafe characters" - self.assert_start_raises_init_error( - 0, ["-uacomment=" + unsafe_char], expected) + expected = "Error: User Agent comment \(" + re.escape( + unsafe_char) + "\) contains unsafe characters." + self.nodes[0].assert_start_raises_init_error( + ["-uacomment=" + unsafe_char], expected) if __name__ == '__main__': diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -312,29 +312,6 @@ self.stop_node(i) self.start_node(i, extra_args) - def assert_start_raises_init_error(self, i, extra_args=None, expected_msg=None, *args, **kwargs): - with tempfile.SpooledTemporaryFile(max_size=2**16) as log_stderr: - try: - self.start_node( - i, extra_args, stderr=log_stderr, *args, **kwargs) - self.stop_node(i) - except Exception as e: - assert 'bitcoind exited' in str(e) # node must have shutdown - self.nodes[i].running = False - self.nodes[i].process = None - if expected_msg is not None: - log_stderr.seek(0) - stderr = log_stderr.read().decode('utf-8') - if expected_msg not in stderr: - raise AssertionError( - "Expected error \"" + expected_msg + "\" not found in:\n" + stderr) - else: - if expected_msg is None: - assert_msg = "bitcoind should have exited with an error" - else: - assert_msg = "bitcoind should have exited with expected error " + expected_msg - raise AssertionError(assert_msg) - def wait_for_node_exit(self, i, timeout): self.nodes[i].process.wait(timeout) diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -13,6 +13,7 @@ import re import subprocess import sys +import tempfile import time from .authproxy import JSONRPCException @@ -33,6 +34,10 @@ BITCOIND_PROC_WAIT_TIMEOUT = 60 +class FailedToStartError(Exception): + """Raised when a node fails to start correctly.""" + + class TestNode(): """A class for representing a bitcoind node under test. @@ -140,8 +145,9 @@ # Poll at a rate of four times per second poll_per_s = 4 for _ in range(poll_per_s * self.rpc_timeout): - assert self.process.poll( - ) is None, "bitcoind exited with status {} during initialization".format(self.process.returncode) + if self.process.poll() is not None: + raise FailedToStartError( + 'bitcoind exited with status {} during initialization'.format(self.process.returncode)) try: self.rpc = get_rpc_proxy(rpc_url(self.datadir, self.host, self.rpc_port), self.index, timeout=self.rpc_timeout, coveragedir=self.coverage_dir) @@ -206,6 +212,43 @@ def wait_until_stopped(self, timeout=BITCOIND_PROC_WAIT_TIMEOUT): wait_until(self.is_node_stopped, timeout=timeout) + def assert_start_raises_init_error(self, extra_args=None, expected_msg=None, partial_match=False, *args, **kwargs): + """Attempt to start the node and expect it to raise an error. + + extra_args: extra arguments to pass through to bitcoind + expected_msg: regex that stderr should match when bitcoind fails + + Will throw if bitcoind starts without an error. + Will throw if an expected_msg is provided and it does not match bitcoind's stdout.""" + with tempfile.SpooledTemporaryFile(max_size=2**16) as log_stderr: + try: + self.start(extra_args, stderr=log_stderr, *args, **kwargs) + self.wait_for_rpc_connection() + self.stop_node() + self.wait_until_stopped() + except FailedToStartError as e: + self.log.debug('bitcoind failed to start: {}'.format(e)) + self.running = False + self.process = None + # Check stderr for expected message + if expected_msg is not None: + log_stderr.seek(0) + stderr = log_stderr.read().decode('utf-8').strip() + if partial_match: + if re.search(expected_msg, stderr, flags=re.MULTILINE) is None: + raise AssertionError( + 'Expected message "{}" does not partially match stderr:\n"{}"'.format(expected_msg, stderr)) + else: + if re.fullmatch(expected_msg, stderr) is None: + raise AssertionError( + 'Expected message "{}" does not fully match stderr:\n"{}"'.format(expected_msg, stderr)) + else: + if expected_msg is None: + assert_msg = "bitcoind should have exited with an error" + else: + assert_msg = "bitcoind should have exited with expected error " + expected_msg + raise AssertionError(assert_msg) + def node_encrypt_wallet(self, passphrase): """"Encrypts the wallet. diff --git a/test/functional/wallet_hd.py b/test/functional/wallet_hd.py --- a/test/functional/wallet_hd.py +++ b/test/functional/wallet_hd.py @@ -22,8 +22,8 @@ # Make sure can't switch off usehd after wallet creation self.stop_node(1) - self.assert_start_raises_init_error( - 1, ['-usehd=0'], 'already existing HD wallet') + self.nodes[1].assert_start_raises_init_error( + ['-usehd=0'], "Error: Error loading : You can't disable HD on an already existing HD wallet") self.start_node(1) connect_nodes_bi(self.nodes[0], self.nodes[1]) diff --git a/test/functional/wallet_multiwallet.py b/test/functional/wallet_multiwallet.py --- a/test/functional/wallet_multiwallet.py +++ b/test/functional/wallet_multiwallet.py @@ -7,6 +7,7 @@ Verify that a bitcoind node can load multiple wallet files """ import os +import re import shutil from test_framework.test_framework import BitcoinTestFramework @@ -67,38 +68,40 @@ assert_equal(os.path.isfile(wallet_dir(wallet_name)), True) # should not initialize if wallet path can't be created - self.assert_start_raises_init_error( - 0, ['-wallet=wallet.dat/bad'], 'Not a directory') + exp_stderr = "boost::filesystem::create_directory: (The system cannot find the path specified|Not a directory):" + self.nodes[0].assert_start_raises_init_error( + ['-wallet=wallet.dat/bad'], exp_stderr, partial_match=True) - self.assert_start_raises_init_error( - 0, ['-walletdir=wallets'], 'Error: Specified -walletdir "wallets" does not exist') - self.assert_start_raises_init_error( - 0, ['-walletdir=wallets'], 'Error: Specified -walletdir "wallets" is a relative path', cwd=data_dir()) - self.assert_start_raises_init_error( - 0, ['-walletdir=debug.log'], 'Error: Specified -walletdir "debug.log" is not a directory', cwd=data_dir()) + self.nodes[0].assert_start_raises_init_error( + ['-walletdir=wallets'], 'Error: Specified -walletdir "wallets" does not exist') + self.nodes[0].assert_start_raises_init_error( + ['-walletdir=wallets'], 'Error: Specified -walletdir "wallets" is a relative path', cwd=data_dir()) + self.nodes[0].assert_start_raises_init_error( + ['-walletdir=debug.log'], 'Error: Specified -walletdir "debug.log" is not a directory', cwd=data_dir()) # should not initialize if there are duplicate wallets - self.assert_start_raises_init_error( - 0, ['-wallet=w1', '-wallet=w1'], 'Error loading wallet w1. Duplicate -wallet filename specified.') + self.nodes[0].assert_start_raises_init_error( + ['-wallet=w1', '-wallet=w1'], 'Error: Error loading wallet w1. Duplicate -wallet filename specified.') # should not initialize if one wallet is a copy of another shutil.copyfile(wallet_dir('w8'), wallet_dir('w8_copy')) - self.assert_start_raises_init_error( - 0, ['-wallet=w8', '-wallet=w8_copy'], 'duplicates fileid') + exp_stderr = "CDB: Can't open database w8_copy \(duplicates fileid \w+ from w8\)" + self.nodes[0].assert_start_raises_init_error( + ['-wallet=w8', '-wallet=w8_copy'], exp_stderr, partial_match=True) # should not initialize if wallet file is a symlink os.symlink('w8', wallet_dir('w8_symlink')) - self.assert_start_raises_init_error( - 0, ['-wallet=w8_symlink'], 'Invalid -wallet path') + self.nodes[0].assert_start_raises_init_error( + ['-wallet=w8_symlink'], 'Error: Invalid -wallet path \'w8_symlink\'\. .*') # should not initialize if the specified walletdir does not exist - self.assert_start_raises_init_error( - 0, ['-walletdir=bad'], 'Error: Specified -walletdir "bad" does not exist') + self.nodes[0].assert_start_raises_init_error( + ['-walletdir=bad'], 'Error: Specified -walletdir "bad" does not exist') # should not initialize if the specified walletdir is not a directory not_a_dir = wallet_dir('notadir') open(not_a_dir, 'a').close() - self.assert_start_raises_init_error( - 0, ['-walletdir='+not_a_dir], 'Error: Specified -walletdir "' + not_a_dir + '" is not a directory') + self.nodes[0].assert_start_raises_init_error( + ['-walletdir=' + not_a_dir], 'Error: Specified -walletdir "' + re.escape(not_a_dir) + '" is not a directory') # if wallets/ doesn't exist, datadir should be the default wallet dir wallet_dir2 = data_dir('walletdir') @@ -120,9 +123,10 @@ competing_wallet_dir = os.path.join( self.options.tmpdir, 'competing_walletdir') os.mkdir(competing_wallet_dir) - self.restart_node(0, ['-walletdir='+competing_wallet_dir]) - self.assert_start_raises_init_error( - 1, ['-walletdir='+competing_wallet_dir], 'Error initializing wallet database environment') + self.restart_node(0, ['-walletdir=' + competing_wallet_dir]) + exp_stderr = "Error: Error initializing wallet database environment \"\S+competing_walletdir\"!" + self.nodes[1].assert_start_raises_init_error( + ['-walletdir=' + competing_wallet_dir], exp_stderr, partial_match=True) self.restart_node(0, extra_args)