2016-03-19 20:58:06 +01:00
#!/usr/bin/env python3
# Copyright (c) 2014-2016 The Bitcoin Core developers
2014-10-23 03:48:19 +02:00
# Distributed under the MIT software license, see the accompanying
2014-07-08 18:07:23 +02:00
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
2017-01-18 00:34:40 +01:00
""" Base class for RPC testing. """
2014-07-08 18:07:23 +02:00
2016-05-09 19:55:49 +02:00
import logging
import optparse
2014-07-08 18:07:23 +02:00
import os
import sys
import shutil
import tempfile
2017-03-21 15:05:59 +01:00
import time
2014-07-08 18:07:23 +02:00
import traceback
2015-10-11 07:41:19 +02:00
from . util import (
initialize_chain ,
start_nodes ,
connect_nodes_bi ,
sync_blocks ,
sync_mempools ,
stop_nodes ,
2016-07-06 20:46:22 +02:00
stop_node ,
2015-10-11 07:41:19 +02:00
enable_coverage ,
check_json_precision ,
initialize_chain_clean ,
2016-05-09 19:55:49 +02:00
PortSeed ,
2015-10-11 07:41:19 +02:00
)
2016-05-09 19:55:49 +02:00
from . authproxy import JSONRPCException
2014-07-08 18:07:23 +02:00
class BitcoinTestFramework ( object ) :
2016-05-14 13:01:31 +02:00
def __init__ ( self ) :
self . num_nodes = 4
self . setup_clean_chain = False
self . nodes = None
2014-10-20 14:14:04 +02:00
def run_test ( self ) :
2016-05-14 13:01:31 +02:00
raise NotImplementedError
2014-07-08 18:07:23 +02:00
def add_options ( self , parser ) :
pass
2014-10-20 14:14:04 +02:00
def setup_chain ( self ) :
2017-02-15 17:36:46 +01:00
self . log . info ( " Initializing test directory " + self . options . tmpdir )
2016-05-14 13:01:31 +02:00
if self . setup_clean_chain :
initialize_chain_clean ( self . options . tmpdir , self . num_nodes )
else :
2016-08-07 23:06:27 +02:00
initialize_chain ( self . options . tmpdir , self . num_nodes , self . options . cachedir )
2014-10-20 14:14:04 +02:00
2016-07-06 20:46:22 +02:00
def stop_node ( self , num_node ) :
stop_node ( self . nodes [ num_node ] , num_node )
2015-01-14 12:45:11 +01:00
def setup_nodes ( self ) :
2016-05-14 13:01:31 +02:00
return start_nodes ( self . num_nodes , self . options . tmpdir )
2015-01-14 12:45:11 +01:00
2014-10-20 14:14:04 +02:00
def setup_network ( self , split = False ) :
2015-01-14 12:45:11 +01:00
self . nodes = self . setup_nodes ( )
2014-10-20 14:14:04 +02:00
# 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 :
2014-10-24 09:06:37 +02:00
connect_nodes_bi ( self . nodes , 1 , 2 )
2014-11-07 10:23:21 +01:00
sync_blocks ( self . nodes [ 1 : 3 ] )
sync_mempools ( self . nodes [ 1 : 3 ] )
2014-10-20 14:14:04 +02:00
2014-10-24 09:06:37 +02:00
connect_nodes_bi ( self . nodes , 0 , 1 )
connect_nodes_bi ( self . nodes , 2 , 3 )
2014-10-20 14:14:04 +02:00
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 :
2014-11-07 10:23:21 +01:00
sync_blocks ( self . nodes [ : 2 ] )
2014-10-20 14:14:04 +02:00
sync_blocks ( self . nodes [ 2 : ] )
2014-11-07 10:23:21 +01:00
sync_mempools ( self . nodes [ : 2 ] )
2014-10-20 14:14:04 +02:00
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 )
2014-07-08 18:07:23 +02:00
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 " )
2015-04-23 14:19:00 +02:00
parser . add_option ( " --noshutdown " , dest = " noshutdown " , default = False , action = " store_true " ,
help = " Don ' t stop bitcoinds after the test execution " )
2016-05-06 12:40:12 +02:00
parser . add_option ( " --srcdir " , dest = " srcdir " , default = os . path . normpath ( os . path . dirname ( os . path . realpath ( __file__ ) ) + " /../../../src " ) ,
2015-04-23 14:19:00 +02:00
help = " Source directory containing bitcoind/bitcoin-cli (default: %d efault) " )
2016-08-07 23:06:27 +02:00
parser . add_option ( " --cachedir " , dest = " cachedir " , default = os . path . normpath ( os . path . dirname ( os . path . realpath ( __file__ ) ) + " /../../cache " ) ,
help = " Directory for caching pregenerated datadirs " )
2014-07-08 18:07:23 +02:00
parser . add_option ( " --tmpdir " , dest = " tmpdir " , default = tempfile . mkdtemp ( prefix = " test " ) ,
help = " Root directory for datadirs " )
2017-02-15 17:36:46 +01:00
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. " )
2014-10-23 19:11:20 +02:00
parser . add_option ( " --tracerpc " , dest = " trace_rpc " , default = False , action = " store_true " ,
help = " Print out all RPC calls as they are made " )
2016-05-09 19:55:49 +02:00
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) " )
2015-10-11 07:41:19 +02:00
parser . add_option ( " --coveragedir " , dest = " coveragedir " ,
help = " Write tested RPC commands into this directory " )
2014-07-08 18:07:23 +02:00
self . add_options ( parser )
( self . options , self . args ) = parser . parse_args ( )
2016-09-02 11:38:04 +02:00
# backup dir variable for removal at cleanup
self . options . root , self . options . tmpdir = self . options . tmpdir , self . options . tmpdir + ' / ' + str ( self . options . port_seed )
2016-05-15 11:06:23 +02:00
2015-10-11 07:41:19 +02:00
if self . options . coveragedir :
enable_coverage ( self . options . coveragedir )
2016-05-09 19:55:49 +02:00
PortSeed . n = self . options . port_seed
2015-12-14 12:54:55 +01:00
os . environ [ ' PATH ' ] = self . options . srcdir + " : " + self . options . srcdir + " /qt: " + os . environ [ ' PATH ' ]
2014-07-08 18:07:23 +02:00
check_json_precision ( )
2017-02-15 17:36:46 +01:00
# Set up temp directory and start logging
os . makedirs ( self . options . tmpdir , exist_ok = False )
self . _start_logging ( )
2014-07-08 18:07:23 +02:00
success = False
2017-02-15 17:36:46 +01:00
2014-07-08 18:07:23 +02:00
try :
2014-10-20 14:14:04 +02:00
self . setup_chain ( )
self . setup_network ( )
self . run_test ( )
2014-07-08 18:07:23 +02:00
success = True
2014-07-09 03:24:40 +02:00
except JSONRPCException as e :
2017-02-15 17:36:46 +01:00
self . log . exception ( " JSONRPC error " )
2014-07-08 18:07:23 +02:00
except AssertionError as e :
2017-02-15 17:36:46 +01:00
self . log . exception ( " Assertion failed " )
2016-04-13 09:24:07 +02:00
except KeyError as e :
2017-02-15 17:36:46 +01:00
self . log . exception ( " Key error " )
2014-07-08 18:07:23 +02:00
except Exception as e :
2017-02-15 17:36:46 +01:00
self . log . exception ( " Unexpected exception caught during testing " )
2016-04-27 22:29:52 +02:00
except KeyboardInterrupt as e :
2017-02-15 17:36:46 +01:00
self . log . warning ( " Exiting after keyboard interrupt " )
2014-07-08 18:07:23 +02:00
2015-04-23 14:19:00 +02:00
if not self . options . noshutdown :
2017-02-15 17:36:46 +01:00
self . log . info ( " Stopping nodes " )
2015-04-23 14:19:00 +02:00
stop_nodes ( self . nodes )
else :
2017-02-15 17:36:46 +01:00
self . log . info ( " Note: bitcoinds were not stopped and may still be running " )
2015-04-20 11:50:33 +02:00
2016-05-24 17:35:06 +02:00
if not self . options . nocleanup and not self . options . noshutdown and success :
2017-02-15 17:36:46 +01:00
self . log . info ( " Cleaning up " )
2014-07-08 18:07:23 +02:00
shutil . rmtree ( self . options . tmpdir )
2016-09-02 11:38:04 +02:00
if not os . listdir ( self . options . root ) :
os . rmdir ( self . options . root )
2016-05-24 17:35:06 +02:00
else :
2017-02-15 17:36:46 +01:00
self . log . warning ( " Not cleaning up dir %s " % self . options . tmpdir )
2016-11-30 17:16:05 +01:00
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 ) ) )
2014-07-08 18:07:23 +02:00
if success :
2017-02-15 17:36:46 +01:00
self . log . info ( " Tests successful " )
2014-07-08 18:07:23 +02:00
sys . exit ( 0 )
else :
2017-02-15 17:36:46 +01:00
self . log . error ( " Test failed. Test logging available at %s /test_framework.log " , self . options . tmpdir )
logging . shutdown ( )
2014-07-08 18:07:23 +02:00
sys . exit ( 1 )
2015-04-28 18:39:47 +02:00
2017-02-15 17:36:46 +01:00
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)03d 000 %(name)s ( %(levelname)s ): %(message)s ' , datefmt = ' % Y- % m- %d % H: % M: % S ' )
2017-03-21 15:05:59 +01:00
formatter . converter = time . gmtime
2017-02-15 17:36:46 +01:00
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 )
2015-04-28 18:39:47 +02:00
# 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 ) :
2016-05-14 13:01:31 +02:00
super ( ) . __init__ ( )
2015-04-28 18:39:47 +02:00
self . num_nodes = 2
2016-05-14 13:01:31 +02:00
self . setup_clean_chain = True
2015-04-28 18:39:47 +02:00
def add_options ( self , parser ) :
2015-04-29 15:18:33 +02:00
parser . add_option ( " --testbinary " , dest = " testbinary " ,
default = os . getenv ( " BITCOIND " , " bitcoind " ) ,
2015-04-28 18:39:47 +02:00
help = " bitcoind binary to test " )
2015-04-29 15:18:33 +02:00
parser . add_option ( " --refbinary " , dest = " refbinary " ,
default = os . getenv ( " BITCOIND " , " bitcoind " ) ,
2015-04-28 18:39:47 +02:00
help = " bitcoind binary to use for reference nodes (if any) " )
def setup_network ( self ) :
2015-10-11 07:41:19 +02:00
self . nodes = start_nodes (
self . num_nodes , self . options . tmpdir ,
2017-02-24 18:39:33 +01:00
extra_args = [ [ ' -whitelist=127.0.0.1 ' ] ] * self . num_nodes ,
2015-10-11 07:41:19 +02:00
binary = [ self . options . testbinary ] +
[ self . options . refbinary ] * ( self . num_nodes - 1 ) )