xref: /aosp_15_r20/external/pigweed/targets/stm32f429i_disc1/py/stm32f429i_disc1_utils/unit_test_server.py (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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_target_runner server to use for multi-device testing."""
16
17import argparse
18import logging
19import sys
20import tempfile
21from typing import IO
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(
40        '--server-port',
41        type=int,
42        default=8080,
43        help='Port to launch the pw_target_runner_server on',
44    )
45    parser.add_argument(
46        '--server-config',
47        type=argparse.FileType('r'),
48        help='Path to server config file',
49    )
50    parser.add_argument(
51        '--verbose',
52        '-v',
53        dest='verbose',
54        action="store_true",
55        help='Output additional logs as the script runs',
56    )
57
58    return parser.parse_args()
59
60
61def generate_runner(command: str, arguments: list[str]) -> str:
62    """Generates a text-proto style pw_target_runner_server configuration."""
63    # TODO(amontanez): Use a real proto library to generate this when we have
64    # one set up.
65    for i, arg in enumerate(arguments):
66        arguments[i] = f'  args: "{arg}"'
67    runner = ['runner {', f'  command:"{command}"']
68    runner.extend(arguments)
69    runner.append('}\n')
70    return '\n'.join(runner)
71
72
73def generate_server_config() -> IO[bytes]:
74    """Returns a temporary generated file for use as the server config."""
75    boards = stm32f429i_detector.detect_boards()
76    if not boards:
77        _LOG.critical('No attached boards detected')
78        sys.exit(1)
79    config_file = tempfile.NamedTemporaryFile()
80    _LOG.debug('Generating test server config at %s', config_file.name)
81    _LOG.debug('Found %d attached devices', len(boards))
82    for board in boards:
83        test_runner_args = [
84            '--stlink-serial',
85            board.serial_number,
86            '--port',
87            board.dev_name,
88        ]
89        config_file.write(
90            generate_runner(_TEST_RUNNER_COMMAND, test_runner_args).encode(
91                'utf-8'
92            )
93        )
94    config_file.flush()
95    return config_file
96
97
98def launch_server(
99    server_config: IO[bytes] | None, server_port: int | None
100) -> int:
101    """Launch a device test server with the provided arguments."""
102    if server_config is None:
103        # Auto-detect attached boards if no config is provided.
104        server_config = generate_server_config()
105
106    cmd = [_TEST_SERVER_COMMAND, '-config', server_config.name]
107
108    if server_port is not None:
109        cmd.extend(['-port', str(server_port)])
110
111    return pw_cli.process.run(*cmd, log_output=True).returncode
112
113
114def main():
115    """Launch a device test server with the provided arguments."""
116    args = parse_args()
117
118    # Try to use pw_cli logs, else default to something reasonable.
119    pw_cli.log.install()
120    if args.verbose:
121        _LOG.setLevel(logging.DEBUG)
122
123    exit_code = launch_server(args.server_config, args.server_port)
124    sys.exit(exit_code)
125
126
127if __name__ == '__main__':
128    main()
129