Skip to content

Commit cad967a

Browse files
committed
[tests] Move stop_node and start_node methods to BitcoinTestFramework
This commit moves functions start_node, start_nodes, stop_node and stop_nodes functions into the BitcoinTestFramework class. It also moves the bitcoind_processes dict and coverage variables into BitcoinTestFramework.
1 parent f1fe536 commit cad967a

File tree

11 files changed

+131
-172
lines changed

11 files changed

+131
-172
lines changed

test/functional/blockchain.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,13 @@
2121
import http.client
2222
import subprocess
2323

24-
from test_framework.test_framework import BitcoinTestFramework
24+
from test_framework.test_framework import (BitcoinTestFramework, BITCOIND_PROC_WAIT_TIMEOUT)
2525
from test_framework.util import (
2626
assert_equal,
2727
assert_raises,
2828
assert_raises_jsonrpc,
2929
assert_is_hex_string,
3030
assert_is_hash_string,
31-
bitcoind_processes,
32-
BITCOIND_PROC_WAIT_TIMEOUT,
3331
)
3432

3533

@@ -141,13 +139,13 @@ def _test_stopatheight(self):
141139
self.nodes[0].generate(6)
142140
assert_equal(self.nodes[0].getblockcount(), 206)
143141
self.log.debug('Node should not stop at this height')
144-
assert_raises(subprocess.TimeoutExpired, lambda: bitcoind_processes[0].wait(timeout=3))
142+
assert_raises(subprocess.TimeoutExpired, lambda: self.bitcoind_processes[0].wait(timeout=3))
145143
try:
146144
self.nodes[0].generate(1)
147145
except (ConnectionError, http.client.BadStatusLine):
148146
pass # The node already shut down before response
149147
self.log.debug('Node should stop at this height...')
150-
bitcoind_processes[0].wait(timeout=BITCOIND_PROC_WAIT_TIMEOUT)
148+
self.bitcoind_processes[0].wait(timeout=BITCOIND_PROC_WAIT_TIMEOUT)
151149
self.nodes[0] = self.start_node(0, self.options.tmpdir)
152150
assert_equal(self.nodes[0].getblockcount(), 207)
153151

test/functional/bumpfee.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def setup_network(self, split=False):
4242

4343
# Encrypt wallet for test_locked_wallet_fails test
4444
self.nodes[1].encryptwallet(WALLET_PASSPHRASE)
45-
bitcoind_processes[1].wait()
45+
self.bitcoind_processes[1].wait()
4646
self.nodes[1] = self.start_node(1, self.options.tmpdir, extra_args[1])
4747
self.nodes[1].walletpassphrase(WALLET_PASSPHRASE, WALLET_PASSPHRASE_TIMEOUT)
4848

test/functional/dbcrash.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def restart_node(self, node_index, expected_tip):
8888
# An exception here should mean the node is about to crash.
8989
# If bitcoind exits, then try again. wait_for_node_exit()
9090
# should raise an exception if bitcoind doesn't exit.
91-
wait_for_node_exit(node_index, timeout=10)
91+
self.wait_for_node_exit(node_index, timeout=10)
9292
self.crashed_on_restart += 1
9393
time.sleep(1)
9494

@@ -140,7 +140,7 @@ def sync_node3blocks(self, block_hashes):
140140
if not self.submit_block_catch_error(i, block):
141141
# TODO: more carefully check that the crash is due to -dbcrashratio
142142
# (change the exit code perhaps, and check that here?)
143-
wait_for_node_exit(i, timeout=30)
143+
self.wait_for_node_exit(i, timeout=30)
144144
self.log.debug("Restarting node %d after block hash %s", i, block_hash)
145145
nodei_utxo_hash = self.restart_node(i, block_hash)
146146
assert nodei_utxo_hash is not None

test/functional/fundrawtransaction.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
55
"""Test the fundrawtransaction RPC."""
66

