- fail closed on MCP input and compile database policy - add shared scan catalogs, finding queries, and tiered gates
150 lines
5.1 KiB
Python
150 lines
5.1 KiB
Python
"""Sanitized repeated-test evidence and flake classification."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
VALID_OUTCOMES = {"pass", "fail", "skip", "missing"}
|
|
|
|
|
|
def parse_meson_testlog(path: Path, attempt: int, exit_code: int) -> dict[str, object]:
|
|
tests: list[dict[str, object]] = []
|
|
try:
|
|
lines = path.read_text(encoding="utf-8").splitlines()
|
|
except OSError as error:
|
|
return {
|
|
"attempt": attempt,
|
|
"exit_code": exit_code,
|
|
"tests": tests,
|
|
"parse_error": str(error),
|
|
}
|
|
for line_number, line in enumerate(lines, start=1):
|
|
if not line.strip():
|
|
continue
|
|
try:
|
|
record = json.loads(line)
|
|
except json.JSONDecodeError as error:
|
|
return {
|
|
"attempt": attempt,
|
|
"exit_code": exit_code,
|
|
"tests": tests,
|
|
"parse_error": f"line {line_number}: {error}",
|
|
}
|
|
if not isinstance(record, dict) or not isinstance(record.get("name"), str):
|
|
continue
|
|
is_fail = record.get("is_fail")
|
|
result = str(record.get("result", "")).upper()
|
|
if isinstance(is_fail, bool) and is_fail:
|
|
outcome = "fail"
|
|
elif result in {"SKIP", "IGNORED"}:
|
|
outcome = "skip"
|
|
elif isinstance(is_fail, bool) and not is_fail:
|
|
outcome = "pass"
|
|
else:
|
|
outcome = "fail"
|
|
duration = record.get("duration")
|
|
tests.append(
|
|
{
|
|
"name": record["name"],
|
|
"outcome": outcome,
|
|
"duration_seconds": (
|
|
round(duration, 6)
|
|
if isinstance(duration, (int, float))
|
|
and not isinstance(duration, bool)
|
|
else None
|
|
),
|
|
"return_code": record.get("returncode"),
|
|
"result": result or "UNKNOWN",
|
|
}
|
|
)
|
|
return {"attempt": attempt, "exit_code": exit_code, "tests": tests}
|
|
|
|
|
|
def classify(outcomes: list[str]) -> str:
|
|
values = set(outcomes)
|
|
if values == {"pass"}:
|
|
return "stable-pass"
|
|
if values == {"fail"}:
|
|
return "stable-fail"
|
|
if values == {"skip"}:
|
|
return "stable-skip"
|
|
if values <= {"pass", "fail"} and values == {"pass", "fail"}:
|
|
return "flaky"
|
|
return "inconsistent"
|
|
|
|
|
|
def summarize_attempts(
|
|
attempts: list[dict[str, object]], *, expected_tests: list[str] | None = None
|
|
) -> dict[str, object]:
|
|
names = set(expected_tests or [])
|
|
attempt_maps: list[dict[str, dict[str, object]]] = []
|
|
for attempt in attempts:
|
|
records = attempt.get("tests", [])
|
|
records = records if isinstance(records, list) else []
|
|
by_name: dict[str, dict[str, object]] = {}
|
|
for record in records:
|
|
if isinstance(record, dict) and isinstance(record.get("name"), str):
|
|
by_name[str(record["name"])] = record
|
|
names.add(str(record["name"]))
|
|
attempt_maps.append(by_name)
|
|
|
|
tests = []
|
|
counts = {
|
|
"stable-pass": 0,
|
|
"stable-fail": 0,
|
|
"stable-skip": 0,
|
|
"flaky": 0,
|
|
"inconsistent": 0,
|
|
}
|
|
for name in sorted(names):
|
|
outcomes: list[str] = []
|
|
durations: list[float | None] = []
|
|
for by_name in attempt_maps:
|
|
record = by_name.get(name)
|
|
outcome = record.get("outcome") if record is not None else "missing"
|
|
if not isinstance(outcome, str) or outcome not in VALID_OUTCOMES:
|
|
outcome = "missing"
|
|
duration = record.get("duration_seconds") if record is not None else None
|
|
outcomes.append(outcome)
|
|
durations.append(
|
|
duration
|
|
if isinstance(duration, (int, float)) and not isinstance(duration, bool)
|
|
else None
|
|
)
|
|
classification = classify(outcomes)
|
|
counts[classification] += 1
|
|
tests.append(
|
|
{
|
|
"name": name,
|
|
"classification": classification,
|
|
"outcomes": outcomes,
|
|
"duration_seconds": durations,
|
|
}
|
|
)
|
|
|
|
exit_codes = []
|
|
parse_errors = []
|
|
for attempt in attempts:
|
|
exit_code = attempt.get("exit_code")
|
|
exit_codes.append(exit_code if isinstance(exit_code, int) else None)
|
|
error = attempt.get("parse_error")
|
|
if isinstance(error, str):
|
|
parse_errors.append({"attempt": attempt.get("attempt"), "error": error})
|
|
all_attempts_passed = bool(attempts) and all(code == 0 for code in exit_codes)
|
|
all_attempts_passed = all_attempts_passed and not parse_errors
|
|
all_attempts_passed = all_attempts_passed and all(
|
|
item["classification"] in {"stable-pass", "stable-skip"} for item in tests
|
|
)
|
|
return {
|
|
"schema_version": 1,
|
|
"attempt_count": len(attempts),
|
|
"exit_codes": exit_codes,
|
|
"all_attempts_passed": all_attempts_passed,
|
|
"counts": counts,
|
|
"flaky_tests": [
|
|
item["name"] for item in tests if item["classification"] == "flaky"
|
|
],
|
|
"parse_errors": parse_errors,
|
|
"tests": tests,
|
|
}
|