Skip to content

Commit 645a7ec

Browse files
committed
Merge #11125: Add bitcoin-cli -stdin and -stdinrpcpass functional tests
29e1dfb [test] Add bitcoin-cli -stdin and -stdinrpcpass functional tests (João Barbosa) ce379b4 [test] Replace check_output with low level version (João Barbosa) 232e3e8 [test] Add assert_raises_process_error to assert process errors (João Barbosa) 5c18a84 [test] Add support for custom arguments to TestNodeCLI (João Barbosa) e127494 [test] Improve assert_raises_jsonrpc docstring (João Barbosa) 7696841 Fix style in -stdin and -stdinrpcpass handling (João Barbosa) Pull request description: This patch adds tests for `bitcoin-cli` options `-stdin` (#7550) and `-stdinrpcpass` #10997. Tree-SHA512: fd8133f44876f2b5b41dfd3762b1988598f6b7bf13fb2385ad95876825d9c0b2b896ce4ea6eeb21012158e1f276907f155d37bb967198b609d2d3dddbfa334c1
2 parents 66a5b41 + 29e1dfb commit 645a7ec

File tree

4 files changed

+65
-16
lines changed

4 files changed

+65
-16
lines changed

src/bitcoin-cli.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -296,19 +296,22 @@ int CommandLineRPC(int argc, char *argv[])
296296
}
297297
std::string rpcPass;
298298
if (gArgs.GetBoolArg("-stdinrpcpass", false)) {
299-
if(!std::getline(std::cin,rpcPass))
299+
if (!std::getline(std::cin, rpcPass)) {
300300
throw std::runtime_error("-stdinrpcpass specified but failed to read from standard input");
301+
}
301302
gArgs.ForceSetArg("-rpcpassword", rpcPass);
302303
}
303304
std::vector<std::string> args = std::vector<std::string>(&argv[1], &argv[argc]);
304305
if (gArgs.GetBoolArg("-stdin", false)) {
305306
// Read one arg per line from stdin and append
306307
std::string line;
307-
while (std::getline(std::cin,line))
308+
while (std::getline(std::cin, line)) {
308309
args.push_back(line);
310+
}
309311
}
310-
if (args.size() < 1)
312+
if (args.size() < 1) {
311313
throw std::runtime_error("too few parameters (need at least command)");
314+
}
312315
std::string strMethod = args[0];
313316
args.erase(args.begin()); // Remove trailing method name from arguments vector
314317

test/functional/bitcoin_cli.py

