blob: 9211bae00e87e8c5b53524b5a81d4f019ad0423d [file] [log] [blame]
#!/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)