1#!/usr/bin/env python3 2# Copyright (c) 2015-2018 The Bitcoin Core developers 3# Distributed under the MIT software license, see the accompanying 4# file COPYING or http://www.opensource.org/licenses/mit-license.php. 5"""Utilities for doing coverage analysis on the RPC interface. 6 7Provides a way to track which RPC commands are exercised during 8testing. 9""" 10 11import os 12 13 14REFERENCE_FILENAME = 'rpc_interface.txt' 15 16 17class AuthServiceProxyWrapper(): 18 """ 19 An object that wraps AuthServiceProxy to record specific RPC calls. 20 21 """ 22 def __init__(self, auth_service_proxy_instance, coverage_logfile=None): 23 """ 24 Kwargs: 25 auth_service_proxy_instance (AuthServiceProxy): the instance 26 being wrapped. 27 coverage_logfile (str): if specified, write each service_name 28 out to a file when called. 29 30 """ 31 self.auth_service_proxy_instance = auth_service_proxy_instance 32 self.coverage_logfile = coverage_logfile 33 34 def __getattr__(self, name): 35 return_val = getattr(self.auth_service_proxy_instance, name) 36 if not isinstance(return_val, type(self.auth_service_proxy_instance)): 37 # If proxy getattr returned an unwrapped value, do the same here. 38 return return_val 39 return AuthServiceProxyWrapper(return_val, self.coverage_logfile) 40 41 def __call__(self, *args, **kwargs): 42 """ 43 Delegates to AuthServiceProxy, then writes the particular RPC method 44 called to a file. 45 46 """ 47 return_val = self.auth_service_proxy_instance.__call__(*args, **kwargs) 48 self._log_call() 49 return return_val 50 51 def _log_call(self): 52 rpc_method = self.auth_service_proxy_instance._service_name 53 54 if self.coverage_logfile: 55 with open(self.coverage_logfile, 'a+', encoding='utf8') as f: 56 f.write("%s\n" % rpc_method) 57 58 def __truediv__(self, relative_uri): 59 return AuthServiceProxyWrapper(self.auth_service_proxy_instance / relative_uri, 60 self.coverage_logfile) 61 62 def get_request(self, *args, **kwargs): 63 self._log_call() 64 return self.auth_service_proxy_instance.get_request(*args, **kwargs) 65 66def get_filename(dirname, n_node): 67 """ 68 Get a filename unique to the test process ID and node. 69 70 This file will contain a list of RPC commands covered. 71 """ 72 pid = str(os.getpid()) 73 return os.path.join( 74 dirname, "coverage.pid%s.node%s.txt" % (pid, str(n_node))) 75 76 77def write_all_rpc_commands(dirname, node): 78 """ 79 Write out a list of all RPC functions available in `bitcoin-cli` for 80 coverage comparison. This will only happen once per coverage 81 directory. 82 83 Args: 84 dirname (str): temporary test dir 85 node (AuthServiceProxy): client 86 87 Returns: 88 bool. if the RPC interface file was written. 89 90 """ 91 filename = os.path.join(dirname, REFERENCE_FILENAME) 92 93 if os.path.isfile(filename): 94 return False 95 96 help_output = node.help().split('\n') 97 commands = set() 98 99 for line in help_output: 100 line = line.strip() 101 102 # Ignore blanks and headers 103 if line and not line.startswith('='): 104 commands.add("%s\n" % line.split()[0]) 105 106 with open(filename, 'w', encoding='utf8') as f: 107 f.writelines(list(commands)) 108 109 return True 110