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 @@ -51,6 +51,7 @@ class BitcoinTestFramework(): + """Base class for a bitcoin test script. Individual bitcoin test scripts should subclass this class and override the set_test_params() and run_test() methods. @@ -77,31 +78,41 @@ """Main function. This should not be overridden by the subclass test scripts.""" parser = argparse.ArgumentParser(usage="%(prog)s [options]") - parser.add_argument("--nocleanup", dest="nocleanup", default=False, action="store_true", + parser.add_argument( + "--nocleanup", dest="nocleanup", default=False, action="store_true", help="Leave bitcoinds and test.* datadir on exit or error") - parser.add_argument("--noshutdown", dest="noshutdown", default=False, action="store_true", + parser.add_argument( + "--noshutdown", dest="noshutdown", default=False, action="store_true", help="Don't stop bitcoinds after the test execution") - parser.add_argument("--srcdir", dest="srcdir", default=os.path.normpath(os.path.dirname(os.path.realpath(__file__)) + "/../../../src"), + parser.add_argument( + "--srcdir", dest="srcdir", default=os.path.normpath(os.path.dirname(os.path.realpath(__file__)) + "/../../../src"), help="Source directory containing bitcoind/bitcoin-cli (default: %(default)s)") - parser.add_argument("--cachedir", dest="cachedir", default=os.path.normpath(os.path.dirname(os.path.realpath(__file__)) + "/../../cache"), + parser.add_argument( + "--cachedir", dest="cachedir", default=os.path.normpath(os.path.dirname(os.path.realpath(__file__)) + "/../../cache"), help="Directory for caching pregenerated datadirs") parser.add_argument("--tmpdir", dest="tmpdir", help="Root directory for datadirs") - parser.add_argument("-l", "--loglevel", dest="loglevel", default="INFO", + parser.add_argument( + "-l", "--loglevel", dest="loglevel", default="INFO", 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.") - parser.add_argument("--tracerpc", dest="trace_rpc", default=False, action="store_true", + parser.add_argument( + "--tracerpc", dest="trace_rpc", default=False, action="store_true", help="Print out all RPC calls as they are made") - parser.add_argument("--portseed", dest="port_seed", default=os.getpid(), type=int, + parser.add_argument( + "--portseed", dest="port_seed", default=os.getpid(), type=int, help="The seed to use for assigning port numbers (default: current process id)") parser.add_argument("--coveragedir", dest="coveragedir", help="Write tested RPC commands into this directory") parser.add_argument("--configfile", dest="configfile", help="Location of the test framework config file") - parser.add_argument("--pdbonfailure", dest="pdbonfailure", default=False, action="store_true", + parser.add_argument( + "--pdbonfailure", dest="pdbonfailure", default=False, action="store_true", help="Attach a python debugger if test fails") - parser.add_argument("--usecli", dest="usecli", default=False, action="store_true", + parser.add_argument( + "--usecli", dest="usecli", default=False, action="store_true", help="use bitcoin-cli instead of RPC for all commands") - parser.add_argument("--with-greatwallactivation", dest="greatwallactivation", default=False, action="store_true", + parser.add_argument( + "--with-greatwallactivation", dest="greatwallactivation", default=False, action="store_true", help="Activate great wall update on timestamp {}".format(TIMESTAMP_IN_THE_PAST)) self.add_options(parser) self.options = parser.parse_args() @@ -152,7 +163,8 @@ self.log.warning("Exiting after keyboard interrupt") if success == TestStatus.FAILED and self.options.pdbonfailure: - print("Testcase failed. Attaching python debugger. Enter ? for help") + print( + "Testcase failed. Attaching python debugger. Enter ? for help") pdb.set_trace() if not self.options.noshutdown: @@ -160,6 +172,8 @@ if self.nodes: self.stop_nodes() else: + for node in self.nodes: + node.cleanup_on_exit = False self.log.info( "Note: bitcoinds were not stopped and may still be running") @@ -238,7 +252,8 @@ """Tests must override this method to define test logic""" raise NotImplementedError - # Public helper methods. These can be accessed by the subclass test scripts. + # Public helper methods. These can be accessed by the subclass test + # scripts. def add_nodes(self, num_nodes, extra_args=None, rpchost=None, timewait=None, binary=None): """Instantiate TestNode objects""" @@ -250,8 +265,10 @@ assert_equal(len(extra_args), num_nodes) assert_equal(len(binary), num_nodes) for i in range(num_nodes): - self.nodes.append(TestNode(i, self.options.tmpdir, extra_args[i], rpchost, rpc_port=rpc_port(i), p2p_port=p2p_port(i), - timewait=timewait, binary=binary[i], stderr=None, mocktime=self.mocktime, coverage_dir=self.options.coveragedir, use_cli=self.options.usecli)) + self.nodes.append( + TestNode( + i, self.options.tmpdir, extra_args[i], rpchost, rpc_port=rpc_port(i), p2p_port=p2p_port(i), + timewait=timewait, binary=binary[i], stderr=None, mocktime=self.mocktime, coverage_dir=self.options.coveragedir, use_cli=self.options.usecli)) if self.options.greatwallactivation: self.nodes[i].extend_default_args( ["-greatwallactivationtime={}".format(TIMESTAMP_IN_THE_PAST)]) @@ -328,7 +345,8 @@ 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 + assert_msg = "bitcoind should have exited with expected error " + \ + expected_msg raise AssertionError(assert_msg) def wait_for_node_exit(self, i, timeout): @@ -357,7 +375,8 @@ sync_blocks(group) sync_mempools(group) - # Private helper methods. These should not be accessed by the subclass test scripts. + # Private helper methods. These should not be accessed by the subclass + # test scripts. def _start_logging(self): # Add logger and logging handlers @@ -374,7 +393,8 @@ ll = int(self.options.loglevel) if self.options.loglevel.isdigit( ) else self.options.loglevel.upper() ch.setLevel(ll) - # Format logs the same as bitcoind's debug.log with microprecision (so log files can be concatenated and sorted) + # Format logs the same as bitcoind's debug.log with microprecision (so + # log files can be concatenated and sorted) formatter = logging.Formatter( fmt='%(asctime)s.%(msecs)03d000Z %(name)s (%(levelname)s): %(message)s', datefmt='%Y-%m-%dT%H:%M:%S') formatter.converter = time.gmtime @@ -416,7 +436,9 @@ # Create cache directories, run bitcoinds: for i in range(MAX_NODES): datadir = initialize_datadir(self.options.cachedir, i) - self.nodes.append(TestNode(i, self.options.cachedir, extra_args=[], host=None, rpc_port=rpc_port(i), p2p_port=p2p_port(i), + self.nodes.append( + TestNode( + i, self.options.cachedir, extra_args=[], host=None, rpc_port=rpc_port(i), p2p_port=p2p_port(i), timewait=None, binary=None, stderr=None, mocktime=self.mocktime, coverage_dir=None)) self.nodes[i].clear_default_args() self.nodes[i].extend_default_args([ @@ -483,6 +505,7 @@ class ComparisonTestFramework(BitcoinTestFramework): + """Test framework for doing p2p comparison testing Sets up some bitcoind binaries: @@ -513,6 +536,7 @@ class SkipTest(Exception): + """This exception is raised to skip a test""" def __init__(self, message): 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 @@ -32,6 +32,7 @@ class TestNode(): + """A class for representing a bitcoind node under test. This class contains: @@ -70,7 +71,9 @@ # For those callers that need more flexibity, they can access the # default args using the provided facilities self.extra_args = extra_args - self.default_args = ["-datadir=" + self.datadir, "-server", "-keypool=1", "-discover=0", "-rest", "-logtimemicros", + self.default_args = [ + "-datadir=" + + self.datadir, "-server", "-keypool=1", "-discover=0", "-rest", "-logtimemicros", "-debug", "-debugexclude=libevent", "-debugexclude=leveldb", "-mocktime=" + str(mocktime), "-uacomment=" + self.name] cli_path = os.getenv("BITCOINCLI", "bitcoin-cli") @@ -87,9 +90,20 @@ self.url = None self.relay_fee_cache = None self.log = logging.getLogger('TestFramework.node{}'.format(i)) + self.cleanup_on_exit = True # Whether to kill the node when this object goes away self.p2ps = [] + def __del__(self): + # Ensure that we don't leave any bitcoind processes lying around after + # the test ends + if self.process and self.cleanup_on_exit: + # Should only happen on test failure + # Avoid using logger, as that may have already been shutdown when + # this destructor is called. + print("Cleaning up leftover process") + self.process.kill() + def __getattr__(self, name): """Dispatches any unrecognised messages to the RPC connection or a CLI instance.""" if self.use_cli: @@ -135,10 +149,12 @@ assert self.process.poll( ) is None, "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.rpc = get_rpc_proxy( + rpc_url(self.datadir, self.host, self.rpc_port), self.index, timeout=self.rpc_timeout, coveragedir=self.coverage_dir) self.rpc.getblockcount() - # If the call to getblockcount() succeeds then the RPC connection is up + # If the call to getblockcount() succeeds then the RPC + # connection is up self.rpc_connected = True self.url = self.rpc.url self.log.debug("RPC successfully started") @@ -259,6 +275,7 @@ class TestNodeCLIAttr: + def __init__(self, cli, command): self.cli = cli self.command = command @@ -271,6 +288,7 @@ class TestNodeCLI(): + """Interface to bitcoin-cli for an individual node""" def __init__(self, binary, datadir):