feat(tester): support isolated full regression workflow

This commit is contained in:
2026-10-16 12:24:49 +08:00
parent 3a18a27dc6
commit 9b2d0a28d8
23 changed files with 990 additions and 60 deletions
+80 -20
View File
@@ -12,6 +12,8 @@ from __future__ import annotations
from collections.abc import Callable, Iterable, Mapping
from typing import Any
from failure_feedback import collect_issues
RESULTS = ("pass", "fail", "environment-fail", "skipped", "not-run")
PHASES = ("preflight", "integration", "journey")
@@ -29,10 +31,18 @@ def _case_id(case: Mapping[str, Any] | str) -> str:
return str(case)
def _not_run(cases: Iterable[Mapping[str, Any] | str], reason: str) -> list[dict[str, str]]:
def _not_run(
cases: Iterable[Mapping[str, Any] | str],
reason: str,
*,
blocked_by: Iterable[str] = (),
) -> list[dict[str, Any]]:
rows: list[dict[str, str]] = []
blockers = [str(item) for item in blocked_by if str(item)]
for case in cases:
row = {"id": _case_id(case), "result": "not-run", "notes": reason}
row: dict[str, Any] = {"id": _case_id(case), "result": "not-run", "notes": reason}
if blockers:
row["blocked_by"] = blockers
if isinstance(case, Mapping):
row["test_type"] = str(case.get("test_type") or "")
row["suite"] = str(
@@ -45,6 +55,37 @@ def _not_run(cases: Iterable[Mapping[str, Any] | str], reason: str) -> list[dict
return rows
def _dependencies(
case: Mapping[str, Any] | str,
*,
default: Iterable[str] = (),
) -> list[str]:
if not isinstance(case, Mapping):
return [str(item) for item in default]
declared = case.get("depends_on", case.get("dependencies"))
if declared is None:
return [str(item) for item in default]
if isinstance(declared, str):
return [declared]
if isinstance(declared, Iterable):
return [str(item) for item in declared if str(item)]
raise ValueError(f"depends_on 必须是字符串或列表: {_case_id(case)}")
def _failed_dependencies(dependencies: Iterable[str], results: Mapping[str, Mapping[str, Any]]) -> list[str]:
return [
dependency
for dependency in dependencies
if str((results.get(dependency) or {}).get("result"))
in {"fail", "environment-fail", "not-run"}
]
def _issue_structures(results: list[dict[str, Any]]) -> dict[str, list[dict[str, Any]]]:
issues = collect_issues(results)
return {"defects": issues, "test_issues": [dict(issue) for issue in issues]}
def run_integration_first(
*,
preflight_status: str,
@@ -65,18 +106,29 @@ def run_integration_first(
journey = list(journey_cases)
if preflight_status != "ready":
reason = f"test-preflight={preflight_status},未创建客户端 session"
result_rows = _not_run(integration, reason, blocked_by=("test-preflight",)) + _not_run(journey, reason, blocked_by=("test-preflight",))
return {
"status": preflight_status,
"phase": "preflight",
"session_created": False,
"results": _not_run(integration, reason) + _not_run(journey, reason),
"results": result_rows,
**_issue_structures(result_rows),
}
results: list[dict[str, Any]] = []
integration_failed = False
for index, case in enumerate(integration):
if integration_failed:
results.extend(_not_run([case], "前置 integration 失败,未轮到执行"))
results_by_id: dict[str, dict[str, Any]] = {}
for case in integration:
case_id = _case_id(case)
dependencies = _dependencies(case)
blocked_by = _failed_dependencies(dependencies, results_by_id)
if blocked_by:
rows = _not_run(
[case],
"依赖的 integration 失败,未执行",
blocked_by=blocked_by,
)
results.extend(rows)
results_by_id[case_id] = rows[0]
continue
outcome = execute_integration(case)
row = dict(outcome) if isinstance(outcome, Mapping) else {"result": outcome}
@@ -89,20 +141,26 @@ def run_integration_first(
raise ValueError(f"未知 integration 结果: {row['id']}={result}")
row["result"] = result
results.append(row)
if result in ("fail", "environment-fail"):
integration_failed = True
results_by_id[case_id] = row
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
integration_ids = [_case_id(case) for case in integration]
has_runnable_journey = any(
not _failed_dependencies(_dependencies(case, default=integration_ids), results_by_id)
for case in journey
)
session = create_journey_session() if has_runnable_journey else None
for case in journey:
dependencies = _dependencies(case, default=integration_ids)
blocked_by = _failed_dependencies(dependencies, results_by_id)
if blocked_by:
row = _not_run(
[case],
"依赖的用例失败,journey 未执行",
blocked_by=blocked_by,
)[0]
results.append(row)
results_by_id[_case_id(case)] = row
continue
outcome = execute_journey(session, case)
row = dict(outcome) if isinstance(outcome, Mapping) else {"result": outcome}
row["id"] = _case_id(case)
@@ -114,12 +172,14 @@ def run_integration_first(
raise ValueError(f"未知 journey 结果: {row['id']}={result}")
row["result"] = result
results.append(row)
results_by_id[_case_id(case)] = 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),
"session_created": has_runnable_journey,
"results": results,
**_issue_structures(results),
}