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

80 lines
2.6 KiB
Python

"""Turn a flat list of pytest node IDs into a selectable tree.
Node ID shapes handled:
tests/unit/test_tokens.py::test_roundtrip
tests/unit/test_route_classifier.py::test_models[gemini-3-flash] (parametrized)
Directories and files become inner nodes; a parametrized function becomes an
inner node whose leaves are its individual cases; a plain function is a leaf.
Only leaves carry a runnable node_id.
"""
from __future__ import annotations
from dataclasses import dataclass, field
@dataclass
class TNode:
label: str
kind: str # 'dir' | 'file' | 'func' | 'case'
node_id: str | None = None # set only on leaves (func without params, or case)
children: list["TNode"] = field(default_factory=list)
@property
def is_leaf(self) -> bool:
return not self.children
def _find_or_add(self, label: str, kind: str) -> "TNode":
for child in self.children:
if child.label == label and child.kind == kind:
return child
child = TNode(label=label, kind=kind)
self.children.append(child)
return child
def leaf_ids(self) -> list[str]:
if self.is_leaf:
return [self.node_id] if self.node_id else []
out: list[str] = []
for child in self.children:
out.extend(child.leaf_ids())
return out
def build_tree(node_ids: list[str], root_label: str = "tests") -> TNode:
root = TNode(label=root_label, kind="dir")
for nid in node_ids:
file_part, _, test_part = nid.partition("::")
segments = [s for s in file_part.split("/") if s]
# Drop a leading "tests" segment so it nests under the single root.
if segments and segments[0] == root_label:
segments = segments[1:]
cursor = root
for seg in segments[:-1]:
cursor = cursor._find_or_add(seg, "dir")
if segments:
cursor = cursor._find_or_add(segments[-1], "file")
if "[" in test_part:
func_name = test_part.split("[", 1)[0]
func_node = cursor._find_or_add(func_name, "func")
func_node.children.append(
TNode(label=test_part, kind="case", node_id=nid)
)
else:
cursor.children.append(
TNode(label=test_part, kind="func", node_id=nid)
)
_sort(root)
return root
def _sort(node: TNode) -> None:
# Dirs first, then files, then funcs/cases; alphabetical within a kind.
order = {"dir": 0, "file": 1, "func": 2, "case": 3}
node.children.sort(key=lambda c: (order.get(c.kind, 9), c.label))
for child in node.children:
_sort(child)