From 4aa99b0f99b9e38fcf8c82c7462cebdc72528836 Mon Sep 17 00:00:00 2001 From: Anton Krivoborodov Date: Tue, 22 Sep 2026 10:52:33 +0000 Subject: [PATCH 1/3] ci: trust bazel cache for unit tests except on push to main - quality_runners.py: add --trust-cache flag; when set, drop --nocache_test_results so Bazel can reuse cached test/coverage results for modules unaffected by the current change. - test_and_docs.yml: pass --trust-cache for every event except 'push' (the only trigger that saves the disk-cache, updates the published coverage numbers, and runs docs-deploy with authoritative results). PR/merge_group/release/workflow_dispatch runs benefit from Bazel's own cache for unaffected modules and get fast feedback; push to main always re-measures every module fresh, so the numbers that get published/cached are never based on a stale cached test result. --- .github/workflows/test_and_docs.yml | 3 ++- scripts/quality_runners.py | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test_and_docs.yml b/.github/workflows/test_and_docs.yml index 3cdebe0084f..9a679d30b99 100644 --- a/.github/workflows/test_and_docs.yml +++ b/.github/workflows/test_and_docs.yml @@ -82,7 +82,8 @@ jobs: rm -rf _build/ - name: Execute Unit Tests with Coverage Analysis run: | - python ./scripts/quality_runners.py + python ./scripts/quality_runners.py \ + ${{ github.event_name != 'push' && '--trust-cache' || '' }} - name: Execute Feature Integration Tests run: | bazel test --config=linux-x86_64 //feature_integration_tests/test_cases:fit diff --git a/scripts/quality_runners.py b/scripts/quality_runners.py index 8c1215bd087..f81333da409 100644 --- a/scripts/quality_runners.py +++ b/scripts/quality_runners.py @@ -59,7 +59,7 @@ def configure_aslr_for_sanitizers() -> None: print(f"QR: Could not lower vm.mmap_rnd_bits (continuing anyway): {result.stderr.strip()}") -def run_unit_test_with_coverage(module: Module) -> dict[str, str | int]: +def run_unit_test_with_coverage(module: Module, trust_cache: bool = False) -> dict[str, str | int]: print_centered("QR: Running unit tests") call = ( @@ -72,7 +72,9 @@ def run_unit_test_with_coverage(module: Module) -> dict[str, str | int]: "--config=ferrocene-coverage", "--test_summary=testcase", "--test_output=errors", - "--nocache_test_results", + ] + + ([] if trust_cache else ["--nocache_test_results"]) + + [ f"--instrumentation_filter=@{module.name}", f"@{module.name}{module.metadata.code_root_path}", ] @@ -302,6 +304,13 @@ def parse_arguments() -> argparse.Namespace: default=[], help="List of modules to test", ) + parser.add_argument( + "--trust-cache", + action="store_true", + help="Allow Bazel to reuse cached test/coverage results for unchanged modules instead of always " + "re-executing them (--nocache_test_results). Intended for fast PR-iteration checks; authoritative " + "runs (e.g. on push to main) should NOT set this, so coverage numbers are always freshly measured.", + ) return parser.parse_args() @@ -325,7 +334,7 @@ def main() -> bool: continue print_centered(f"QR: Testing module: {module.name}") - unit_tests_summary[module.name] = run_unit_test_with_coverage(module=module) + unit_tests_summary[module.name] = run_unit_test_with_coverage(module=module, trust_cache=args.trust_cache) if "cpp" in module.metadata.langs: coverage_summary[f"{module.name}_cpp"] = run_cpp_coverage_extraction( From ff857c4fb363b7488908f529f26cfbc70f333054 Mon Sep 17 00:00:00 2001 From: Anton Krivoborodov Date: Tue, 22 Sep 2026 10:53:23 +0000 Subject: [PATCH 2/3] ci: checkout repository before Setup Bazel so disk-cache/repository-cache hashes reflect real content bazel-contrib/setup-bazel computes its disk-cache and repository-cache keys by hashing MODULE.bazel/WORKSPACE*/BUILD*/BUILD.bazel files found under the job's current working directory at the time it runs. Since Setup Bazel previously ran before any checkout step, that directory was always empty at hash-computation time, making the resulting cache key constant regardless of actual repo content - verified empirically by pushing real content changes to MODULE.bazel and a BUILD file on a fork and observing the restore-key hash stay identical across both. Practical consequence: once a cache entry existed under that constant key, every subsequent job's cache-save silently failed ('another job may be creating this cache', GitHub cache entries are immutable per key), freezing the disk-cache/repository-cache at whatever state they were in on their first successful save - never picking up newer external dependency pins or build graph changes since. This did not affect build correctness (Bazel's own per-action content hashing, computed after checkout against the real current sources, still governs whether any individual action result is reused), only cache freshness/size and the fraction of the build that has to run cold. Moving the checkout steps ahead of Setup Bazel lets the hash actually reflect the repository state at each run, allowing the cache to evolve normally instead of staying pinned to its first snapshot. --- .github/workflows/test_and_docs.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test_and_docs.yml b/.github/workflows/test_and_docs.yml index 9a679d30b99..1a88883f47b 100644 --- a/.github/workflows/test_and_docs.yml +++ b/.github/workflows/test_and_docs.yml @@ -43,6 +43,15 @@ jobs: uses: eclipse-score/more-disk-space@v1.1 with: level: 4 + - name: Checkout repository (pull_request_target via workflow_call) + if: ${{ github.event_name == 'pull_request_target' }} + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + ref: ${{ github.head_ref || github.event.pull_request.head.ref || github.ref }} + repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} + - name: Checkout repository + if: ${{ github.event_name != 'pull_request_target' }} + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Setup Bazel uses: bazel-contrib/setup-bazel@0.18.0 with: @@ -63,15 +72,6 @@ jobs: with: packages: graphviz cache: false - - name: Checkout repository (pull_request_target via workflow_call) - if: ${{ github.event_name == 'pull_request_target' }} - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - with: - ref: ${{ github.head_ref || github.event.pull_request.head.ref || github.ref }} - repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} - - name: Checkout repository - if: ${{ github.event_name != 'pull_request_target' }} - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Build documentation - preflight # If we have doc errors, we dont wait of them 1h +... run: | bazel run \ From 94d74a42c1f90cc1e542dda3452df4e9c9f7e967 Mon Sep 17 00:00:00 2001 From: Anton Krivoborodov Date: Tue, 22 Sep 2026 11:36:40 +0000 Subject: [PATCH 3/3] ci: also treat release events as authoritative (no --trust-cache) release builds attach the test-report ZIP and build-tools SBOM as official downloadable assets on the GitHub Release (see the "Create archive of test reports" / "Upload release asset" steps, gated on github.ref_type == 'tag'). Those published artifacts should reflect a real, freshly-executed test run, not a potentially stale cached result - same reasoning as for push to main. --- .github/workflows/test_and_docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test_and_docs.yml b/.github/workflows/test_and_docs.yml index 1a88883f47b..5c669616987 100644 --- a/.github/workflows/test_and_docs.yml +++ b/.github/workflows/test_and_docs.yml @@ -83,7 +83,7 @@ jobs: - name: Execute Unit Tests with Coverage Analysis run: | python ./scripts/quality_runners.py \ - ${{ github.event_name != 'push' && '--trust-cache' || '' }} + ${{ github.event_name != 'push' && github.event_name != 'release' && '--trust-cache' || '' }} - name: Execute Feature Integration Tests run: | bazel test --config=linux-x86_64 //feature_integration_tests/test_cases:fit