mirror of
https://github.com/dashpay/dash.git
synced 2024-12-26 04:22:55 +01:00
Merge #10711: [tests] Introduce TestNode
789733891
[tests] Introduce TestNode (John Newbery)
Pull request description:
Continues #10082
TestNode is a class responsible for all state related to a bitcoind node
under test. It stores local state, is responsible for tracking the
bitcoind process and delegates unrecognised messages to the RPC
connection.
This commit changes start_nodes and stop_nodes to start and stop the
bitcoind nodes in parallel, making test setup and teardown much faster.
On my vm, this changeset reduces total test_runner runtime for the base set of tests
(including building the cache) from 263s to 195s (a 25% speedup). Note that the time
reported by test_runner does not include time spent building the cache:
*with TestNode*:
```
→ date +"%T" ; ./test_runner.py -q ; date +"%T"
12:48:04
..................................................................................................................................................................................................................................................................................................................................
TEST | STATUS | DURATION
abandonconflict.py | ✓ Passed | 12 s
bip68-112-113-p2p.py | ✓ Passed | 19 s
blockchain.py | ✓ Passed | 8 s
bumpfee.py | ✓ Passed | 13 s
decodescript.py | ✓ Passed | 3 s
disablewallet.py | ✓ Passed | 3 s
disconnect_ban.py | ✓ Passed | 6 s
fundrawtransaction.py | ✓ Passed | 37 s
getchaintips.py | ✓ Passed | 4 s
httpbasics.py | ✓ Passed | 3 s
import-rescan.py | ✓ Passed | 4 s
importmulti.py | ✓ Passed | 6 s
importprunedfunds.py | ✓ Passed | 3 s
invalidblockrequest.py | ✓ Passed | 4 s
invalidtxrequest.py | ✓ Passed | 4 s
keypool.py | ✓ Passed | 7 s
listsinceblock.py | ✓ Passed | 4 s
listtransactions.py | ✓ Passed | 33 s
mempool_limit.py | ✓ Passed | 4 s
mempool_persist.py | ✓ Passed | 15 s
mempool_reorg.py | ✓ Passed | 4 s
mempool_resurrect_test.py | ✓ Passed | 3 s
mempool_spendcoinbase.py | ✓ Passed | 3 s
merkle_blocks.py | ✓ Passed | 3 s
multi_rpc.py | ✓ Passed | 4 s
net.py | ✓ Passed | 3 s
nulldummy.py | ✓ Passed | 3 s
p2p-compactblocks.py | ✓ Passed | 28 s
p2p-fullblocktest.py | ✓ Passed | 126 s
p2p-leaktests.py | ✓ Passed | 8 s
p2p-mempool.py | ✓ Passed | 3 s
p2p-segwit.py | ✓ Passed | 59 s
p2p-versionbits-warning.py | ✓ Passed | 8 s
preciousblock.py | ✓ Passed | 3 s
prioritise_transaction.py | ✓ Passed | 5 s
proxy_test.py | ✓ Passed | 3 s
rawtransactions.py | ✓ Passed | 9 s
receivedby.py | ✓ Passed | 19 s
reindex.py | ✓ Passed | 12 s
rest.py | ✓ Passed | 9 s
rpcnamedargs.py | ✓ Passed | 3 s
segwit.py | ✓ Passed | 7 s
sendheaders.py | ✓ Passed | 24 s
signmessages.py | ✓ Passed | 3 s
signrawtransactions.py | ✓ Passed | 3 s
txn_clone.py | ✓ Passed | 4 s
txn_doublespend.py --mineblock | ✓ Passed | 4 s
uptime.py | ✓ Passed | 3 s
wallet-accounts.py | ✓ Passed | 3 s
wallet-dump.py | ✓ Passed | 7 s
wallet-encryption.py | ✓ Passed | 8 s
wallet-hd.py | ✓ Passed | 15 s
wallet.py | ✓ Passed | 31 s
walletbackup.py | ✓ Passed | 104 s
zapwallettxes.py | ✓ Passed | 9 s
zmq_test.py | ○ Skipped | 0 s
ALL | ✓ Passed | 735 s (accumulated)
Runtime: 189 s
12:51:19
```
*master*:
```
→ date +"%T" ; ./test_runner.py -q ; date +"%T"
12:40:13
..........................................................................................................................................................................................................................................................................................................................................................................................................................................
TEST | STATUS | DURATION
abandonconflict.py | ✓ Passed | 15 s
bip68-112-113-p2p.py | ✓ Passed | 19 s
blockchain.py | ✓ Passed | 8 s
bumpfee.py | ✓ Passed | 20 s
decodescript.py | ✓ Passed | 3 s
disablewallet.py | ✓ Passed | 3 s
disconnect_ban.py | ✓ Passed | 8 s
fundrawtransaction.py | ✓ Passed | 36 s
getchaintips.py | ✓ Passed | 11 s
httpbasics.py | ✓ Passed | 7 s
import-rescan.py | ✓ Passed | 16 s
importmulti.py | ✓ Passed | 10 s
importprunedfunds.py | ✓ Passed | 5 s
invalidblockrequest.py | ✓ Passed | 4 s
invalidtxrequest.py | ✓ Passed | 3 s
keypool.py | ✓ Passed | 7 s
listsinceblock.py | ✓ Passed | 11 s
listtransactions.py | ✓ Passed | 37 s
mempool_limit.py | ✓ Passed | 4 s
mempool_persist.py | ✓ Passed | 23 s
mempool_reorg.py | ✓ Passed | 7 s
mempool_resurrect_test.py | ✓ Passed | 3 s
mempool_spendcoinbase.py | ✓ Passed | 3 s
merkle_blocks.py | ✓ Passed | 10 s
multi_rpc.py | ✓ Passed | 6 s
net.py | ✓ Passed | 6 s
nulldummy.py | ✓ Passed | 3 s
p2p-compactblocks.py | ✓ Passed | 30 s
p2p-fullblocktest.py | ✓ Passed | 126 s
p2p-leaktests.py | ✓ Passed | 8 s
p2p-mempool.py | ✓ Passed | 3 s
p2p-segwit.py | ✓ Passed | 62 s
p2p-versionbits-warning.py | ✓ Passed | 8 s
preciousblock.py | ✓ Passed | 8 s
prioritise_transaction.py | ✓ Passed | 7 s
proxy_test.py | ✓ Passed | 10 s
rawtransactions.py | ✓ Passed | 15 s
receivedby.py | ✓ Passed | 28 s
reindex.py | ✓ Passed | 12 s
rest.py | ✓ Passed | 12 s
rpcnamedargs.py | ✓ Passed | 3 s
segwit.py | ✓ Passed | 12 s
sendheaders.py | ✓ Passed | 26 s
signmessages.py | ✓ Passed | 3 s
signrawtransactions.py | ✓ Passed | 3 s
txn_clone.py | ✓ Passed | 10 s
txn_doublespend.py --mineblock | ✓ Passed | 10 s
uptime.py | ✓ Passed | 3 s
wallet-accounts.py | ✓ Passed | 3 s
wallet-dump.py | ✓ Passed | 6 s
wallet-encryption.py | ✓ Passed | 8 s
wallet-hd.py | ✓ Passed | 18 s
wallet.py | ✓ Passed | 69 s
walletbackup.py | ✓ Passed | 130 s
zapwallettxes.py | ✓ Passed | 15 s
zmq_test.py | ○ Skipped | 0 s
ALL | ✓ Passed | 936 s (accumulated)
Runtime: 242 s
12:44:36
```
Tree-SHA512: 6dfc4c11fd0caf7de6954c93679cf22c3df0acc6f432e616d1151062a61f456faa8ae2fe670b427868af55bb564802df84c8fd76e90b4b338750dbc23f46ad88
This commit is contained in:
parent
d6ca9a78ef
commit
f55da3aa54
@ -138,13 +138,13 @@ class BlockchainTest(BitcoinTestFramework):
|
|||||||
self.nodes[0].generate(6)
|
self.nodes[0].generate(6)
|
||||||
assert_equal(self.nodes[0].getblockcount(), 206)
|
assert_equal(self.nodes[0].getblockcount(), 206)
|
||||||
self.log.debug('Node should not stop at this height')
|
self.log.debug('Node should not stop at this height')
|
||||||
assert_raises(subprocess.TimeoutExpired, lambda: self.bitcoind_processes[0].wait(timeout=3))
|
assert_raises(subprocess.TimeoutExpired, lambda: self.nodes[0].process.wait(timeout=3))
|
||||||
try:
|
try:
|
||||||
self.nodes[0].generate(1)
|
self.nodes[0].generate(1)
|
||||||
except (ConnectionError, http.client.BadStatusLine):
|
except (ConnectionError, http.client.BadStatusLine):
|
||||||
pass # The node already shut down before response
|
pass # The node already shut down before response
|
||||||
self.log.debug('Node should stop at this height...')
|
self.log.debug('Node should stop at this height...')
|
||||||
self.bitcoind_processes[0].wait(timeout=BITCOIND_PROC_WAIT_TIMEOUT)
|
self.nodes[0].process.wait(timeout=BITCOIND_PROC_WAIT_TIMEOUT)
|
||||||
self.nodes[0] = self.start_node(0, self.options.tmpdir)
|
self.nodes[0] = self.start_node(0, self.options.tmpdir)
|
||||||
assert_equal(self.nodes[0].getblockcount(), 207)
|
assert_equal(self.nodes[0].getblockcount(), 207)
|
||||||
|
|
||||||
|
@ -452,8 +452,7 @@ class RawTransactionsTest(BitcoinTestFramework):
|
|||||||
self.stop_node(0)
|
self.stop_node(0)
|
||||||
self.stop_node(2)
|
self.stop_node(2)
|
||||||
self.stop_node(3)
|
self.stop_node(3)
|
||||||
self.nodes[1].encryptwallet("test")
|
self.nodes[1].node_encrypt_wallet("test")
|
||||||
self.bitcoind_processes[1].wait(timeout=BITCOIND_PROC_WAIT_TIMEOUT)
|
|
||||||
|
|
||||||
self.nodes = self.start_nodes(self.num_nodes, self.options.tmpdir, [['-usehd=0']] * self.num_nodes)
|
self.nodes = self.start_nodes(self.num_nodes, self.options.tmpdir, [['-usehd=0']] * self.num_nodes)
|
||||||
# This test is not meant to test fee estimation and we'd like
|
# This test is not meant to test fee estimation and we'd like
|
||||||
|
@ -17,7 +17,7 @@ class LongpollThread(threading.Thread):
|
|||||||
self.longpollid = templat['longpollid']
|
self.longpollid = templat['longpollid']
|
||||||
# create a new connection to the node, we can't use the same
|
# create a new connection to the node, we can't use the same
|
||||||
# connection from two threads
|
# connection from two threads
|
||||||
self.node = get_rpc_proxy(node.url, 1, timeout=600)
|
self.node = get_rpc_proxy(node.url, 1, timeout=600, coveragedir=node.coverage_dir)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.node.getblocktemplate({'longpollid':self.longpollid})
|
self.node.getblocktemplate({'longpollid':self.longpollid})
|
||||||
|
@ -18,8 +18,7 @@ class KeyPoolTest(BitcoinTestFramework):
|
|||||||
nodes = self.nodes
|
nodes = self.nodes
|
||||||
|
|
||||||
# Encrypt wallet and wait to terminate
|
# Encrypt wallet and wait to terminate
|
||||||
nodes[0].encryptwallet('test')
|
nodes[0].node_encrypt_wallet('test')
|
||||||
self.bitcoind_processes[0].wait()
|
|
||||||
# Restart node 0
|
# Restart node 0
|
||||||
nodes[0] = self.start_node(0, self.options.tmpdir, ['-usehd=0'])
|
nodes[0] = self.start_node(0, self.options.tmpdir, ['-usehd=0'])
|
||||||
# Keep creating keys
|
# Keep creating keys
|
||||||
|
@ -35,11 +35,15 @@ class MultiWalletTest(BitcoinTestFramework):
|
|||||||
|
|
||||||
self.nodes[0] = self.start_node(0, self.options.tmpdir, self.extra_args[0])
|
self.nodes[0] = self.start_node(0, self.options.tmpdir, self.extra_args[0])
|
||||||
|
|
||||||
w1 = self.nodes[0] / "wallet/w1"
|
w1 = self.nodes[0].get_wallet_rpc("w1")
|
||||||
|
w2 = self.nodes[0].get_wallet_rpc("w2")
|
||||||
|
w3 = self.nodes[0].get_wallet_rpc("w3")
|
||||||
|
wallet_bad = self.nodes[0].get_wallet_rpc("bad")
|
||||||
|
|
||||||
w1.generate(1)
|
w1.generate(1)
|
||||||
|
|
||||||
# accessing invalid wallet fails
|
# accessing invalid wallet fails
|
||||||
assert_raises_jsonrpc(-18, "Requested wallet does not exist or is not loaded", (self.nodes[0] / "wallet/bad").getwalletinfo)
|
assert_raises_jsonrpc(-18, "Requested wallet does not exist or is not loaded", wallet_bad.getwalletinfo)
|
||||||
|
|
||||||
# accessing wallet RPC without using wallet endpoint fails
|
# accessing wallet RPC without using wallet endpoint fails
|
||||||
assert_raises_jsonrpc(-19, "Wallet file not specified", self.nodes[0].getwalletinfo)
|
assert_raises_jsonrpc(-19, "Wallet file not specified", self.nodes[0].getwalletinfo)
|
||||||
@ -50,14 +54,12 @@ class MultiWalletTest(BitcoinTestFramework):
|
|||||||
w1_name = w1_info['walletname']
|
w1_name = w1_info['walletname']
|
||||||
assert_equal(w1_name, "w1")
|
assert_equal(w1_name, "w1")
|
||||||
|
|
||||||
# check w1 wallet balance
|
# check w2 wallet balance
|
||||||
w2 = self.nodes[0] / "wallet/w2"
|
|
||||||
w2_info = w2.getwalletinfo()
|
w2_info = w2.getwalletinfo()
|
||||||
assert_equal(w2_info['immature_balance'], 0)
|
assert_equal(w2_info['immature_balance'], 0)
|
||||||
w2_name = w2_info['walletname']
|
w2_name = w2_info['walletname']
|
||||||
assert_equal(w2_name, "w2")
|
assert_equal(w2_name, "w2")
|
||||||
|
|
||||||
w3 = self.nodes[0] / "wallet/w3"
|
|
||||||
w3_name = w3.getwalletinfo()['walletname']
|
w3_name = w3.getwalletinfo()['walletname']
|
||||||
assert_equal(w3_name, "w3")
|
assert_equal(w3_name, "w3")
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ class RPCBindTest(BitcoinTestFramework):
|
|||||||
base_args += ['-rpcallowip=' + x for x in allow_ips]
|
base_args += ['-rpcallowip=' + x for x in allow_ips]
|
||||||
binds = ['-rpcbind='+addr for addr in addresses]
|
binds = ['-rpcbind='+addr for addr in addresses]
|
||||||
self.nodes = self.start_nodes(self.num_nodes, self.options.tmpdir, [base_args + binds], connect_to)
|
self.nodes = self.start_nodes(self.num_nodes, self.options.tmpdir, [base_args + binds], connect_to)
|
||||||
pid = self.bitcoind_processes[0].pid
|
pid = self.nodes[0].process.pid
|
||||||
assert_equal(set(get_bind_addrs(pid)), set(expected))
|
assert_equal(set(get_bind_addrs(pid)), set(expected))
|
||||||
self.stop_nodes()
|
self.stop_nodes()
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ class RPCBindTest(BitcoinTestFramework):
|
|||||||
base_args = ['-disablewallet', '-nolisten'] + ['-rpcallowip='+x for x in allow_ips]
|
base_args = ['-disablewallet', '-nolisten'] + ['-rpcallowip='+x for x in allow_ips]
|
||||||
self.nodes = self.start_nodes(self.num_nodes, self.options.tmpdir, [base_args])
|
self.nodes = self.start_nodes(self.num_nodes, self.options.tmpdir, [base_args])
|
||||||
# connect to node through non-loopback interface
|
# connect to node through non-loopback interface
|
||||||
node = get_rpc_proxy(rpc_url(get_datadir_path(self.options.tmpdir, 0), 0, "%s:%d" % (rpchost, rpcport)), 0)
|
node = get_rpc_proxy(rpc_url(get_datadir_path(self.options.tmpdir, 0), 0, "%s:%d" % (rpchost, rpcport)), 0, coveragedir=self.options.coveragedir)
|
||||||
node.getnetworkinfo()
|
node.getnetworkinfo()
|
||||||
self.stop_nodes()
|
self.stop_nodes()
|
||||||
|
|
||||||
|
@ -6,15 +6,12 @@
|
|||||||
"""Base class for RPC testing."""
|
"""Base class for RPC testing."""
|
||||||
|
|
||||||
from collections import deque
|
from collections import deque
|
||||||
import errno
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
import http.client
|
|
||||||
import logging
|
import logging
|
||||||
import optparse
|
import optparse
|
||||||
import os
|
import os
|
||||||
import pdb
|
import pdb
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
@ -23,6 +20,7 @@ from concurrent.futures import ThreadPoolExecutor
|
|||||||
|
|
||||||
from .authproxy import JSONRPCException
|
from .authproxy import JSONRPCException
|
||||||
from . import coverage
|
from . import coverage
|
||||||
|
from .test_node import TestNode
|
||||||
from .util import (
|
from .util import (
|
||||||
PortSeed,
|
PortSeed,
|
||||||
MAX_NODES,
|
MAX_NODES,
|
||||||
@ -32,12 +30,9 @@ from .util import (
|
|||||||
connect_nodes,
|
connect_nodes,
|
||||||
copy_datadir,
|
copy_datadir,
|
||||||
disconnect_nodes,
|
disconnect_nodes,
|
||||||
get_rpc_proxy,
|
|
||||||
initialize_datadir,
|
initialize_datadir,
|
||||||
get_datadir_path,
|
|
||||||
log_filename,
|
log_filename,
|
||||||
p2p_port,
|
p2p_port,
|
||||||
rpc_url,
|
|
||||||
set_node_times,
|
set_node_times,
|
||||||
satoshi_round,
|
satoshi_round,
|
||||||
sync_blocks,
|
sync_blocks,
|
||||||
@ -78,7 +73,6 @@ class BitcoinTestFramework(object):
|
|||||||
self.num_nodes = 4
|
self.num_nodes = 4
|
||||||
self.setup_clean_chain = False
|
self.setup_clean_chain = False
|
||||||
self.nodes = []
|
self.nodes = []
|
||||||
self.bitcoind_processes = {}
|
|
||||||
self.mocktime = 0
|
self.mocktime = 0
|
||||||
|
|
||||||
def add_options(self, parser):
|
def add_options(self, parser):
|
||||||
@ -227,68 +221,64 @@ class BitcoinTestFramework(object):
|
|||||||
def start_node(self, i, dirname, extra_args=None, rpchost=None, timewait=None, binary=None, stderr=None):
|
def start_node(self, i, dirname, extra_args=None, rpchost=None, timewait=None, binary=None, stderr=None):
|
||||||
"""Start a dashd and return RPC connection to it"""
|
"""Start a dashd and return RPC connection to it"""
|
||||||
|
|
||||||
datadir = os.path.join(dirname, "node" + str(i))
|
if extra_args is None:
|
||||||
|
extra_args = []
|
||||||
if binary is None:
|
if binary is None:
|
||||||
binary = os.getenv("BITCOIND", "dashd")
|
binary = os.getenv("BITCOIND", "dashd")
|
||||||
args = [binary, "-datadir=" + datadir, "-server", "-keypool=1", "-discover=0", "-rest", "-logtimemicros", "-debug", "-debugexclude=libevent", "-debugexclude=leveldb", "-mocktime=" + str(self.mocktime), "-uacomment=testnode%d" % i]
|
|
||||||
# Don't try auto backups (they fail a lot when running tests)
|
# Don't try auto backups (they fail a lot when running tests)
|
||||||
args += [ "-createwalletbackups=0" ]
|
extra_args = extra_args + [ "-createwalletbackups=0" ]
|
||||||
if extra_args is not None:
|
node = TestNode(i, dirname, extra_args, rpchost, timewait, binary, stderr, self.mocktime, coverage_dir=self.options.coveragedir)
|
||||||
args.extend(extra_args)
|
node.start()
|
||||||
self.bitcoind_processes[i] = subprocess.Popen(args, stderr=stderr)
|
node.wait_for_rpc_connection()
|
||||||
self.log.debug("initialize_chain: dashd started, waiting for RPC to come up")
|
|
||||||
self._wait_for_bitcoind_start(self.bitcoind_processes[i], datadir, i, rpchost)
|
|
||||||
self.log.debug("initialize_chain: RPC successfully started")
|
|
||||||
proxy = get_rpc_proxy(rpc_url(datadir, i, rpchost), i, timeout=timewait)
|
|
||||||
|
|
||||||
if self.options.coveragedir:
|
if self.options.coveragedir is not None:
|
||||||
coverage.write_all_rpc_commands(self.options.coveragedir, proxy)
|
coverage.write_all_rpc_commands(self.options.coveragedir, node.rpc)
|
||||||
|
|
||||||
return proxy
|
return node
|
||||||
|
|
||||||
def start_nodes(self, num_nodes, dirname, extra_args=None, rpchost=None, timewait=None, binary=None, stderr=None):
|
def start_nodes(self, num_nodes, dirname, extra_args=None, rpchost=None, timewait=None, binary=None, stderr=None):
|
||||||
"""Start multiple dashds, return RPC connections to them"""
|
"""Start multiple dashds, return RPC connections to them"""
|
||||||
|
|
||||||
if extra_args is None:
|
if extra_args is None:
|
||||||
extra_args = [None] * num_nodes
|
extra_args = [[]] * num_nodes
|
||||||
if binary is None:
|
if binary is None:
|
||||||
binary = [None] * num_nodes
|
binary = [None] * num_nodes
|
||||||
assert_equal(len(extra_args), num_nodes)
|
assert_equal(len(extra_args), num_nodes)
|
||||||
assert_equal(len(binary), num_nodes)
|
assert_equal(len(binary), num_nodes)
|
||||||
rpcs = []
|
nodes = []
|
||||||
try:
|
try:
|
||||||
for i in range(num_nodes):
|
for i in range(num_nodes):
|
||||||
rpcs.append(self.start_node(i, dirname, extra_args[i], rpchost, timewait=timewait, binary=binary[i], stderr=stderr))
|
nodes.append(TestNode(i, dirname, extra_args[i], rpchost, timewait=timewait, binary=binary[i], stderr=stderr, mocktime=self.mocktime, coverage_dir=self.options.coveragedir))
|
||||||
|
nodes[i].start()
|
||||||
|
for node in nodes:
|
||||||
|
node.wait_for_rpc_connection()
|
||||||
except:
|
except:
|
||||||
# If one node failed to start, stop the others
|
# If one node failed to start, stop the others
|
||||||
# TODO: abusing self.nodes in this way is a little hacky.
|
|
||||||
# Eventually we should do a better job of tracking nodes
|
|
||||||
self.nodes.extend(rpcs)
|
|
||||||
self.stop_nodes()
|
self.stop_nodes()
|
||||||
self.nodes = []
|
|
||||||
raise
|
raise
|
||||||
return rpcs
|
|
||||||
|
if self.options.coveragedir is not None:
|
||||||
|
for node in nodes:
|
||||||
|
coverage.write_all_rpc_commands(self.options.coveragedir, node.rpc)
|
||||||
|
|
||||||
|
return nodes
|
||||||
|
|
||||||
def stop_node(self, i, wait=True):
|
def stop_node(self, i, wait=True):
|
||||||
"""Stop a dashd test node"""
|
"""Stop a dashd test node"""
|
||||||
|
self.nodes[i].stop_node()
|
||||||
self.log.debug("Stopping node %d" % i)
|
while not self.nodes[i].is_node_stopped():
|
||||||
try:
|
time.sleep(0.1)
|
||||||
self.nodes[i].stop()
|
|
||||||
except http.client.CannotSendRequest as e:
|
|
||||||
self.log.exception("Unable to stop node")
|
|
||||||
if wait:
|
|
||||||
self.wait_node(i)
|
|
||||||
|
|
||||||
def stop_nodes(self, fast=True):
|
def stop_nodes(self, fast=True):
|
||||||
"""Stop multiple dashd test nodes"""
|
"""Stop multiple dashd test nodes"""
|
||||||
|
for node in self.nodes:
|
||||||
|
# Issue RPC to stop nodes
|
||||||
|
node.stop_node()
|
||||||
|
|
||||||
for i in range(len(self.nodes)):
|
for node in self.nodes:
|
||||||
self.stop_node(i, not fast)
|
# Wait for nodes to stop
|
||||||
if fast:
|
while not node.is_node_stopped():
|
||||||
for i in range(len(self.nodes)):
|
time.sleep(0.1)
|
||||||
self.wait_node(i)
|
|
||||||
assert not self.bitcoind_processes.values() # All connections must be gone now
|
|
||||||
|
|
||||||
def assert_start_raises_init_error(self, i, dirname, extra_args=None, expected_msg=None):
|
def assert_start_raises_init_error(self, i, dirname, extra_args=None, expected_msg=None):
|
||||||
with tempfile.SpooledTemporaryFile(max_size=2**16) as log_stderr:
|
with tempfile.SpooledTemporaryFile(max_size=2**16) as log_stderr:
|
||||||
@ -297,6 +287,8 @@ class BitcoinTestFramework(object):
|
|||||||
self.stop_node(i)
|
self.stop_node(i)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
assert 'dashd exited' in str(e) # node must have shutdown
|
assert 'dashd exited' in str(e) # node must have shutdown
|
||||||
|
self.nodes[i].running = False
|
||||||
|
self.nodes[i].process = None
|
||||||
if expected_msg is not None:
|
if expected_msg is not None:
|
||||||
log_stderr.seek(0)
|
log_stderr.seek(0)
|
||||||
stderr = log_stderr.read().decode('utf-8')
|
stderr = log_stderr.read().decode('utf-8')
|
||||||
@ -315,7 +307,7 @@ class BitcoinTestFramework(object):
|
|||||||
assert_equal(return_code, 0)
|
assert_equal(return_code, 0)
|
||||||
|
|
||||||
def wait_for_node_exit(self, i, timeout):
|
def wait_for_node_exit(self, i, timeout):
|
||||||
self.bitcoind_processes[i].wait(timeout)
|
self.nodes[i].process.wait(timeout)
|
||||||
|
|
||||||
def split_network(self):
|
def split_network(self):
|
||||||
"""
|
"""
|
||||||
@ -415,18 +407,13 @@ class BitcoinTestFramework(object):
|
|||||||
args.append("-connect=127.0.0.1:" + str(p2p_port(0)))
|
args.append("-connect=127.0.0.1:" + str(p2p_port(0)))
|
||||||
if extra_args is not None:
|
if extra_args is not None:
|
||||||
args.extend(extra_args)
|
args.extend(extra_args)
|
||||||
self.bitcoind_processes[i] = subprocess.Popen(args, stderr=stderr)
|
self.nodes.append(TestNode(i, cachedir, extra_args=[], rpchost=None, timewait=None, binary=None, stderr=stderr, mocktime=self.mocktime, coverage_dir=None))
|
||||||
self.log.debug("initialize_chain: dashd started, waiting for RPC to come up")
|
self.nodes[i].args = args
|
||||||
self._wait_for_bitcoind_start(self.bitcoind_processes[i], datadir, i)
|
self.nodes[i].start()
|
||||||
self.log.debug("initialize_chain: RPC successfully started")
|
|
||||||
|
|
||||||
self.nodes = []
|
# Wait for RPC connections to be ready
|
||||||
for i in range(MAX_NODES):
|
for node in self.nodes:
|
||||||
try:
|
node.wait_for_rpc_connection()
|
||||||
self.nodes.append(get_rpc_proxy(rpc_url(get_datadir_path(cachedir, i), i), i))
|
|
||||||
except:
|
|
||||||
self.log.exception("Error connecting to node %d" % i)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Create a 200-block-long chain; each of the 4 first nodes
|
# Create a 200-block-long chain; each of the 4 first nodes
|
||||||
# gets 25 mature blocks and 25 immature.
|
# gets 25 mature blocks and 25 immature.
|
||||||
@ -469,30 +456,6 @@ class BitcoinTestFramework(object):
|
|||||||
for i in range(num_nodes):
|
for i in range(num_nodes):
|
||||||
initialize_datadir(test_dir, i)
|
initialize_datadir(test_dir, i)
|
||||||
|
|
||||||
def _wait_for_bitcoind_start(self, process, datadir, i, rpchost=None):
|
|
||||||
"""Wait for dashd to start.
|
|
||||||
|
|
||||||
This means that RPC is accessible and fully initialized.
|
|
||||||
Raise an exception if dashd exits during initialization."""
|
|
||||||
while True:
|
|
||||||
if process.poll() is not None:
|
|
||||||
raise Exception('dashd exited with status %i during initialization' % process.returncode)
|
|
||||||
try:
|
|
||||||
# Check if .cookie file to be created
|
|
||||||
rpc = get_rpc_proxy(rpc_url(datadir, i, rpchost), i, coveragedir=self.options.coveragedir)
|
|
||||||
rpc.getblockcount()
|
|
||||||
break # break out of loop on success
|
|
||||||
except IOError as e:
|
|
||||||
if e.errno != errno.ECONNREFUSED: # Port not yet open?
|
|
||||||
raise # unknown IO error
|
|
||||||
except JSONRPCException as e: # Initialization phase
|
|
||||||
if e.error['code'] != -28: # RPC in warmup?
|
|
||||||
raise # unknown JSON RPC exception
|
|
||||||
except ValueError as e: # cookie file not found and no rpcuser or rpcassword. dashd still starting
|
|
||||||
if "No RPC credentials" not in str(e):
|
|
||||||
raise
|
|
||||||
time.sleep(0.25)
|
|
||||||
|
|
||||||
MASTERNODE_COLLATERAL = 1000
|
MASTERNODE_COLLATERAL = 1000
|
||||||
|
|
||||||
|
|
||||||
|
134
test/functional/test_framework/test_node.py
Executable file
134
test/functional/test_framework/test_node.py
Executable file
@ -0,0 +1,134 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# Copyright (c) 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.
|
||||||
|
"""Class for bitcoind node under test"""
|
||||||
|
|
||||||
|
import errno
|
||||||
|
import http.client
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
|
||||||
|
from .util import (
|
||||||
|
assert_equal,
|
||||||
|
get_rpc_proxy,
|
||||||
|
rpc_url,
|
||||||
|
)
|
||||||
|
from .authproxy import JSONRPCException
|
||||||
|
|
||||||
|
class TestNode():
|
||||||
|
"""A class for representing a bitcoind node under test.
|
||||||
|
|
||||||
|
This class contains:
|
||||||
|
|
||||||
|
- state about the node (whether it's running, etc)
|
||||||
|
- a Python subprocess.Popen object representing the running process
|
||||||
|
- an RPC connection to the node
|
||||||
|
|
||||||
|
To make things easier for the test writer, a bit of magic is happening under the covers.
|
||||||
|
Any unrecognised messages will be dispatched to the RPC connection."""
|
||||||
|
|
||||||
|
def __init__(self, i, dirname, extra_args, rpchost, timewait, binary, stderr, mocktime, coverage_dir):
|
||||||
|
self.index = i
|
||||||
|
self.datadir = os.path.join(dirname, "node" + str(i))
|
||||||
|
self.rpchost = rpchost
|
||||||
|
self.rpc_timeout = timewait
|
||||||
|
if binary is None:
|
||||||
|
self.binary = os.getenv("BITCOIND", "bitcoind")
|
||||||
|
else:
|
||||||
|
self.binary = binary
|
||||||
|
self.stderr = stderr
|
||||||
|
self.coverage_dir = coverage_dir
|
||||||
|
# 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.
|
||||||
|
self.extra_args = extra_args
|
||||||
|
self.args = [self.binary, "-datadir=" + self.datadir, "-server", "-keypool=1", "-discover=0", "-rest", "-logtimemicros", "-debug", "-debugexclude=libevent", "-debugexclude=leveldb", "-mocktime=" + str(mocktime), "-uacomment=testnode%d" % i]
|
||||||
|
|
||||||
|
self.running = False
|
||||||
|
self.process = None
|
||||||
|
self.rpc_connected = False
|
||||||
|
self.rpc = None
|
||||||
|
self.url = None
|
||||||
|
self.log = logging.getLogger('TestFramework.node%d' % i)
|
||||||
|
|
||||||
|
def __getattr__(self, *args, **kwargs):
|
||||||
|
"""Dispatches any unrecognised messages to the RPC connection."""
|
||||||
|
assert self.rpc_connected and self.rpc is not None, "Error: no RPC connection"
|
||||||
|
return self.rpc.__getattr__(*args, **kwargs)
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
"""Start the node."""
|
||||||
|
self.process = subprocess.Popen(self.args + self.extra_args, stderr=self.stderr)
|
||||||
|
self.running = True
|
||||||
|
self.log.debug("bitcoind started, waiting for RPC to come up")
|
||||||
|
|
||||||
|
def wait_for_rpc_connection(self):
|
||||||
|
"""Sets up an RPC connection to the bitcoind process. Returns False if unable to connect."""
|
||||||
|
|
||||||
|
# Wait for up to 10 seconds for the RPC server to respond
|
||||||
|
for _ in range(40):
|
||||||
|
assert not self.process.poll(), "bitcoind exited with status %i during initialization" % self.process.returncode
|
||||||
|
try:
|
||||||
|
self.rpc = get_rpc_proxy(rpc_url(self.datadir, self.index, self.rpchost), self.index, coveragedir=self.coverage_dir)
|
||||||
|
self.rpc.getblockcount()
|
||||||
|
# If the call to getblockcount() succeeds then the RPC connection is up
|
||||||
|
self.rpc_connected = True
|
||||||
|
self.url = self.rpc.url
|
||||||
|
self.log.debug("RPC successfully started")
|
||||||
|
return
|
||||||
|
except IOError as e:
|
||||||
|
if e.errno != errno.ECONNREFUSED: # Port not yet open?
|
||||||
|
raise # unknown IO error
|
||||||
|
except JSONRPCException as e: # Initialization phase
|
||||||
|
if e.error['code'] != -28: # RPC in warmup?
|
||||||
|
raise # unknown JSON RPC exception
|
||||||
|
except ValueError as e: # cookie file not found and no rpcuser or rpcassword. bitcoind still starting
|
||||||
|
if "No RPC credentials" not in str(e):
|
||||||
|
raise
|
||||||
|
time.sleep(0.25)
|
||||||
|
raise AssertionError("Unable to connect to bitcoind")
|
||||||
|
|
||||||
|
def get_wallet_rpc(self, wallet_name):
|
||||||
|
assert self.rpc_connected
|
||||||
|
assert self.rpc
|
||||||
|
wallet_path = "wallet/%s" % wallet_name
|
||||||
|
return self.rpc / wallet_path
|
||||||
|
|
||||||
|
def stop_node(self):
|
||||||
|
"""Stop the node."""
|
||||||
|
if not self.running:
|
||||||
|
return
|
||||||
|
self.log.debug("Stopping node")
|
||||||
|
try:
|
||||||
|
self.stop()
|
||||||
|
except http.client.CannotSendRequest:
|
||||||
|
self.log.exception("Unable to stop node.")
|
||||||
|
|
||||||
|
def is_node_stopped(self):
|
||||||
|
"""Checks whether the node has stopped.
|
||||||
|
|
||||||
|
Returns True if the node has stopped. False otherwise.
|
||||||
|
This method is responsible for freeing resources (self.process)."""
|
||||||
|
if not self.running:
|
||||||
|
return True
|
||||||
|
return_code = self.process.poll()
|
||||||
|
if return_code is not None:
|
||||||
|
# process has stopped. Assert that it didn't return an error code.
|
||||||
|
assert_equal(return_code, 0)
|
||||||
|
self.running = False
|
||||||
|
self.process = None
|
||||||
|
self.log.debug("Node stopped")
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def node_encrypt_wallet(self, passphrase):
|
||||||
|
""""Encrypts the wallet.
|
||||||
|
|
||||||
|
This causes bitcoind to shutdown, so this method takes
|
||||||
|
care of cleaning up resources."""
|
||||||
|
self.encryptwallet(passphrase)
|
||||||
|
while not self.is_node_stopped():
|
||||||
|
time.sleep(0.1)
|
||||||
|
self.rpc = None
|
||||||
|
self.rpc_connected = False
|
@ -220,7 +220,7 @@ def rpc_port(n):
|
|||||||
return PORT_MIN + PORT_RANGE + n + (MAX_NODES * PortSeed.n) % (PORT_RANGE - 1 - MAX_NODES)
|
return PORT_MIN + PORT_RANGE + n + (MAX_NODES * PortSeed.n) % (PORT_RANGE - 1 - MAX_NODES)
|
||||||
|
|
||||||
def rpc_url(datadir, i, rpchost=None):
|
def rpc_url(datadir, i, rpchost=None):
|
||||||
rpc_u, rpc_p = get_auth_cookie(datadir, i)
|
rpc_u, rpc_p = get_auth_cookie(datadir)
|
||||||
host = '127.0.0.1'
|
host = '127.0.0.1'
|
||||||
port = rpc_port(i)
|
port = rpc_port(i)
|
||||||
if rpchost:
|
if rpchost:
|
||||||
@ -248,7 +248,7 @@ def initialize_datadir(dirname, n):
|
|||||||
def get_datadir_path(dirname, n):
|
def get_datadir_path(dirname, n):
|
||||||
return os.path.join(dirname, "node" + str(n))
|
return os.path.join(dirname, "node" + str(n))
|
||||||
|
|
||||||
def get_auth_cookie(datadir, n):
|
def get_auth_cookie(datadir):
|
||||||
user = None
|
user = None
|
||||||
password = None
|
password = None
|
||||||
if os.path.isfile(os.path.join(datadir, "dash.conf")):
|
if os.path.isfile(os.path.join(datadir, "dash.conf")):
|
||||||
|
@ -96,8 +96,7 @@ class WalletDumpTest(BitcoinTestFramework):
|
|||||||
assert_equal(found_addr_rsv, 180) # keypool size (external+internal)
|
assert_equal(found_addr_rsv, 180) # keypool size (external+internal)
|
||||||
|
|
||||||
#encrypt wallet, restart, unlock and dump
|
#encrypt wallet, restart, unlock and dump
|
||||||
self.nodes[0].encryptwallet('test')
|
self.nodes[0].node_encrypt_wallet('test')
|
||||||
self.bitcoind_processes[0].wait()
|
|
||||||
self.nodes[0] = self.start_node(0, tmpdir, self.extra_args[0])
|
self.nodes[0] = self.start_node(0, tmpdir, self.extra_args[0])
|
||||||
self.nodes[0].walletpassphrase('test', 10)
|
self.nodes[0].walletpassphrase('test', 10)
|
||||||
# Should be a no-op:
|
# Should be a no-op:
|
||||||
|
@ -30,8 +30,7 @@ class WalletEncryptionTest(BitcoinTestFramework):
|
|||||||
assert_equal(len(privkey), 52)
|
assert_equal(len(privkey), 52)
|
||||||
|
|
||||||
# Encrypt the wallet
|
# Encrypt the wallet
|
||||||
self.nodes[0].encryptwallet(passphrase)
|
self.nodes[0].node_encrypt_wallet(passphrase)
|
||||||
self.bitcoind_processes[0].wait(timeout=BITCOIND_PROC_WAIT_TIMEOUT)
|
|
||||||
self.nodes[0] = self.start_node(0, self.options.tmpdir)
|
self.nodes[0] = self.start_node(0, self.options.tmpdir)
|
||||||
|
|
||||||
# Test that the wallet is encrypted
|
# Test that the wallet is encrypted
|
||||||
|
Loading…
Reference in New Issue
Block a user