aboutsummaryrefslogtreecommitdiff
path: root/qa/rpc-tests
diff options
context:
space:
mode:
authorJames O'Beirne <james.obeirne@gmail.com>2015-10-10 22:41:19 -0700
committerJames O'Beirne <james.obeirne@gmail.com>2015-11-11 10:33:43 -0800
commitb5cbd396ca7214f4f944163ed314456038fdd818 (patch)
tree840fb2890c1b703c0b5d08d19a103003ae238eb4 /qa/rpc-tests
parent3038eb63e8a674b4818cb5d5e461f1ccf4b2932f (diff)
Add basic coverage reporting for RPC tests
Thanks to @MarcoFalke @dexX7 @laanwj for review.
Diffstat (limited to 'qa/rpc-tests')
-rwxr-xr-xqa/rpc-tests/getblocktemplate_longpoll.py2
-rwxr-xr-xqa/rpc-tests/rpcbind_test.py2
-rw-r--r--qa/rpc-tests/test_framework/authproxy.py10
-rw-r--r--qa/rpc-tests/test_framework/coverage.py101
-rwxr-xr-xqa/rpc-tests/test_framework/test_framework.py28
-rw-r--r--qa/rpc-tests/test_framework/util.py62
6 files changed, 181 insertions, 24 deletions
diff --git a/qa/rpc-tests/getblocktemplate_longpoll.py b/qa/rpc-tests/getblocktemplate_longpoll.py
index aab4562422..1ddff8a298 100755
--- a/qa/rpc-tests/getblocktemplate_longpoll.py
+++ b/qa/rpc-tests/getblocktemplate_longpoll.py
@@ -38,7 +38,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})
diff --git a/qa/rpc-tests/rpcbind_test.py b/qa/rpc-tests/rpcbind_test.py
index 04110c2831..7a9da66787 100755
--- a/qa/rpc-tests/rpcbind_test.py
+++ b/qa/rpc-tests/rpcbind_test.py
@@ -47,7 +47,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
diff --git a/qa/rpc-tests/test_framework/authproxy.py b/qa/rpc-tests/test_framework/authproxy.py
index 33014dc139..fba469a0dd 100644
--- a/qa/rpc-tests/test_framework/authproxy.py
+++ b/qa/rpc-tests/test_framework/authproxy.py
@@ -69,7 +69,7 @@ class AuthServiceProxy(object):
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.urlparse(service_url)
if self.__url.port is None:
port = 80
@@ -102,8 +102,8 @@ class AuthServiceProxy(object):
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):
@@ -129,10 +129,10 @@ class AuthServiceProxy(object):
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)
diff --git a/qa/rpc-tests/test_framework/coverage.py b/qa/rpc-tests/test_framework/coverage.py
new file mode 100644
index 0000000000..50f066a850
--- /dev/null
+++ b/qa/rpc-tests/test_framework/coverage.py
@@ -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
diff --git a/qa/rpc-tests/test_framework/test_framework.py b/qa/rpc-tests/test_framework/test_framework.py
index 5671431f6e..ae2d91ab60 100755
--- a/qa/rpc-tests/test_framework/test_framework.py
+++ b/qa/rpc-tests/test_framework/test_framework.py
@@ -13,8 +13,20 @@ import shutil
import tempfile
import traceback
+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,
+)
from authproxy import AuthServiceProxy, JSONRPCException
-from util import *
class BitcoinTestFramework(object):
@@ -96,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()
@@ -103,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()
@@ -173,7 +190,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,
- extra_args=[['-debug', '-whitelist=127.0.0.1']] * self.num_nodes,
- binary=[self.options.testbinary] +
- [self.options.refbinary]*(self.num_nodes-1))
+ 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))
diff --git a/qa/rpc-tests/test_framework/util.py b/qa/rpc-tests/test_framework/util.py
index 3759cc8162..30dd5de585 100644
--- a/qa/rpc-tests/test_framework/util.py
+++ b/qa/rpc-tests/test_framework/util.py
@@ -17,8 +17,43 @@ import subprocess
import time
import re
-from authproxy import AuthServiceProxy, JSONRPCException
-from util import *
+from . import coverage
+from .authproxy import AuthServiceProxy, JSONRPCException
+
+COVERAGE_DIR = None
+
+
+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
@@ -79,13 +114,13 @@ def initialize_chain(test_dir):
"""
if (not os.path.isdir(os.path.join("cache","node0"))
- or not os.path.isdir(os.path.join("cache","node1"))
- or not os.path.isdir(os.path.join("cache","node2"))
+ or not os.path.isdir(os.path.join("cache","node1"))
+ or not os.path.isdir(os.path.join("cache","node2"))
or not os.path.isdir(os.path.join("cache","node3"))):
#find and delete old cache directories if any exist
for i in range(4):
- if os.path.isdir(os.path.join("cache","node"+str(i))):
+ if os.path.isdir(os.path.join("cache","node"+str(i))):
shutil.rmtree(os.path.join("cache","node"+str(i)))
devnull = open(os.devnull, "w")
@@ -103,11 +138,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))
+ url = "http://rt:rt@127.0.0.1:%d" % (rpc_port(i),)
+ rpcs.append(get_rpc_proxy(url, i))
except:
sys.stderr.write("Error connecting to "+url+"\n")
sys.exit(1)
@@ -190,11 +227,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):