stock-analysis-api / eval /runner.py
vjeai's picture
Deploy: all fixes β€” yfinance candles, ml_signal 2y history, no handoff schemas, sequential report phase
3be03dd
Raw
History Blame Contribute Delete
3.52 kB
"""
eval.runner β€” shared test infrastructure for the evaluation suite.
Provides:
ROOT β€” absolute path to the repository root (sys.path entry)
ANSI colours β€” GREEN, RED, YELLOW, CYAN, BOLD, DIM, RESET, and icons
EvalResult β€” records per-test outcomes
_results β€” module-level singleton shared by all suites
run_test() β€” execute one test, print result, record it
section() β€” print a bold section header
"""
import os
import sys
import time
import traceback
from typing import Callable
# ── Make repo root importable when running from any directory ───────────────
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if ROOT not in sys.path:
sys.path.insert(0, ROOT)
# ── ANSI colours ─────────────────────────────────────────────────────────────
GREEN = "\033[92m"
RED = "\033[91m"
YELLOW = "\033[93m"
CYAN = "\033[96m"
BOLD = "\033[1m"
DIM = "\033[2m"
RESET = "\033[0m"
PASS_ICON = f"{GREEN}βœ“{RESET}"
FAIL_ICON = f"{RED}βœ—{RESET}"
SKIP_ICON = f"{YELLOW}β—‹{RESET}"
RUN_ICON = f"{CYAN}β†’{RESET}"
# ── EvalResult ────────────────────────────────────────────────────────────────
class EvalResult:
def __init__(self):
self.results: list[dict] = []
def record(self, suite: str, name: str, passed: bool,
elapsed: float, detail: str = ""):
self.results.append(dict(suite=suite, name=name, passed=passed,
elapsed=elapsed, detail=detail))
def passed(self): return [r for r in self.results if r["passed"]]
def failed(self): return [r for r in self.results if not r["passed"]]
def count(self): return len(self.results)
# Module-level singleton β€” imported by all suite files so results accumulate.
_results = EvalResult()
# ── Test primitives ──────────────────────────────────────────────────────────
def run_test(suite: str, name: str, fn: Callable,
skip_if: bool = False, skip_reason: str = "") -> bool:
"""Run one test, print result, record it in _results."""
label = f"{DIM}{suite:<12}{RESET} {name}"
if skip_if:
print(f" {SKIP_ICON} {label:<55} {YELLOW}SKIP{RESET} {DIM}{skip_reason}{RESET}")
return True
print(f" {RUN_ICON} {label:<55}", end="", flush=True)
t0 = time.time()
try:
detail = fn() or ""
elapsed = time.time() - t0
_results.record(suite, name, True, elapsed, str(detail)[:120])
print(f"\r {PASS_ICON} {label:<55} {GREEN}{elapsed:5.2f}s{RESET} {DIM}{detail}{RESET}")
return True
except Exception as exc:
elapsed = time.time() - t0
short = f"{type(exc).__name__}: {str(exc)[:80]}"
_results.record(suite, name, False, elapsed, short)
print(f"\r {FAIL_ICON} {label:<55} {RED}{elapsed:5.2f}s{RESET} {RED}{short}{RESET}")
if os.getenv("EVAL_VERBOSE"):
traceback.print_exc()
return False
def section(title: str):
print(f"\n{BOLD}{CYAN}{'─'*70}{RESET}")
print(f"{BOLD}{CYAN} {title}{RESET}")
print(f"{BOLD}{CYAN}{'─'*70}{RESET}")