aboutsummaryrefslogtreecommitdiff
path: root/test/functional/test_framework
diff options
context:
space:
mode:
Diffstat (limited to 'test/functional/test_framework')
-rwxr-xr-xtest/functional/test_framework/comptool.py6
-rwxr-xr-xtest/functional/test_framework/messages.py2
-rwxr-xr-xtest/functional/test_framework/mininode.py4
-rw-r--r--test/functional/test_framework/netutil.py3
-rwxr-xr-xtest/functional/test_framework/test_framework.py59
-rwxr-xr-xtest/functional/test_framework/test_node.py45
-rw-r--r--test/functional/test_framework/util.py64
7 files changed, 91 insertions, 92 deletions
diff --git a/test/functional/test_framework/comptool.py b/test/functional/test_framework/comptool.py
index 61ea2280e2..e0ca78e5d1 100755
--- a/test/functional/test_framework/comptool.py
+++ b/test/functional/test_framework/comptool.py
@@ -8,7 +8,7 @@ To use, create a class that implements get_tests(), and pass it in
as the test generator to TestManager. get_tests() should be a python
generator that returns TestInstance objects. See below for definition.
-TestNode behaves as follows:
+TestP2PConn behaves as follows:
Configure with a BlockStore and TxStore
on_inv: log the message but don't request
on_headers: log the chain tip
@@ -39,7 +39,7 @@ class RejectResult():
def __repr__(self):
return '%i:%s' % (self.code,self.reason or '*')
-class TestNode(P2PInterface):
+class TestP2PConn(P2PInterface):
def __init__(self, block_store, tx_store):
super().__init__()
@@ -170,7 +170,7 @@ class TestManager():
def add_all_connections(self, nodes):
for i in range(len(nodes)):
# Create a p2p connection to each node
- node = TestNode(self.block_store, self.tx_store)
+ node = TestP2PConn(self.block_store, self.tx_store)
node.peer_connect('127.0.0.1', p2p_port(i))
self.p2p_connections.append(node)
diff --git a/test/functional/test_framework/messages.py b/test/functional/test_framework/messages.py
index e032be1337..ee573e01cc 100755
--- a/test/functional/test_framework/messages.py
+++ b/test/functional/test_framework/messages.py
@@ -4,7 +4,7 @@
# Copyright (c) 2010-2017 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
-"""Bitcoin test framework primitive and message strcutures
+"""Bitcoin test framework primitive and message structures
CBlock, CTransaction, CBlockHeader, CTxIn, CTxOut, etc....:
data structures that should map to corresponding structures in
diff --git a/test/functional/test_framework/mininode.py b/test/functional/test_framework/mininode.py
index 99d0abc3f9..f1f7d0c0cd 100755
--- a/test/functional/test_framework/mininode.py
+++ b/test/functional/test_framework/mininode.py
@@ -405,7 +405,7 @@ class P2PInterface(P2PConnection):
# Keep our own socket map for asyncore, so that we can track disconnects
-# ourselves (to workaround an issue with closing an asyncore socket when
+# ourselves (to work around an issue with closing an asyncore socket when
# using select)
mininode_socket_map = dict()
@@ -424,7 +424,7 @@ class NetworkThread(threading.Thread):
def run(self):
while mininode_socket_map:
# We check for whether to disconnect outside of the asyncore
- # loop to workaround the behavior of asyncore when using
+ # loop to work around the behavior of asyncore when using
# select
disconnected = []
for fd, obj in mininode_socket_map.items():
diff --git a/test/functional/test_framework/netutil.py b/test/functional/test_framework/netutil.py
index 96fe283347..36d1a2f856 100644
--- a/test/functional/test_framework/netutil.py
+++ b/test/functional/test_framework/netutil.py
@@ -9,7 +9,6 @@ Roughly based on http://voorloopnul.com/blog/a-python-netstat-in-less-than-100-l
import sys
import socket
-import fcntl
import struct
import array
import os
@@ -90,6 +89,8 @@ def all_interfaces():
'''
Return all interfaces that are up
'''
+ import fcntl # Linux only, so only import when required
+
is_64bits = sys.maxsize > 2**32
struct_size = 40 if is_64bits else 32
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py
index ecb91b315e..d427f62856 100755
--- a/test/functional/test_framework/test_framework.py
+++ b/test/functional/test_framework/test_framework.py
@@ -24,8 +24,8 @@ from .util import (
check_json_precision,
connect_nodes_bi,
disconnect_nodes,
+ get_datadir_path,
initialize_datadir,
- log_filename,
p2p_port,
set_node_times,
sync_blocks,
@@ -63,6 +63,7 @@ class BitcoinTestFramework():
self.nodes = []
self.mocktime = 0
self.supports_cli = False
+ self.bind_to_localhost_only = True
self.set_test_params()
assert hasattr(self, "num_nodes"), "Test must set self.num_nodes in set_test_params()"
@@ -215,15 +216,19 @@ class BitcoinTestFramework():
def add_nodes(self, num_nodes, extra_args=None, rpchost=None, timewait=None, binary=None):
"""Instantiate TestNode objects"""
-
+ if self.bind_to_localhost_only:
+ extra_confs = [["bind=127.0.0.1"]] * num_nodes
+ else:
+ extra_confs = [[]] * num_nodes
if extra_args is None:
extra_args = [[]] * num_nodes
if binary is None:
binary = [None] * num_nodes
+ assert_equal(len(extra_confs), num_nodes)
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, 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, get_datadir_path(self.options.tmpdir, i), rpchost=rpchost, timewait=timewait, binary=binary[i], stderr=None, mocktime=self.mocktime, coverage_dir=self.options.coveragedir, extra_conf=extra_confs[i], extra_args=extra_args[i], use_cli=self.options.usecli))
def start_node(self, i, *args, **kwargs):
"""Start a bitcoind"""
@@ -276,27 +281,6 @@ class BitcoinTestFramework():
self.stop_node(i)
self.start_node(i, extra_args)
- def assert_start_raises_init_error(self, i, extra_args=None, expected_msg=None, *args, **kwargs):
- with tempfile.SpooledTemporaryFile(max_size=2**16) as log_stderr:
- try:
- self.start_node(i, extra_args, stderr=log_stderr, *args, **kwargs)
- self.stop_node(i)
- except Exception as e:
- assert 'bitcoind exited' in str(e) # node must have shutdown
- self.nodes[i].running = False
- self.nodes[i].process = None
- 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.nodes[i].process.wait(timeout)
@@ -330,7 +314,7 @@ class BitcoinTestFramework():
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
+ For backward 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)
@@ -353,7 +337,7 @@ class BitcoinTestFramework():
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')
+ 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
fh.setFormatter(formatter)
ch.setFormatter(formatter)
@@ -377,7 +361,7 @@ class BitcoinTestFramework():
assert self.num_nodes <= MAX_NODES
create_cache = False
for i in range(MAX_NODES):
- if not os.path.isdir(os.path.join(self.options.cachedir, 'node' + str(i))):
+ if not os.path.isdir(get_datadir_path(self.options.cachedir, i)):
create_cache = True
break
@@ -386,8 +370,8 @@ class BitcoinTestFramework():
# find and delete old cache directories if any exist
for i in range(MAX_NODES):
- if os.path.isdir(os.path.join(self.options.cachedir, "node" + str(i))):
- shutil.rmtree(os.path.join(self.options.cachedir, "node" + str(i)))
+ if os.path.isdir(get_datadir_path(self.options.cachedir, i)):
+ shutil.rmtree(get_datadir_path(self.options.cachedir, i))
# Create cache directories, run bitcoinds:
for i in range(MAX_NODES):
@@ -395,7 +379,7 @@ class BitcoinTestFramework():
args = [os.getenv("BITCOIND", "bitcoind"), "-datadir=" + datadir]
if i > 0:
args.append("-connect=127.0.0.1:" + str(p2p_port(0)))
- self.nodes.append(TestNode(i, self.options.cachedir, extra_args=[], rpchost=None, timewait=None, binary=None, stderr=None, mocktime=self.mocktime, coverage_dir=None))
+ self.nodes.append(TestNode(i, get_datadir_path(self.options.cachedir, i), extra_conf=["bind=127.0.0.1"], extra_args=[],rpchost=None, timewait=None, binary=None, stderr=None, mocktime=self.mocktime, coverage_dir=None))
self.nodes[i].args = args
self.start_node(i)
@@ -425,15 +409,18 @@ class BitcoinTestFramework():
self.stop_nodes()
self.nodes = []
self.disable_mocktime()
+
+ def cache_path(n, *paths):
+ return os.path.join(get_datadir_path(self.options.cachedir, n), "regtest", *paths)
+
for i in range(MAX_NODES):
- os.remove(log_filename(self.options.cachedir, i, "debug.log"))
- os.remove(log_filename(self.options.cachedir, i, "wallets/db.log"))
- os.remove(log_filename(self.options.cachedir, i, "peers.dat"))
- os.remove(log_filename(self.options.cachedir, i, "fee_estimates.dat"))
+ for entry in os.listdir(cache_path(i)):
+ if entry not in ['wallets', 'chainstate', 'blocks']:
+ os.remove(cache_path(i, entry))
for i in range(self.num_nodes):
- from_dir = os.path.join(self.options.cachedir, "node" + str(i))
- to_dir = os.path.join(self.options.tmpdir, "node" + str(i))
+ from_dir = get_datadir_path(self.options.cachedir, i)
+ to_dir = get_datadir_path(self.options.tmpdir, i)
shutil.copytree(from_dir, to_dir)
initialize_datadir(self.options.tmpdir, i) # Overwrite port/rpcport in bitcoin.conf
diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py
index 93a052f785..583d07deec 100755
--- a/test/functional/test_framework/test_node.py
+++ b/test/functional/test_framework/test_node.py
@@ -12,10 +12,12 @@ import logging
import os
import re
import subprocess
+import tempfile
import time
from .authproxy import JSONRPCException
from .util import (
+ append_config,
assert_equal,
get_rpc_proxy,
rpc_url,
@@ -42,9 +44,9 @@ class TestNode():
To make things easier for the test writer, any unrecognised messages will
be dispatched to the RPC connection."""
- def __init__(self, i, dirname, extra_args, rpchost, timewait, binary, stderr, mocktime, coverage_dir, use_cli=False):
+ def __init__(self, i, datadir, rpchost, timewait, binary, stderr, mocktime, coverage_dir, extra_conf=None, extra_args=None, use_cli=False):
self.index = i
- self.datadir = os.path.join(dirname, "node" + str(i))
+ self.datadir = datadir
self.rpchost = rpchost
if timewait:
self.rpc_timeout = timewait
@@ -57,8 +59,10 @@ class TestNode():
self.binary = binary
self.stderr = stderr
self.coverage_dir = coverage_dir
+ if extra_conf != None:
+ append_config(datadir, extra_conf)
# Most callers will just need to add extra args to the standard list below.
- # For those callers that need more flexibity, they can just set the args property directly.
+ # For those callers that need more flexibility, they can just set the args property directly.
# Note that common args are set in the config file (see initialize_datadir)
self.extra_args = extra_args
self.args = [self.binary, "-datadir=" + self.datadir, "-logtimemicros", "-debug", "-debugexclude=libevent", "-debugexclude=leveldb", "-mocktime=" + str(mocktime), "-uacomment=testnode%d" % i]
@@ -162,6 +166,41 @@ class TestNode():
def wait_until_stopped(self, timeout=BITCOIND_PROC_WAIT_TIMEOUT):
wait_until(self.is_node_stopped, timeout=timeout)
+ def assert_start_raises_init_error(self, extra_args=None, expected_msg=None, partial_match=False, *args, **kwargs):
+ """Attempt to start the node and expect it to raise an error.
+
+ extra_args: extra arguments to pass through to bitcoind
+ expected_msg: regex that stderr should match when bitcoind fails
+
+ Will throw if bitcoind starts without an error.
+ Will throw if an expected_msg is provided and it does not match bitcoind's stdout."""
+ with tempfile.SpooledTemporaryFile(max_size=2**16) as log_stderr:
+ try:
+ self.start(extra_args, stderr=log_stderr, *args, **kwargs)
+ self.wait_for_rpc_connection()
+ self.stop_node()
+ self.wait_util_stopped()
+ except Exception as e:
+ assert 'bitcoind exited' in str(e) # node must have shutdown
+ self.running = False
+ self.process = None
+ # Check stderr for expected message
+ if expected_msg is not None:
+ log_stderr.seek(0)
+ stderr = log_stderr.read().decode('utf-8').strip()
+ if partial_match:
+ if re.search(expected_msg, stderr, flags=re.MULTILINE) is None:
+ raise AssertionError('Expected message "{}" does not partially match stderr:\n"{}"'.format(expected_msg, stderr))
+ else:
+ if re.fullmatch(expected_msg, stderr) is None:
+ raise AssertionError('Expected message "{}" does not fully match stderr:\n"{}"'.format(expected_msg, 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 node_encrypt_wallet(self, passphrase):
""""Encrypts the wallet.
diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py
index e9e7bbd8e4..041e2b86e8 100644
--- a/test/functional/test_framework/util.py
+++ b/test/functional/test_framework/util.py
@@ -284,7 +284,7 @@ def rpc_url(datadir, i, rpchost=None):
################
def initialize_datadir(dirname, n):
- datadir = os.path.join(dirname, "node" + str(n))
+ datadir = get_datadir_path(dirname, n)
if not os.path.isdir(datadir):
os.makedirs(datadir)
with open(os.path.join(datadir, "bitcoin.conf"), 'w', encoding='utf8') as f:
@@ -300,6 +300,11 @@ def initialize_datadir(dirname, n):
def get_datadir_path(dirname, n):
return os.path.join(dirname, "node" + str(n))
+def append_config(datadir, options):
+ with open(os.path.join(datadir, "bitcoin.conf"), 'a', encoding='utf8') as f:
+ for option in options:
+ f.write(option + "\n")
+
def get_auth_cookie(datadir):
user = None
password = None
@@ -322,9 +327,6 @@ def get_auth_cookie(datadir):
raise ValueError("No RPC credentials")
return user, password
-def log_filename(dirname, n_node, logname):
- return os.path.join(dirname, "node" + str(n_node), "regtest", logname)
-
def get_bip9_status(node, key):
info = node.getblockchaininfo()
return info['bip9_softforks'][key]
@@ -337,20 +339,15 @@ def disconnect_nodes(from_connection, node_num):
for peer_id in [peer['id'] for peer in from_connection.getpeerinfo() if "testnode%d" % node_num in peer['subver']]:
from_connection.disconnectnode(nodeid=peer_id)
- for _ in range(50):
- if [peer['id'] for peer in from_connection.getpeerinfo() if "testnode%d" % node_num in peer['subver']] == []:
- break
- time.sleep(0.1)
- else:
- raise AssertionError("timed out waiting for disconnect")
+ # wait to disconnect
+ wait_until(lambda: [peer['id'] for peer in from_connection.getpeerinfo() if "testnode%d" % node_num in peer['subver']] == [], timeout=5)
def connect_nodes(from_connection, node_num):
ip_port = "127.0.0.1:" + str(p2p_port(node_num))
from_connection.addnode(ip_port, "onetry")
# poll until version handshake complete to avoid race conditions
# with transaction relaying
- while any(peer['version'] == 0 for peer in from_connection.getpeerinfo()):
- time.sleep(0.1)
+ wait_until(lambda: all(peer['version'] != 0 for peer in from_connection.getpeerinfo()))
def connect_nodes_bi(nodes, a, b):
connect_nodes(nodes[a], b)
@@ -364,54 +361,29 @@ def sync_blocks(rpc_connections, *, wait=1, timeout=60):
one node already synced to the latest, stable tip, otherwise there's a
chance it might return before all nodes are stably synced.
"""
- # Use getblockcount() instead of waitforblockheight() to determine the
- # initial max height because the two RPCs look at different internal global
- # variables (chainActive vs latestBlock) and the former gets updated
- # earlier.
- maxheight = max(x.getblockcount() for x in rpc_connections)
- start_time = cur_time = time.time()
- while cur_time <= start_time + timeout:
- tips = [r.waitforblockheight(maxheight, int(wait * 1000)) for r in rpc_connections]
- if all(t["height"] == maxheight for t in tips):
- if all(t["hash"] == tips[0]["hash"] for t in tips):
- return
- raise AssertionError("Block sync failed, mismatched block hashes:{}".format(
- "".join("\n {!r}".format(tip) for tip in tips)))
- cur_time = time.time()
- raise AssertionError("Block sync to height {} timed out:{}".format(
- maxheight, "".join("\n {!r}".format(tip) for tip in tips)))
-
-def sync_chain(rpc_connections, *, wait=1, timeout=60):
- """
- Wait until everybody has the same best block
- """
- while timeout > 0:
+ stop_time = time.time() + timeout
+ while time.time() <= stop_time:
best_hash = [x.getbestblockhash() for x in rpc_connections]
- if best_hash == [best_hash[0]] * len(best_hash):
+ if best_hash.count(best_hash[0]) == len(rpc_connections):
return
time.sleep(wait)
- timeout -= wait
- raise AssertionError("Chain sync failed: Best block hashes don't match")
+ raise AssertionError("Block sync timed out:{}".format("".join("\n {!r}".format(b) for b in best_hash)))
def sync_mempools(rpc_connections, *, wait=1, timeout=60, flush_scheduler=True):
"""
Wait until everybody has the same transactions in their memory
pools
"""
- while timeout > 0:
- pool = set(rpc_connections[0].getrawmempool())
- num_match = 1
- for i in range(1, len(rpc_connections)):
- if set(rpc_connections[i].getrawmempool()) == pool:
- num_match = num_match + 1
- if num_match == len(rpc_connections):
+ stop_time = time.time() + timeout
+ while time.time() <= stop_time:
+ pool = [set(r.getrawmempool()) for r in rpc_connections]
+ if pool.count(pool[0]) == len(rpc_connections):
if flush_scheduler:
for r in rpc_connections:
r.syncwithvalidationinterfacequeue()
return
time.sleep(wait)
- timeout -= wait
- raise AssertionError("Mempool sync failed")
+ raise AssertionError("Mempool sync timed out:{}".format("".join("\n {!r}".format(m) for m in pool)))
# Transaction/Block functions
#############################