Changeset View
Changeset View
Standalone View
Standalone View
test/functional/test_framework/test_framework.py
#!/usr/bin/env python3 | #!/usr/bin/env python3 | ||||
# Copyright (c) 2014-2016 The Bitcoin Core developers | # Copyright (c) 2014-2016 The Bitcoin Core developers | ||||
# Distributed under the MIT software license, see the accompanying | # Distributed under the MIT software license, see the accompanying | ||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php. | # file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||||
from collections import deque | from collections import deque | ||||
import errno | |||||
from enum import Enum | from enum import Enum | ||||
import http.client | |||||
import logging | import logging | ||||
import optparse | import optparse | ||||
import os | import os | ||||
import shutil | import shutil | ||||
import subprocess | import subprocess | ||||
import sys | import sys | ||||
import tempfile | import tempfile | ||||
import time | import time | ||||
import traceback | import traceback | ||||
from .authproxy import JSONRPCException | |||||
from . import coverage | |||||
from .util import ( | from .util import ( | ||||
PortSeed, | |||||
MAX_NODES, | MAX_NODES, | ||||
bitcoind_processes, | PortSeed, | ||||
assert_equal, | |||||
check_json_precision, | check_json_precision, | ||||
connect_nodes_bi, | connect_nodes_bi, | ||||
disable_mocktime, | |||||
disconnect_nodes, | disconnect_nodes, | ||||
enable_coverage, | |||||
enable_mocktime, | |||||
get_mocktime, | |||||
get_rpc_proxy, | get_rpc_proxy, | ||||
initialize_datadir, | initialize_datadir, | ||||
get_datadir_path, | get_datadir_path, | ||||
log_filename, | log_filename, | ||||
p2p_port, | p2p_port, | ||||
rpc_url, | rpc_url, | ||||
set_node_times, | set_node_times, | ||||
_start_node, | |||||
_start_nodes, | |||||
_stop_node, | |||||
_stop_nodes, | |||||
sync_blocks, | sync_blocks, | ||||
sync_mempools, | sync_mempools, | ||||
wait_for_bitcoind_start, | |||||
) | ) | ||||
from .authproxy import JSONRPCException | |||||
class TestStatus(Enum): | class TestStatus(Enum): | ||||
PASSED = 1 | PASSED = 1 | ||||
FAILED = 2 | FAILED = 2 | ||||
SKIPPED = 3 | SKIPPED = 3 | ||||
TEST_EXIT_PASSED = 0 | TEST_EXIT_PASSED = 0 | ||||
TEST_EXIT_FAILED = 1 | TEST_EXIT_FAILED = 1 | ||||
TEST_EXIT_SKIPPED = 77 | TEST_EXIT_SKIPPED = 77 | ||||
BITCOIND_PROC_WAIT_TIMEOUT = 60 | |||||
class BitcoinTestFramework(object): | class BitcoinTestFramework(object): | ||||
"""Base class for a bitcoin test script. | """Base class for a bitcoin test script. | ||||
Individual bitcoin test scripts should subclass this class and override the following methods: | Individual bitcoin test scripts should subclass this class and override the following methods: | ||||
- __init__() | - __init__() | ||||
- add_options() | - add_options() | ||||
- setup_chain() | - setup_chain() | ||||
- setup_network() | - setup_network() | ||||
- run_test() | - run_test() | ||||
The main() method should not be overridden. | The main() method should not be overridden. | ||||
This class also contains various public and private helper methods.""" | This class also contains various public and private helper methods.""" | ||||
# Methods to override in subclass test scripts. | # Methods to override in subclass test scripts. | ||||
def __init__(self): | def __init__(self): | ||||
self.num_nodes = 4 | self.num_nodes = 4 | ||||
self.setup_clean_chain = False | self.setup_clean_chain = False | ||||
self.nodes = None | self.nodes = [] | ||||
self.bitcoind_processes = {} | |||||
self.mocktime = 0 | |||||
def add_options(self, parser): | def add_options(self, parser): | ||||
pass | pass | ||||
def setup_chain(self): | def setup_chain(self): | ||||
self.log.info("Initializing test directory " + self.options.tmpdir) | self.log.info("Initializing test directory " + self.options.tmpdir) | ||||
if self.setup_clean_chain: | if self.setup_clean_chain: | ||||
self._initialize_chain_clean(self.options.tmpdir, self.num_nodes) | self._initialize_chain_clean(self.options.tmpdir, self.num_nodes) | ||||
Show All 14 Lines | def setup_network(self): | ||||
for i in range(self.num_nodes - 1): | for i in range(self.num_nodes - 1): | ||||
connect_nodes_bi(self.nodes, i, i + 1) | connect_nodes_bi(self.nodes, i, i + 1) | ||||
self.sync_all() | self.sync_all() | ||||
def setup_nodes(self): | def setup_nodes(self): | ||||
extra_args = None | extra_args = None | ||||
if hasattr(self, "extra_args"): | if hasattr(self, "extra_args"): | ||||
extra_args = self.extra_args | extra_args = self.extra_args | ||||
self.nodes = _start_nodes( | self.nodes = self.start_nodes( | ||||
self.num_nodes, self.options.tmpdir, extra_args) | self.num_nodes, self.options.tmpdir, extra_args) | ||||
def run_test(self): | def run_test(self): | ||||
raise NotImplementedError | raise NotImplementedError | ||||
# Main function. This should not be overridden by the subclass test scripts. | # Main function. This should not be overridden by the subclass test scripts. | ||||
def main(self): | def main(self): | ||||
parser = optparse.OptionParser(usage="%prog [options]") | parser = optparse.OptionParser(usage="%prog [options]") | ||||
parser.add_option("--nocleanup", dest="nocleanup", default=False, action="store_true", | parser.add_option("--nocleanup", dest="nocleanup", default=False, action="store_true", | ||||
help="Leave bitcoinds and test.* datadir on exit or error") | help="Leave bitcoinds and test.* datadir on exit or error") | ||||
parser.add_option("--noshutdown", dest="noshutdown", default=False, action="store_true", | parser.add_option("--noshutdown", dest="noshutdown", default=False, action="store_true", | ||||
help="Don't stop bitcoinds after the test execution") | help="Don't stop bitcoinds after the test execution") | ||||
parser.add_option("--srcdir", dest="srcdir", default=os.path.normpath(os.path.dirname(os.path.realpath(__file__)) + "/../../../src"), | parser.add_option("--srcdir", dest="srcdir", default=os.path.normpath(os.path.dirname(os.path.realpath(__file__)) + "/../../../src"), | ||||
help="Source directory containing bitcoind/bitcoin-cli (default: %default)") | help="Source directory containing bitcoind/bitcoin-cli (default: %default)") | ||||
parser.add_option("--cachedir", dest="cachedir", default=os.path.normpath(os.path.dirname(os.path.realpath(__file__)) + "/../../cache"), | parser.add_option("--cachedir", dest="cachedir", default=os.path.normpath(os.path.dirname(os.path.realpath(__file__)) + "/../../cache"), | ||||
Show All 10 Lines | def main(self): | ||||
help="The seed to use for assigning port numbers (default: current process id)") | help="The seed to use for assigning port numbers (default: current process id)") | ||||
parser.add_option("--coveragedir", dest="coveragedir", | parser.add_option("--coveragedir", dest="coveragedir", | ||||
help="Write tested RPC commands into this directory") | help="Write tested RPC commands into this directory") | ||||
parser.add_option("--configfile", dest="configfile", | parser.add_option("--configfile", dest="configfile", | ||||
help="Location of the test framework config file") | help="Location of the test framework config file") | ||||
self.add_options(parser) | self.add_options(parser) | ||||
(self.options, self.args) = parser.parse_args() | (self.options, self.args) = parser.parse_args() | ||||
if self.options.coveragedir: | |||||
enable_coverage(self.options.coveragedir) | |||||
PortSeed.n = self.options.port_seed | PortSeed.n = self.options.port_seed | ||||
os.environ['PATH'] = self.options.srcdir + ":" + \ | os.environ['PATH'] = self.options.srcdir + ":" + \ | ||||
self.options.srcdir + "/qt:" + os.environ['PATH'] | self.options.srcdir + "/qt:" + os.environ['PATH'] | ||||
check_json_precision() | check_json_precision() | ||||
# Set up temp directory and start logging | # Set up temp directory and start logging | ||||
▲ Show 20 Lines • Show All 64 Lines • ▼ Show 20 Lines | def main(self): | ||||
self.log.error( | self.log.error( | ||||
"Test failed. Test logging available at %s/test_framework.log", self.options.tmpdir) | "Test failed. Test logging available at %s/test_framework.log", self.options.tmpdir) | ||||
logging.shutdown() | logging.shutdown() | ||||
sys.exit(TEST_EXIT_FAILED) | sys.exit(TEST_EXIT_FAILED) | ||||
# 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 start_node(self, i, dirname, extra_args=None, rpchost=None, timewait=None, binary=None, stderr=None): | 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(self.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): | 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""" | ||||
def stop_node(self, num_node): | if extra_args is None: | ||||
_stop_node(self.nodes[num_node], num_node) | 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""" | |||||
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) | |||||
del self.bitcoind_processes[i] | |||||
assert_equal(return_code, 0) | |||||
def stop_nodes(self): | 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): | def split_network(self): | ||||
""" | """ | ||||
Split the network of four nodes into nodes 0/1 and 2/3. | Split the network of four nodes into nodes 0/1 and 2/3. | ||||
""" | """ | ||||
disconnect_nodes(self.nodes[1], 2) | disconnect_nodes(self.nodes[1], 2) | ||||
disconnect_nodes(self.nodes[2], 1) | disconnect_nodes(self.nodes[2], 1) | ||||
self.sync_all([self.nodes[:2], self.nodes[2:]]) | self.sync_all([self.nodes[:2], self.nodes[2:]]) | ||||
def join_network(self): | def join_network(self): | ||||
""" | """ | ||||
Join the (previously split) network halves together. | Join the (previously split) network halves together. | ||||
""" | """ | ||||
connect_nodes_bi(self.nodes, 1, 2) | connect_nodes_bi(self.nodes, 1, 2) | ||||
self.sync_all() | self.sync_all() | ||||
def sync_all(self, node_groups=None): | def sync_all(self, node_groups=None): | ||||
if not node_groups: | if not node_groups: | ||||
node_groups = [self.nodes] | node_groups = [self.nodes] | ||||
for group in node_groups: | for group in node_groups: | ||||
sync_blocks(group) | sync_blocks(group) | ||||
sync_mempools(group) | sync_mempools(group) | ||||
def enable_mocktime(self): | |||||
"""Enable mocktime for the script. | |||||
mocktime may be needed for scripts that use the cached version of the | |||||
blockchain. If the cached version of the blockchain is used without | |||||
mocktime then the mempools will not sync due to IBD. | |||||
For backwared compatibility of the python scripts with previous | |||||
versions of the cache, this helper function sets mocktime to Jan 1, | |||||
2014 + (201 * 10 * 60)""" | |||||
self.mocktime = 1388534400 + (201 * 10 * 60) | |||||
def disable_mocktime(self): | |||||
self.mocktime = 0 | |||||
# 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): | def _start_logging(self): | ||||
# Add logger and logging handlers | # Add logger and logging handlers | ||||
self.log = logging.getLogger('TestFramework') | self.log = logging.getLogger('TestFramework') | ||||
self.log.setLevel(logging.DEBUG) | self.log.setLevel(logging.DEBUG) | ||||
# Create file handler to log all messages | # Create file handler to log all messages | ||||
fh = logging.FileHandler(self.options.tmpdir + '/test_framework.log') | fh = logging.FileHandler(self.options.tmpdir + '/test_framework.log') | ||||
fh.setLevel(logging.DEBUG) | fh.setLevel(logging.DEBUG) | ||||
# Create console handler to log messages to stderr. By default this | # Create console handler to log messages to stderr. By default this | ||||
# logs only error messages, but can be configured with --loglevel. | # logs only error messages, but can be configured with --loglevel. | ||||
ch = logging.StreamHandler(sys.stdout) | ch = logging.StreamHandler(sys.stdout) | ||||
# User can provide log level as a number or string (eg DEBUG). loglevel | # 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 | # was caught as a string, so try to convert it to an int | ||||
ll = int( | ll = int( | ||||
self.options.loglevel) if self.options.loglevel.isdigit() else self.options.loglevel.upper() | self.options.loglevel) if self.options.loglevel.isdigit() else self.options.loglevel.upper() | ||||
ch.setLevel(ll) | ch.setLevel(ll) | ||||
# Format logs the same as bitcoind's debug.log with microprecision (so | # Format logs the same as bitcoind's debug.log with microprecision (so log files can be concatenated and sorted) | ||||
# log files can be concatenated and sorted) | |||||
formatter = logging.Formatter( | formatter = logging.Formatter( | ||||
fmt='%(asctime)s.%(msecs)03d000 %(name)s (%(levelname)s): %(message)s', datefmt='%Y-%m-%d %H:%M:%S') | fmt='%(asctime)s.%(msecs)03d000 %(name)s (%(levelname)s): %(message)s', datefmt='%Y-%m-%d %H:%M:%S') | ||||
formatter.converter = time.gmtime | formatter.converter = time.gmtime | ||||
fh.setFormatter(formatter) | fh.setFormatter(formatter) | ||||
ch.setFormatter(formatter) | ch.setFormatter(formatter) | ||||
# add the handlers to the logger | # add the handlers to the logger | ||||
self.log.addHandler(fh) | self.log.addHandler(fh) | ||||
Show All 29 Lines | def _initialize_chain(self, test_dir, num_nodes, cachedir): | ||||
# Create cache directories, run bitcoinds: | # Create cache directories, run bitcoinds: | ||||
for i in range(MAX_NODES): | for i in range(MAX_NODES): | ||||
datadir = initialize_datadir(cachedir, i) | datadir = initialize_datadir(cachedir, i) | ||||
args = [os.getenv("BITCOIND", "bitcoind"), "-server", | args = [os.getenv("BITCOIND", "bitcoind"), "-server", | ||||
"-keypool=1", "-datadir=" + datadir, "-discover=0"] | "-keypool=1", "-datadir=" + datadir, "-discover=0"] | ||||
if i > 0: | if i > 0: | ||||
args.append("-connect=127.0.0.1:" + str(p2p_port(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( | self.log.debug( | ||||
"initialize_chain: bitcoind started, waiting for RPC to come up") | "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.log.debug("initialize_chain: RPC successfully started") | ||||
self.nodes = [] | self.nodes = [] | ||||
for i in range(MAX_NODES): | for i in range(MAX_NODES): | ||||
try: | try: | ||||
self.nodes.append(get_rpc_proxy( | self.nodes.append(get_rpc_proxy( | ||||
rpc_url(get_datadir_path(cachedir, i), i), i)) | rpc_url(get_datadir_path(cachedir, i), i), i)) | ||||
except: | except: | ||||
self.log.exception("Error connecting to node %d" % i) | self.log.exception("Error connecting to node %d" % i) | ||||
sys.exit(1) | sys.exit(1) | ||||
# Create a 200-block-long chain; each of the 4 first nodes | # Create a 200-block-long chain; each of the 4 first nodes | ||||
# gets 25 mature blocks and 25 immature. | # gets 25 mature blocks and 25 immature. | ||||
# Note: To preserve compatibility with older versions of | # Note: To preserve compatibility with older versions of | ||||
# initialize_chain, only 4 nodes will generate coins. | # initialize_chain, only 4 nodes will generate coins. | ||||
# | # | ||||
# blocks are created with timestamps 10 minutes apart | # blocks are created with timestamps 10 minutes apart | ||||
# starting from 2010 minutes in the past | # starting from 2010 minutes in the past | ||||
enable_mocktime() | self.enable_mocktime() | ||||
block_time = get_mocktime() - (201 * 10 * 60) | block_time = self.mocktime - (201 * 10 * 60) | ||||
for i in range(2): | for i in range(2): | ||||
for peer in range(4): | for peer in range(4): | ||||
for j in range(25): | for j in range(25): | ||||
set_node_times(self.nodes, block_time) | set_node_times(self.nodes, block_time) | ||||
self.nodes[peer].generate(1) | self.nodes[peer].generate(1) | ||||
block_time += 10 * 60 | block_time += 10 * 60 | ||||
# Must sync before next peer starts generating blocks | # Must sync before next peer starts generating blocks | ||||
sync_blocks(self.nodes) | sync_blocks(self.nodes) | ||||
# Shut them down, and clean up cache directories: | # Shut them down, and clean up cache directories: | ||||
self.stop_nodes() | self.stop_nodes() | ||||
self.nodes = [] | self.nodes = [] | ||||
disable_mocktime() | self.disable_mocktime() | ||||
for i in range(MAX_NODES): | for i in range(MAX_NODES): | ||||
os.remove(log_filename(cachedir, i, "debug.log")) | os.remove(log_filename(cachedir, i, "debug.log")) | ||||
os.remove(log_filename(cachedir, i, "db.log")) | os.remove(log_filename(cachedir, i, "db.log")) | ||||
os.remove(log_filename(cachedir, i, "peers.dat")) | os.remove(log_filename(cachedir, i, "peers.dat")) | ||||
os.remove(log_filename(cachedir, i, "fee_estimates.dat")) | os.remove(log_filename(cachedir, i, "fee_estimates.dat")) | ||||
for i in range(num_nodes): | for i in range(num_nodes): | ||||
from_dir = os.path.join(cachedir, "node" + str(i)) | from_dir = os.path.join(cachedir, "node" + str(i)) | ||||
to_dir = os.path.join(test_dir, "node" + str(i)) | to_dir = os.path.join(test_dir, "node" + str(i)) | ||||
shutil.copytree(from_dir, to_dir) | shutil.copytree(from_dir, to_dir) | ||||
# Overwrite port/rpcport in bitcoin.conf | # Overwrite port/rpcport in bitcoin.conf | ||||
initialize_datadir(test_dir, i) | initialize_datadir(test_dir, i) | ||||
def _initialize_chain_clean(self, test_dir, num_nodes): | def _initialize_chain_clean(self, test_dir, num_nodes): | ||||
"""Initialize empty blockchain for use by the test. | """Initialize empty blockchain for use by the test. | ||||
Create an empty blockchain and num_nodes wallets. | Create an empty blockchain and num_nodes wallets. | ||||
Useful if a test case wants complete control over initialization.""" | Useful if a test case wants complete control over initialization.""" | ||||
for i in range(num_nodes): | for i in range(num_nodes): | ||||
initialize_datadir(test_dir, i) | initialize_datadir(test_dir, i) | ||||
# Test framework for doing p2p comparison testing, which sets up some bitcoind | def _wait_for_bitcoind_start(self, process, datadir, i, rpchost=None): | ||||
# binaries: | """Wait for bitcoind to start. | ||||
# 1 binary: test binary | |||||
# 2 binaries: 1 test binary, 1 ref binary | |||||
# n>2 binaries: 1 test binary, n-1 ref binaries | |||||
This means that RPC is accessible and fully initialized. | |||||
class SkipTest(Exception): | Raise an exception if bitcoind exits during initialization.""" | ||||
"""This exception is raised to skip a test""" | while True: | ||||
if process.poll() is not None: | |||||
def __init__(self, message): | raise Exception( | ||||
self.message = message | '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): | class ComparisonTestFramework(BitcoinTestFramework): | ||||
"""Test framework for doing p2p comparison testing | |||||
Sets up some bitcoind binaries: | |||||
- 1 binary: test binary | |||||
- 2 binaries: 1 test binary, 1 ref binary | |||||
- n>2 binaries: 1 test binary, n-1 ref binaries""" | |||||
def __init__(self): | def __init__(self): | ||||
super().__init__() | super().__init__() | ||||
self.num_nodes = 2 | self.num_nodes = 2 | ||||
self.setup_clean_chain = True | self.setup_clean_chain = True | ||||
def add_options(self, parser): | def add_options(self, parser): | ||||
parser.add_option("--testbinary", dest="testbinary", | parser.add_option("--testbinary", dest="testbinary", | ||||
default=os.getenv("BITCOIND", "bitcoind"), | default=os.getenv("BITCOIND", "bitcoind"), | ||||
help="bitcoind binary to test") | help="bitcoind binary to test") | ||||
parser.add_option("--refbinary", dest="refbinary", | parser.add_option("--refbinary", dest="refbinary", | ||||
default=os.getenv("BITCOIND", "bitcoind"), | default=os.getenv("BITCOIND", "bitcoind"), | ||||
help="bitcoind binary to use for reference nodes (if any)") | help="bitcoind binary to use for reference nodes (if any)") | ||||
def setup_network(self): | def setup_network(self): | ||||
extra_args = [['-whitelist=127.0.0.1']] * self.num_nodes | extra_args = [['-whitelist=127.0.0.1']] * self.num_nodes | ||||
if hasattr(self, "extra_args"): | if hasattr(self, "extra_args"): | ||||
extra_args = self.extra_args | extra_args = self.extra_args | ||||
self.nodes = self.start_nodes( | self.nodes = self.start_nodes( | ||||
self.num_nodes, self.options.tmpdir, extra_args, | self.num_nodes, self.options.tmpdir, extra_args, | ||||
binary=[self.options.testbinary] + | binary=[self.options.testbinary] + | ||||
[self.options.refbinary] * (self.num_nodes - 1)) | [self.options.refbinary] * (self.num_nodes - 1)) | ||||
class SkipTest(Exception): | |||||
"""This exception is raised to skip a test""" | |||||
def __init__(self, message): | |||||
self.message = message |