Changeset View
Changeset View
Standalone View
Standalone View
test/functional/test_framework/test_node.py
#!/usr/bin/env python3 | #!/usr/bin/env python3 | ||||
# Copyright (c) 2017 The Bitcoin Core developers | # Copyright (c) 2017 The Bitcoin Core developers | ||||
# Distributed under the MIT software license, see the accompanying | # Distributed under the MIT software license, see the accompanying | ||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php. | # file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||||
"""Class for bitcoind node under test""" | """Class for bitcoind node under test""" | ||||
import decimal | import decimal | ||||
import errno | import errno | ||||
import http.client | import http.client | ||||
import json | import json | ||||
import logging | import logging | ||||
import os | import os | ||||
import re | import re | ||||
import subprocess | import subprocess | ||||
import sys | import sys | ||||
import tempfile | |||||
import time | import time | ||||
from .authproxy import JSONRPCException | from .authproxy import JSONRPCException | ||||
from .messages import COIN, CTransaction, FromHex | from .messages import COIN, CTransaction, FromHex | ||||
from .util import ( | from .util import ( | ||||
append_config, | append_config, | ||||
assert_equal, | assert_equal, | ||||
delete_cookie_file, | delete_cookie_file, | ||||
get_rpc_proxy, | get_rpc_proxy, | ||||
p2p_port, | p2p_port, | ||||
rpc_url, | rpc_url, | ||||
wait_until, | wait_until, | ||||
) | ) | ||||
# For Python 3.4 compatibility | # For Python 3.4 compatibility | ||||
JSONDecodeError = getattr(json, "JSONDecodeError", ValueError) | JSONDecodeError = getattr(json, "JSONDecodeError", ValueError) | ||||
BITCOIND_PROC_WAIT_TIMEOUT = 60 | BITCOIND_PROC_WAIT_TIMEOUT = 60 | ||||
class FailedToStartError(Exception): | |||||
"""Raised when a node fails to start correctly.""" | |||||
class TestNode(): | class TestNode(): | ||||
"""A class for representing a bitcoind node under test. | """A class for representing a bitcoind node under test. | ||||
This class contains: | This class contains: | ||||
- state about the node (whether it's running, etc) | - state about the node (whether it's running, etc) | ||||
- a Python subprocess.Popen object representing the running process | - a Python subprocess.Popen object representing the running process | ||||
- an RPC connection to the node | - an RPC connection to the node | ||||
▲ Show 20 Lines • Show All 91 Lines • ▼ Show 20 Lines | def start(self, extra_args=None, stderr=None, *args, **kwargs): | ||||
self.running = True | self.running = True | ||||
self.log.debug("bitcoind started, waiting for RPC to come up") | self.log.debug("bitcoind started, waiting for RPC to come up") | ||||
def wait_for_rpc_connection(self): | def wait_for_rpc_connection(self): | ||||
"""Sets up an RPC connection to the bitcoind process. Returns False if unable to connect.""" | """Sets up an RPC connection to the bitcoind process. Returns False if unable to connect.""" | ||||
# Poll at a rate of four times per second | # Poll at a rate of four times per second | ||||
poll_per_s = 4 | poll_per_s = 4 | ||||
for _ in range(poll_per_s * self.rpc_timeout): | for _ in range(poll_per_s * self.rpc_timeout): | ||||
assert self.process.poll( | if self.process.poll() is not None: | ||||
) is None, "bitcoind exited with status {} during initialization".format(self.process.returncode) | raise FailedToStartError( | ||||
'bitcoind exited with status {} during initialization'.format(self.process.returncode)) | |||||
try: | try: | ||||
self.rpc = get_rpc_proxy(rpc_url(self.datadir, self.host, self.rpc_port), | self.rpc = get_rpc_proxy(rpc_url(self.datadir, self.host, self.rpc_port), | ||||
self.index, timeout=self.rpc_timeout, coveragedir=self.coverage_dir) | self.index, timeout=self.rpc_timeout, coveragedir=self.coverage_dir) | ||||
self.rpc.getblockcount() | self.rpc.getblockcount() | ||||
# If the call to getblockcount() succeeds then the RPC connection is up | # If the call to getblockcount() succeeds then the RPC connection is up | ||||
self.rpc_connected = True | self.rpc_connected = True | ||||
self.url = self.rpc.url | self.url = self.rpc.url | ||||
self.log.debug("RPC successfully started") | self.log.debug("RPC successfully started") | ||||
▲ Show 20 Lines • Show All 48 Lines • ▼ Show 20 Lines | def is_node_stopped(self): | ||||
self.rpc_connected = False | self.rpc_connected = False | ||||
self.rpc = None | self.rpc = None | ||||
self.log.debug("Node stopped") | self.log.debug("Node stopped") | ||||
return True | return True | ||||
def wait_until_stopped(self, timeout=BITCOIND_PROC_WAIT_TIMEOUT): | def wait_until_stopped(self, timeout=BITCOIND_PROC_WAIT_TIMEOUT): | ||||
wait_until(self.is_node_stopped, timeout=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_until_stopped() | |||||
except FailedToStartError as e: | |||||
self.log.debug('bitcoind failed to start: {}'.format(e)) | |||||
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): | def node_encrypt_wallet(self, passphrase): | ||||
""""Encrypts the wallet. | """"Encrypts the wallet. | ||||
This causes bitcoind to shutdown, so this method takes | This causes bitcoind to shutdown, so this method takes | ||||
care of cleaning up resources.""" | care of cleaning up resources.""" | ||||
self.encryptwallet(passphrase) | self.encryptwallet(passphrase) | ||||
self.wait_until_stopped() | self.wait_until_stopped() | ||||
▲ Show 20 Lines • Show All 124 Lines • Show Last 20 Lines |