From dd4b5491c60194b72f53823538c5c93a1be6a119 Mon Sep 17 00:00:00 2001 From: Martin Vogel Date: Mon, 2 Nov 2026 18:22:27 +0100 Subject: [PATCH] ci: make the glibc-floor PR leg a gating lane The glibc-floor job builds the production binary in the pinned ubuntu:22.04 release container and holds it to the release glibc floor (2.35 / GLIBCXX 3.4.30). It ran report-only; it now gates like every other lane. - select-lanes.sh: new lane glibc-floor, part of the full set and selected whenever smoke-ubuntu is (a FOLLOWS rule), i.e. whenever the production Linux binary can change. Docs-only and tests-only changes do not select it. - pr.yml: the glibc-floor job runs when its own lane is selected, and ci-ok needs it. - require-all-green.sh: maps the glibc-floor job to its lane, so a selected glibc-floor that fails, is skipped or cancelled, or never runs fails ci-ok; when it is not selected it may skip. - Contracts: the selector table and the FULL set carry the lane, and every selector case asserts glibc-floor is selected exactly when smoke-ubuntu is; the lane-gate contract adds green, failed, skipped, cancelled, missing and unselected cases; the lane-wiring contract requires the job to serve the lane, ci-ok to need it, and no continue-on-error on it. - scripts/ci/README.md notes the rule. Refs #2486 Signed-off-by: Martin Vogel --- .github/workflows/pr.yml | 26 +++++++++---------- scripts/ci/README.md | 2 +- scripts/ci/require-all-green.sh | 1 + scripts/ci/select-lanes.sh | 10 +++++++- tests/fixtures/select-lanes/cases.tsv | 20 +++++++-------- tests/test_lane_gate_contract.sh | 37 +++++++++++++++++++++++++-- tests/test_lane_wiring_contract.sh | 16 ++++++++---- tests/test_select_lanes.sh | 12 +++++++-- 8 files changed, 90 insertions(+), 34 deletions(-) diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 2ed425a7c..fed91c4b3 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -289,20 +289,20 @@ jobs: with: mode: pr - # ── glibc floor, REPORT-ONLY: builds the production binary in the SAME - # pinned ubuntu:22.04 container the standard Linux release binaries build - # in (_build.yml build-linux) and runs the composition gate with the same - # declared floor, so a change that needs a newer glibc or libstdc++ symbol - # version -- or stops compiling with that GCC 11 toolchain -- shows up at - # PR time instead of in a release run. Light on purpose: a headless build - # (the shipped composition only swaps src/ui/embedded_stub.c for the - # generated asset data) and no smoke. Deliberately NOT in ci-ok's needs - # and no lane of its own: it never blocks or passes a merge. It runs - # whenever the selection holds smoke-ubuntu, i.e. whenever the production - # binary can change. ── + # ── glibc floor (lane glibc-floor, GATING): builds the production binary in + # the SAME pinned ubuntu:22.04 container the standard Linux release + # binaries build in (_build.yml build-linux) and runs the composition gate + # with the same declared floor, so a change that needs a newer glibc or + # libstdc++ symbol version -- or stops compiling with that GCC 11 + # toolchain -- fails the PR instead of a release run. Light on purpose: a + # headless build (the shipped composition only swaps + # src/ui/embedded_stub.c for the generated asset data) and no smoke. The + # selector picks the lane whenever it picks smoke-ubuntu, i.e. whenever + # the production binary can change; ci-ok needs it, and a selected + # glibc-floor that fails, skips or never runs fails ci-ok. ── glibc-floor: needs: [changes] - if: ${{ !cancelled() && needs.changes.result == 'success' && contains(fromJSON(needs.changes.outputs.lanes), 'smoke-ubuntu') }} + if: ${{ !cancelled() && needs.changes.result == 'success' && contains(fromJSON(needs.changes.outputs.lanes), 'glibc-floor') }} runs-on: ubuntu-latest container: image: ubuntu:22.04@sha256:076e7bf82319619f28dd0aa9c02ab34b055ad37100395b4f49a2ffba04fb5e11 @@ -429,7 +429,7 @@ jobs: # every lane the selector chose ran green: `skipped` is OK only for a lane # nobody selected (a selected lane that skipped is a silent gate loss). # memwaste and the test-impact shadow are report-only and not needed here. - needs: [changes, security, lint, test, pr-smoke, contracts, codeql] + needs: [changes, security, lint, test, pr-smoke, contracts, codeql, glibc-floor] if: ${{ always() }} runs-on: ubuntu-latest timeout-minutes: 5 diff --git a/scripts/ci/README.md b/scripts/ci/README.md index f38cf36a8..9a806ecb6 100644 --- a/scripts/ci/README.md +++ b/scripts/ci/README.md @@ -14,7 +14,7 @@ CI and the local infrastructure — both of which the venue-parity contract | `check-glibc-compat.sh` | Run a linux binary in debian:bullseye (glibc 2.31) — the portable binary must start on old glibc. | `_smoke.yml` portable legs | | `generate-sbom.py` | The release SPDX SBOM (vendored versions reviewable here, diffable by vendoring PRs — was inline YAML). | `release.yml` | | `require-all-green.sh` | The aggregate gate: fail unless every needed job succeeded or legitimately skipped (was inline YAML) — and, under lane selection, every SELECTED lane ran and succeeded (skipped/cancelled/missing = red; unselected lanes may skip). | `pr.yml ci-ok` | -| `select-lanes.sh` | Decide which PR lanes a change set runs (tier, lanes, full, reasons) from its file list — the R1 rule set, fail-safe to every lane. Pinned by `tests/test_select_lanes.sh` and the September 2026 history replay (`scripts/test-impact/replay-selector.sh`). | `pr.yml changes` | +| `select-lanes.sh` | Decide which PR lanes a change set runs (tier, lanes, full, reasons) from its file list — the R1 rule set, fail-safe to every lane; `glibc-floor` rides with `smoke-ubuntu`. Pinned by `tests/test_select_lanes.sh` and the September 2026 history replay (`scripts/test-impact/replay-selector.sh`). | `pr.yml changes` | | `test-impact-shadow.sh` | The report-only test-impact shadow: fetch the latest released binary (checksum-verified) or take a given one, index this checkout into a throwaway cache, run `detect_changes` against the base and map it to suites (`scripts/test-impact/predict.py`). Never gates. | `pr.yml test-impact-shadow` | | `verify-shard-union.sh` | Prove sharded test legs lost nothing: shard count agreement, indices 1..n, identical suite lists, union of slices == full list (was inline YAML). No manifests is green only when the lane selection holds no test leg. | `_test.yml` shard-completeness | | `prepare-release-candidates.sh` | Copy one linker output into stripped/unstripped candidates, finalize signatures, composition-check them without execution, and record their hashes. | `_build.yml`, local artifact smoke | diff --git a/scripts/ci/require-all-green.sh b/scripts/ci/require-all-green.sh index 149eef2da..1758eefb3 100755 --- a/scripts/ci/require-all-green.sh +++ b/scripts/ci/require-all-green.sh @@ -70,6 +70,7 @@ if 'LANES' in os.environ: 'test / test-diag': 'diag', 'test / test-msan': 'msan', 'test / test-lsan-macos': 'lsan-macos', 'test / test-windows-guards': 'windows-guards', 'test / shard-completeness': 'shard-completeness', 'contracts': 'contracts', + 'glibc-floor': 'glibc-floor', } matrix = ('test / test-unix (', 'test / test-windows (', 'test / test-tsan (', 'pr-smoke (', 'codeql / analyze (') diff --git a/scripts/ci/select-lanes.sh b/scripts/ci/select-lanes.sh index 18f72c578..f4062271e 100755 --- a/scripts/ci/select-lanes.sh +++ b/scripts/ci/select-lanes.sh @@ -320,7 +320,7 @@ def tier_of(cats): # lane, by any full run: an undeterminable diff scans every language. CODEQL_LANES = {"c-cpp": "codeql-cpp", "javascript-typescript": "codeql-js", "python": "codeql-py", "actions": "codeql-actions"} -FULL = {"diag", "license-gate", "lint", "lint-mem", "lsan-macos", +FULL = {"diag", "glibc-floor", "license-gate", "lint", "lint-mem", "lsan-macos", "memwaste", "msan", "pkg-wrappers", "security-static", "shard-completeness", "smoke-mac", "smoke-ubuntu", "smoke-win", "tsan-arm", "tsan-mac", "tsan-x86", "unix-arm64", "unix-macos-intel", "unix-macos14", "unix-x86", "windows", @@ -330,6 +330,11 @@ LINT = {"lint", "lint-mem"} CORE_LEGS = {"unix-x86", "unix-macos14", "windows", "shard-completeness"} TEST_LEGS = {"unix-x86", "unix-arm64", "unix-macos14", "unix-macos-intel", "windows"} ALWAYS = {"security-static"} +# Lanes that ride with another lane, whatever selected it: glibc-floor (pr.yml: +# the production binary built in the pinned ubuntu:22.04 release container and +# held to the release glibc floor) runs whenever smoke-ubuntu does, i.e. +# whenever the production Linux binary can change. +FOLLOWS = {"smoke-ubuntu": "glibc-floor"} R1 = { "T0a-docs": {"contracts"}, # The contract steps police test-infrastructure/ and lint config too. @@ -474,6 +479,9 @@ def select(paths, expect=None): lanes = lanes | extra if lanes & TEST_LEGS: lanes = lanes | {"shard-completeness"} + for leader, follower in sorted(FOLLOWS.items()): + if leader in lanes: + lanes = lanes | {follower} for language, lane in sorted(CODEQL_LANES.items()): hits = [p for p in paths if codeql_scope(language, p)] if hits and lane not in lanes: diff --git a/tests/fixtures/select-lanes/cases.tsv b/tests/fixtures/select-lanes/cases.tsv index 29d07ca33..3ac0b27b4 100644 --- a/tests/fixtures/select-lanes/cases.tsv +++ b/tests/fixtures/select-lanes/cases.tsv @@ -10,7 +10,7 @@ npm-readme-runs-the-test-legs pkg/npm/README.md T1-tests false lint lint-mem sec issue-template-is-docs .github/ISSUE_TEMPLATE/bug.yml T0a-docs false contracts security-static local-infra test-infrastructure/run.sh T0b-nonproduct false contracts lint lint-mem security-static lint-config scripts/memory-core-baseline.txt T0b-nonproduct false contracts lint lint-mem security-static -graph-ui-adds-guards graph-ui/src/App.tsx T0b-nonproduct false contracts lint lint-mem security-static smoke-ubuntu windows-guards codeql-js +graph-ui-adds-guards graph-ui/src/App.tsx T0b-nonproduct false contracts glibc-floor lint lint-mem security-static smoke-ubuntu windows-guards codeql-js memwaste-tools-add-memwaste scripts/memwaste.sh T0b-nonproduct false contracts lint lint-mem memwaste security-static non-gating-script-runs-contracts-leg scripts/benchmark-index.sh T0b-nonproduct false contracts lint lint-mem security-static shard-completeness unix-x86 test-impact-tooling scripts/test-impact/replay-selector.sh T0b-nonproduct false contracts lint lint-mem security-static shard-completeness unix-x86 @@ -19,13 +19,13 @@ test-fixture tests/fixtures/cpp_include/a.h T1-tests false lint lint-mem securit contract-test tests/test_version_metadata_contract.sh T1-tests false lint lint-mem security-static shard-completeness unix-macos14 unix-x86 windows fuzz-harness tests/fuzz/fuzz_cypher.c T1-tests false lint lint-mem security-static shard-completeness unix-macos14 unix-x86 windows docs-plus-tests README.md tests/test_store.c T1-tests false lint lint-mem security-static shard-completeness unix-macos14 unix-x86 windows -lsp-language internal/cbm/lsp/go_lsp.c T2-extract false lint lint-mem security-static shard-completeness smoke-ubuntu tsan-x86 unix-macos14 unix-x86 windows codeql-cpp -lsp-core internal/cbm/lsp/type_registry.c T2-extract false lint lint-mem security-static shard-completeness smoke-ubuntu tsan-x86 unix-macos14 unix-x86 windows codeql-cpp -grammar-is-extract-not-full internal/cbm/vendored/grammars/go/parser.c T2-extract false lint lint-mem security-static shard-completeness smoke-ubuntu tsan-x86 unix-macos14 unix-x86 windows codeql-cpp -extraction internal/cbm/extract_calls.c T2-extract false lint lint-mem security-static shard-completeness smoke-ubuntu tsan-x86 unix-macos14 unix-x86 windows codeql-cpp -pipeline src/pipeline/pass_calls.c T3-mid false lint lint-mem security-static shard-completeness smoke-ubuntu tsan-x86 unix-macos14 unix-x86 windows codeql-cpp -mcp-plus-tests src/mcp/mcp.c tests/test_mcp.c T3-mid false lint lint-mem security-static shard-completeness smoke-ubuntu tsan-x86 unix-macos14 unix-x86 windows codeql-cpp -mid-plus-memwaste-tools src/store/store.c scripts/memwaste-report.py T3-mid false lint lint-mem memwaste security-static shard-completeness smoke-ubuntu tsan-x86 unix-macos14 unix-x86 windows codeql-cpp codeql-py +lsp-language internal/cbm/lsp/go_lsp.c T2-extract false glibc-floor lint lint-mem security-static shard-completeness smoke-ubuntu tsan-x86 unix-macos14 unix-x86 windows codeql-cpp +lsp-core internal/cbm/lsp/type_registry.c T2-extract false glibc-floor lint lint-mem security-static shard-completeness smoke-ubuntu tsan-x86 unix-macos14 unix-x86 windows codeql-cpp +grammar-is-extract-not-full internal/cbm/vendored/grammars/go/parser.c T2-extract false glibc-floor lint lint-mem security-static shard-completeness smoke-ubuntu tsan-x86 unix-macos14 unix-x86 windows codeql-cpp +extraction internal/cbm/extract_calls.c T2-extract false glibc-floor lint lint-mem security-static shard-completeness smoke-ubuntu tsan-x86 unix-macos14 unix-x86 windows codeql-cpp +pipeline src/pipeline/pass_calls.c T3-mid false glibc-floor lint lint-mem security-static shard-completeness smoke-ubuntu tsan-x86 unix-macos14 unix-x86 windows codeql-cpp +mcp-plus-tests src/mcp/mcp.c tests/test_mcp.c T3-mid false glibc-floor lint lint-mem security-static shard-completeness smoke-ubuntu tsan-x86 unix-macos14 unix-x86 windows codeql-cpp +mid-plus-memwaste-tools src/store/store.c scripts/memwaste-report.py T3-mid false glibc-floor lint lint-mem memwaste security-static shard-completeness smoke-ubuntu tsan-x86 unix-macos14 unix-x86 windows codeql-cpp codeql-py daemon src/daemon/application.c T4-platform false FULL -msan -diag -memwaste -codeql-js -codeql-py -codeql-actions main-c-is-cli src/main.c T4-platform false FULL -msan -diag -memwaste -codeql-js -codeql-py -codeql-actions foundation-non-memory src/foundation/log.c T4-platform false FULL -msan -diag -memwaste -codeql-js -codeql-py -codeql-actions @@ -59,7 +59,7 @@ harness-driver-forces-full scripts/test.sh T5-core true FULL harness-scheduler-forces-full scripts/run-test-wave.py T5-core true FULL lower-tier-plus-trigger-is-full docs/x.md src/pipeline/a.c tests/test_main.c T3-mid true FULL empty-list-fails-safe - T0a-docs true FULL -graph-ui-python-agent graph-ui/agents/agent.py T0b-nonproduct false codeql-py contracts lint lint-mem security-static smoke-ubuntu windows-guards -vendored-js-is-not-scanned graph-ui/vendor/lib.js T0b-nonproduct false contracts lint lint-mem security-static smoke-ubuntu windows-guards +graph-ui-python-agent graph-ui/agents/agent.py T0b-nonproduct false codeql-py contracts glibc-floor lint lint-mem security-static smoke-ubuntu windows-guards +vendored-js-is-not-scanned graph-ui/vendor/lib.js T0b-nonproduct false contracts glibc-floor lint lint-mem security-static smoke-ubuntu windows-guards vendored-only-c-scans-c vendored/sqlite3/sqlite3.c T5-core true FULL codeql-config-is-full .github/codeql/codeql-config.yml T5-core true FULL diff --git a/tests/test_lane_gate_contract.sh b/tests/test_lane_gate_contract.sh index f016f2e99..9078780d5 100755 --- a/tests/test_lane_gate_contract.sh +++ b/tests/test_lane_gate_contract.sh @@ -33,9 +33,14 @@ trap 'rm -rf "$WORK"' EXIT failures=0 # ── ci-ok ──────────────────────────────────────────────────────────────── -NEEDS_OK='{"changes":{"result":"success"},"security":{"result":"success"},"lint":{"result":"success"},"test":{"result":"success"},"pr-smoke":{"result":"skipped"},"contracts":{"result":"skipped"}}' -NEEDS_RED='{"changes":{"result":"success"},"security":{"result":"success"},"lint":{"result":"failure"},"test":{"result":"skipped"},"pr-smoke":{"result":"skipped"},"contracts":{"result":"skipped"}}' +NEEDS_OK='{"changes":{"result":"success"},"security":{"result":"success"},"lint":{"result":"success"},"test":{"result":"success"},"pr-smoke":{"result":"skipped"},"contracts":{"result":"skipped"},"glibc-floor":{"result":"skipped"}}' +NEEDS_RED='{"changes":{"result":"success"},"security":{"result":"success"},"lint":{"result":"failure"},"test":{"result":"skipped"},"pr-smoke":{"result":"skipped"},"contracts":{"result":"skipped"},"glibc-floor":{"result":"skipped"}}' +NEEDS_T3='{"changes":{"result":"success"},"security":{"result":"success"},"lint":{"result":"success"},"test":{"result":"success"},"pr-smoke":{"result":"success"},"contracts":{"result":"skipped"},"glibc-floor":{"result":"success"}}' +NEEDS_FLOOR_RED='{"changes":{"result":"success"},"security":{"result":"success"},"lint":{"result":"success"},"test":{"result":"success"},"pr-smoke":{"result":"success"},"contracts":{"result":"skipped"},"glibc-floor":{"result":"failure"}}' T1='["lint","lint-mem","security-static","shard-completeness","unix-macos14","unix-x86","windows"]' +# A product change: the Ubuntu smoke leg and, riding with it, the gating +# release-container build (glibc-floor). +T3='["glibc-floor","lint","lint-mem","security-static","shard-completeness","smoke-ubuntu","unix-macos14","unix-x86","windows"]' job() { # name conclusion [status] printf '{"name":"%s","status":"%s","conclusion":%s}\n' "$1" "${3:-completed}" \ @@ -60,10 +65,16 @@ t1_jobs() { # the jobs of a green T1 run; $1 = conclusion of the x86 leg job "test / shard-completeness" success job "pr-smoke" skipped job "contracts" skipped + job "glibc-floor" skipped job "memwaste" skipped job "test-impact-shadow" failure job "ci-ok" null in_progress } +t3_jobs() { # a green T3 run's jobs; $1 = glibc-floor's conclusion, or "absent" + t1_jobs success | grep -v -e '"name":"pr-smoke"' -e '"name":"glibc-floor"' + job "pr-smoke (ubuntu-latest, smoke-ubuntu)" success + [ "$1" = absent ] || job "glibc-floor" "$1" +} gate() { # description want needs lanes jobs-file|- local description="$1" want="$2" needs="$3" lanes="$4" jobs="$5" got @@ -109,6 +120,28 @@ gate "an unknown lane is red" 1 "$NEEDS_OK" '["lint","security-static","no-such- gate "a malformed selection is red" 1 "$NEEDS_OK" 'lint,unix-x86' "$WORK/t1-green" gate "a selection without the job list is red" 1 "$NEEDS_OK" "$T1" "$WORK/does-not-exist" +# glibc-floor GATES (user decision R103, #2887): selected with smoke-ubuntu, it +# must run and succeed like any lane; unselected (docs, tests) it may skip. +t3_jobs success >"$WORK/t3-green" +t3_jobs failure >"$WORK/t3-floor-failed" +t3_jobs skipped >"$WORK/t3-floor-skipped" +t3_jobs cancelled >"$WORK/t3-floor-cancelled" +t3_jobs absent >"$WORK/t3-floor-absent" +{ + job "changes" success + job "security / security-static" success + job "contracts" success + job "glibc-floor" skipped + job "ci-ok" null in_progress +} >"$WORK/docs-floor-skipped" +gate "glibc-floor selected and green" 0 "$NEEDS_T3" "$T3" "$WORK/t3-green" +gate "a selected glibc-floor that FAILED is red" 1 "$NEEDS_T3" "$T3" "$WORK/t3-floor-failed" +gate "a selected glibc-floor that was skipped is red" 1 "$NEEDS_T3" "$T3" "$WORK/t3-floor-skipped" +gate "a selected glibc-floor that was cancelled is red" 1 "$NEEDS_T3" "$T3" "$WORK/t3-floor-cancelled" +gate "a selected glibc-floor with no job at all is red" 1 "$NEEDS_T3" "$T3" "$WORK/t3-floor-absent" +gate "an unselected glibc-floor (docs-only) may skip" 0 "$NEEDS_OK" '["contracts","security-static"]' "$WORK/docs-floor-skipped" +gate "a failed glibc-floor fails the needs-level gate" 1 "$NEEDS_FLOOR_RED" - - + # Matrix jobs carry their lane in the name; the neighbouring single job # (test-windows-guards) must never be read as the windows leg. { diff --git a/tests/test_lane_wiring_contract.sh b/tests/test_lane_wiring_contract.sh index bd49dc2f7..56763f3a1 100755 --- a/tests/test_lane_wiring_contract.sh +++ b/tests/test_lane_wiring_contract.sh @@ -8,8 +8,9 @@ # before lane selection. Only pr.yml asks for the PR shard profile. # 2. pr.yml feeds the selector's lanes to every reusable workflow, runs the # T0a/T0b contracts job, and hands ci-ok both the selection and the -# run's job list (actions: read) -- ci-ok needs `contracts` but never the -# report-only memwaste / shadow jobs, and the shadow is continue-on-error. +# run's job list (actions: read) -- ci-ok needs `contracts` and the +# gating `glibc-floor` but never the report-only memwaste / shadow jobs, +# and the shadow is continue-on-error. # 3. every lane of scripts/ci/select-lanes.sh --list-lanes is served by a # job: matrix lanes as a "lane":"" tag in the leg data (the tag # ci-ok reads back from the job name), single lanes by an `if:` that @@ -102,10 +103,14 @@ require("scripts/test.sh --contracts-only" in pr.get("contracts", ""), ci_ok = pr.get("ci-ok", "") needs = re.search(r"needs: \[([^\]]*)\]", ci_ok) needs = {n.strip() for n in needs.group(1).split(",")} if needs else set() -require({"changes", "security", "lint", "test", "pr-smoke", "contracts"} <= needs, - f"ci-ok must need every gating stage incl. contracts, has {sorted(needs)}") +require({"changes", "security", "lint", "test", "pr-smoke", "contracts", "glibc-floor"} <= needs, + f"ci-ok must need every gating stage incl. contracts and glibc-floor, has {sorted(needs)}") require(not needs & {"memwaste", "test-impact-shadow"}, "ci-ok must not need the report-only memwaste / shadow jobs") +# glibc-floor gates (user decision R103, #2887): no continue-on-error may turn +# the release-container build back into a report. +require("continue-on-error" not in pr.get("glibc-floor", "continue-on-error"), + "pr.yml glibc-floor must exist and gate: no continue-on-error") for needle, why in (("LANES: ${{ needs.changes.outputs.lanes }}", "the selection"), ("JOBS: ", "the run's job list"), ("actions: read", "actions: read to list jobs"), @@ -164,7 +169,8 @@ SINGLE = {"lint": ("_lint.yml", "lint"), "lint-mem": ("_lint.yml", "lint-mem"), "diag": ("_test.yml", "test-diag"), "msan": ("_test.yml", "test-msan"), "lsan-macos": ("_test.yml", "test-lsan-macos"), "windows-guards": ("_test.yml", "test-windows-guards"), - "memwaste": ("pr.yml", "memwaste"), "contracts": ("pr.yml", "contracts")} + "memwaste": ("pr.yml", "memwaste"), "contracts": ("pr.yml", "contracts"), + "glibc-floor": ("pr.yml", "glibc-floor")} ALWAYS = {"security-static": ("_security.yml", "security-static"), "shard-completeness": ("_test.yml", "shard-completeness")} for lane in lanes: diff --git a/tests/test_select_lanes.sh b/tests/test_select_lanes.sh index 4c8183fdc..19b8b8f44 100755 --- a/tests/test_select_lanes.sh +++ b/tests/test_select_lanes.sh @@ -10,7 +10,9 @@ # - determinism: order and duplicates never change a byte of the output; # - one decision per input channel: stdin, a file, --batch and the # github-output form all agree; -# - a truncated file list (the pulls/files API stops at 3000) selects FULL. +# - a truncated file list (the pulls/files API stops at 3000) selects FULL; +# - glibc-floor (the gating release-container build, pr.yml) is selected +# exactly when smoke-ubuntu is, in every case. # The history replay (every September PR push + the real failures) is # scripts/test-impact/replay-selector.sh, a separate step. # @@ -48,7 +50,7 @@ failures = [] # Every lane of the full PR pipeline, spelled out here rather than read from # the selector, so a lane silently dropped from the selector's universe fails. FULL = { - "diag", "license-gate", "lint", "lint-mem", "lsan-macos", + "diag", "glibc-floor", "license-gate", "lint", "lint-mem", "lsan-macos", "memwaste", "msan", "pkg-wrappers", "security-static", "shard-completeness", "smoke-mac", "smoke-ubuntu", "smoke-win", "tsan-arm", "tsan-mac", "tsan-x86", "unix-arm64", "unix-macos-intel", @@ -106,6 +108,12 @@ for name, files, tier, full, lanes in cases: failures.append(f"{name}: lanes/reasons are not sorted") if full and not any(r.startswith("full:") for r in got["reasons"]): failures.append(f"{name}: full=true without a 'full:' reason") + # The release-container build gates whenever the production Linux binary + # can change -- the same condition that selects the Ubuntu smoke leg. + if ("glibc-floor" in got["lanes"]) != ("smoke-ubuntu" in got["lanes"]): + failures.append(f"{name}: glibc-floor must be selected exactly when smoke-ubuntu is, " + f"got glibc-floor={'glibc-floor' in got['lanes']} " + f"smoke-ubuntu={'smoke-ubuntu' in got['lanes']}") # 2. Determinism: shuffled, duplicated, CRLF-terminated input with blank lines # is byte-identical to the canonical run.