From 769684132985cd67cbe8c7629a917aba6a333688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Barbosa?= Date: Thu, 24 Aug 2017 18:35:06 +0100 Subject: [PATCH 1/6] Fix style in -stdin and -stdinrpcpass handling --- src/bitcoin-cli.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index fca6083ea..3c94c99b3 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -296,19 +296,22 @@ int CommandLineRPC(int argc, char *argv[]) } std::string rpcPass; if (gArgs.GetBoolArg("-stdinrpcpass", false)) { - if(!std::getline(std::cin,rpcPass)) + if (!std::getline(std::cin, rpcPass)) { throw std::runtime_error("-stdinrpcpass specified but failed to read from standard input"); + } gArgs.ForceSetArg("-rpcpassword", rpcPass); } std::vector args = std::vector(&argv[1], &argv[argc]); if (gArgs.GetBoolArg("-stdin", false)) { // Read one arg per line from stdin and append std::string line; - while (std::getline(std::cin,line)) + while (std::getline(std::cin, line)) { args.push_back(line); + } } - if (args.size() < 1) + if (args.size() < 1) { throw std::runtime_error("too few parameters (need at least command)"); + } std::string strMethod = args[0]; args.erase(args.begin()); // Remove trailing method name from arguments vector From e1274947d4574bb83a020d0e178f0e5db7fc6282 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Barbosa?= Date: Wed, 6 Sep 2017 16:52:25 +0100 Subject: [PATCH 2/6] [test] Improve assert_raises_jsonrpc docstring --- test/functional/test_framework/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index a14cda07d..a5bc495df 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -62,13 +62,13 @@ def assert_raises_jsonrpc(code, message, fun, *args, **kwds): Calls function `fun` with arguments `args` and `kwds`. Catches a JSONRPCException and verifies that the error code and message are as expected. Throws AssertionError if - no JSONRPCException was returned or if the error code/message are not as expected. + no JSONRPCException was raised or if the error code/message are not as expected. Args: code (int), optional: the error code returned by the RPC call (defined in src/rpc/protocol.h). Set to None if checking the error code is not required. message (string), optional: [a substring of] the error string returned by the - RPC call. Set to None if checking the error string is not required + RPC call. Set to None if checking the error string is not required. fun (function): the function to call. This should be the name of an RPC. args*: positional arguments for the function. kwds**: named arguments for the function. From 5c18a84b9a49e3f9dacf0502dbf7d5d755f38da6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Barbosa?= Date: Wed, 6 Sep 2017 17:07:21 +0100 Subject: [PATCH 3/6] [test] Add support for custom arguments to TestNodeCLI --- test/functional/test_framework/test_node.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index f58a372a1..363025740 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -155,8 +155,16 @@ class TestNodeCLI(): """Interface to bitcoin-cli for an individual node""" def __init__(self, binary, datadir): + self.args = [] self.binary = binary self.datadir = datadir + self.input = None + + def __call__(self, *args, input=None): + # TestNodeCLI is callable with bitcoin-cli command-line args + self.args = [str(arg) for arg in args] + self.input = input + return self def __getattr__(self, command): def dispatcher(*args, **kwargs): @@ -169,9 +177,9 @@ class TestNodeCLI(): pos_args = [str(arg) for arg in args] named_args = [str(key) + "=" + str(value) for (key, value) in kwargs.items()] assert not (pos_args and named_args), "Cannot use positional arguments and named arguments in the same bitcoin-cli call" - p_args = [self.binary, "-datadir=" + self.datadir] + p_args = [self.binary, "-datadir=" + self.datadir] + self.args if named_args: p_args += ["-named"] p_args += [command] + pos_args + named_args - cli_output = subprocess.check_output(p_args, universal_newlines=True) + cli_output = subprocess.check_output(p_args, input=self.input, universal_newlines=True) return json.loads(cli_output, parse_float=decimal.Decimal) From 232e3e8471edb346c09f906c996b2f350cabc72a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Barbosa?= Date: Wed, 6 Sep 2017 16:35:57 +0100 Subject: [PATCH 4/6] [test] Add assert_raises_process_error to assert process errors --- test/functional/test_framework/util.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/functional/test_framework/util.py b/test/functional/test_framework/util.py index a5bc495df..b2d8199d1 100644 --- a/test/functional/test_framework/util.py +++ b/test/functional/test_framework/util.py @@ -12,6 +12,7 @@ import logging import os import random import re +from subprocess import CalledProcessError import time from . import coverage @@ -57,6 +58,30 @@ def assert_raises_message(exc, message, fun, *args, **kwds): else: raise AssertionError("No exception raised") +def assert_raises_process_error(returncode, output, fun, *args, **kwds): + """Execute a process and asserts the process return code and output. + + Calls function `fun` with arguments `args` and `kwds`. Catches a CalledProcessError + and verifies that the return code and output are as expected. Throws AssertionError if + no CalledProcessError was raised or if the return code and output are not as expected. + + Args: + returncode (int): the process return code. + output (string): [a substring of] the process output. + fun (function): the function to call. This should execute a process. + args*: positional arguments for the function. + kwds**: named arguments for the function. + """ + try: + fun(*args, **kwds) + except CalledProcessError as e: + if returncode != e.returncode: + raise AssertionError("Unexpected returncode %i" % e.returncode) + if output not in e.output: + raise AssertionError("Expected substring not found:" + e.output) + else: + raise AssertionError("No exception raised") + def assert_raises_jsonrpc(code, message, fun, *args, **kwds): """Run an RPC and verify that a specific JSONRPC exception code and message is raised. From ce379b47b92d6c04250c21719bb1abfb41c586ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Barbosa?= Date: Wed, 6 Sep 2017 16:36:13 +0100 Subject: [PATCH 5/6] [test] Replace check_output with low level version --- test/functional/test_framework/test_node.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/test/functional/test_framework/test_node.py b/test/functional/test_framework/test_node.py index 363025740..12dab57a0 100755 --- a/test/functional/test_framework/test_node.py +++ b/test/functional/test_framework/test_node.py @@ -181,5 +181,10 @@ class TestNodeCLI(): if named_args: p_args += ["-named"] p_args += [command] + pos_args + named_args - cli_output = subprocess.check_output(p_args, input=self.input, universal_newlines=True) - return json.loads(cli_output, parse_float=decimal.Decimal) + process = subprocess.Popen(p_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True) + cli_stdout, cli_stderr = process.communicate(input=self.input) + returncode = process.poll() + if returncode: + # Ignore cli_stdout, raise with cli_stderr + raise subprocess.CalledProcessError(returncode, self.binary, output=cli_stderr) + return json.loads(cli_stdout, parse_float=decimal.Decimal) From 29e1dfbd9793203479b8499c55ffec2086f5ab39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Barbosa?= Date: Fri, 25 Aug 2017 03:31:27 +0100 Subject: [PATCH 6/6] [test] Add bitcoin-cli -stdin and -stdinrpcpass functional tests --- test/functional/bitcoin_cli.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/test/functional/bitcoin_cli.py b/test/functional/bitcoin_cli.py index 04847252e..ffed5b0d3 100755 --- a/test/functional/bitcoin_cli.py +++ b/test/functional/bitcoin_cli.py @@ -4,7 +4,7 @@ # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test bitcoin-cli""" from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import assert_equal +from test_framework.util import assert_equal, assert_raises_process_error, get_auth_cookie class TestBitcoinCli(BitcoinTestFramework): @@ -16,16 +16,24 @@ class TestBitcoinCli(BitcoinTestFramework): """Main test logic""" self.log.info("Compare responses from gewalletinfo RPC and `bitcoin-cli getwalletinfo`") - cli_get_info = self.nodes[0].cli.getwalletinfo() - rpc_get_info = self.nodes[0].getwalletinfo() - - assert_equal(cli_get_info, rpc_get_info) + cli_response = self.nodes[0].cli.getwalletinfo() + rpc_response = self.nodes[0].getwalletinfo() + assert_equal(cli_response, rpc_response) self.log.info("Compare responses from getblockchaininfo RPC and `bitcoin-cli getblockchaininfo`") - cli_get_info = self.nodes[0].cli.getblockchaininfo() - rpc_get_info = self.nodes[0].getblockchaininfo() + cli_response = self.nodes[0].cli.getblockchaininfo() + rpc_response = self.nodes[0].getblockchaininfo() + assert_equal(cli_response, rpc_response) + + user, password = get_auth_cookie(self.nodes[0].datadir) + + self.log.info("Test -stdinrpcpass option") + assert_equal(0, self.nodes[0].cli('-rpcuser=%s' % user, '-stdinrpcpass', input=password).getblockcount()) + assert_raises_process_error(1, "incorrect rpcuser or rpcpassword", self.nodes[0].cli('-rpcuser=%s' % user, '-stdinrpcpass', input="foo").echo) - assert_equal(cli_get_info, rpc_get_info) + self.log.info("Test -stdin and -stdinrpcpass") + assert_equal(["foo", "bar"], self.nodes[0].cli('-rpcuser=%s' % user, '-stdin', '-stdinrpcpass', input=password + "\nfoo\nbar").echo()) + assert_raises_process_error(1, "incorrect rpcuser or rpcpassword", self.nodes[0].cli('-rpcuser=%s' % user, '-stdin', '-stdinrpcpass', input="foo").echo) if __name__ == '__main__': TestBitcoinCli().main()