diff options
Diffstat (limited to 'test/functional/test_framework/test_framework.py')
-rwxr-xr-x | test/functional/test_framework/test_framework.py | 135 |
1 files changed, 114 insertions, 21 deletions
diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py index 37e59f79db..557c9db482 100755 --- a/test/functional/test_framework/test_framework.py +++ b/test/functional/test_framework/test_framework.py @@ -5,7 +5,9 @@ """Base class for RPC testing.""" from collections import deque +import errno from enum import Enum +import http.client import logging import optparse import os @@ -16,15 +18,16 @@ import tempfile import time import traceback +from .authproxy import JSONRPCException +from . import coverage from .util import ( - PortSeed, MAX_NODES, - bitcoind_processes, + PortSeed, + assert_equal, check_json_precision, connect_nodes_bi, disable_mocktime, disconnect_nodes, - enable_coverage, enable_mocktime, get_mocktime, get_rpc_proxy, @@ -34,15 +37,9 @@ from .util import ( p2p_port, rpc_url, set_node_times, - _start_node, - _start_nodes, - _stop_node, - _stop_nodes, sync_blocks, sync_mempools, - wait_for_bitcoind_start, ) -from .authproxy import JSONRPCException class TestStatus(Enum): PASSED = 1 @@ -53,6 +50,8 @@ TEST_EXIT_PASSED = 0 TEST_EXIT_FAILED = 1 TEST_EXIT_SKIPPED = 77 +BITCOIND_PROC_WAIT_TIMEOUT = 60 + class BitcoinTestFramework(object): """Base class for a bitcoin test script. @@ -72,7 +71,8 @@ class BitcoinTestFramework(object): def __init__(self): self.num_nodes = 4 self.setup_clean_chain = False - self.nodes = None + self.nodes = [] + self.bitcoind_processes = {} def add_options(self, parser): pass @@ -98,7 +98,7 @@ class BitcoinTestFramework(object): extra_args = None if hasattr(self, "extra_args"): extra_args = self.extra_args - self.nodes = _start_nodes(self.num_nodes, self.options.tmpdir, extra_args) + self.nodes = self.start_nodes(self.num_nodes, self.options.tmpdir, extra_args) def run_test(self): raise NotImplementedError @@ -130,9 +130,6 @@ class BitcoinTestFramework(object): self.add_options(parser) (self.options, self.args) = parser.parse_args() - if self.options.coveragedir: - enable_coverage(self.options.coveragedir) - PortSeed.n = self.options.port_seed os.environ['PATH'] = self.options.srcdir + ":" + self.options.srcdir + "/qt:" + os.environ['PATH'] @@ -209,16 +206,88 @@ class BitcoinTestFramework(object): # Public helper methods. These can be accessed by the subclass test scripts. def start_node(self, i, dirname, extra_args=None, rpchost=None, timewait=None, binary=None, stderr=None): - return _start_node(i, dirname, extra_args, rpchost, timewait, binary, stderr) + """Start a bitcoind and return RPC connection to it""" + + datadir = os.path.join(dirname, "node" + str(i)) + if binary is None: + binary = os.getenv("BITCOIND", "bitcoind") + args = [binary, "-datadir=" + datadir, "-server", "-keypool=1", "-discover=0", "-rest", "-logtimemicros", "-debug", "-debugexclude=libevent", "-debugexclude=leveldb", "-mocktime=" + str(get_mocktime()), "-uacomment=testnode%d" % i] + if extra_args is not None: + args.extend(extra_args) + self.bitcoind_processes[i] = subprocess.Popen(args, stderr=stderr) + self.log.debug("initialize_chain: bitcoind started, waiting for RPC to come up") + self._wait_for_bitcoind_start(self.bitcoind_processes[i], datadir, i, rpchost) + self.log.debug("initialize_chain: RPC successfully started") + proxy = get_rpc_proxy(rpc_url(datadir, i, rpchost), i, timeout=timewait) + + if self.options.coveragedir: + coverage.write_all_rpc_commands(self.options.coveragedir, proxy) + + return proxy def start_nodes(self, num_nodes, dirname, extra_args=None, rpchost=None, timewait=None, binary=None): - return _start_nodes(num_nodes, dirname, extra_args, rpchost, timewait, binary) + """Start multiple bitcoinds, return RPC connections to them""" + + if extra_args is None: + extra_args = [None] * num_nodes + if binary is None: + binary = [None] * num_nodes + assert_equal(len(extra_args), num_nodes) + assert_equal(len(binary), num_nodes) + rpcs = [] + try: + for i in range(num_nodes): + rpcs.append(self.start_node(i, dirname, extra_args[i], rpchost, timewait=timewait, binary=binary[i])) + except: + # If one node failed to start, stop the others + # TODO: abusing self.nodes in this way is a little hacky. + # Eventually we should do a better job of tracking nodes + self.nodes.extend(rpcs) + self.stop_nodes() + self.nodes = [] + raise + return rpcs + + def stop_node(self, i): + """Stop a bitcoind test node""" - def stop_node(self, num_node): - _stop_node(self.nodes[num_node], num_node) + self.log.debug("Stopping node %d" % i) + try: + self.nodes[i].stop() + except http.client.CannotSendRequest as e: + self.log.exception("Unable to stop node") + return_code = self.bitcoind_processes[i].wait(timeout=BITCOIND_PROC_WAIT_TIMEOUT) + assert_equal(return_code, 0) + del self.bitcoind_processes[i] def stop_nodes(self): - _stop_nodes(self.nodes) + """Stop multiple bitcoind test nodes""" + + for i in range(len(self.nodes)): + self.stop_node(i) + assert not self.bitcoind_processes.values() # All connections must be gone now + + def assert_start_raises_init_error(self, i, dirname, extra_args=None, expected_msg=None): + with tempfile.SpooledTemporaryFile(max_size=2**16) as log_stderr: + try: + self.start_node(i, dirname, extra_args, stderr=log_stderr) + self.stop_node(i) + except Exception as e: + assert 'bitcoind exited' in str(e) # node must have shutdown + 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.bitcoind_processes[i].wait(timeout) def split_network(self): """ @@ -300,9 +369,9 @@ class BitcoinTestFramework(object): args = [os.getenv("BITCOIND", "bitcoind"), "-server", "-keypool=1", "-datadir=" + datadir, "-discover=0"] if i > 0: args.append("-connect=127.0.0.1:" + str(p2p_port(0))) - bitcoind_processes[i] = subprocess.Popen(args) + self.bitcoind_processes[i] = subprocess.Popen(args) self.log.debug("initialize_chain: bitcoind started, waiting for RPC to come up") - wait_for_bitcoind_start(bitcoind_processes[i], datadir, i) + self._wait_for_bitcoind_start(self.bitcoind_processes[i], datadir, i) self.log.debug("initialize_chain: RPC successfully started") self.nodes = [] @@ -355,6 +424,30 @@ class BitcoinTestFramework(object): for i in range(num_nodes): initialize_datadir(test_dir, i) + def _wait_for_bitcoind_start(self, process, datadir, i, rpchost=None): + """Wait for bitcoind to start. + + This means that RPC is accessible and fully initialized. + Raise an exception if bitcoind exits during initialization.""" + while True: + if process.poll() is not None: + raise Exception('bitcoind exited with status %i during initialization' % process.returncode) + try: + # Check if .cookie file to be created + rpc = get_rpc_proxy(rpc_url(datadir, i, rpchost), i, coveragedir=self.options.coveragedir) + rpc.getblockcount() + break # break out of loop on success + except IOError as e: + if e.errno != errno.ECONNREFUSED: # Port not yet open? + raise # unknown IO error + except JSONRPCException as e: # Initialization phase + if e.error['code'] != -28: # RPC in warmup? + raise # unknown JSON RPC exception + except ValueError as e: # cookie file not found and no rpcuser or rpcassword. bitcoind still starting + if "No RPC credentials" not in str(e): + raise + time.sleep(0.25) + class ComparisonTestFramework(BitcoinTestFramework): """Test framework for doing p2p comparison testing |