7-
from test_framework.test_framework import BitcoinTestFramework
7+
from test_framework.test_framework import BitcoinTestFramework, BITCOIND_PROC_WAIT_TIMEOUT
88
from test_framework.util import *
99

1010

@@ -452,7 +452,7 @@ def run_test(self):
452452
self.stop_node(2)
453453
self.stop_node(3)
454454
self.nodes[1].encryptwallet("test")
455-
bitcoind_processes[1].wait(timeout=BITCOIND_PROC_WAIT_TIMEOUT)
455+
self.bitcoind_processes[1].wait(timeout=BITCOIND_PROC_WAIT_TIMEOUT)
456456

457457
self.nodes = self.start_nodes(self.num_nodes, self.options.tmpdir)
458458
# This test is not meant to test fee estimation and we'd like

test/functional/keypool.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ def run_test(self):
1818

1919
# Encrypt wallet and wait to terminate
2020
nodes[0].encryptwallet('test')
21-
bitcoind_processes[0].wait()
21+
self.bitcoind_processes[0].wait()
2222
# Restart node 0
2323
nodes[0] = self.start_node(0, self.options.tmpdir)
2424
# Keep creating keys

test/functional/rpcbind_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def run_bind_test(self, allow_ips, connect_to, addresses, expected):
3737
base_args += ['-rpcallowip=' + x for x in allow_ips]
3838
binds = ['-rpcbind='+addr for addr in addresses]
3939
self.nodes = self.start_nodes(self.num_nodes, self.options.tmpdir, [base_args + binds], connect_to)
40-
pid = bitcoind_processes[0].pid
40+
pid = self.bitcoind_processes[0].pid
4141
assert_equal(set(get_bind_addrs(pid)), set(expected))
4242
self.stop_nodes()
4343

test/functional/test_framework/test_framework.py

Lines changed: 114 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
"""Base class for RPC testing."""
66

77
from collections import deque
8+
import errno
89
from enum import Enum
10+
import http.client
911
import logging
1012
import optparse
1113
import os
@@ -16,15 +18,16 @@
1618
import time
1719
import traceback
1820

21+
from .authproxy import JSONRPCException
22+
from . import coverage
1923
from .util import (
20-
PortSeed,
2124
MAX_NODES,
22-
bitcoind_processes,
25+
PortSeed,
26+
assert_equal,
2327
check_json_precision,
2428
connect_nodes_bi,
2529
disable_mocktime,
2630
disconnect_nodes,
27-
enable_coverage,
2831
enable_mocktime,
2932
get_mocktime,
3033
get_rpc_proxy,
@@ -34,15 +37,9 @@
3437
p2p_port,
3538
rpc_url,
3639
set_node_times,
37-
_start_node,
38-
_start_nodes,
39-
_stop_node,
40-
_stop_nodes,
4140
sync_blocks,
4241
sync_mempools,
43-
wait_for_bitcoind_start,
4442
)
45-
from .authproxy import JSONRPCException
4643

4744
class TestStatus(Enum):
4845
PASSED = 1
@@ -53,6 +50,8 @@ class TestStatus(Enum):
5350
TEST_EXIT_FAILED = 1
5451
TEST_EXIT_SKIPPED = 77
5552

