5
5
"""Base class for RPC testing."""
6
6
7
7
from collections import deque
8
+ import errno
8
9
from enum import Enum
10
+ import http .client
9
11
import logging
10
12
import optparse
11
13
import os
16
18
import time
17
19
import traceback
18
20
21
+ from .authproxy import JSONRPCException
22
+ from . import coverage
19
23
from .util import (
20
- PortSeed ,
21
24
MAX_NODES ,
22
- bitcoind_processes ,
25
+ PortSeed ,
26
+ assert_equal ,
23
27
check_json_precision ,
24
28
connect_nodes_bi ,
25
29
disable_mocktime ,
26
30
disconnect_nodes ,
27
- enable_coverage ,
28
31
enable_mocktime ,
29
32
get_mocktime ,
30
33
get_rpc_proxy ,
34
37
p2p_port ,
35
38
rpc_url ,
36
39
set_node_times ,
37
- _start_node ,
38
- _start_nodes ,
39
- _stop_node ,
40
- _stop_nodes ,
41
40
sync_blocks ,
42
41
sync_mempools ,
43
- wait_for_bitcoind_start ,
44
42
)
45
- from .authproxy import JSONRPCException
46
43
47
44
class TestStatus (Enum ):
48
45
PASSED = 1
@@ -53,6 +50,8 @@ class TestStatus(Enum):
53
50
TEST_EXIT_FAILED = 1
54
51
TEST_EXIT_SKIPPED = 77
55
52
53
+ BITCOIND_PROC_WAIT_TIMEOUT = 60
54
+
56
55
class BitcoinTestFramework (object ):
57
56
"""Base class for a bitcoin test script.
58
57
@@ -72,7 +71,8 @@ class BitcoinTestFramework(object):
72
71
def __init__ (self ):
73
72
self .num_nodes = 4
74
73
self .setup_clean_chain = False
75
- self .nodes = None
74
+ self .nodes = []
75
+ self .bitcoind_processes = {}
76
76
77
77
def add_options (self , parser ):
78
78
pass
@@ -98,7 +98,7 @@ def setup_nodes(self):
98
98
extra_args = None
99
99
if hasattr (self , "extra_args" ):
100
100
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 )
102
102
103
103
def run_test (self ):
104
104
raise NotImplementedError
@@ -130,9 +130,6 @@ def main(self):
130
130
self .add_options (parser )
131
131
(self .options , self .args ) = parser .parse_args ()
132
132
133
- if self .options .coveragedir :
134
- enable_coverage (self .options .coveragedir )
135
-
136
133
PortSeed .n = self .options .port_seed
137
134
138
135
os .environ ['PATH' ] = self .options .srcdir + ":" + self .options .srcdir + "/qt:" + os .environ ['PATH' ]
@@ -209,16 +206,88 @@ def main(self):
209
206
# Public helper methods. These can be accessed by the subclass test scripts.
210
207
211
208
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
213
227
214
228
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"""
216
253
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 ]
219
262
220
263
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 )
222
291
223
292
def split_network (self ):
224
293
"""
@@ -300,9 +369,9 @@ def _initialize_chain(self, test_dir, num_nodes, cachedir):
300
369
args = [os .getenv ("BITCOIND" , "bitcoind" ), "-server" , "-keypool=1" , "-datadir=" + datadir , "-discover=0" ]
301
370
if i > 0 :
302
371
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 )
304
373
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 )
306
375
self .log .debug ("initialize_chain: RPC successfully started" )
307
376
308
377
self .nodes = []
@@ -355,6 +424,30 @@ def _initialize_chain_clean(self, test_dir, num_nodes):
355
424
for i in range (num_nodes ):
356
425
initialize_datadir (test_dir , i )
357
426
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
+
358
451
class ComparisonTestFramework (BitcoinTestFramework ):
359
452
"""Test framework for doing p2p comparison testing
360
453
0 commit comments