| #!/usr/bin/env python3 | |
| # Copyright lowRISC contributors. | |
| # Licensed under the Apache License, Version 2.0, see LICENSE for details. | |
| # SPDX-License-Identifier: Apache-2.0 | |
| """Runs, i.e. walks through, the test points of a manual test plan. | |
| This script prints the descriptions of each test point in a manual test plan | |
| one by one and prompts whether they pass or fail. A manual test plan passes if | |
| all test points are interactively verified. | |
| Typical usage: | |
| # This script can be run either directly, or | |
| util/run_manual_tests.py sw/device/silicon_creator/rom/data/rom_manual_testplan.hjson | |
| # via the `manual_test()` bazel rule. | |
| bazel test -t- //sw/device/silicon_creator/rom:manual | |
| """ | |
| import os | |
| import re | |
| import subprocess | |
| from pathlib import Path | |
| from typing import Any, Dict, List, Tuple | |
| import hjson | |
| import typer | |
| from pluralizer import Pluralizer | |
| from rich.console import Console | |
| from rich.markdown import Markdown | |
| from rich.prompt import Confirm | |
| from rich.text import Text | |
| pluralize = Pluralizer().pluralize | |
| def _get_tty(): | |
| """Returns the terminal that we should use. | |
| When this script is run directly, the terminal should be the terminal of the process. | |
| When this script is run by bazel, the terminal should be the terminal of the bazel | |
| client. Note that we cannot simply traverse the process tree, since this script is | |
| run by the bazel server, which could be started from a different terminal. | |
| """ | |
| term = subprocess.check_output( | |
| ["/bin/ps", "-p", f"{os.getpid()}", "-o", "tty="]).decode().strip() | |
| if term == "?": | |
| res = subprocess.check_output(["/bin/ps", "-axo", | |
| "tty=,cmd="]).decode() | |
| m = re.search(r"^(?P<term>[\w/]*) *bazel.*test.*$", res, re.MULTILINE) | |
| if not m: | |
| raise RuntimeError("Could not find the terminal") | |
| term = m["term"] | |
| return f"/dev/{term}" | |
| # Set the terminal manually since this test is interactive. | |
| # This is really a hack since bazel does not support interactive commands. | |
| stdin = open(_get_tty(), "r") | |
| stdout = open(_get_tty(), "w") | |
| console = Console(force_interactive=True, force_terminal=True, file=stdout) | |
| def _run_manual_testpoint(testplan_name: str, | |
| testpoint: Dict[str, Any]) -> Tuple[str, bool]: | |
| """Runs a testpoint in a manual testplan.""" | |
| name = f"{testplan_name}/{testpoint['name']}" | |
| md = f"**{name}**: {testpoint['desc']}" | |
| console.print(Markdown(md)) | |
| console.print("") | |
| prompt = Text.styled("\n> Was the test successful? [y/n]", style="bold") | |
| return (name, | |
| Confirm.ask(prompt, | |
| show_choices=False, | |
| console=console, | |
| stream=stdin)) | |
| def _print_results(testplan_name: str, results: List[Tuple[str, | |
| bool]]) -> None: | |
| """Prints the results for a testplan.""" | |
| title = Text.assemble( | |
| ("Results of testplan ", "bold"), | |
| (f"{testplan_name}", "bold reverse"), | |
| (":", "bold"), | |
| ) | |
| console.print("") | |
| console.print(title) | |
| console.print("") | |
| for testpoint, passed in results: | |
| line = Text.assemble(f" {testpoint}: ", | |
| ("PASSED", "bold green") if passed else | |
| ("FAILED", "bold red")) | |
| console.print(line) | |
| console.print("") | |
| num_tests = len(results) | |
| num_pass = len([res[1] for res in results if res[1]]) | |
| num_fail = num_tests - num_pass | |
| summary = Text.assemble(f"{num_pass} of {num_tests} tests passed. ", | |
| ("All tests PASSED", | |
| "bold green") if not num_fail else | |
| (f"{pluralize('test', num_fail, True)} FAILED", | |
| "bold white on red")) | |
| console.print(summary) | |
| raise typer.Exit(code=num_fail) | |
| def main(testplan_path: Path): | |
| """Run manual tests listed in a testplan hjson file.""" | |
| testplan = hjson.load(testplan_path.open()) | |
| results = [ | |
| _run_manual_testpoint(testplan['name'], t) | |
| for t in testplan['testpoints'] | |
| ] | |
| _print_results(testplan["name"], results) | |
| if __name__ == "__main__": | |
| typer.run(main) |