Add basic coverage reporting for RPC tests
Thanks to @MarcoFalke @dexX7 @laanwj for review. Zcash: Includes all the backend changes, but excludes the --coverage flag added to rpc-tests.py as we have not yet migrated away from rpc-tests.sh.
This commit is contained in:
parent
8f863b2817
commit
d38a012970
|
@ -4,8 +4,7 @@
|
|||
# file COPYING or https://www.opensource.org/licenses/mit-license.php .
|
||||
|
||||
from test_framework.test_framework import BitcoinTestFramework
|
||||
from test_framework.authproxy import AuthServiceProxy
|
||||
from test_framework.util import random_transaction
|
||||
from test_framework.util import get_rpc_proxy, random_transaction
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
|
@ -40,7 +39,7 @@ class LongpollThread(threading.Thread):
|
|||
self.longpollid = templat['longpollid']
|
||||
# create a new connection to the node, we can't use the same
|
||||
# connection from two threads
|
||||
self.node = AuthServiceProxy(node.url, timeout=600)
|
||||
self.node = get_rpc_proxy(node.url, 1, timeout=600)
|
||||
|
||||
def run(self):
|
||||
self.node.getblocktemplate({'longpollid':self.longpollid})
|
||||
|
|
|
@ -7,10 +7,17 @@
|
|||
|
||||
# Dependency: python-bitcoinrpc
|
||||
|
||||
from test_framework.util import assert_equal, check_json_precision, \
|
||||
initialize_chain, start_nodes, stop_nodes, wait_bitcoinds, \
|
||||
bitcoind_processes, rpc_port
|
||||
from test_framework.authproxy import AuthServiceProxy
|
||||
from test_framework.util import (
|
||||
assert_equal,
|
||||
bitcoind_processes,
|
||||
check_json_precision,
|
||||
get_rpc_proxy,
|
||||
initialize_chain,
|
||||
rpc_port,
|
||||
start_nodes,
|
||||
stop_nodes,
|
||||
wait_bitcoinds,
|
||||
)
|
||||
from test_framework.netutil import addr_to_hex, get_bind_addrs, all_interfaces
|
||||
|
||||
import os
|
||||
|
@ -48,7 +55,7 @@ def run_allowip_test(tmpdir, allow_ips, rpchost, rpcport):
|
|||
try:
|
||||
# connect to node through non-loopback interface
|
||||
url = "http://rt:rt@%s:%d" % (rpchost, rpcport,)
|
||||
node = AuthServiceProxy(url)
|
||||
node = get_rpc_proxy(url, 1)
|
||||
node.getinfo()
|
||||
finally:
|
||||
node = None # make sure connection will be garbage collected and closed
|
||||
|
|
|
@ -62,7 +62,7 @@ class AuthServiceProxy():
|
|||
|
||||
def __init__(self, service_url, service_name=None, timeout=HTTP_TIMEOUT, connection=None):
|
||||
self.__service_url = service_url
|
||||
self.__service_name = service_name
|
||||
self._service_name = service_name
|
||||
self.__url = urlparse(service_url)
|
||||
(user, passwd) = (self.__url.username, self.__url.password)
|
||||
try:
|
||||
|
@ -93,8 +93,8 @@ class AuthServiceProxy():
|
|||
if name.startswith('__') and name.endswith('__'):
|
||||
# Python internal stuff
|
||||
raise AttributeError
|
||||
if self.__service_name is not None:
|
||||
name = "%s.%s" % (self.__service_name, name)
|
||||
if self._service_name is not None:
|
||||
name = "%s.%s" % (self._service_name, name)
|
||||
return AuthServiceProxy(self.__service_url, name, connection=self.__conn)
|
||||
|
||||
def _request(self, method, path, postdata):
|
||||
|
@ -126,10 +126,10 @@ class AuthServiceProxy():
|
|||
def __call__(self, *args):
|
||||
AuthServiceProxy.__id_count += 1
|
||||
|
||||
log.debug("-%s-> %s %s"%(AuthServiceProxy.__id_count, self.__service_name,
|
||||
log.debug("-%s-> %s %s"%(AuthServiceProxy.__id_count, self._service_name,
|
||||
json.dumps(args, default=EncodeDecimal)))
|
||||
postdata = json.dumps({'version': '1.1',
|
||||
'method': self.__service_name,
|
||||
'method': self._service_name,
|
||||
'params': args,
|
||||
'id': AuthServiceProxy.__id_count}, default=EncodeDecimal)
|
||||
response = self._request('POST', self.__url.path, postdata)
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
"""
|
||||
This module contains utilities for doing coverage analysis on the RPC
|
||||
interface.
|
||||
|
||||
It provides a way to track which RPC commands are exercised during
|
||||
testing.
|
||||
|
||||
"""
|
||||
import os
|
||||
|
||||
|
||||
REFERENCE_FILENAME = 'rpc_interface.txt'
|
||||
|
||||
|
||||
class AuthServiceProxyWrapper(object):
|
||||
"""
|
||||
An object that wraps AuthServiceProxy to record specific RPC calls.
|
||||
|
||||
"""
|
||||
def __init__(self, auth_service_proxy_instance, coverage_logfile=None):
|
||||
"""
|
||||
Kwargs:
|
||||
auth_service_proxy_instance (AuthServiceProxy): the instance
|
||||
being wrapped.
|
||||
coverage_logfile (str): if specified, write each service_name
|
||||
out to a file when called.
|
||||
|
||||
"""
|
||||
self.auth_service_proxy_instance = auth_service_proxy_instance
|
||||
self.coverage_logfile = coverage_logfile
|
||||
|
||||
def __getattr__(self, *args, **kwargs):
|
||||
return_val = self.auth_service_proxy_instance.__getattr__(
|
||||
*args, **kwargs)
|
||||
|
||||
return AuthServiceProxyWrapper(return_val, self.coverage_logfile)
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
"""
|
||||
Delegates to AuthServiceProxy, then writes the particular RPC method
|
||||
called to a file.
|
||||
|
||||
"""
|
||||
return_val = self.auth_service_proxy_instance.__call__(*args, **kwargs)
|
||||
rpc_method = self.auth_service_proxy_instance._service_name
|
||||
|
||||
if self.coverage_logfile:
|
||||
with open(self.coverage_logfile, 'a+') as f:
|
||||
f.write("%s\n" % rpc_method)
|
||||
|
||||
return return_val
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
return self.auth_service_proxy_instance.url
|
||||
|
||||
|
||||
def get_filename(dirname, n_node):
|
||||
"""
|
||||
Get a filename unique to the test process ID and node.
|
||||
|
||||
This file will contain a list of RPC commands covered.
|
||||
"""
|
||||
pid = str(os.getpid())
|
||||
return os.path.join(
|
||||
dirname, "coverage.pid%s.node%s.txt" % (pid, str(n_node)))
|
||||
|
||||
|
||||
def write_all_rpc_commands(dirname, node):
|
||||
"""
|
||||
Write out a list of all RPC functions available in `bitcoin-cli` for
|
||||
coverage comparison. This will only happen once per coverage
|
||||
directory.
|
||||
|
||||
Args:
|
||||
dirname (str): temporary test dir
|
||||
node (AuthServiceProxy): client
|
||||
|
||||
Returns:
|
||||
bool. if the RPC interface file was written.
|
||||
|
||||
"""
|
||||
filename = os.path.join(dirname, REFERENCE_FILENAME)
|
||||
|
||||
if os.path.isfile(filename):
|
||||
return False
|
||||
|
||||
help_output = node.help().split('\n')
|
||||
commands = set()
|
||||
|
||||
for line in help_output:
|
||||
line = line.strip()
|
||||
|
||||
# Ignore blanks and headers
|
||||
if line and not line.startswith('='):
|
||||
commands.add("%s\n" % line.split()[0])
|
||||
|
||||
with open(filename, 'w') as f:
|
||||
f.writelines(list(commands))
|
||||
|
||||
return True
|
|
@ -14,10 +14,19 @@ import tempfile
|
|||
import traceback
|
||||
|
||||
from .authproxy import JSONRPCException
|
||||
from .util import assert_equal, check_json_precision, \
|
||||
initialize_chain, initialize_chain_clean, \
|
||||
start_nodes, connect_nodes_bi, stop_nodes, \
|
||||
sync_blocks, sync_mempools, wait_bitcoinds
|
||||
from .util import (
|
||||
initialize_chain,
|
||||
assert_equal,
|
||||
start_nodes,
|
||||
connect_nodes_bi,
|
||||
sync_blocks,
|
||||
sync_mempools,
|
||||
stop_nodes,
|
||||
wait_bitcoinds,
|
||||
enable_coverage,
|
||||
check_json_precision,
|
||||
initialize_chain_clean,
|
||||
)
|
||||
|
||||
|
||||
class BitcoinTestFramework(object):
|
||||
|
@ -99,6 +108,8 @@ class BitcoinTestFramework(object):
|
|||
help="Root directory for datadirs")
|
||||
parser.add_option("--tracerpc", dest="trace_rpc", default=False, action="store_true",
|
||||
help="Print out all RPC calls as they are made")
|
||||
parser.add_option("--coveragedir", dest="coveragedir",
|
||||
help="Write tested RPC commands into this directory")
|
||||
self.add_options(parser)
|
||||
(self.options, self.args) = parser.parse_args()
|
||||
|
||||
|
@ -106,6 +117,9 @@ class BitcoinTestFramework(object):
|
|||
import logging
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
if self.options.coveragedir:
|
||||
enable_coverage(self.options.coveragedir)
|
||||
|
||||
os.environ['PATH'] = self.options.srcdir+":"+os.environ['PATH']
|
||||
|
||||
check_json_precision()
|
||||
|
@ -179,7 +193,8 @@ class ComparisonTestFramework(BitcoinTestFramework):
|
|||
initialize_chain_clean(self.options.tmpdir, self.num_nodes)
|
||||
|
||||
def setup_network(self):
|
||||
self.nodes = start_nodes(self.num_nodes, self.options.tmpdir,
|
||||
self.nodes = start_nodes(
|
||||
self.num_nodes, self.options.tmpdir,
|
||||
extra_args=[['-debug', '-whitelist=127.0.0.1']] * self.num_nodes,
|
||||
binary=[self.options.testbinary] +
|
||||
[self.options.refbinary]*(self.num_nodes-1))
|
||||
|
|
|
@ -21,11 +21,46 @@ import subprocess
|
|||
import time
|
||||
import re
|
||||
|
||||
from . import coverage
|
||||
from .authproxy import AuthServiceProxy
|
||||
|
||||
COVERAGE_DIR = None
|
||||
PRE_BLOSSOM_BLOCK_TARGET_SPACING = 150
|
||||
POST_BLOSSOM_BLOCK_TARGET_SPACING = 75
|
||||
|
||||
|
||||
def enable_coverage(dirname):
|
||||
"""Maintain a log of which RPC calls are made during testing."""
|
||||
global COVERAGE_DIR
|
||||
COVERAGE_DIR = dirname
|
||||
|
||||
|
||||
def get_rpc_proxy(url, node_number, timeout=None):
|
||||
"""
|
||||
Args:
|
||||
url (str): URL of the RPC server to call
|
||||
node_number (int): the node number (or id) that this calls to
|
||||
|
||||
Kwargs:
|
||||
timeout (int): HTTP timeout in seconds
|
||||
|
||||
Returns:
|
||||
AuthServiceProxy. convenience object for making RPC calls.
|
||||
|
||||
"""
|
||||
proxy_kwargs = {}
|
||||
if timeout is not None:
|
||||
proxy_kwargs['timeout'] = timeout
|
||||
|
||||
proxy = AuthServiceProxy(url, **proxy_kwargs)
|
||||
proxy.url = url # store URL on proxy for info
|
||||
|
||||
coverage_logfile = coverage.get_filename(
|
||||
COVERAGE_DIR, node_number) if COVERAGE_DIR else None
|
||||
|
||||
return coverage.AuthServiceProxyWrapper(proxy, coverage_logfile)
|
||||
|
||||
|
||||
def p2p_port(n):
|
||||
return 11000 + n + os.getpid()%999
|
||||
def rpc_port(n):
|
||||
|
@ -165,11 +200,13 @@ def initialize_chain(test_dir):
|
|||
if os.getenv("PYTHON_DEBUG", ""):
|
||||
print("initialize_chain: bitcoin-cli -rpcwait getblockcount completed")
|
||||
devnull.close()
|
||||
|
||||
rpcs = []
|
||||
|
||||
for i in range(4):
|
||||
try:
|
||||
url = "http://rt:rt@127.0.0.1:%d" % (rpc_port(i),)
|
||||
rpcs.append(AuthServiceProxy(url))
|
||||
rpcs.append(get_rpc_proxy(url, i))
|
||||
except:
|
||||
sys.stderr.write("Error connecting to "+url+"\n")
|
||||
sys.exit(1)
|
||||
|
@ -259,11 +296,12 @@ def start_node(i, dirname, extra_args=None, rpchost=None, timewait=None, binary=
|
|||
print("start_node: calling bitcoin-cli -rpcwait getblockcount returned")
|
||||
devnull.close()
|
||||
url = "http://rt:rt@%s:%d" % (rpchost or '127.0.0.1', rpc_port(i))
|
||||
if timewait is not None:
|
||||
proxy = AuthServiceProxy(url, timeout=timewait)
|
||||
else:
|
||||
proxy = AuthServiceProxy(url)
|
||||
proxy.url = url # store URL on proxy for info
|
||||
|
||||
proxy = get_rpc_proxy(url, i, timeout=timewait)
|
||||
|
||||
if COVERAGE_DIR:
|
||||
coverage.write_all_rpc_commands(COVERAGE_DIR, proxy)
|
||||
|
||||
return proxy
|
||||
|
||||
def start_nodes(num_nodes, dirname, extra_args=None, rpchost=None, binary=None):
|
||||
|
|
Loading…
Reference in New Issue