diff options
Diffstat (limited to 'test/functional/test_framework/test_framework.py')
-rwxr-xr-x | test/functional/test_framework/test_framework.py | 244 |
1 files changed, 244 insertions, 0 deletions
diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py new file mode 100755 index 0000000000..d7072fa78d --- /dev/null +++ b/test/functional/test_framework/test_framework.py @@ -0,0 +1,244 @@ +#!/usr/bin/env python3 +# Copyright (c) 2014-2016 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. +"""Base class for RPC testing.""" + +import logging +import optparse +import os +import sys +import shutil +import tempfile +import traceback + +from .util import ( + initialize_chain, + start_nodes, + connect_nodes_bi, + sync_blocks, + sync_mempools, + stop_nodes, + stop_node, + enable_coverage, + check_json_precision, + initialize_chain_clean, + PortSeed, +) +from .authproxy import JSONRPCException + + +class BitcoinTestFramework(object): + + def __init__(self): + self.num_nodes = 4 + self.setup_clean_chain = False + self.nodes = None + + def run_test(self): + raise NotImplementedError + + def add_options(self, parser): + pass + + def setup_chain(self): + self.log.info("Initializing test directory "+self.options.tmpdir) + if self.setup_clean_chain: + initialize_chain_clean(self.options.tmpdir, self.num_nodes) + else: + initialize_chain(self.options.tmpdir, self.num_nodes, self.options.cachedir) + + def stop_node(self, num_node): + stop_node(self.nodes[num_node], num_node) + + def setup_nodes(self): + return start_nodes(self.num_nodes, self.options.tmpdir) + + def setup_network(self, split = False): + self.nodes = self.setup_nodes() + + # Connect the nodes as a "chain". This allows us + # to split the network between nodes 1 and 2 to get + # two halves that can work on competing chains. + + # If we joined network halves, connect the nodes from the joint + # on outward. This ensures that chains are properly reorganised. + if not split: + connect_nodes_bi(self.nodes, 1, 2) + sync_blocks(self.nodes[1:3]) + sync_mempools(self.nodes[1:3]) + + connect_nodes_bi(self.nodes, 0, 1) + connect_nodes_bi(self.nodes, 2, 3) + self.is_network_split = split + self.sync_all() + + def split_network(self): + """ + Split the network of four nodes into nodes 0/1 and 2/3. + """ + assert not self.is_network_split + stop_nodes(self.nodes) + self.setup_network(True) + + def sync_all(self): + if self.is_network_split: + sync_blocks(self.nodes[:2]) + sync_blocks(self.nodes[2:]) + sync_mempools(self.nodes[:2]) + sync_mempools(self.nodes[2:]) + else: + sync_blocks(self.nodes) + sync_mempools(self.nodes) + + def join_network(self): + """ + Join the (previously split) network halves together. + """ + assert self.is_network_split + stop_nodes(self.nodes) + self.setup_network(False) + + def main(self): + + parser = optparse.OptionParser(usage="%prog [options]") + parser.add_option("--nocleanup", dest="nocleanup", default=False, action="store_true", + help="Leave bitcoinds and test.* datadir on exit or error") + parser.add_option("--noshutdown", dest="noshutdown", default=False, action="store_true", + 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"), + 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"), + help="Directory for caching pregenerated datadirs") + parser.add_option("--tmpdir", dest="tmpdir", default=tempfile.mkdtemp(prefix="test"), + help="Root directory for datadirs") + parser.add_option("-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_option("--tracerpc", dest="trace_rpc", default=False, action="store_true", + help="Print out all RPC calls as they are made") + parser.add_option("--portseed", dest="port_seed", default=os.getpid(), type='int', + help="The seed to use for assigning port numbers (default: current process id)") + parser.add_option("--coveragedir", dest="coveragedir", + help="Write tested RPC commands into this directory") + self.add_options(parser) + (self.options, self.args) = parser.parse_args() + + # backup dir variable for removal at cleanup + self.options.root, self.options.tmpdir = self.options.tmpdir, self.options.tmpdir + '/' + str(self.options.port_seed) + + 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'] + + check_json_precision() + + # Set up temp directory and start logging + os.makedirs(self.options.tmpdir, exist_ok=False) + self._start_logging() + + success = False + + try: + self.setup_chain() + self.setup_network() + self.run_test() + success = True + except JSONRPCException as e: + self.log.exception("JSONRPC error") + except AssertionError as e: + self.log.exception("Assertion failed") + except KeyError as e: + self.log.exception("Key error") + except Exception as e: + self.log.exception("Unexpected exception caught during testing") + except KeyboardInterrupt as e: + self.log.warning("Exiting after keyboard interrupt") + + if not self.options.noshutdown: + self.log.info("Stopping nodes") + stop_nodes(self.nodes) + else: + self.log.info("Note: bitcoinds were not stopped and may still be running") + + if not self.options.nocleanup and not self.options.noshutdown and success: + self.log.info("Cleaning up") + shutil.rmtree(self.options.tmpdir) + if not os.listdir(self.options.root): + os.rmdir(self.options.root) + else: + self.log.warning("Not cleaning up dir %s" % self.options.tmpdir) + if os.getenv("PYTHON_DEBUG", ""): + # Dump the end of the debug logs, to aid in debugging rare + # travis failures. + import glob + filenames = glob.glob(self.options.tmpdir + "/node*/regtest/debug.log") + MAX_LINES_TO_PRINT = 1000 + for f in filenames: + print("From" , f, ":") + from collections import deque + print("".join(deque(open(f), MAX_LINES_TO_PRINT))) + if success: + self.log.info("Tests successful") + sys.exit(0) + else: + self.log.error("Test failed. Test logging available at %s/test_framework.log", self.options.tmpdir) + logging.shutdown() + sys.exit(1) + + def _start_logging(self): + # Add logger and logging handlers + self.log = logging.getLogger('TestFramework') + self.log.setLevel(logging.DEBUG) + # Create file handler to log all messages + fh = logging.FileHandler(self.options.tmpdir + '/test_framework.log') + fh.setLevel(logging.DEBUG) + # Create console handler to log messages to stderr. By default this logs only error messages, but can be configured with --loglevel. + ch = logging.StreamHandler(sys.stdout) + # 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 + 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) + formatter = logging.Formatter(fmt = '%(asctime)s.%(msecs)03d000 %(name)s (%(levelname)s): %(message)s', datefmt='%Y-%m-%d %H:%M:%S') + fh.setFormatter(formatter) + ch.setFormatter(formatter) + # add the handlers to the logger + self.log.addHandler(fh) + self.log.addHandler(ch) + + if self.options.trace_rpc: + rpc_logger = logging.getLogger("BitcoinRPC") + rpc_logger.setLevel(logging.DEBUG) + rpc_handler = logging.StreamHandler(sys.stdout) + rpc_handler.setLevel(logging.DEBUG) + rpc_logger.addHandler(rpc_handler) + +# Test framework for doing p2p comparison testing, which 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 + +class ComparisonTestFramework(BitcoinTestFramework): + + def __init__(self): + super().__init__() + self.num_nodes = 2 + self.setup_clean_chain = True + + def add_options(self, parser): + parser.add_option("--testbinary", dest="testbinary", + default=os.getenv("BITCOIND", "bitcoind"), + help="bitcoind binary to test") + parser.add_option("--refbinary", dest="refbinary", + default=os.getenv("BITCOIND", "bitcoind"), + help="bitcoind binary to use for reference nodes (if any)") + + def setup_network(self): + self.nodes = start_nodes( + self.num_nodes, self.options.tmpdir, + extra_args=[['-whitelist=127.0.0.1']] * self.num_nodes, + binary=[self.options.testbinary] + + [self.options.refbinary]*(self.num_nodes-1)) |