#!/usr/bin/env python3 """Aggregate test runner for the backend suite. The backend tests are standalone scripts (each with `if __name__ == "__main__"`, no pytest). This discovers every backend/**/test_*.py and runs each in its OWN subprocess (tests set os.environ and import `server` with different configs, so isolation matters), prints a one-line PASS/FAIL per test, dumps output only for failures, and exits non-zero if any test fails. Run: python3 backend/run_tests.py (from the repo root) or: cd backend && python3 run_tests.py Filter: python3 backend/run_tests.py soft_delete redaction # substring match on path """ import os import subprocess import sys import time BACKEND = os.path.dirname(os.path.abspath(__file__)) def discover(filters): found = [] for root, dirs, files in os.walk(BACKEND): dirs[:] = [d for d in dirs if d != "__pycache__"] for f in files: if f.startswith("test_") and f.endswith(".py"): path = os.path.join(root, f) rel = os.path.relpath(path, BACKEND) if not filters or any(flt in rel for flt in filters): found.append(path) return sorted(found) def main(): filters = sys.argv[1:] tests = discover(filters) if not tests: print("No tests matched.") sys.exit(1) print(f"Running {len(tests)} backend test(s)\n") passed, failed = [], [] t0 = time.time() for path in tests: rel = os.path.relpath(path, BACKEND) proc = subprocess.run([sys.executable, path], cwd=BACKEND, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) if proc.returncode == 0: passed.append(rel) print(f" PASS {rel}") else: failed.append(rel) print(f" FAIL {rel}") sys.stdout.write(proc.stdout.decode("utf-8", "replace").rstrip() + "\n") print(f"\n{len(passed)}/{len(tests)} passed in {time.time() - t0:.1f}s") if failed: print("FAILED:") for f in failed: print(f" - {f}") sys.exit(1) print("ALL PASS") if __name__ == "__main__": main()