Files
haikdcandGitHub 9c95342704 Haik/feat/test runner (#86)
* [haik]: ckpt, added in the test runner skelton i made in another repo -> still gotta make tweaks to fold it onto the current repo

* [haik]: restructure backend test layout: move 37 test files from backend/tests/ into backend/tests/test_cases/, add backend/tests/run.sh one-command launcher that auto-provisions both runner and test venvs with stamp-based caching, update runner config.json to point test_paths at test_cases/ and fix venv_python path, revise runner README with run.sh usage and clearer setup instructions, and gitignore the .runner-venv directory

* [haik]: refactor: reorganize backend/tests/test_cases from flat structure into domain subdirectories — moved 36 test files into auth/, browser/, labeling/, service/, settings/, and web_search/ for better discoverability and grouping

* [haik]: overhaul test picker UI and add post-run rerun loop: replace checkbox glyphs with fzf-style row recolouring (coral=full, lighter coral=partial) and a right-pinned selection dot, add config-driven icon tiers (nerd/emoji/unicode/ascii) with per-glyph graceful degradation, replace the modal -k keyword screen with an inline live-filtering search bar that prunes the tree on every keystroke, add warm Anthropic-dark coral theme, toolbar flag chips replacing the old status line, and a floating help badge overlay; add rerun_prompt.py with inline Textual pill prompt (rerun all/failed/passed/exit) shown after each TTY run, wire it into main.py as a post-run loop; change run_tests to return (exit_code, RunSummary) tracking collected/passed/failed node IDs via new Dashboard methods; add icons field to config.py and config.json (set to nerd), document icon tiers and Nerd Font setup in README, set Hack Nerd Font in .vscode/settings.json, add .runner-venv to linter excludes
2026-06-14 07:02:30 -07:00

98 lines
3.6 KiB
Python

"""Runner configuration: paths + the test venv interpreter.
A single ``config.json`` next to this module centralizes everything that used to
be hard-coded in ``discovery.py`` / ``run.py`` so the runner can be dropped into
another repo by editing data instead of code:
repo_root where pytest runs (cwd); test_paths are relative to it
test_paths default search roots when no paths are given
venv_python the interpreter the *tests* run in (separate from the
runner's own venv); falls back to the current interpreter
coverage_source packages measured under --cov, and the report path filter
icons picker glyph tier (a *ceiling*): "nerd" | "emoji" |
"unicode" | "ascii", default "unicode". Each icon falls
back to the fanciest variant at or below this tier, so a
terminal without a Nerd Font still renders the rest.
All paths in the JSON are resolved relative to this directory (the runner
folder) unless absolute. Missing keys fall back to today's behavior.
"""
from __future__ import annotations
import json
import sys
from dataclasses import dataclass
from pathlib import Path
RUNNER_DIR = Path(__file__).resolve().parent
DEFAULT_CONFIG_PATH = RUNNER_DIR / "config.json"
# Defaults that reproduce the pre-config behavior of the runner.
_DEFAULT_REPO_ROOT = RUNNER_DIR.parents[1] # <repo>/tests/runner -> <repo>
_DEFAULT_TEST_PATHS = ["tests/unit", "tests/api"]
_DEFAULT_COVERAGE_SOURCE = ["backend"]
# Allowed picker glyph tiers, fanciest first. "unicode" is the safe default:
# plain BMP shapes that render on any terminal without a patched font.
_ICON_TIERS = ("nerd", "emoji", "unicode", "ascii")
_DEFAULT_ICONS = "unicode"
@dataclass(frozen=True)
class Config:
repo_root: Path
test_paths: list[str]
venv_python: str
coverage_source: list[str]
icons: str
def _resolve(base: Path, value: str) -> Path:
p = Path(value).expanduser()
return p if p.is_absolute() else (base / p).resolve()
def load_config(path: Path | None = None) -> Config:
"""Load and resolve the runner config. Unknown/missing keys use defaults."""
cfg_path = path or DEFAULT_CONFIG_PATH
raw: dict = {}
if cfg_path.is_file():
raw = json.loads(cfg_path.read_text())
repo_root = (
_resolve(RUNNER_DIR, raw["repo_root"])
if raw.get("repo_root")
else _DEFAULT_REPO_ROOT
)
test_paths = raw.get("test_paths") or list(_DEFAULT_TEST_PATHS)
coverage_source = raw.get("coverage_source") or list(_DEFAULT_COVERAGE_SOURCE)
# An unknown/misspelled tier silently falls back to the safe default rather
# than crashing the picker on launch.
icons = raw.get("icons")
if icons not in _ICON_TIERS:
icons = _DEFAULT_ICONS
# The test interpreter is resolved against repo_root. If it is unset or does
# not exist, fall back to the interpreter running the runner so the tool
# still works in a single-venv setup. We deliberately do NOT call
# ``.resolve()`` here: a venv's ``python`` is a symlink to the base
# interpreter, and following it would dereference away the venv.
venv_python = sys.executable
if raw.get("venv_python"):
candidate = Path(raw["venv_python"]).expanduser()
if not candidate.is_absolute():
candidate = repo_root / candidate
if candidate.exists():
venv_python = str(candidate)
return Config(
repo_root=repo_root,
test_paths=test_paths,
venv_python=venv_python,
coverage_source=coverage_source,
icons=icons,
)