Viewing files at commit 3354584a5d91Commit detailsView latest

Browse files
scripts/vcs_conformance.py 93 lines · 4.3 KB · python
1
#!/usr/bin/env python3
2
"""Run local VCS correctness diagnostics; stdout is diagnostic JSONL, not timing rows.
3
4
Suites: ``legacy`` (the five v1 restore/roundtrip scenarios), ``matrix`` (the
5
roundtrip conformance matrix, commit -> push -> fresh clone/pull, compared via
6
the independent reader and filesystem bytes), ``all`` (both, the default).
7
8
``--gate`` evaluates the Oak rows against config/conformance_expected_failures.json
9
and exits 0 PASS / 1 REGRESSED / 2 UNMEASURED (the devloop convention).
10
Without ``--gate`` the exit code is 1 when any row failed, else 0.
11
"""
12
import argparse
13
import json
14
from pathlib import Path
15
import sys
16
17
from oakbench.conformance import SCENARIOS, run_scenario
18
from oakbench import conformance_gate
19
from oakbench.conformance_matrix import BEHAVIOURS, BY_ID, fs_profile, probe_capabilities, run_matrix
20
21
22
def main():
23
    parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
24
    parser.add_argument("--workdir", type=Path, required=False)
25
    parser.add_argument("--subjects", default="git,oak")
26
    parser.add_argument("--git-bin", default="git")
27
    parser.add_argument("--oak-bin", default="oak")
28
    parser.add_argument("--suite", choices=("legacy", "matrix", "all"), default="all")
29
    parser.add_argument("--scenarios", default=",".join(SCENARIOS), help="legacy scenarios")
30
    parser.add_argument("--behaviours", default="", help="comma list of matrix behaviour IDs (default: all)")
31
    parser.add_argument("--include-gated", action="store_true", help="also run gated behaviours (huge files)")
32
    parser.add_argument("--output", type=Path, help="also write the JSONL rows here")
33
    parser.add_argument("--quiet", action="store_true", help="do not echo rows on stdout (use with --output)")
34
    parser.add_argument("--gate", action="store_true", help="exit by the conformance gate verdict")
35
    parser.add_argument("--expected-failures", type=Path, default=conformance_gate.EXPECTED_FAILURES)
36
    parser.add_argument("--list", action="store_true", help="print the behaviour catalog as JSONL and exit")
37
    args = parser.parse_args()
38
    if args.list:
39
        for behaviour in BEHAVIOURS:
40
            print(json.dumps(behaviour.describe(), sort_keys=True))
41
        return 0
42
    if args.workdir is None:
43
        parser.error("--workdir is required")
44
    subjects = args.subjects.split(",")
45
    scenarios = [s for s in args.scenarios.split(",") if s]
46
    behaviours = [b for b in args.behaviours.split(",") if b]
47
    if (not set(subjects) <= {"git", "oak"} or not set(scenarios) <= set(SCENARIOS)
48
            or not set(behaviours) <= set(BY_ID)):
49
        parser.error("unknown subject, scenario or behaviour")
50
    rows = []
51
    if args.output:
52
        args.output.parent.mkdir(parents=True, exist_ok=True)
53
    sink = args.output.open("w") if args.output else None
54
55
    profile = fs_profile(probe_capabilities(args.workdir.resolve()))
56
57
    def emit(row):
58
        row.setdefault("fs_profile", profile)
59
        line = json.dumps(row, sort_keys=True)
60
        if not args.quiet:
61
            print(line, flush=True)
62
        if sink:
63
            sink.write(line + "\n")
64
            sink.flush()
65
        rows.append(row)
66
67
    try:
68
        if args.suite in ("legacy", "all"):
69
            for subject in subjects:
70
                for scenario in scenarios:
71
                    emit(run_scenario(args.workdir.resolve(), subject, getattr(args, f"{subject}_bin"), scenario))
72
        if args.suite in ("matrix", "all"):
73
            run_matrix(args.workdir.resolve() / "matrix", {s: getattr(args, f"{s}_bin") for s in subjects},
74
                       behaviours or None, include_gated=args.include_gated or None, progress=emit)
75
    finally:
76
        if sink:
77
            sink.close()
78
    if args.gate:
79
        expected = conformance_gate.load_expected(args.expected_failures)
80
        required = []
81
        if "oak" in subjects:
82
            if args.suite in ("legacy", "all"):
83
                required += scenarios
84
            if args.suite in ("matrix", "all"):
85
                required += behaviours or [b.id for b in BEHAVIOURS]
86
        result = conformance_gate.evaluate(rows, expected, required)
87
        print("\n".join(conformance_gate.summary_lines(result)), file=sys.stderr)
88
        return {"PASS": 0, "REGRESSED": 1}.get(result["verdict"], 2)
89
    return int(any(row["returncode"] == 1 for row in rows))
90
91
92
if __name__ == "__main__":
93
    raise SystemExit(main())