blob: 947b2ae8ee34cd8f8bddcfc4c1698a83da62fa17 [file] [log] [blame]
#!/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()