| # Copyright 2025 The Fuchsia Authors. All rights reserved. | |
| # Use of this source code is governed by a BSD-style license that can be | |
| # found in the LICENSE file. | |
| """Entry point of the program. | |
| App ensures CTF tests are built, scans the available tests, | |
| and then hands off control to a UI. | |
| """ | |
| import argparse | |
| import os | |
| import sys | |
| import time | |
| from typing import Protocol | |
| import cli | |
| import command_runner | |
| import data_structure | |
| import util | |
| class UI(Protocol): | |
| """The program uses a UI to interact with the user.""" | |
| def inform(self, out: util.Outputs) -> None: | |
| """Outputs contains text the user may want to see.""" | |
| ... | |
| def test_list(self, tests: data_structure.Tests) -> None: | |
| """The UI should take over with the given tests.""" | |
| ... | |
| def bail(self, failure_message: str) -> None: | |
| """Something failed. Tell the user, and exit.""" | |
| ... | |
| class App: | |
| def __init__(self, argv: list[str]) -> None: | |
| args = self._parse_args(argv) | |
| if args.gui: | |
| try: | |
| import gui | |
| self.ui: UI = gui.GUI() | |
| except ImportError: | |
| if os.path.exists(__file__): | |
| script_path = __file__ | |
| elif os.path.isfile((dirname := os.path.dirname(__file__))): | |
| script_path = dirname | |
| else: | |
| script_path = None | |
| print( | |
| "ERROR: Failed to load GUI modules. Your Python installation may not have Tkinter installed.\n" | |
| ) | |
| if script_path: | |
| print( | |
| "If you are using fx, you can try running this script using your installed Python version:\n" | |
| f"$ python3 {script_path} --gui\n" | |
| ) | |
| else: | |
| print("Try reinstalling Python.\n") | |
| sys.exit(1) | |
| else: | |
| self.ui = cli.CLI() | |
| self.build_target = "//sdk/ctf/release:tests" | |
| self.scan_target = "//sdk/ctf/release" | |
| if args.tiny_scan: | |
| print( | |
| "Developer shortcut mode: only scanning fdio-spawn-tests-package" | |
| ) | |
| self.build_target = ( | |
| "//sdk/ctf/tests/pkg/fdio:fdio-spawn-tests-package" | |
| ) | |
| self.scan_target = self.build_target | |
| def _parse_args(self, argv: list[str]) -> argparse.Namespace: | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument( | |
| "--gui", | |
| help="Use a GUI", | |
| action="store_true", | |
| ) | |
| parser.add_argument( | |
| "--tiny-scan", | |
| help="Just scan a couple of tests, to save time when debugging", | |
| action="store_true", | |
| ) | |
| return parser.parse_args(argv) | |
| def run(self) -> None: | |
| """Main entry point.""" | |
| builder = command_runner.TargetBuilder(self.build_target) | |
| if self._run_command(builder) != util.Status.SUCCESS: | |
| self.ui.bail("Builder failed") | |
| scanner = command_runner.TestEnumerator(self.scan_target) | |
| if self._run_command(scanner) != util.Status.SUCCESS: | |
| self.ui.bail("Test scanner failed") | |
| merged_tests = data_structure.load_and_merge(scanner.tests) | |
| self.ui.test_list(merged_tests) | |
| def _run_command( | |
| self, command: command_runner.CommandRunner | |
| ) -> util.Status: | |
| while True: | |
| results = command.poll() | |
| self.ui.inform(results.outputs) | |
| if ( | |
| results.status == util.Status.FAILURE | |
| or results.status == util.Status.SUCCESS | |
| ): | |
| return results.status | |
| time.sleep(0.1) | |
| def main() -> int: | |
| app = App(sys.argv[1:]) | |
| app.run() | |
| return 0 | |
| if __name__ == "__main__": | |
| sys.exit(main()) |