mirror of
https://github.com/usestrix/strix.git
synced 2026-09-15 23:31:27 +00:00
176 lines
4.1 KiB
Python
176 lines
4.1 KiB
Python
import sys
|
|
import threading
|
|
|
|
|
|
_pretty = "--pretty" in sys.argv
|
|
_lock = threading.Lock()
|
|
|
|
_failures: list[dict[str, str | dict[str, object] | None]] = []
|
|
_passed = 0
|
|
_failed = 0
|
|
|
|
_live = None
|
|
_spinner = None
|
|
_console = None
|
|
|
|
if _pretty:
|
|
from rich.console import Console
|
|
from rich.live import Live
|
|
from rich.markup import escape as rich_escape
|
|
from rich.spinner import Spinner
|
|
|
|
_console = Console(stderr=True, force_terminal=True)
|
|
|
|
|
|
def is_pretty():
|
|
return _pretty
|
|
|
|
|
|
def start():
|
|
global _live, _spinner
|
|
if not _pretty:
|
|
return
|
|
_spinner = Spinner("dots", text="Starting…")
|
|
_live = Live(
|
|
_spinner,
|
|
console=_console,
|
|
refresh_per_second=12,
|
|
)
|
|
_live.start()
|
|
|
|
|
|
def stop():
|
|
global _live
|
|
if _live is not None:
|
|
_live.stop()
|
|
_live = None
|
|
if _pretty and (_passed or _failed):
|
|
_print_summary()
|
|
|
|
|
|
def status(msg):
|
|
if not _pretty:
|
|
return
|
|
with _lock:
|
|
if _spinner is not None:
|
|
_spinner.update(text=f" {msg}")
|
|
|
|
|
|
def _print_line(text, **kwargs):
|
|
with _lock:
|
|
if _live is not None:
|
|
_live.console.print(text, **kwargs)
|
|
elif _console is not None:
|
|
_console.print(text, **kwargs)
|
|
|
|
|
|
def test_start(name):
|
|
if not _pretty:
|
|
return
|
|
_print_line(f" [bold]●[/] [bold]{name}[/]")
|
|
|
|
|
|
def log(msg, style="dim"):
|
|
if not _pretty:
|
|
return
|
|
_print_line(f" [dim]│[/] {msg}", style=style, highlight=False)
|
|
|
|
|
|
def log_error(msg):
|
|
if not _pretty:
|
|
return
|
|
_print_line(f" [red]│[/] {msg}", style="bold red", highlight=False)
|
|
|
|
|
|
def log_warn(msg):
|
|
if not _pretty:
|
|
return
|
|
_print_line(f" [yellow]│[/] {msg}", style="yellow", highlight=False)
|
|
|
|
|
|
def test_passed(name):
|
|
global _passed
|
|
_passed += 1
|
|
if not _pretty:
|
|
return
|
|
_print_line(" [bold green]╰─ ✓ pass[/]")
|
|
|
|
|
|
def test_failed(name, details=""):
|
|
global _failed
|
|
_failed += 1
|
|
if details:
|
|
_failures.append({"name": name, "details": details})
|
|
if not _pretty:
|
|
return
|
|
_print_line(" [bold red]╰─ ✗ fail[/]")
|
|
|
|
|
|
def record_failure(
|
|
test_name,
|
|
label,
|
|
reason,
|
|
result=None,
|
|
screenshot_path=None,
|
|
):
|
|
_failures.append(
|
|
{
|
|
"name": test_name,
|
|
"label": label,
|
|
"reason": reason,
|
|
"result": result,
|
|
"screenshot": screenshot_path,
|
|
}
|
|
)
|
|
|
|
|
|
def _print_summary():
|
|
if _console is None:
|
|
return
|
|
_console.print()
|
|
_console.rule(style="dim")
|
|
_console.print()
|
|
|
|
if _failures:
|
|
_console.print(" [bold red]failures[/]")
|
|
_console.print()
|
|
for f in _failures:
|
|
name = f.get("name", "?")
|
|
reason = str(f.get("reason", f.get("details", "")))
|
|
result = f.get("result")
|
|
screenshot = f.get("screenshot")
|
|
|
|
_console.print(f" [bold red]── {name} ──[/]")
|
|
|
|
if reason:
|
|
for line in reason.split("\n"):
|
|
_console.print(f" [red]│[/] {rich_escape(line)}")
|
|
|
|
if result and isinstance(result, dict):
|
|
_console.print(" [red]│[/]")
|
|
_console.print(" [red]╰─▶[/] [dim]result:[/]")
|
|
for k, v in result.items():
|
|
if k == "screenshot":
|
|
continue
|
|
v_str = str(v)
|
|
if len(v_str) > 120:
|
|
v_str = v_str[:120] + "…"
|
|
_console.print(f" [dim]{k}:[/] {rich_escape(v_str)}")
|
|
|
|
if screenshot:
|
|
_console.print(f" [dim]screenshot:[/] [underline]{rich_escape(screenshot)}[/]")
|
|
|
|
_console.print()
|
|
|
|
total = _passed + _failed
|
|
if _failed:
|
|
_console.print(
|
|
f" [bold]result:[/] [bold red]FAILED[/] "
|
|
f"{_passed} passed, {_failed} failed "
|
|
f"({total} total)",
|
|
)
|
|
else:
|
|
_console.print(
|
|
f" [bold]result:[/] [bold green]ok[/] {_passed} passed ({total} total)",
|
|
)
|
|
_console.print()
|