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