| #!/usr/bin/env python3 |
| # Copyright 2020 The Pigweed Authors |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); you may not |
| # use this file except in compliance with the License. You may obtain a copy of |
| # the License at |
| # |
| # https://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| # License for the specific language governing permissions and limitations under |
| # the License. |
| """Launch a pw_target_runner server to use for multi-device testing.""" |
| |
| import argparse |
| import logging |
| import sys |
| import tempfile |
| from typing import IO, List, Optional |
| |
| import pw_cli.process |
| |
| import pw_arduino_build.log |
| from pw_arduino_build import teensy_detector |
| from pw_arduino_build.file_operations import decode_file_json |
| from pw_arduino_build.unit_test_runner import ArduinoCoreNotSupported |
| |
| _LOG = logging.getLogger('unit_test_server') |
| |
| _TEST_RUNNER_COMMAND = 'arduino_unit_test_runner' |
| |
| _TEST_SERVER_COMMAND = 'pw_target_runner_server' |
| |
| |
| class UnknownArduinoCore(Exception): |
| """Exception raised when no Arduino core can be found.""" |
| |
| |
| def parse_args(): |
| """Parses command-line arguments.""" |
| |
| parser = argparse.ArgumentParser(description=__doc__) |
| parser.add_argument('--server-port', |
| type=int, |
| default=8081, |
| help='Port to launch the pw_target_runner_server on') |
| parser.add_argument('--server-config', |
| type=argparse.FileType('r'), |
| help='Path to server config file') |
| parser.add_argument('--verbose', |
| '-v', |
| dest='verbose', |
| action="store_true", |
| help='Output additional logs as the script runs') |
| parser.add_argument("-c", |
| "--config-file", |
| required=True, |
| help="Path to an arduino_builder config file.") |
| # TODO(tonymd): Explicitly split args using "--". See example in: |
| # //pw_unit_test/py/pw_unit_test/test_runner.py:326 |
| parser.add_argument('runner_args', |
| nargs=argparse.REMAINDER, |
| help='Arguments to forward to the test runner') |
| |
| return parser.parse_args() |
| |
| |
| def generate_runner(command: str, arguments: List[str]) -> str: |
| """Generates a text-proto style pw_target_runner_server configuration.""" |
| # TODO(amontanez): Use a real proto library to generate this when we have |
| # one set up. |
| for i, arg in enumerate(arguments): |
| arguments[i] = f' args: "{arg}"' |
| runner = ['runner {', f' command:"{command}"'] |
| runner.extend(arguments) |
| runner.append('}\n') |
| return '\n'.join(runner) |
| |
| |
| def generate_server_config(runner_args: Optional[List[str]], |
| arduino_package_path: str) -> IO[bytes]: |
| """Returns a temporary generated file for use as the server config.""" |
| |
| if "teensy" not in arduino_package_path: |
| raise ArduinoCoreNotSupported(arduino_package_path) |
| |
| boards = teensy_detector.detect_boards(arduino_package_path) |
| if not boards: |
| _LOG.critical('No attached boards detected') |
| sys.exit(1) |
| config_file = tempfile.NamedTemporaryFile() |
| _LOG.debug('Generating test server config at %s', config_file.name) |
| _LOG.debug('Found %d attached devices', len(boards)) |
| for board in boards: |
| test_runner_args = [] |
| if runner_args: |
| test_runner_args += runner_args |
| test_runner_args += ["-v"] + board.test_runner_args() |
| test_runner_args += ["--port", board.dev_name] |
| test_runner_args += ["--upload-tool", board.arduino_upload_tool_name] |
| config_file.write( |
| generate_runner(_TEST_RUNNER_COMMAND, |
| test_runner_args).encode('utf-8')) |
| config_file.flush() |
| return config_file |
| |
| |
| def launch_server(server_config: Optional[IO[bytes]], |
| server_port: Optional[int], runner_args: Optional[List[str]], |
| arduino_package_path: str) -> int: |
| """Launch a device test server with the provided arguments.""" |
| if server_config is None: |
| # Auto-detect attached boards if no config is provided. |
| server_config = generate_server_config(runner_args, |
| arduino_package_path) |
| |
| cmd = [_TEST_SERVER_COMMAND, '-config', server_config.name] |
| |
| if server_port is not None: |
| cmd.extend(['-port', str(server_port)]) |
| |
| return pw_cli.process.run(*cmd, log_output=True).returncode |
| |
| |
| def main(): |
| """Launch a device test server with the provided arguments.""" |
| args = parse_args() |
| |
| if "--" in args.runner_args: |
| args.runner_args.remove("--") |
| |
| log_level = logging.DEBUG if args.verbose else logging.INFO |
| pw_arduino_build.log.install(log_level) |
| |
| # Get arduino_package_path from either the config file or command line args. |
| arduino_package_path = None |
| if args.config_file: |
| json_file_options, unused_config_path = decode_file_json( |
| args.config_file) |
| arduino_package_path = json_file_options.get("arduino_package_path", |
| None) |
| # Must pass --config-file option in the runner_args. |
| if "--config-file" not in args.runner_args: |
| args.runner_args.append("--config-file") |
| args.runner_args.append(args.config_file) |
| |
| # Check for arduino_package_path in the runner_args |
| try: |
| arduino_package_path = args.runner_args[ |
| args.runner_args.index("--arduino-package-path") + 1] |
| except (ValueError, IndexError): |
| # Only raise an error if arduino_package_path not set from the json. |
| if arduino_package_path is None: |
| raise UnknownArduinoCore("Test runner arguments: '{}'".format( |
| " ".join(args.runner_args))) |
| |
| exit_code = launch_server(args.server_config, args.server_port, |
| args.runner_args, arduino_package_path) |
| sys.exit(exit_code) |
| |
| |
| if __name__ == '__main__': |
| main() |