Skip to content

Commit

Permalink
[util] add runCommandParseJSON
Browse files Browse the repository at this point in the history
  • Loading branch information
Sjors committed Feb 12, 2019
1 parent c7dbf3c commit e2f681b
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 0 deletions.
30 changes: 30 additions & 0 deletions src/rpc/misc.cpp
Expand Up @@ -364,6 +364,35 @@ static UniValue setmocktime(const JSONRPCRequest& request)
return NullUniValue;
}

static UniValue runcommand(const JSONRPCRequest& request)
{
if (request.fHelp || request.params.size() != 1)
throw std::runtime_error(
RPCHelpMan{"runcommand",
"\nRun command and parse JSON from stdout (-regtest only)\n",
{
{"command", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", "Command must return JSON\n"},
},
RPCResults{},
RPCExamples{""},
}.ToString()
);

if (!Params().MineBlocksOnDemand())
throw std::runtime_error("runcommand for regression testing (-regtest mode) only");

RPCTypeCheck(request.params, {UniValue::VSTR});

#ifdef WIN32
throw std::runtime_error("runcommand currently does not support Windows");
UniValue result = UniValue::VNULL;
#else
UniValue result = runCommandParseJSON(request.params[0].get_str());
#endif

return result;
}

static UniValue RPCLockedMemoryInfo()
{
LockedPool::Stats stats = LockedPoolManager::Instance().stats();
Expand Down Expand Up @@ -571,6 +600,7 @@ static const CRPCCommand commands[] =
{ "hidden", "setmocktime", &setmocktime, {"timestamp"}},
{ "hidden", "echo", &echo, {"arg0","arg1","arg2","arg3","arg4","arg5","arg6","arg7","arg8","arg9"}},
{ "hidden", "echojson", &echo, {"arg0","arg1","arg2","arg3","arg4","arg5","arg6","arg7","arg8","arg9"}},
{ "hidden", "runcommand", &runcommand, { "command"}},
};
// clang-format on

Expand Down
19 changes: 19 additions & 0 deletions src/util/system.cpp
Expand Up @@ -8,9 +8,12 @@
#include <chainparamsbase.h>
#include <random.h>
#include <serialize.h>
#include <univalue.h>
#include <util/strencodings.h>
#include <subprocess/subprocess.hpp>

#include <stdarg.h>
#include <array>

#if (defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__))
#include <pthread.h>
Expand Down Expand Up @@ -1145,6 +1148,22 @@ void runCommand(const std::string& strCommand)
LogPrintf("runCommand error: system(%s) returned %d\n", strCommand, nErr);
}

#ifndef WIN32
UniValue runCommandParseJSON(const std::string& strCommand)
{
UniValue resultJSON;

if (strCommand.empty()) return UniValue::VNULL;

auto obuf = subprocess::check_output({strCommand});
const std::string result(obuf.buf.data());

if (!resultJSON.read(result)) throw std::runtime_error("Unable to parse JSON: " + result);

return resultJSON;
}
#endif

void RenameThread(const char* name)
{
#if defined(PR_SET_NAME)
Expand Down
5 changes: 5 additions & 0 deletions src/util/system.h
Expand Up @@ -35,6 +35,8 @@

#include <boost/thread/condition_variable.hpp> // for boost::thread_interrupted

class UniValue;

// Application startup time (used for uptime calculation)
int64_t GetStartupTime();

Expand Down Expand Up @@ -93,6 +95,9 @@ void CreatePidFile(const fs::path &path, pid_t pid);
fs::path GetSpecialFolderPath(int nFolder, bool fCreate = true);
#endif
void runCommand(const std::string& strCommand);
#ifndef WIN32
UniValue runCommandParseJSON(const std::string& strCommand);
#endif

/**
* Most paths passed as configuration arguments are treated as relative to
Expand Down
33 changes: 33 additions & 0 deletions test/functional/interface_runcommand.py
@@ -0,0 +1,33 @@
#!/usr/bin/env python3
# Copyright (c) 2019 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
"""Tests runCommandParseJSON via the runcommand regtest RPC."""

import os

from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import assert_equal, assert_raises_rpc_error

class RunCommandInterfaceTest(BitcoinTestFramework):
def set_test_params(self):
self.num_nodes = 1
self.setup_clean_chain = True

def test_runcommand(self):
self.log.info("Testing runcommand...")
command_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'mocks/command.py')

res = self.nodes[0].runcommand(command_path + " --success")
assert_equal(res, {"success": True})

res = self.nodes[0].runcommand(command_path + " --fail")
assert_equal(res, {"success": False, "error": "reason"})

assert_raises_rpc_error(-1, "a", self.nodes[0].runcommand, command_path + " --invalid")

def run_test(self):
self.test_runcommand()

if __name__ == '__main__':
RunCommandInterfaceTest().main()
32 changes: 32 additions & 0 deletions test/functional/mocks/command.py
@@ -0,0 +1,32 @@
#!/usr/bin/env python3
# Copyright (c) 2019 The Bitcoin Core developers
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.

import argparse
import json
import sys

def command(args):
if args.invalid:
sys.stdout.write("{")
elif args.success:
sys.stdout.write(json.dumps({"success": True}))
elif args.fail:
sys.stdout.write(json.dumps({"success": False, "error": "reason"}))
else:
raise RuntimeError("Missing arguments")


parser = argparse.ArgumentParser(prog='./mock_command.py', description='Test runCommandParseJSON() via runcommand RPC')
parser.add_argument('--success', action='store_true', help='Respond with {success: true}')
parser.add_argument('--fail', action='store_true', help='Respond with {success: false, error: "reason"}')
parser.add_argument('--invalid', action='store_true', help='Return malformed JSON')
parser.set_defaults(func=command)

if len(sys.argv) == 1:
args = parser.parse_args(['-h'])
exit()

args = parser.parse_args()
args.func(args)
1 change: 1 addition & 0 deletions test/functional/test_runner.py
Expand Up @@ -120,6 +120,7 @@
'wallet_createwallet.py --usecli',
'interface_http.py',
'interface_rpc.py',
'interface_runcommand.py',
'rpc_psbt.py',
'rpc_users.py',
'feature_proxy.py',
Expand Down

0 comments on commit e2f681b

Please sign in to comment.