53+
BITCOIND_PROC_WAIT_TIMEOUT = 60
54+
5655
class BitcoinTestFramework(object):
5756
"""Base class for a bitcoin test script.
5857
@@ -72,7 +71,8 @@ class BitcoinTestFramework(object):
7271
def __init__(self):
7372
self.num_nodes = 4
7473
self.setup_clean_chain = False
75-
self.nodes = None
74+
self.nodes = []
75+
self.bitcoind_processes = {}
7676

7777
def add_options(self, parser):
7878
pass
@@ -98,7 +98,7 @@ def setup_nodes(self):
9898
extra_args = None
9999
if hasattr(self, "extra_args"):
100100
extra_args = self.extra_args
101-
self.nodes = _start_nodes(self.num_nodes, self.options.tmpdir, extra_args)
101+
self.nodes = self.start_nodes(self.num_nodes, self.options.tmpdir, extra_args)
102102

103103
def run_test(self):
104104
raise NotImplementedError
@@ -130,9 +130,6 @@ def main(self):
130130
self.add_options(parser)
131131
(self.options, self.args) = parser.parse_args()
132132

133-
if self.options.coveragedir:
134-
enable_coverage(self.options.coveragedir)
135-
136133
PortSeed.n = self.options.port_seed
137134

138135
os.environ['PATH'] = self.options.srcdir + ":" + self.options.srcdir + "/qt:" + os.environ['PATH']
@@ -209,16 +206,88 @@ def main(self):
209206
# Public helper methods. These can be accessed by the subclass test scripts.
210207

211208
def start_node(self, i, dirname, extra_args=None, rpchost=None, timewait=None, binary=None, stderr=None):
212-
return _start_node(i, dirname, extra_args, rpchost, timewait, binary, stderr)
209+
"""Start a bitcoind and return RPC connection to it"""
210+
211+
datadir = os.path.join(dirname, "node" + str(i))
212+
if binary is None:
213+
binary = os.getenv("BITCOIND", "bitcoind")
214+
args = [binary, "-datadir=" + datadir, "-server", "-keypool=1", "-discover=0", "-rest", "-logtimemicros", "-debug", "-debugexclude=libevent", "-debugexclude=leveldb", "-mocktime=" + str(get_mocktime()), "-uacomment=testnode%d" % i]
215+
if extra_args is not None:
216+
args.extend(extra_args)
217+
self.bitcoind_processes[i] = subprocess.Popen(args, stderr=stderr)
218+
self.log.debug("initialize_chain: bitcoind started, waiting for RPC to come up")
219+
self._wait_for_bitcoind_start(self.bitcoind_processes[i], datadir, i, rpchost)
220+
self.log.debug("initialize_chain: RPC successfully started")
221+
proxy = get_rpc_proxy(rpc_url(datadir, i, rpchost), i, timeout=timewait)
222+
223+
if self.options.coveragedir:
224+
coverage.write_all_rpc_commands(self.options.coveragedir, proxy)
225+
226+
return proxy
213227

214228
def start_nodes(self, num_nodes, dirname, extra_args=None, rpchost=None, timewait=None, binary=None):
215-
return _start_nodes(num_nodes, dirname, extra_args, rpchost, timewait, binary)
229+
"""Start multiple bitcoinds, return RPC connections to them"""
230+
231+
if extra_args is None:
232+
extra_args = [None] * num_nodes
233+
if binary is None:
234+
binary = [None] * num_nodes
235+
assert_equal(len(extra_args), num_nodes)
236+
assert_equal(len(binary), num_nodes)
237+
rpcs = []
238+
try:
239+
for i in range(num_nodes):
240+
rpcs.append(self.start_node(i, dirname, extra_args[i], rpchost, timewait=timewait, binary=binary[i]))
241+
except:
242+
# If one node failed to start, stop the others
243+
# TODO: abusing self.nodes in this way is a little hacky.
244+
# Eventually we should do a better job of tracking nodes
245+
self.nodes.extend(rpcs)
246+
self.stop_nodes()
247+
self.nodes = []
248+
raise
249+
return rpcs
250+
251+
def stop_node(self, i):
252+
"""Stop a bitcoind test node"""
216253

217-
def stop_node(self, num_node):
218-
_stop_node(self.nodes[num_node], num_node)
254+
self.log.debug("Stopping node %d" % i)
255+
try:
256+
self.nodes[i].stop()
257+
except http.client.CannotSendRequest as e:
258+
self.log.exception("Unable to stop node")
259+
return_code = self.bitcoind_processes[i].wait(timeout=BITCOIND_PROC_WAIT_TIMEOUT)
260+
assert_equal(return_code, 0)
261+
del self.bitcoind_processes[i]
219262

220263
def stop_nodes(self):
221-
_stop_nodes(self.nodes)
264+
"""Stop multiple bitcoind test nodes"""
265+
266+
for i in range(len(self.nodes)):
267+
self.stop_node(i)
268+
assert not self.bitcoind_processes.values() # All connections must be gone now
269+
270+
def assert_start_raises_init_error(self, i, dirname, extra_args=None, expected_msg=None):
271+
with tempfile.SpooledTemporaryFile(max_size=2**16) as log_stderr:
272+
try:
273+
self.start_node(i, dirname, extra_args, stderr=log_stderr)
274+
self.stop_node(i)
275+
except Exception as e:
276+
assert 'bitcoind exited' in str(e) # node must have shutdown
277+
if expected_msg is not None:
278+
log_stderr.seek(0)
279+
stderr = log_stderr.read().decode('utf-8')
280+
if expected_msg not in stderr:
281+
raise AssertionError("Expected error \"" + expected_msg + "\" not found in:\n" + stderr)
282+
else:
283+
if expected_msg is None:
284+
assert_msg = "bitcoind should have exited with an error"
285+
else:
286+
assert_msg = "bitcoind should have exited with expected error " + expected_msg
287+
raise AssertionError(assert_msg)
288+
289+
def wait_for_node_exit(self, i, timeout):
290+
self.bitcoind_processes[i].wait(timeout)
222291

223292
def split_network(self):
224293
"""
@@ -300,9 +369,9 @@ def _initialize_chain(self, test_dir, num_nodes, cachedir):
300369
args = [os.getenv("BITCOIND", "bitcoind"), "-server", "-keypool=1", "-datadir=" + datadir, "-discover=0"]
301370
if i > 0:
302371
args.append("-connect=127.0.0.1:" + str(p2p_port(0)))
303-
bitcoind_processes[i] = subprocess.Popen(args)
372+
self.bitcoind_processes[i] = subprocess.Popen(args)
304373
self.log.debug("initialize_chain: bitcoind started, waiting for RPC to come up")
305-
wait_for_bitcoind_start(bitcoind_processes[i], datadir, i)
374+
self._wait_for_bitcoind_start(self.bitcoind_processes[i], datadir, i)
306375
self.log.debug("initialize_chain: RPC successfully started")
307376

