ci(R-1297 A): heavy suites by path on a branch push, all on a tag and nightly; no second full gate run
gates / gates (push) Successful in 3m23s
gates / gates (push) Successful in 3m23s
- script_tests_gate.py: test_gate_decoys / test_due_checks_gate / dooplex-offsite run on a CI branch push only when one of their paths changed (FELHOM_CHANGED_FILES); no list, an empty or unreadable list = every suite - gates.yml: nightly schedule 03:20 UTC; the list is set only for push:refs/heads/*; a trimmed commits array (total_commits > listed) is an unknown list - test_repo_gates.py and test_due_checks_gate.py group G drive the real runner with GATES narrowed to their labels, instead of the whole gate set a second time (130 s each in CI) - red-proved: filter cases, workflow pins (old gates.yml), inert run_gate, unregistered due-checks
This commit is contained in:
1 parent
8143a2c872
commit
ecd82abab8
5 files changed
+194
-10
No files matched your search
@@ -16,7 +16,8 @@ GATE = os.path.join(HERE, "script_tests_gate.py")
|
||||
|
||||
COVERS = {
|
||||
"script-tests": ("a suite that PRINTS OK and exits 1; a failing suite three levels deep; an empty scripts/ "
|
||||
"tree; plus the genuine article (must pass) and the nested-run mark (must step aside)"),
|
||||
"tree; plus the genuine article (must pass) and the nested-run mark (must step aside); R-1297: a "
|
||||
"failing heavy suite on a push that touched its own path, with no list, an empty or a missing list"),
|
||||
}
|
||||
|
||||
|
||||
@@ -54,8 +55,79 @@ def timeouts_ok():
|
||||
return (g.timeout_for("scripts/test_gate_decoys.py") == 600 and g.timeout_for("scripts/test_other.py") == g.TIMEOUT_S == 300)
|
||||
|
||||
|
||||
# R-1297 (A): a heavy suite runs on a CI push only when one of ITS paths changed; with no list (a tag, the nightly run,
|
||||
# the pre-push hook) or an unreadable or empty list, every suite runs. Each case plants a FAILING heavy suite, so
|
||||
# „it ran" is rc 1 and „it was skipped" is rc 0 — the verdict, not a log line, says which happened.
|
||||
CHANGED = "FELHOM_CHANGED_FILES"
|
||||
|
||||
|
||||
def gate_changed(root, changed):
|
||||
"""changed: None (variable unset), "MISSING" (points at no file), or a list of pushed paths."""
|
||||
env = {k: v for k, v in os.environ.items() if k not in ("FELHOM_SCRIPT_TESTS_GATE", CHANGED)}
|
||||
if changed == "MISSING":
|
||||
env[CHANGED] = os.path.join(root, "no-such-list.txt")
|
||||
elif changed is not None:
|
||||
lst = os.path.join(root, "pushed.txt")
|
||||
open(lst, "w").write("".join(f + "\n" for f in changed))
|
||||
env[CHANGED] = lst
|
||||
p = subprocess.run([sys.executable, GATE, root], env=env, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
return p.returncode, p.stdout.decode("utf-8", "replace")
|
||||
|
||||
|
||||
HEAVY = {"test_ok.py": "pass\n", "test_gate_decoys.py": "raise SystemExit(1)\n",
|
||||
"dooplex-offsite/test_dooplex_offsite.py": "raise SystemExit(1)\n",
|
||||
"test_due_checks_gate.py": "raise SystemExit(1)\n"}
|
||||
FILTER_CASES = [
|
||||
# (name, files, changed, want rc, text the output must hold)
|
||||
("filter: no list -> every suite runs", HEAVY, None, 1, "test_gate_decoys.py"),
|
||||
("filter: docs-only push skips the 3", HEAVY, ["documentation/x.md", "STATUS.md"], 0, "SKIP"),
|
||||
("filter: a gate script runs decoys", {"test_ok.py": "pass\n", "test_gate_decoys.py": "raise SystemExit(1)\n"},
|
||||
["scripts/site_gates.py"], 1, "test_gate_decoys.py"),
|
||||
("filter: the due gate runs its suite", {"test_ok.py": "pass\n", "test_due_checks_gate.py": "raise SystemExit(1)\n"},
|
||||
["scripts/due_checks_gate.py"], 1, "test_due_checks_gate.py"),
|
||||
("filter: offsite path runs offsite", {"test_ok.py": "pass\n", "dooplex-offsite/test_dooplex_offsite.py": "raise SystemExit(1)\n"},
|
||||
["scripts/dooplex-offsite/dooplex-offsite.sh"], 1, "test_dooplex_offsite.py"),
|
||||
("filter: hub push skips offsite", {"test_ok.py": "pass\n", "dooplex-offsite/test_dooplex_offsite.py": "raise SystemExit(1)\n"},
|
||||
["hub/internal/web/hosts.go"], 0, "SKIP"),
|
||||
("filter: empty list -> every suite", HEAVY, [], 1, "test_gate_decoys.py"),
|
||||
("filter: missing list -> every suite", HEAVY, "MISSING", 1, "test_gate_decoys.py"),
|
||||
("filter: other suites never skipped", {"test_other.py": "raise SystemExit(1)\n"}, ["documentation/x.md"], 1, "test_other.py"),
|
||||
]
|
||||
|
||||
|
||||
def workflow_pins():
|
||||
"""R-1297: the filter is safe only while the nightly run exists and only a branch push sets the list. Read from the
|
||||
real workflow and hook, so removing the schedule, or setting the list on every event, turns this red."""
|
||||
root = os.path.dirname(HERE)
|
||||
wf = open(os.path.join(root, ".gitea", "workflows", "gates.yml"), encoding="utf-8").read()
|
||||
hook = open(os.path.join(root, ".githooks", "pre-push"), encoding="utf-8").read()
|
||||
problems = []
|
||||
if "schedule:" not in wf or "- cron:" not in wf:
|
||||
problems.append("gates.yml has no nightly schedule — the filtered suites would run only when touched")
|
||||
arm = wf.find("push:refs/heads/*)")
|
||||
sets = [i for i in range(len(wf)) if wf.startswith('FELHOM_CHANGED_FILES=', i)]
|
||||
if not sets or arm < 0 or any(i < arm or i > wf.find(";;", arm) for i in sets):
|
||||
problems.append("gates.yml sets FELHOM_CHANGED_FILES outside the branch-push arm")
|
||||
if "total_commits" not in wf:
|
||||
problems.append("gates.yml does not treat a trimmed commits array as an unknown list")
|
||||
if "FELHOM_CHANGED_FILES" in hook:
|
||||
problems.append("the pre-push hook sets the suite filter — the hook must run every suite")
|
||||
for p in problems:
|
||||
print("FAIL workflow pin: " + p)
|
||||
if not problems:
|
||||
print("ok workflow pins (nightly run, branch-push-only list, trimmed payload, hook unfiltered)")
|
||||
return not problems
|
||||
|
||||
|
||||
def main():
|
||||
bad = 0
|
||||
bad += not workflow_pins()
|
||||
for name, files, changed, want, text in FILTER_CASES:
|
||||
got, out = gate_changed(fake_repo(files), changed)
|
||||
ok = got == want and text in out
|
||||
bad += not ok
|
||||
print("%s %-38s want rc=%d got rc=%d%s" % ("ok " if ok else "FAIL", name, want, got,
|
||||
"" if text in out else " (no %r in output)" % text))
|
||||
t_ok = timeouts_ok()
|
||||
bad += not t_ok
|
||||
print("%s %-32s" % ("ok " if t_ok else "FAIL", "per-suite time limits (R-1209)"))
|
||||
@@ -64,7 +136,7 @@ def main():
|
||||
ok = got == want
|
||||
bad += not ok
|
||||
print("%s %-32s want rc=%d got rc=%d" % ("ok " if ok else "FAIL", name, want, got))
|
||||
print("script-tests decoys: %d/%d" % (len(CASES) - bad, len(CASES)))
|
||||
print("script-tests decoys: %d failed of %d" % (bad, len(CASES) + len(FILTER_CASES) + 1))
|
||||
return 1 if bad else 0
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user