feat(2): Q0.1/Q0.2 — harness.http + discovery recurses functional/playwright (Phase 2)
- runner/harness/http.py: canonical Phase-2 recipe-test HTTP API (vendored from
recipe-maintainer/utils/tests/helpers.py): http_get/http_post, retry variants,
wait_for_http, assert_converges. JSON-parsing, header support, form/JSON POST
bodies, transport-failure -> status=0. Self-contained (cc-ci does not import
recipe-maintainer at runtime per DECISIONS Phase 2).
- harness.discovery.custom_tests now also recurses into
tests/<recipe>/{functional,playwright}/test_*.py (Phase 2 §4.1 layout) while
excluding lifecycle test_<op>.py names and honoring the HC2 repo-local gate.
- Unit tests:
tests/unit/test_http.py — in-process http.server fixture; deterministic
proofs of parsing/retry/convergence semantics, no network egress.
tests/unit/test_discovery_phase2.py — functional/+playwright/ recursion
+ HC2 gate still applies to subdirs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -101,16 +101,30 @@ def resolve_op(recipe: str, op: str, repo_local_dir: str | None) -> tuple[str, s
|
||||
|
||||
def custom_tests(recipe: str, repo_local_dir: str | None) -> list[tuple[str, str]]:
|
||||
"""All non-lifecycle test_*.py from cc-ci's tests/<recipe>/ and (if approved) the recipe's
|
||||
repo-local tests/. These have no generic equivalent and run only when present (opt-in), additively
|
||||
from both. Repo-local is consulted only for allowlist-approved recipes (HC2)."""
|
||||
repo-local tests/. Discovered locations (Phase 2 §4.1):
|
||||
- the top-level dir tests/<recipe>/test_*.py (legacy + cross-cutting)
|
||||
- functional/ tests/<recipe>/functional/test_*.py (parity ports + recipe-specific)
|
||||
- playwright/ tests/<recipe>/playwright/test_*.py (UI flows P6)
|
||||
Files named `test_<op>.py` (lifecycle ops) are excluded from this list — the orchestrator runs
|
||||
those in their lifecycle tier, not the custom one. Repo-local is consulted only for
|
||||
allowlist-approved recipes (HC2)."""
|
||||
lifecycle_names = {f"test_{op}.py" for op in LIFECYCLE_OPS}
|
||||
subdirs = ("functional", "playwright")
|
||||
found: list[tuple[str, str]] = []
|
||||
for source, d in (("cc-ci", cc_ci_dir(recipe)), ("repo-local", _gated(recipe, repo_local_dir))):
|
||||
if not d or not os.path.isdir(d):
|
||||
continue
|
||||
# top-level (legacy / cross-cutting tests not under functional/playwright)
|
||||
for p in sorted(glob.glob(os.path.join(d, "test_*.py"))):
|
||||
if os.path.basename(p) not in lifecycle_names:
|
||||
found.append((source, p))
|
||||
# functional/ and playwright/ subdirs (Phase 2 §4.1)
|
||||
for sub in subdirs:
|
||||
for p in sorted(glob.glob(os.path.join(d, sub, "test_*.py"))):
|
||||
# Phase-2 layout: lifecycle ops never live under functional/playwright, but be
|
||||
# explicit so a misfiled file doesn't silently get double-run.
|
||||
if os.path.basename(p) not in lifecycle_names:
|
||||
found.append((source, p))
|
||||
return found
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user