1#!/usr/bin/env python3 2# Copyright 2019 The Pigweed Authors 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); you may not 5# use this file except in compliance with the License. You may obtain a copy of 6# the License at 7# 8# https://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13# License for the specific language governing permissions and limitations under 14# the License. 15"""Launch a pw_test_server server to use for multi-device testing.""" 16 17import argparse 18import logging 19import sys 20import tempfile 21from typing import IO, List, Optional 22 23import pw_cli.process 24import pw_cli.log 25 26from stm32f429i_disc1_utils import stm32f429i_detector 27 28_LOG = logging.getLogger('unit_test_server') 29 30_TEST_RUNNER_COMMAND = 'stm32f429i_disc1_unit_test_runner' 31 32_TEST_SERVER_COMMAND = 'pw_target_runner_server' 33 34 35def parse_args(): 36 """Parses command-line arguments.""" 37 38 parser = argparse.ArgumentParser(description=__doc__) 39 parser.add_argument('--server-port', 40 type=int, 41 default=8080, 42 help='Port to launch the pw_target_runner_server on') 43 parser.add_argument('--server-config', 44 type=argparse.FileType('r'), 45 help='Path to server config file') 46 parser.add_argument('--verbose', 47 '-v', 48 dest='verbose', 49 action="store_true", 50 help='Output additional logs as the script runs') 51 52 return parser.parse_args() 53 54 55def generate_runner(command: str, arguments: List[str]) -> str: 56 """Generates a text-proto style pw_target_runner_server configuration.""" 57 # TODO(amontanez): Use a real proto library to generate this when we have 58 # one set up. 59 for i, arg in enumerate(arguments): 60 arguments[i] = f' args: "{arg}"' 61 runner = ['runner {', f' command:"{command}"'] 62 runner.extend(arguments) 63 runner.append('}\n') 64 return '\n'.join(runner) 65 66 67def generate_server_config() -> IO[bytes]: 68 """Returns a temporary generated file for use as the server config.""" 69 boards = stm32f429i_detector.detect_boards() 70 if not boards: 71 _LOG.critical('No attached boards detected') 72 sys.exit(1) 73 config_file = tempfile.NamedTemporaryFile() 74 _LOG.debug('Generating test server config at %s', config_file.name) 75 _LOG.debug('Found %d attached devices', len(boards)) 76 for board in boards: 77 test_runner_args = [ 78 '--stlink-serial', board.serial_number, '--port', board.dev_name 79 ] 80 config_file.write( 81 generate_runner(_TEST_RUNNER_COMMAND, 82 test_runner_args).encode('utf-8')) 83 config_file.flush() 84 return config_file 85 86 87def launch_server(server_config: Optional[IO[bytes]], 88 server_port: Optional[int]) -> int: 89 """Launch a device test server with the provided arguments.""" 90 if server_config is None: 91 # Auto-detect attached boards if no config is provided. 92 server_config = generate_server_config() 93 94 cmd = [_TEST_SERVER_COMMAND, '-config', server_config.name] 95 96 if server_port is not None: 97 cmd.extend(['-port', str(server_port)]) 98 99 return pw_cli.process.run(*cmd, log_output=True).returncode 100 101 102def main(): 103 """Launch a device test server with the provided arguments.""" 104 args = parse_args() 105 106 # Try to use pw_cli logs, else default to something reasonable. 107 pw_cli.log.install() 108 if args.verbose: 109 _LOG.setLevel(logging.DEBUG) 110 111 exit_code = launch_server(args.server_config, args.server_port) 112 sys.exit(exit_code) 113 114 115if __name__ == '__main__': 116 main() 117