308377
self.nodes = []
@@ -355,6 +424,30 @@ def _initialize_chain_clean(self, test_dir, num_nodes):
355424
for i in range(num_nodes):
356425
initialize_datadir(test_dir, i)
357426

427+
def _wait_for_bitcoind_start(self, process, datadir, i, rpchost=None):
428+
"""Wait for bitcoind to start.
429+
430+
This means that RPC is accessible and fully initialized.
431+
Raise an exception if bitcoind exits during initialization."""
432+
while True:
433+
if process.poll() is not None:
434+
raise Exception('bitcoind exited with status %i during initialization' % process.returncode)
435+
try:
436+
# Check if .cookie file to be created
437+
rpc = get_rpc_proxy(rpc_url(datadir, i, rpchost), i, coveragedir=self.options.coveragedir)
438+
rpc.getblockcount()
439+
break # break out of loop on success
440+
except IOError as e:
441+
if e.errno != errno.ECONNREFUSED: # Port not yet open?
442+
raise # unknown IO error
443+
except JSONRPCException as e: # Initialization phase
444+
if e.error['code'] != -28: # RPC in warmup?
445+
raise # unknown JSON RPC exception
446+
except ValueError as e: # cookie file not found and no rpcuser or rpcassword. bitcoind still starting
447+
if "No RPC credentials" not in str(e):
448+
raise
449+
time.sleep(0.25)
450+
358451
class ComparisonTestFramework(BitcoinTestFramework):
359452
"""Test framework for doing p2p comparison testing
360453

0 commit comments

Comments
 (0)