aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rwxr-xr-xtest/functional/feature_includeconf.py75
-rwxr-xr-xtest/functional/rpc_fundrawtransaction.py20
-rwxr-xr-xtest/functional/test_framework/test_framework.py18
-rwxr-xr-xtest/functional/test_framework/test_node.py41
-rw-r--r--test/functional/test_framework/util.py23
-rwxr-xr-xtest/functional/test_runner.py4
6 files changed, 157 insertions, 24 deletions
diff --git a/test/functional/feature_includeconf.py b/test/functional/feature_includeconf.py
new file mode 100755
index 0000000000..1ead2fcb02
--- /dev/null
+++ b/test/functional/feature_includeconf.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python3
+# Copyright (c) 2018 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+"""Tests the includeconf argument
+
+Verify that:
+
+1. adding includeconf to the configuration file causes the includeconf
+ file to be loaded in the correct order.
+2. includeconf cannot be used as a command line argument.
+3. includeconf cannot be used recursively (ie includeconf can only
+ be used from the base config file).
+4. multiple includeconf arguments can be specified in the main config
+ file.
+"""
+import os
+
+from test_framework.test_framework import BitcoinTestFramework
+
+class IncludeConfTest(BitcoinTestFramework):
+ def set_test_params(self):
+ self.setup_clean_chain = False
+ self.num_nodes = 1
+
+ def setup_chain(self):
+ super().setup_chain()
+ # Create additional config files
+ # - tmpdir/node0/relative.conf
+ with open(os.path.join(self.options.tmpdir, "node0", "relative.conf"), "w", encoding="utf8") as f:
+ f.write("uacomment=relative\n")
+ # - tmpdir/node0/relative2.conf
+ with open(os.path.join(self.options.tmpdir, "node0", "relative2.conf"), "w", encoding="utf8") as f:
+ f.write("uacomment=relative2\n")
+ with open(os.path.join(self.options.tmpdir, "node0", "bitcoin.conf"), "a", encoding='utf8') as f:
+ f.write("uacomment=main\nincludeconf=relative.conf\n")
+
+ def run_test(self):
+ self.log.info("-includeconf works from config file. subversion should end with 'main; relative)/'")
+
+ subversion = self.nodes[0].getnetworkinfo()["subversion"]
+ assert subversion.endswith("main; relative)/")
+
+ self.log.info("-includeconf cannot be used as command-line arg. subversion should still end with 'main; relative)/'")
+ self.stop_node(0)
+
+ self.start_node(0, extra_args=["-includeconf=relative2.conf"])
+
+ subversion = self.nodes[0].getnetworkinfo()["subversion"]
+ assert subversion.endswith("main; relative)/")
+ self.stop_node(0, expected_stderr="warning: -includeconf cannot be used from commandline; ignoring -includeconf=relative2.conf")
+
+ self.log.info("-includeconf cannot be used recursively. subversion should end with 'main; relative)/'")
+ with open(os.path.join(self.options.tmpdir, "node0", "relative.conf"), "a", encoding="utf8") as f:
+ f.write("includeconf=relative2.conf\n")
+
+ self.start_node(0)
+
+ subversion = self.nodes[0].getnetworkinfo()["subversion"]
+ assert subversion.endswith("main; relative)/")
+
+ self.log.info("multiple -includeconf args can be used from the base config file. subversion should end with 'main; relative; relative2)/'")
+ with open(os.path.join(self.options.tmpdir, "node0", "relative.conf"), "w", encoding="utf8") as f:
+ f.write("uacomment=relative\n")
+
+ with open(os.path.join(self.options.tmpdir, "node0", "bitcoin.conf"), "a", encoding='utf8') as f:
+ f.write("includeconf=relative2.conf\n")
+
+ self.restart_node(0)
+
+ subversion = self.nodes[0].getnetworkinfo()["subversion"]
+ assert subversion.endswith("main; relative; relative2)/")
+
+if __name__ == '__main__':
+ IncludeConfTest().main()
diff --git a/test/functional/rpc_fundrawtransaction.py b/test/functional/rpc_fundrawtransaction.py
index 0f09c3c552..1e91cf598c 100755
--- a/test/functional/rpc_fundrawtransaction.py
+++ b/test/functional/rpc_fundrawtransaction.py
@@ -4,8 +4,18 @@
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Test the fundrawtransaction RPC."""
+from decimal import Decimal
from test_framework.test_framework import BitcoinTestFramework
-from test_framework.util import *
+from test_framework.util import (
+ assert_equal,
+ assert_fee_amount,
+ assert_greater_than,
+ assert_greater_than_or_equal,
+ assert_raises_rpc_error,
+ connect_nodes_bi,
+ count_bytes,
+ find_vout_for_address,
+)
def get_unspent(listunspent, amount):
@@ -57,6 +67,11 @@ class RawTransactionsTest(BitcoinTestFramework):
watchonly_amount = Decimal(200)
self.nodes[3].importpubkey(watchonly_pubkey, "", True)
watchonly_txid = self.nodes[0].sendtoaddress(watchonly_address, watchonly_amount)
+
+ # Lock UTXO so nodes[0] doesn't accidentally spend it
+ watchonly_vout = find_vout_for_address(self.nodes[0], watchonly_txid, watchonly_address)
+ self.nodes[0].lockunspent(False, [{"txid": watchonly_txid, "vout": watchonly_vout}])
+
self.nodes[0].sendtoaddress(self.nodes[3].getnewaddress(), watchonly_amount / 10)
self.nodes[0].sendtoaddress(self.nodes[2].getnewaddress(), 1.5)
@@ -475,6 +490,9 @@ class RawTransactionsTest(BitcoinTestFramework):
connect_nodes_bi(self.nodes,1,2)
connect_nodes_bi(self.nodes,0,2)
connect_nodes_bi(self.nodes,0,3)
+ # Again lock the watchonly UTXO or nodes[0] may spend it, because
+ # lockunspent is memory-only and thus lost on restart
+ self.nodes[0].lockunspent(False, [{"txid": watchonly_txid, "vout": watchonly_vout}])
self.sync_all()
# drain the keypool
diff --git a/test/functional/test_framework/test_framework.py b/test/functional/test_framework/test_framework.py
index 472664a314..b842e6ef4e 100755
--- a/test/functional/test_framework/test_framework.py
+++ b/test/functional/test_framework/test_framework.py
@@ -98,8 +98,6 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
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.abspath(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.abspath(os.path.dirname(os.path.realpath(__file__)) + "/../../cache"),
help="Directory for caching pregenerated datadirs (default: %default)")
parser.add_option("--tmpdir", dest="tmpdir", help="Root directory for datadirs")
@@ -123,10 +121,6 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
PortSeed.n = self.options.port_seed
- os.environ['PATH'] = self.options.srcdir + os.pathsep + \
- self.options.srcdir + os.path.sep + "qt" + os.pathsep + \
- os.environ['PATH']
-
check_json_precision()
self.options.cachedir = os.path.abspath(self.options.cachedir)
@@ -136,6 +130,10 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
self.options.bitcoind = os.getenv("BITCOIND", default=config["environment"]["BUILDDIR"] + '/src/bitcoind' + config["environment"]["EXEEXT"])
self.options.bitcoincli = os.getenv("BITCOINCLI", default=config["environment"]["BUILDDIR"] + '/src/bitcoin-cli' + config["environment"]["EXEEXT"])
+ os.environ['PATH'] = config['environment']['BUILDDIR'] + os.pathsep + \
+ config['environment']['BUILDDIR'] + os.path.sep + "qt" + os.pathsep + \
+ os.environ['PATH']
+
# Set up temp directory and start logging
if self.options.tmpdir:
self.options.tmpdir = os.path.abspath(self.options.tmpdir)
@@ -258,7 +256,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
assert_equal(len(extra_args), num_nodes)
assert_equal(len(binary), num_nodes)
for i in range(num_nodes):
- self.nodes.append(TestNode(i, get_datadir_path(self.options.tmpdir, i), rpchost=rpchost, timewait=timewait, bitcoind=binary[i], bitcoin_cli=self.options.bitcoincli, 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))
+ self.nodes.append(TestNode(i, get_datadir_path(self.options.tmpdir, i), rpchost=rpchost, timewait=timewait, bitcoind=binary[i], bitcoin_cli=self.options.bitcoincli, 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"""
@@ -291,9 +289,9 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
for node in self.nodes:
coverage.write_all_rpc_commands(self.options.coveragedir, node.rpc)
- def stop_node(self, i):
+ def stop_node(self, i, expected_stderr=''):
"""Stop a bitcoind test node"""
- self.nodes[i].stop_node()
+ self.nodes[i].stop_node(expected_stderr)
self.nodes[i].wait_until_stopped()
def stop_nodes(self):
@@ -409,7 +407,7 @@ class BitcoinTestFramework(metaclass=BitcoinTestMetaClass):
args = [self.options.bitcoind, "-datadir=" + datadir]
if i > 0:
args.append("-connect=127.0.0.1:" + str(p2p_port(0)))
- 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, bitcoind=self.options.bitcoind, bitcoin_cli=self.options.bitcoincli, 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, bitcoind=self.options.bitcoind, bitcoin_cli=self.options.bitcoincli, mocktime=self.mocktime, coverage_dir=None))
self.nodes[i].args = args
self.start_node(i)
diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py
index 5a6a659392..0353fc0712 100755
--- a/test/functional/test_framework/test_node.py
+++ b/test/functional/test_framework/test_node.py
@@ -10,6 +10,7 @@ from enum import Enum
import http.client
import json
import logging
+import os
import re
import subprocess
import tempfile
@@ -55,9 +56,11 @@ class TestNode():
To make things easier for the test writer, any unrecognised messages will
be dispatched to the RPC connection."""
- def __init__(self, i, datadir, rpchost, timewait, bitcoind, bitcoin_cli, stderr, mocktime, coverage_dir, extra_conf=None, extra_args=None, use_cli=False):
+ def __init__(self, i, datadir, rpchost, timewait, bitcoind, bitcoin_cli, mocktime, coverage_dir, extra_conf=None, extra_args=None, use_cli=False):
self.index = i
self.datadir = datadir
+ self.stdout_dir = os.path.join(self.datadir, "stdout")
+ self.stderr_dir = os.path.join(self.datadir, "stderr")
self.rpchost = rpchost
if timewait:
self.rpc_timeout = timewait
@@ -65,7 +68,6 @@ class TestNode():
# Wait for up to 60 seconds for the RPC server to respond
self.rpc_timeout = 60
self.binary = bitcoind
- self.stderr = stderr
self.coverage_dir = coverage_dir
if extra_conf != None:
append_config(datadir, extra_conf)
@@ -81,8 +83,7 @@ class TestNode():
"-debugexclude=libevent",
"-debugexclude=leveldb",
"-mocktime=" + str(mocktime),
- "-uacomment=testnode%d" % i,
- "-noprinttoconsole"
+ "-uacomment=testnode%d" % i
]
self.cli = TestNodeCLI(bitcoin_cli, self.datadir)
@@ -124,17 +125,29 @@ class TestNode():
assert self.rpc_connected and self.rpc is not None, self._node_msg("Error: no RPC connection")
return getattr(self.rpc, name)
- def start(self, extra_args=None, stderr=None, *args, **kwargs):
+ def start(self, extra_args=None, stdout=None, stderr=None, *args, **kwargs):
"""Start the node."""
if extra_args is None:
extra_args = self.extra_args
+
+ # Add a new stdout and stderr file each time bitcoind is started
if stderr is None:
- stderr = self.stderr
+ stderr = tempfile.NamedTemporaryFile(dir=self.stderr_dir, delete=False)
+ if stdout is None:
+ stdout = tempfile.NamedTemporaryFile(dir=self.stdout_dir, delete=False)
+ self.stderr = stderr
+ self.stdout = stdout
+
# Delete any existing cookie file -- if such a file exists (eg due to
# unclean shutdown), it will get overwritten anyway by bitcoind, and
# potentially interfere with our attempt to authenticate
delete_cookie_file(self.datadir)
- self.process = subprocess.Popen(self.args + extra_args, stderr=stderr, *args, **kwargs)
+
+ # add environment variable LIBC_FATAL_STDERR_=1 so that libc errors are written to stderr and not the terminal
+ subp_env = dict(os.environ, LIBC_FATAL_STDERR_="1")
+
+ self.process = subprocess.Popen(self.args + extra_args, env=subp_env, stdout=stdout, stderr=stderr, *args, **kwargs)
+
self.running = True
self.log.debug("bitcoind started, waiting for RPC to come up")
@@ -174,7 +187,7 @@ class TestNode():
wallet_path = "wallet/%s" % wallet_name
return self.rpc / wallet_path
- def stop_node(self):
+ def stop_node(self, expected_stderr=''):
"""Stop the node."""
if not self.running:
return
@@ -183,6 +196,13 @@ class TestNode():
self.stop()
except http.client.CannotSendRequest:
self.log.exception("Unable to stop node.")
+
+ # Check that stderr is as expected
+ self.stderr.seek(0)
+ stderr = self.stderr.read().decode('utf-8').strip()
+ if stderr != expected_stderr:
+ raise AssertionError("Unexpected stderr {} != {}".format(stderr, expected_stderr))
+
del self.p2ps[:]
def is_node_stopped(self):
@@ -217,9 +237,10 @@ class TestNode():
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:
+ with tempfile.NamedTemporaryFile(dir=self.stderr_dir, delete=False) as log_stderr, \
+ tempfile.NamedTemporaryFile(dir=self.stdout_dir, delete=False) as log_stdout:
try:
- self.start(extra_args, stderr=log_stderr, *args, **kwargs)
+ self.start(extra_args, stdout=log_stdout, stderr=log_stderr, *args, **kwargs)
self.wait_for_rpc_connection()
self.stop_node()
self.wait_until_stopped()
diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py
index 4ec3175cd6..e016148f70 100644
--- a/test/functional/test_framework/util.py
+++ b/test/functional/test_framework/util.py
@@ -301,6 +301,9 @@ def initialize_datadir(dirname, n):
f.write("keypool=1\n")
f.write("discover=0\n")
f.write("listenonion=0\n")
+ f.write("printtoconsole=0\n")
+ os.makedirs(os.path.join(datadir, 'stderr'), exist_ok=True)
+ os.makedirs(os.path.join(datadir, 'stdout'), exist_ok=True)
return datadir
def get_datadir_path(dirname, n):
@@ -349,7 +352,14 @@ def set_node_times(nodes, t):
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)
+ try:
+ from_connection.disconnectnode(nodeid=peer_id)
+ except JSONRPCException as e:
+ # If this node is disconnected between calculating the peer id
+ # and issuing the disconnect, don't worry about it.
+ # This avoids a race condition if we're mass-disconnecting peers.
+ if e.error['code'] != -29: # RPC_CLIENT_NODE_NOT_CONNECTED
+ raise
# wait to disconnect
wait_until(lambda: [peer['id'] for peer in from_connection.getpeerinfo() if "testnode%d" % node_num in peer['subver']] == [], timeout=5)
@@ -556,3 +566,14 @@ def mine_large_block(node, utxos=None):
fee = 100 * node.getnetworkinfo()["relayfee"]
create_lots_of_big_transactions(node, txouts, utxos, num, fee=fee)
node.generate(1)
+
+def find_vout_for_address(node, txid, addr):
+ """
+ Locate the vout index of the given transaction sending to the
+ given address. Raises runtime error exception if not found.
+ """
+ tx = node.getrawtransaction(txid, True)
+ for i in range(len(tx["vout"])):
+ if any([addr == a for a in tx["vout"][i]["scriptPubKey"]["addresses"]]):
+ return i
+ raise RuntimeError("Vout not found for address: txid=%s, addr=%s" % (txid, addr))
diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py
index ff4b480165..dfa8c33728 100755
--- a/test/functional/test_runner.py
+++ b/test/functional/test_runner.py
@@ -138,6 +138,7 @@ BASE_SCRIPTS = [
'p2p_fingerprint.py',
'feature_uacomment.py',
'p2p_unrequested_blocks.py',
+ 'feature_includeconf.py',
'feature_logging.py',
'p2p_node_network_limited.py',
'feature_blocksdir.py',
@@ -313,8 +314,7 @@ def run_tests(test_list, src_dir, build_dir, tmpdir, jobs=1, enable_coverage=Fal
tests_dir = src_dir + '/test/functional/'
- flags = ["--srcdir={}/src".format(build_dir)] + args
- flags.append("--cachedir=%s" % cache_dir)
+ flags = ['--cachedir={}'.format(cache_dir)] + args
if enable_coverage:
coverage = RPCCoverage()