feat(tester): add integration-first journey execution
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Shared execution policy for tester's integration-first runs.
|
||||
|
||||
This module intentionally has no browser, mobile, deployment, or API driver.
|
||||
Those adapters belong to the caller. It only controls the phase gate and the
|
||||
stable five-state testcase result propagation so a caller cannot create a
|
||||
journey session before preflight and integration have passed.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable, Iterable, Mapping
|
||||
from typing import Any
|
||||
|
||||
RESULTS = ("pass", "fail", "environment-fail", "skipped", "not-run")
|
||||
PHASES = ("preflight", "integration", "journey")
|
||||
|
||||
|
||||
def classify_integration(*, ok: bool, service_reachable: bool = True) -> str:
|
||||
"""Separate a contract assertion failure from an unavailable service."""
|
||||
if not service_reachable:
|
||||
return "environment-fail"
|
||||
return "pass" if ok else "fail"
|
||||
|
||||
|
||||
def _case_id(case: Mapping[str, Any] | str) -> str:
|
||||
if isinstance(case, Mapping):
|
||||
return str(case.get("id") or "")
|
||||
return str(case)
|
||||
|
||||
|
||||
def _not_run(cases: Iterable[Mapping[str, Any] | str], reason: str) -> list[dict[str, str]]:
|
||||
rows: list[dict[str, str]] = []
|
||||
for case in cases:
|
||||
row = {"id": _case_id(case), "result": "not-run", "notes": reason}
|
||||
if isinstance(case, Mapping):
|
||||
row["test_type"] = str(case.get("test_type") or "")
|
||||
row["suite"] = str(
|
||||
case.get("client_suite")
|
||||
or case.get("integration_suite")
|
||||
or case.get("suite")
|
||||
or ""
|
||||
)
|
||||
rows.append(row)
|
||||
return rows
|
||||
|
||||
|
||||
def run_integration_first(
|
||||
*,
|
||||
preflight_status: str,
|
||||
integration_cases: Iterable[Mapping[str, Any] | str],
|
||||
journey_cases: Iterable[Mapping[str, Any] | str],
|
||||
execute_integration: Callable[[Mapping[str, Any] | str], str | Mapping[str, Any]],
|
||||
create_journey_session: Callable[[], Any],
|
||||
execute_journey: Callable[[Any, Mapping[str, Any] | str], str | Mapping[str, Any]],
|
||||
) -> dict[str, Any]:
|
||||
"""Execute integration cases before creating any journey session.
|
||||
|
||||
``preflight_status`` must be the status emitted by test-preflight. A
|
||||
blocked/environment-fail gate records only ``not-run`` testcase rows and
|
||||
never invokes either adapter. An integration failure stops the phase and
|
||||
propagates ``not-run`` to all journey cases.
|
||||
"""
|
||||
integration = list(integration_cases)
|
||||
journey = list(journey_cases)
|
||||
if preflight_status != "ready":
|
||||
reason = f"test-preflight={preflight_status},未创建客户端 session"
|
||||
return {
|
||||
"status": preflight_status,
|
||||
"phase": "preflight",
|
||||
"session_created": False,
|
||||
"results": _not_run(integration, reason) + _not_run(journey, reason),
|
||||
}
|
||||
|
||||
results: list[dict[str, Any]] = []
|
||||
integration_failed = False
|
||||
for index, case in enumerate(integration):
|
||||
if integration_failed:
|
||||
results.extend(_not_run([case], "前置 integration 失败,未轮到执行"))
|
||||
continue
|
||||
outcome = execute_integration(case)
|
||||
row = dict(outcome) if isinstance(outcome, Mapping) else {"result": outcome}
|
||||
row["id"] = _case_id(case)
|
||||
if isinstance(case, Mapping):
|
||||
row.setdefault("test_type", str(case.get("test_type") or "integration"))
|
||||
row.setdefault("suite", str(case.get("integration_suite") or case.get("suite") or ""))
|
||||
result = str(row.get("result") or "not-run")
|
||||
if result not in RESULTS:
|
||||
raise ValueError(f"未知 integration 结果: {row['id']}={result}")
|
||||
row["result"] = result
|
||||
results.append(row)
|
||||
if result in ("fail", "environment-fail"):
|
||||
integration_failed = True
|
||||
|
||||
if integration_failed:
|
||||
results.extend(_not_run(journey, "integration 失败,依赖的 journey 未执行"))
|
||||
return {
|
||||
"status": "fail" if any(row["result"] == "fail" for row in results) else "environment-fail",
|
||||
"phase": "integration",
|
||||
"session_created": False,
|
||||
"results": results,
|
||||
}
|
||||
|
||||
session = create_journey_session() if journey else None
|
||||
for case in journey:
|
||||
outcome = execute_journey(session, case)
|
||||
row = dict(outcome) if isinstance(outcome, Mapping) else {"result": outcome}
|
||||
row["id"] = _case_id(case)
|
||||
if isinstance(case, Mapping):
|
||||
row.setdefault("test_type", str(case.get("test_type") or "journey"))
|
||||
row.setdefault("suite", str(case.get("client_suite") or case.get("suite") or ""))
|
||||
result = str(row.get("result") or "not-run")
|
||||
if result not in RESULTS:
|
||||
raise ValueError(f"未知 journey 结果: {row['id']}={result}")
|
||||
row["result"] = result
|
||||
results.append(row)
|
||||
status = "fail" if any(row["result"] == "fail" for row in results) else (
|
||||
"environment-fail" if any(row["result"] == "environment-fail" for row in results) else "pass"
|
||||
)
|
||||
return {
|
||||
"status": status,
|
||||
"phase": "journey" if journey else "integration",
|
||||
"session_created": bool(journey),
|
||||
"results": results,
|
||||
}
|
||||
Reference in New Issue
Block a user