Lines changed: 16 additions & 8 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 bitcoin-cli"""
66
from test_framework.test_framework import BitcoinTestFramework
7-
from test_framework.util import assert_equal
7+
from test_framework.util import assert_equal, assert_raises_process_error, get_auth_cookie
88

99
class TestBitcoinCli(BitcoinTestFramework):
1010

@@ -16,16 +16,24 @@ def run_test(self):
1616
"""Main test logic"""
1717

1818
self.log.info("Compare responses from gewalletinfo RPC and `bitcoin-cli getwalletinfo`")
19-
cli_get_info = self.nodes[0].cli.getwalletinfo()
20-
rpc_get_info = self.nodes[0].getwalletinfo()
21-
22-
assert_equal(cli_get_info, rpc_get_info)
19+
cli_response = self.nodes[0].cli.getwalletinfo()
20+
rpc_response = self.nodes[0].getwalletinfo()
21+
assert_equal(cli_response, rpc_response)
2322

2423
self.log.info("Compare responses from getblockchaininfo RPC and `bitcoin-cli getblockchaininfo`")
25-
cli_get_info = self.nodes[0].cli.getblockchaininfo()
26-
rpc_get_info = self.nodes[0].getblockchaininfo()
24+
cli_response = self.nodes[0].cli.getblockchaininfo()
25+
rpc_response = self.nodes[0].getblockchaininfo()
26+
assert_equal(cli_response, rpc_response)
27+
28+
user, password = get_auth_cookie(self.nodes[0].datadir)
29+
30+
self.log.info("Test -stdinrpcpass option")
31+
assert_equal(0, self.nodes[0].cli('-rpcuser=%s' % user, '-stdinrpcpass', input=password).getblockcount())
32+
assert_raises_process_error(1, "incorrect rpcuser or rpcpassword", self.nodes[0].cli('-rpcuser=%s' % user, '-stdinrpcpass', input="foo").echo)
2733

28-
assert_equal(cli_get_info, rpc_get_info)
34+
self.log.info("Test -stdin and -stdinrpcpass")
35+
assert_equal(["foo", "bar"], self.nodes[0].cli('-rpcuser=%s' % user, '-stdin', '-stdinrpcpass', input=password + "\nfoo\nbar").echo())
36+
assert_raises_process_error(1, "incorrect rpcuser or rpcpassword", self.nodes[0].cli('-rpcuser=%s' % user, '-stdin', '-stdinrpcpass', input="foo").echo)
2937

3038
if __name__ == '__main__':
3139
TestBitcoinCli().main()

test/functional/test_framework/test_node.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,16 @@ class TestNodeCLI():
155155
"""Interface to bitcoin-cli for an individual node"""
156156

157157
def __init__(self, binary, datadir):
158+
self.args = []
158159
self.binary = binary
159160
self.datadir = datadir
161+
self.input = None
162+
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
160168

161169
def __getattr__(self, command):
162170
def dispatcher(*args, **kwargs):
@@ -169,9 +177,14 @@ def send_cli(self, command, *args, **kwargs):
169177
pos_args = [str(arg) for arg in args]
170178
named_args = [str(key) + "=" + str(value) for (key, value) in kwargs.items()]
171179
assert not (pos_args and named_args), "Cannot use positional arguments and named arguments in the same bitcoin-cli call"
172-
p_args = [self.binary, "-datadir=" + self.datadir]
180+
p_args = [self.binary, "-datadir=" + self.datadir] + self.args
173181
if named_args:
174182
p_args += ["-named"]
175183
p_args += [command] + pos_args + named_args
176-
cli_output = subprocess.check_output(p_args, universal_newlines=True)
177-
return json.loads(cli_output, parse_float=decimal.Decimal)
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)

test/functional/test_framework/util.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import os
1313
import random
1414
import re
15+
from subprocess import CalledProcessError
1516
import time
1617

1718
from . import coverage
@@ -57,18 +58,42 @@ def assert_raises_message(exc, message, fun, *args, **kwds):
5758
else:
5859
raise AssertionError("No exception raised")
5960

61+
def assert_raises_process_error(returncode, output, fun, *args, **kwds):
62+
"""Execute a process and asserts the process return code and output.
63+
64+
Calls function `fun` with arguments `args` and `kwds`. Catches a CalledProcessError
65+
and verifies that the return code and output are as expected. Throws AssertionError if
66+
no CalledProcessError was raised or if the return code and output are not as expected.
67+
68+
Args:
69+
returncode (int): the process return code.
70+
output (string): [a substring of] the process output.
71+
fun (function): the function to call. This should execute a process.
72+
args*: positional arguments for the function.
73+
kwds**: named arguments for the function.
74+
"""
75+
try:
76+
fun(*args, **kwds)
77+
except CalledProcessError as e:
78+
if returncode != e.returncode:
79+
raise AssertionError("Unexpected returncode %i" % e.returncode)
80+
if output not in e.output:
81+
raise AssertionError("Expected substring not found:" + e.output)
82+
else:
83+
raise AssertionError("No exception raised")
84+
6085
def assert_raises_jsonrpc(code, message, fun, *args, **kwds):
6186
"""Run an RPC and verify that a specific JSONRPC exception code and message is raised.
6287
6388
Calls function `fun` with arguments `args` and `kwds`. Catches a JSONRPCException
6489
and verifies that the error code and message are as expected. Throws AssertionError if
65-
no JSONRPCException was returned or if the error code/message are not as expected.
90+
no JSONRPCException was raised or if the error code/message are not as expected.
6691
6792
Args:
6893
code (int), optional: the error code returned by the RPC call (defined
6994
in src/rpc/protocol.h). Set to None if checking the error code is not required.
7095
message (string), optional: [a substring of] the error string returned by the
71-
RPC call. Set to None if checking the error string is not required
96+
RPC call. Set to None if checking the error string is not required.
7297
fun (function): the function to call. This should be the name of an RPC.
7398
args*: positional arguments for the function.
7499
kwds**: named arguments for the function.

0 commit comments

Comments
 (0)