[tests] fix TestNode.__getattr__() method
[bitcoinplatinum.git] / test / functional / test_framework / test_node.py
blob41c31c2d3d4400e3cd20a7314d0c920c19669bc8
1 #!/usr/bin/env python3
2 # Copyright (c) 2017 The Bitcoin Core developers
3 # Distributed under the MIT software license, see the accompanying
4 # file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 """Class for bitcoind node under test"""
7 import decimal
8 import errno
9 import http.client
10 import json
11 import logging
12 import os
13 import subprocess
14 import time
16 from .util import (
17 assert_equal,
18 get_rpc_proxy,
19 rpc_url,
20 wait_until,
22 from .authproxy import JSONRPCException
24 BITCOIND_PROC_WAIT_TIMEOUT = 60
26 class TestNode():
27 """A class for representing a bitcoind node under test.
29 This class contains:
31 - state about the node (whether it's running, etc)
32 - a Python subprocess.Popen object representing the running process
33 - an RPC connection to the node
35 To make things easier for the test writer, a bit of magic is happening under the covers.
36 Any unrecognised messages will be dispatched to the RPC connection."""
38 def __init__(self, i, dirname, extra_args, rpchost, timewait, binary, stderr, mocktime, coverage_dir):
39 self.index = i
40 self.datadir = os.path.join(dirname, "node" + str(i))
41 self.rpchost = rpchost
42 if timewait:
43 self.rpc_timeout = timewait
44 else:
45 # Wait for up to 60 seconds for the RPC server to respond
46 self.rpc_timeout = 60
47 if binary is None:
48 self.binary = os.getenv("BITCOIND", "bitcoind")
49 else:
50 self.binary = binary
51 self.stderr = stderr
52 self.coverage_dir = coverage_dir
53 # 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.
54 self.extra_args = extra_args
55 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]
57 self.cli = TestNodeCLI(os.getenv("BITCOINCLI", "bitcoin-cli"), self.datadir)
59 self.running = False
60 self.process = None
61 self.rpc_connected = False
62 self.rpc = None
63 self.url = None
64 self.log = logging.getLogger('TestFramework.node%d' % i)
66 def __getattr__(self, name):
67 """Dispatches any unrecognised messages to the RPC connection."""
68 assert self.rpc_connected and self.rpc is not None, "Error: no RPC connection"
69 return getattr(self.rpc, name)
71 def start(self, extra_args=None, stderr=None):
72 """Start the node."""
73 if extra_args is None:
74 extra_args = self.extra_args
75 if stderr is None:
76 stderr = self.stderr
77 self.process = subprocess.Popen(self.args + extra_args, stderr=stderr)
78 self.running = True
79 self.log.debug("bitcoind started, waiting for RPC to come up")
81 def wait_for_rpc_connection(self):
82 """Sets up an RPC connection to the bitcoind process. Returns False if unable to connect."""
83 # Poll at a rate of four times per second
84 poll_per_s = 4
85 for _ in range(poll_per_s * self.rpc_timeout):
86 assert self.process.poll() is None, "bitcoind exited with status %i during initialization" % self.process.returncode
87 try:
88 self.rpc = get_rpc_proxy(rpc_url(self.datadir, self.index, self.rpchost), self.index, timeout=self.rpc_timeout, coveragedir=self.coverage_dir)
89 self.rpc.getblockcount()
90 # If the call to getblockcount() succeeds then the RPC connection is up
91 self.rpc_connected = True
92 self.url = self.rpc.url
93 self.log.debug("RPC successfully started")
94 return
95 except IOError as e:
96 if e.errno != errno.ECONNREFUSED: # Port not yet open?
97 raise # unknown IO error
98 except JSONRPCException as e: # Initialization phase
99 if e.error['code'] != -28: # RPC in warmup?
100 raise # unknown JSON RPC exception
101 except ValueError as e: # cookie file not found and no rpcuser or rpcassword. bitcoind still starting
102 if "No RPC credentials" not in str(e):
103 raise
104 time.sleep(1.0 / poll_per_s)
105 raise AssertionError("Unable to connect to bitcoind")
107 def get_wallet_rpc(self, wallet_name):
108 assert self.rpc_connected
109 assert self.rpc
110 wallet_path = "wallet/%s" % wallet_name
111 return self.rpc / wallet_path
113 def stop_node(self):
114 """Stop the node."""
115 if not self.running:
116 return
117 self.log.debug("Stopping node")
118 try:
119 self.stop()
120 except http.client.CannotSendRequest:
121 self.log.exception("Unable to stop node.")
123 def is_node_stopped(self):
124 """Checks whether the node has stopped.
126 Returns True if the node has stopped. False otherwise.
127 This method is responsible for freeing resources (self.process)."""
128 if not self.running:
129 return True
130 return_code = self.process.poll()
131 if return_code is None:
132 return False
134 # process has stopped. Assert that it didn't return an error code.
135 assert_equal(return_code, 0)
136 self.running = False
137 self.process = None
138 self.rpc_connected = False
139 self.rpc = None
140 self.log.debug("Node stopped")
141 return True
143 def wait_until_stopped(self, timeout=BITCOIND_PROC_WAIT_TIMEOUT):
144 wait_until(self.is_node_stopped, timeout=timeout)
146 def node_encrypt_wallet(self, passphrase):
147 """"Encrypts the wallet.
149 This causes bitcoind to shutdown, so this method takes
150 care of cleaning up resources."""
151 self.encryptwallet(passphrase)
152 self.wait_until_stopped()
154 class TestNodeCLI():
155 """Interface to bitcoin-cli for an individual node"""
157 def __init__(self, binary, datadir):
158 self.args = []
159 self.binary = binary
160 self.datadir = datadir
161 self.input = None
163 def __call__(self, *args, input=None):
164 # TestNodeCLI is callable with bitcoin-cli command-line args
165 self.args = [str(arg) for arg in args]
166 self.input = input
167 return self
169 def __getattr__(self, command):
170 def dispatcher(*args, **kwargs):
171 return self.send_cli(command, *args, **kwargs)
172 return dispatcher
174 def send_cli(self, command, *args, **kwargs):
175 """Run bitcoin-cli command. Deserializes returned string as python object."""
177 pos_args = [str(arg) for arg in args]
178 named_args = [str(key) + "=" + str(value) for (key, value) in kwargs.items()]
179 assert not (pos_args and named_args), "Cannot use positional arguments and named arguments in the same bitcoin-cli call"
180 p_args = [self.binary, "-datadir=" + self.datadir] + self.args
181 if named_args:
182 p_args += ["-named"]
183 p_args += [command] + pos_args + named_args
184 process = subprocess.Popen(p_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
185 cli_stdout, cli_stderr = process.communicate(input=self.input)
186 returncode = process.poll()
187 if returncode:
188 # Ignore cli_stdout, raise with cli_stderr
189 raise subprocess.CalledProcessError(returncode, self.binary, output=cli_stderr)
190 return json.loads(cli_stdout, parse_float=decimal.Decimal)