1# Copyright 2021 The Pigweed Authors 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); you may not 4# use this file except in compliance with the License. You may obtain a copy of 5# the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12# License for the specific language governing permissions and limitations under 13# the License. 14"""Utilities for testing pw_rpc.""" 15 16import argparse 17import shlex 18import subprocess 19import sys 20import tempfile 21import time 22from typing import Sequence 23 24TEMP_DIR_MARKER = '(pw_rpc:CREATE_TEMP_DIR)' 25 26 27def parse_test_server_args( 28 parser: argparse.ArgumentParser | None = None, 29) -> argparse.Namespace: 30 """Parses arguments for running a Python-based integration test.""" 31 if parser is None: 32 parser = argparse.ArgumentParser( 33 description=sys.modules['__main__'].__doc__ 34 ) 35 36 parser.add_argument( 37 '--test-server-command', 38 nargs='+', 39 required=True, 40 help='Command that starts the test server.', 41 ) 42 parser.add_argument( 43 '--port', 44 type=int, 45 required=True, 46 help=( 47 'The port to use to connect to the test server. This value is ' 48 'passed to the test server as the last argument.' 49 ), 50 ) 51 parser.add_argument( 52 'unittest_args', 53 nargs=argparse.REMAINDER, 54 help='Arguments after "--" are passed to unittest.', 55 ) 56 57 args = parser.parse_args() 58 59 # Append the port number to the test server command. 60 args.test_server_command.append(str(args.port)) 61 62 # Make the script name argv[0] and drop the "--". 63 args.unittest_args = sys.argv[:1] + args.unittest_args[1:] 64 65 return args 66 67 68def _parse_subprocess_integration_test_args() -> argparse.Namespace: 69 parser = argparse.ArgumentParser( 70 description='Executes a test between two subprocesses' 71 ) 72 parser.add_argument( 73 '--client', 74 required=True, 75 help=( 76 'Client command to run. ' 77 'Use quotes and whitespace to pass client-specifc arguments.' 78 ), 79 ) 80 parser.add_argument( 81 '--server', 82 required=True, 83 help=( 84 'Server command to run. ' 85 'Use quotes and whitespace to pass client-specifc arguments.' 86 ), 87 ) 88 parser.add_argument( 89 'common_args', 90 metavar='-- ...', 91 nargs=argparse.REMAINDER, 92 help=( 93 'Arguments to pass to both the server and client; ' 94 f'pass {TEMP_DIR_MARKER} to generate a temporary directory' 95 ), 96 ) 97 98 args = parser.parse_args() 99 100 if not args.common_args or args.common_args[0] != '--': 101 parser.error('The common arguments must start with "--"') 102 103 args.common_args.pop(0) 104 105 return args 106 107 108def execute_integration_test( 109 server: str, 110 client: str, 111 common_args: Sequence[str], 112 setup_time_s: float = 0.2, 113) -> int: 114 """Runs an RPC server and client as part of an integration test.""" 115 temp_dir: tempfile.TemporaryDirectory | None = None 116 117 if TEMP_DIR_MARKER in common_args: 118 temp_dir = tempfile.TemporaryDirectory(prefix='pw_rpc_test_') 119 common_args = [ 120 temp_dir.name if a == TEMP_DIR_MARKER else a for a in common_args 121 ] 122 123 try: 124 server_cmdline = shlex.split(server) 125 client_cmdline = shlex.split(client) 126 if common_args: 127 server_cmdline += [*common_args] 128 client_cmdline += [*common_args] 129 130 server_process = subprocess.Popen(server_cmdline) 131 # TODO: b/234879791 - Replace this delay with some sort of IPC. 132 time.sleep(setup_time_s) 133 134 result = subprocess.run(client_cmdline).returncode 135 136 server_process.terminate() 137 server_process.communicate() 138 finally: 139 if temp_dir: 140 temp_dir.cleanup() 141 142 return result 143 144 145if __name__ == '__main__': 146 sys.exit( 147 execute_integration_test( 148 **vars(_parse_subprocess_integration_test_args()) 149 ) 150 ) 151