diff --git a/.github/workflows/ci-pr.yml b/.github/workflows/ci-pr.yml index ca76266b4..86eba5d10 100644 --- a/.github/workflows/ci-pr.yml +++ b/.github/workflows/ci-pr.yml @@ -5,12 +5,12 @@ name: CI PR # Execution flow: # rc-changelog-freeze # └─ lint -# ├─ tsan-native (ubuntu-latest gcc, advisory compile smoke) +# ├─ tsan-native (ubuntu-26.04 gcc, advisory compile smoke) # │ └─ sanitizer-matrix (multi-OS ASan/UBSan) -# └─ tsan (ubuntu-latest gcc+clang, gating TSan) -# └─ build-primary (ubuntu-latest gcc, SBOM/size/path-a gates) -# ├─ build-arm (ubuntu-24.04-arm gcc, ABI #681) ──┐ -# └─ build-mbedtls (ubuntu-latest gcc, crypto) ──┼─ build-matrix +# └─ tsan (ubuntu-26.04 gcc+clang, gating TSan) +# └─ build-primary (ubuntu-26.04 gcc, SBOM/size/path-a gates) +# ├─ build-arm (ubuntu-26.04-arm gcc, ABI #681) ──┐ +# └─ build-mbedtls (ubuntu-26.04 gcc, crypto) ──┼─ build-matrix # # CodeQL runs in its own workflow (GitHub default-setup); not invoked from here. @@ -45,7 +45,7 @@ jobs: # ========================================================================== build-scope: name: Detect build-triggering changes - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 outputs: build-required: ${{ steps.scope.outputs.build-required }} steps: @@ -95,7 +95,7 @@ jobs: # otherwise registered inside the skipped Meson build. docs-policy: name: Docs / policy validation - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - uses: actions/checkout@v5 with: @@ -110,7 +110,7 @@ jobs: # ========================================================================== rc-changelog-freeze: name: RC changelog freeze gate - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - uses: actions/checkout@v5 with: @@ -132,16 +132,16 @@ jobs: base-ref: ${{ github.base_ref }} # ========================================================================== - # Phase 2a: Build Primary (ubuntu-latest + gcc) + # Phase 2a: Build Primary (ubuntu-26.04 + gcc) # Fast-path job that gates critical checks: SBOM, binary size, Path A example. # Gated on the tsan leg; the first build to run and unblocks build-arm, # build-mbedtls, and build-matrix. # ========================================================================== build-primary: - name: Build / ubuntu-latest / gcc + name: Build / ubuntu-26.04 / gcc needs: [tsan, build-scope] if: ${{ needs.build-scope.outputs.build-required == 'true' }} - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 strategy: fail-fast: false steps: @@ -155,10 +155,7 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y ninja-build libmbedtls-dev uncrustify abigail-tools b3sum - - - name: Set up Meson 1.12.0 - uses: ./.github/actions/setup-meson + sudo apt-get install -y meson ninja-build libmbedtls-dev uncrustify abigail-tools b3sum - name: Configure run: meson setup builddir -Dtests=true -DmbedTLS=disabled @@ -308,14 +305,14 @@ jobs: ./builddir-path-a/pcap_skeleton # ========================================================================== - # Phase: Build ARM (ubuntu-24.04-arm gcc, gated by build-primary) + # Phase: Build ARM (ubuntu-26.04-arm gcc, gated by build-primary) # ABI compliance check (#681); must pass before build-matrix proceeds. # ========================================================================== build-arm: - name: Build / ubuntu-24.04-arm / gcc + name: Build / ubuntu-26.04-arm / gcc needs: [build-primary, build-scope] if: ${{ needs.build-scope.outputs.build-required == 'true' }} - runs-on: ubuntu-24.04-arm + runs-on: ubuntu-26.04-arm strategy: fail-fast: false @@ -325,10 +322,7 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y ninja-build libmbedtls-dev uncrustify abigail-tools b3sum - - - name: Set up Meson 1.12.0 - uses: ./.github/actions/setup-meson + sudo apt-get install -y meson ninja-build libmbedtls-dev uncrustify abigail-tools b3sum - name: Configure run: meson setup builddir -Dtests=true -DmbedTLS=disabled @@ -360,7 +354,7 @@ jobs: matrix: include: # Linux - Clang - - os: ubuntu-latest + - os: ubuntu-26.04 compiler: clang cc: clang timeout_minutes: 60 @@ -384,7 +378,7 @@ jobs: if: runner.os == 'Linux' run: | sudo apt-get update - sudo apt-get install -y ninja-build libmbedtls-dev uncrustify abigail-tools b3sum + sudo apt-get install -y meson ninja-build libmbedtls-dev uncrustify abigail-tools b3sum if [ "${{ matrix.compiler }}" = "clang" ]; then sudo apt-get install -y clang fi @@ -392,20 +386,64 @@ jobs: - name: Install dependencies (macOS) if: runner.os == 'macOS' run: | - brew install ninja mbedtls uncrustify coreutils gnu-tar + brew install meson ninja mbedtls uncrustify coreutils gnu-tar echo "$(brew --prefix coreutils)/libexec/gnubin" >> "$GITHUB_PATH" echo "$(brew --prefix gnu-tar)/libexec/gnubin" >> "$GITHUB_PATH" - name: Install dependencies (Windows) if: runner.os == 'Windows' + shell: pwsh run: | # pkgconfiglite ships from a SourceForge mirror without a # checksum; chocolatey's default policy now blocks empty # checksums for non-secure sources, so opt in explicitly. - choco install pkgconfiglite uncrustify -y --allow-empty-checksums - - - name: Set up Meson 1.12.0 - uses: ./.github/actions/setup-meson + # `ninja` is named explicitly: meson requires it to spawn the + # build, and the block below verifies it is resolvable no + # matter how each package delivered it. + choco install pkgconfiglite uncrustify meson ninja -y --allow-empty-checksums + if ($LASTEXITCODE -ne 0) { throw "choco install failed with exit code $LASTEXITCODE" } + # The Chocolatey meson package is an MSI. Its PATH entry goes + # into the machine registry, which this job's already-snapshotted + # environment never sees, and chocolatey creates no shim for it, + # so `meson` was unresolvable in every later step (run + # 40932685444). Resolve the install directory (chocolatey's own + # registry record first, the MSI's documented default locations + # next, filesystem search last) and publish it on GITHUB_PATH + # -- the same mechanism the former setup-meson action used. + # Fails loudly if either tool is missing or broken. + $mesonDir = $null + $reg = Get-ItemProperty 'HKLM:\SOFTWARE\chocolatey\installed\meson' -ErrorAction SilentlyContinue + if ($reg.installLocation -and (Test-Path (Join-Path $reg.installLocation 'meson.exe'))) { + $mesonDir = $reg.installLocation + } + if (-not $mesonDir) { + foreach ($cand in 'C:\Program Files\Meson', 'C:\Program Files\meson', 'C:\Program Files (x86)\Meson', 'C:\Program Files (x86)\meson') { + if (Test-Path (Join-Path $cand 'meson.exe')) { $mesonDir = $cand; break } + } + } + if (-not $mesonDir) { + $found = Get-ChildItem 'C:\Program Files', 'C:\Program Files (x86)' -Recurse -Filter 'meson.exe' -ErrorAction SilentlyContinue | + Select-Object -First 1 + if (-not $found) { throw 'chocolatey reported success but meson.exe was not found' } + $mesonDir = Split-Path $found.FullName -Parent + } + $mesonExe = Join-Path $mesonDir 'meson.exe' + if (-not (Test-Path $mesonExe)) { throw "meson.exe not found in $mesonDir" } + & $mesonExe --version + if ($LASTEXITCODE -ne 0) { throw "meson --version failed with exit code $LASTEXITCODE" } + # GITHUB_PATH changes only take effect in later steps, so verify + # ninja here by full path (MSI-bundled copy) or via the + # chocolatey shim if the package delivered it that way. + $ninjaExe = Join-Path $mesonDir 'ninja.exe' + if (Test-Path $ninjaExe) { + & $ninjaExe --version + if ($LASTEXITCODE -ne 0) { throw "ninja --version failed with exit code $LASTEXITCODE" } + } else { + if (-not (Get-Command ninja -ErrorAction SilentlyContinue)) { throw 'ninja was not resolvable after chocolatey install' } + ninja --version + if ($LASTEXITCODE -ne 0) { throw "ninja --version failed with exit code $LASTEXITCODE" } + } + Add-Content $env:GITHUB_PATH $mesonDir - name: Configure (Linux / macOS) if: runner.os != 'Windows' @@ -520,10 +558,10 @@ jobs: # Optional crypto coverage; must pass before build-matrix proceeds. # ========================================================================== build-mbedtls: - name: mbedtls-enabled / ubuntu-latest / gcc + name: mbedtls-enabled / ubuntu-26.04 / gcc needs: [build-primary, build-scope] if: ${{ needs.build-scope.outputs.build-required == 'true' }} - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - uses: actions/checkout@v5 @@ -531,10 +569,7 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y ninja-build pkg-config libmbedtls-dev - - - name: Set up Meson 1.12.0 - uses: ./.github/actions/setup-meson + sudo apt-get install -y meson ninja-build pkg-config libmbedtls-dev - name: Configure with mbedTLS enabled run: meson setup builddir-mbedtls -Dtests=true -DmbedTLS=enabled @@ -548,9 +583,10 @@ jobs: # WL_MBEDTLS_ENABLED consumer, a project-wide argument (meson.build) # -- so compiling exactly those two targets preserves the # crypto-compiler coverage this job uniquely asserts; the rest of the - # default target set is covered by the other jobs. Verified on meson - # 1.12.0: `meson compile -C dir ` builds only the named - # targets. + # default target set is covered by the other jobs. The scoping + # behaviour below was verified on meson 1.12.0: `meson compile -C dir + # ` builds only the named targets (the PR runner's own meson + # version is the distro-provided one and is not pinned). - name: Build with mbedTLS enabled run: meson compile -C builddir-mbedtls test_cryptographic_hashes test_symbol_digests @@ -583,17 +619,17 @@ jobs: matrix: include: # Linux - GCC - - os: ubuntu-latest + - os: ubuntu-26.04 compiler: gcc cc: gcc # Linux - Clang - - os: ubuntu-latest + - os: ubuntu-26.04 compiler: clang cc: clang # Linux ARM64 - GCC - - os: ubuntu-24.04-arm + - os: ubuntu-26.04-arm compiler: gcc cc: gcc @@ -612,7 +648,7 @@ jobs: if: runner.os == 'Linux' run: | sudo apt-get update - sudo apt-get install -y ninja-build libmbedtls-dev abigail-tools + sudo apt-get install -y meson ninja-build libmbedtls-dev abigail-tools if [ "${{ matrix.compiler }}" = "clang" ]; then sudo apt-get install -y clang fi @@ -620,13 +656,10 @@ jobs: - name: Install dependencies (macOS) if: runner.os == 'macOS' run: | - brew install ninja mbedtls coreutils gnu-tar + brew install meson ninja mbedtls coreutils gnu-tar echo "$(brew --prefix coreutils)/libexec/gnubin" >> "$GITHUB_PATH" echo "$(brew --prefix gnu-tar)/libexec/gnubin" >> "$GITHUB_PATH" - - name: Set up Meson 1.12.0 - uses: ./.github/actions/setup-meson - - name: Configure with sanitizers run: > meson setup builddir-san @@ -652,8 +685,15 @@ jobs: - name: Test with sanitizers # The required, pinned-Syft SBOM gate runs in build-primary. A host - # Syft here can make its redundant scan time out under sanitizer load. - run: meson test -C builddir-san --exclude sbom_snapshot --print-errorlogs + # Syft here can make its redundant scan time out under sanitizer load, + # so sbom_snapshot is excluded here; every other test still runs, + # including the other sbom-suite contract checks. + # Distro meson (1.10.x on ubuntu-26.04) has no `meson test --exclude`, + # and `--no-suite sbom` would drop those contract checks too, so the + # exclusion is built from the configured test list at run time. + run: | + rest="$(meson introspect --tests builddir-san | jq -r '.[].name' | grep -Fxv sbom_snapshot | tr '\n' ' ')" + meson test -C builddir-san $rest --print-errorlogs env: ASAN_OPTIONS: abort_on_error=1:halt_on_error=1 UBSAN_OPTIONS: abort_on_error=1:halt_on_error=1:print_stacktrace=1 @@ -667,10 +707,10 @@ jobs: # (thrd_create, mtx_lock) are uninstrumented and cause false SEGV. # ========================================================================== tsan: - name: TSan / ubuntu-latest / ${{ matrix.compiler }} + name: TSan / ubuntu-26.04 / ${{ matrix.compiler }} needs: [lint, build-scope] if: ${{ needs.build-scope.outputs.build-required == 'true' }} - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 strategy: fail-fast: false matrix: @@ -689,14 +729,11 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y ninja-build libmbedtls-dev + sudo apt-get install -y meson ninja-build libmbedtls-dev if [ "${{ matrix.compiler }}" = "clang" ]; then sudo apt-get install -y clang fi - - name: Set up Meson 1.12.0 - uses: ./.github/actions/setup-meson - - name: Configure with TSan run: | meson setup builddir-tsan \ @@ -746,10 +783,10 @@ jobs: # - Issue #826 (native C11 runtime under TSan is not a valid signal) # ========================================================================== tsan-native: - name: TSan-native / ubuntu-latest / gcc + name: TSan-native / ubuntu-26.04 / gcc needs: [lint, build-scope] if: ${{ needs.build-scope.outputs.build-required == 'true' }} - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - uses: actions/checkout@v5 @@ -760,10 +797,7 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y ninja-build libmbedtls-dev - - - name: Set up Meson 1.12.0 - uses: ./.github/actions/setup-meson + sudo apt-get install -y meson ninja-build libmbedtls-dev - name: Configure with TSan (native threads) run: | @@ -783,8 +817,10 @@ jobs: # thread_c11.c is a source of the `wirelog` shared-library target, so # compiling exactly that target preserves the documented compile/link # coverage; the remaining default targets' backend coverage comes from - # the ordinary non-TSan matrix. Verified on meson 1.12.0: `meson - # compile -C dir wirelog` builds only the library. + # the ordinary non-TSan matrix. The scoping behaviour below was + # verified on meson 1.12.0: `meson compile -C dir wirelog` builds only + # the library (the PR runner's own meson version is the + # distro-provided one and is not pinned). - name: Build with TSan (native threads) run: meson compile -C builddir-tsan-native wirelog diff --git a/.github/workflows/lint-pr.yml b/.github/workflows/lint-pr.yml index 2b9f7f0fa..1709c1d9a 100644 --- a/.github/workflows/lint-pr.yml +++ b/.github/workflows/lint-pr.yml @@ -24,7 +24,7 @@ jobs: # ========================================================================== editorconfig-check: name: EditorConfig check - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - uses: actions/checkout@v5 @@ -65,7 +65,7 @@ jobs: uncrustify-check: name: uncrustify check needs: [editorconfig-check] - runs-on: ubuntu-latest + runs-on: ubuntu-26.04 steps: - uses: actions/checkout@v5 with: diff --git a/docs/RELEASE_PROCESS.md b/docs/RELEASE_PROCESS.md index 1a9dd4290..6866442a3 100644 --- a/docs/RELEASE_PROCESS.md +++ b/docs/RELEASE_PROCESS.md @@ -417,22 +417,22 @@ the cutover commit sets `project_version` to `1.0.0-rc1`. - Tag protection on `v1.x.*`. - Required status checks: - Stable policy plan: `default`, `abi`, `asan`, `tsan`, `perf`, - plus `mbedtls-enabled / ubuntu-latest / gcc`. + plus `mbedtls-enabled / ubuntu-26.04 / gcc`. - `perf` must be represented by a dedicated always-emitting release perf context that runs with `WIRELOG_PERF_REQUIRE=1`; do not rely on path-filtered contexts for branch protection. - Currently known concrete PR contexts to require at cutover (unless replaced by a later policy-consolidation change): `lint / EditorConfig check`, `lint / uncrustify check`, - `Build / ubuntu-latest / gcc`, `Build / ubuntu-24.04-arm / gcc`, + `Build / ubuntu-26.04 / gcc`, `Build / ubuntu-26.04-arm / gcc`, `RC changelog freeze gate`, `CLA signoff gate`, - `mbedtls-enabled / ubuntu-latest / gcc`, - `Build / ubuntu-latest / clang`, `Build / macos-latest / clang`, + `mbedtls-enabled / ubuntu-26.04 / gcc`, + `Build / ubuntu-26.04 / clang`, `Build / macos-latest / clang`, `Build / windows-latest / msvc`, - `Sanitizers / ubuntu-latest / gcc`, - `Sanitizers / ubuntu-latest / clang`, + `Sanitizers / ubuntu-26.04 / gcc`, + `Sanitizers / ubuntu-26.04 / clang`, `Sanitizers / macos-latest / clang`, - `TSan / ubuntu-latest / gcc`. + `TSan / ubuntu-26.04 / gcc`. #### Synthetic PR verification plan (Phase B) @@ -488,7 +488,7 @@ separate policy change. ### mbedTLS-enabled validation policy The stable check name for optional crypto validation is -`mbedtls-enabled / ubuntu-latest / gcc`. The check is validation +`mbedtls-enabled / ubuntu-26.04 / gcc`. The check is validation coverage only: it proves that `-DmbedTLS=enabled` still configures, builds, and runs the crypto tests against a system PSA Crypto provider. It does **not** change the default release artifact posture, which diff --git a/sbom/snapshot.txt b/sbom/snapshot.txt index 1eb19844e..6d8e7fa69 100644 --- a/sbom/snapshot.txt +++ b/sbom/snapshot.txt @@ -9,7 +9,6 @@ ./.github/actions/setup-meson@UNKNOWN:NOASSERTION ./.github/actions/setup-meson@UNKNOWN:NOASSERTION ./.github/actions/setup-meson@UNKNOWN:NOASSERTION -./.github/actions/setup-meson@UNKNOWN:NOASSERTION ./.github/workflows/fuzz-evidence-shard.yml@UNKNOWN:NOASSERTION ./.github/workflows/lint-main.yml@UNKNOWN:NOASSERTION ./.github/workflows/lint-pr.yml@UNKNOWN:NOASSERTION diff --git a/scripts/ci/check-clang-tidy-ratchet.py b/scripts/ci/check-clang-tidy-ratchet.py index 42e662d99..767112a2d 100755 --- a/scripts/ci/check-clang-tidy-ratchet.py +++ b/scripts/ci/check-clang-tidy-ratchet.py @@ -42,7 +42,7 @@ command line. The architecture guard is not redundant with the OS one: `build-arm` -(`.github/workflows/ci-pr.yml`, ubuntu-24.04-arm) is Linux, uninstrumented +(`.github/workflows/ci-pr.yml`, ubuntu-26.04-arm) is Linux, uninstrumented and not MSVC, and runs an unfiltered `meson test`. It is protected today only by clang-tidy 22 being absent from that image, which is an accident of the runner rather than a guarantee. `clang-analyzer-*` results are diff --git a/scripts/ci/ci-telemetry-fixtures/run-clock-skew-jobs.json b/scripts/ci/ci-telemetry-fixtures/run-clock-skew-jobs.json index a84089b17..6bc1f019f 100644 --- a/scripts/ci/ci-telemetry-fixtures/run-clock-skew-jobs.json +++ b/scripts/ci/ci-telemetry-fixtures/run-clock-skew-jobs.json @@ -6,7 +6,7 @@ "created_at": "2026-09-11T08:33:37Z", "id": 103200409721, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], "name": "RC changelog freeze gate", "runner_group_name": "default", @@ -62,7 +62,7 @@ "created_at": "2026-09-11T08:33:37Z", "id": 103200409951, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], "name": "Detect build-triggering changes", "runner_group_name": "GitHub Actions", @@ -118,7 +118,7 @@ "created_at": "2026-09-11T08:33:37Z", "id": 103200409955, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], "name": "Docs / policy validation", "runner_group_name": "default", @@ -174,7 +174,7 @@ "created_at": "2026-09-11T08:35:15Z", "id": 103200838630, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], "name": "lint / EditorConfig check", "runner_group_name": "GitHub Actions", @@ -246,7 +246,7 @@ "created_at": "2026-09-11T08:43:11Z", "id": 103202876645, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], "name": "lint / uncrustify check", "runner_group_name": "default", diff --git a/scripts/ci/ci-telemetry-fixtures/run-missing-data-jobs.json b/scripts/ci/ci-telemetry-fixtures/run-missing-data-jobs.json index 435cb36b7..dc4ce3d97 100644 --- a/scripts/ci/ci-telemetry-fixtures/run-missing-data-jobs.json +++ b/scripts/ci/ci-telemetry-fixtures/run-missing-data-jobs.json @@ -6,7 +6,7 @@ "created_at": "2026-09-10T10:00:01Z", "id": 301, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], "name": "Detect build-triggering changes", "runner_group_name": "GitHub Actions", @@ -21,9 +21,9 @@ "created_at": "2026-09-10T10:00:11Z", "id": 302, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], - "name": "Build / ubuntu-latest / gcc", + "name": "Build / ubuntu-26.04 / gcc", "runner_group_name": "GitHub Actions", "runner_name": "GitHub Actions 1", "started_at": null, @@ -45,9 +45,9 @@ "created_at": "2026-09-10T10:02:00Z", "id": 303, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], - "name": "TSan / ubuntu-latest / gcc", + "name": "TSan / ubuntu-26.04 / gcc", "runner_group_name": "GitHub Actions", "runner_name": "GitHub Actions 1", "started_at": "2026-09-10T10:02:05Z", diff --git a/scripts/ci/ci-telemetry-fixtures/run-queued-jobs.json b/scripts/ci/ci-telemetry-fixtures/run-queued-jobs.json index 26e5c42ce..e9e6c07c3 100644 --- a/scripts/ci/ci-telemetry-fixtures/run-queued-jobs.json +++ b/scripts/ci/ci-telemetry-fixtures/run-queued-jobs.json @@ -6,7 +6,7 @@ "created_at": "2026-09-10T10:00:01Z", "id": 201, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], "name": "Detect build-triggering changes", "runner_group_name": "GitHub Actions", @@ -54,9 +54,9 @@ "created_at": "2026-09-10T10:00:18Z", "id": 202, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], - "name": "Build / ubuntu-latest / gcc", + "name": "Build / ubuntu-26.04 / gcc", "runner_group_name": "GitHub Actions", "runner_name": "GitHub Actions 1", "started_at": "2026-09-10T10:00:30Z", @@ -94,9 +94,9 @@ "created_at": "2026-09-10T10:00:18Z", "id": 203, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], - "name": "TSan / ubuntu-latest / gcc", + "name": "TSan / ubuntu-26.04 / gcc", "runner_group_name": null, "runner_name": null, "started_at": null, diff --git a/scripts/ci/ci-telemetry-fixtures/run-rerun-jobs.json b/scripts/ci/ci-telemetry-fixtures/run-rerun-jobs.json index 38a585940..4de5cf031 100644 --- a/scripts/ci/ci-telemetry-fixtures/run-rerun-jobs.json +++ b/scripts/ci/ci-telemetry-fixtures/run-rerun-jobs.json @@ -6,7 +6,7 @@ "created_at": "2026-09-10T10:00:01Z", "id": 401, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], "name": "Detect build-triggering changes", "runner_group_name": "GitHub Actions", diff --git a/scripts/ci/ci-telemetry-fixtures/run-skip-cascade-jobs.json b/scripts/ci/ci-telemetry-fixtures/run-skip-cascade-jobs.json index b4f1684c1..a572797e7 100644 --- a/scripts/ci/ci-telemetry-fixtures/run-skip-cascade-jobs.json +++ b/scripts/ci/ci-telemetry-fixtures/run-skip-cascade-jobs.json @@ -6,7 +6,7 @@ "created_at": "2026-09-11T06:49:48Z", "id": 103174726297, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], "name": "Detect build-triggering changes", "runner_group_name": "GitHub Actions", @@ -62,7 +62,7 @@ "created_at": "2026-09-11T06:49:48Z", "id": 103174726522, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], "name": "Docs / policy validation", "runner_group_name": "default", @@ -118,7 +118,7 @@ "created_at": "2026-09-11T06:49:48Z", "id": 103174726533, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], "name": "RC changelog freeze gate", "runner_group_name": "default", @@ -174,7 +174,7 @@ "created_at": "2026-09-11T06:50:17Z", "id": 103174840940, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], "name": "lint / EditorConfig check", "runner_group_name": "default", @@ -246,7 +246,7 @@ "created_at": "2026-09-11T06:50:36Z", "id": 103174910112, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], "name": "lint / uncrustify check", "runner_group_name": "GitHub Actions", @@ -326,9 +326,9 @@ "created_at": "2026-09-11T06:50:52Z", "id": 103174971848, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], - "name": "Sanitizers / ubuntu-latest / clang", + "name": "Sanitizers / ubuntu-26.04 / clang", "runner_group_name": "GitHub Actions", "runner_name": "GitHub Actions 1000055705", "started_at": "2026-09-11T06:50:54Z", @@ -438,9 +438,9 @@ "created_at": "2026-09-11T06:50:52Z", "id": 103174971875, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], - "name": "Build / ubuntu-latest / gcc", + "name": "Build / ubuntu-26.04 / gcc", "runner_group_name": "GitHub Actions", "runner_name": "GitHub Actions 1000055706", "started_at": "2026-09-11T06:50:55Z", @@ -582,9 +582,9 @@ "created_at": "2026-09-11T06:50:52Z", "id": 103174971879, "labels": [ - "ubuntu-24.04-arm" + "ubuntu-26.04-arm" ], - "name": "Sanitizers / ubuntu-24.04-arm / gcc", + "name": "Sanitizers / ubuntu-26.04-arm / gcc", "runner_group_name": "GitHub Actions", "runner_name": "GitHub Actions 1000055708", "started_at": "2026-09-11T06:50:57Z", @@ -694,9 +694,9 @@ "created_at": "2026-09-11T06:50:52Z", "id": 103174971902, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], - "name": "Sanitizers / ubuntu-latest / gcc", + "name": "Sanitizers / ubuntu-26.04 / gcc", "runner_group_name": "GitHub Actions", "runner_name": "GitHub Actions 1000055707", "started_at": "2026-09-11T06:50:55Z", @@ -918,9 +918,9 @@ "created_at": "2026-09-11T07:14:47Z", "id": 103180567746, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], - "name": "mbedtls-enabled / ubuntu-latest / gcc", + "name": "mbedtls-enabled / ubuntu-26.04 / gcc", "runner_group_name": null, "runner_name": null, "started_at": "2026-09-11T07:14:47Z", @@ -933,9 +933,9 @@ "created_at": "2026-09-11T07:14:47Z", "id": 103180567967, "labels": [ - "ubuntu-24.04-arm" + "ubuntu-26.04-arm" ], - "name": "Build / ubuntu-24.04-arm / gcc", + "name": "Build / ubuntu-26.04-arm / gcc", "runner_group_name": null, "runner_name": null, "started_at": "2026-09-11T07:14:47Z", @@ -961,9 +961,9 @@ "created_at": "2026-09-11T07:34:52Z", "id": 103185485539, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], - "name": "TSan / ubuntu-latest / ${{ matrix.compiler }}", + "name": "TSan / ubuntu-26.04 / ${{ matrix.compiler }}", "runner_group_name": null, "runner_name": null, "started_at": "2026-09-11T07:34:52Z", @@ -976,9 +976,9 @@ "created_at": "2026-09-11T07:34:52Z", "id": 103185485852, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], - "name": "TSan-native / ubuntu-latest / gcc", + "name": "TSan-native / ubuntu-26.04 / gcc", "runner_group_name": null, "runner_name": null, "started_at": "2026-09-11T07:34:52Z", diff --git a/scripts/ci/ci-telemetry-fixtures/run-success-jobs.json b/scripts/ci/ci-telemetry-fixtures/run-success-jobs.json index 42ba0b693..eecbde01d 100644 --- a/scripts/ci/ci-telemetry-fixtures/run-success-jobs.json +++ b/scripts/ci/ci-telemetry-fixtures/run-success-jobs.json @@ -6,7 +6,7 @@ "created_at": "2026-09-10T18:27:12Z", "id": 102996159107, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], "name": "Detect build-triggering changes", "runner_group_name": "GitHub Actions", @@ -62,7 +62,7 @@ "created_at": "2026-09-10T18:27:12Z", "id": 102996159417, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], "name": "RC changelog freeze gate", "runner_group_name": "GitHub Actions", @@ -118,7 +118,7 @@ "created_at": "2026-09-10T18:27:12Z", "id": 102996159476, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], "name": "Docs / policy validation", "runner_group_name": "GitHub Actions", @@ -174,7 +174,7 @@ "created_at": "2026-09-10T18:36:40Z", "id": 102999400767, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], "name": "lint / EditorConfig check", "runner_group_name": "default", @@ -246,7 +246,7 @@ "created_at": "2026-09-10T18:50:56Z", "id": 103004228926, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], "name": "lint / uncrustify check", "runner_group_name": "GitHub Actions", @@ -326,9 +326,9 @@ "created_at": "2026-09-10T18:59:50Z", "id": 103007248983, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], - "name": "Build / ubuntu-latest / gcc", + "name": "Build / ubuntu-26.04 / gcc", "runner_group_name": "GitHub Actions", "runner_name": "GitHub Actions 1000054702", "started_at": "2026-09-10T19:22:16Z", @@ -470,9 +470,9 @@ "created_at": "2026-09-10T18:59:50Z", "id": 103007249006, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], - "name": "Sanitizers / ubuntu-latest / gcc", + "name": "Sanitizers / ubuntu-26.04 / gcc", "runner_group_name": "default", "runner_name": "i406-d2", "started_at": "2026-09-10T19:19:52Z", @@ -582,9 +582,9 @@ "created_at": "2026-09-10T18:59:50Z", "id": 103007249042, "labels": [ - "ubuntu-24.04-arm" + "ubuntu-26.04-arm" ], - "name": "Sanitizers / ubuntu-24.04-arm / gcc", + "name": "Sanitizers / ubuntu-26.04-arm / gcc", "runner_group_name": "GitHub Actions", "runner_name": "GitHub Actions 1000054683", "started_at": "2026-09-10T19:14:42Z", @@ -806,9 +806,9 @@ "created_at": "2026-09-10T18:59:50Z", "id": 103007249070, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], - "name": "Sanitizers / ubuntu-latest / clang", + "name": "Sanitizers / ubuntu-26.04 / clang", "runner_group_name": "GitHub Actions", "runner_name": "GitHub Actions 1000054699", "started_at": "2026-09-10T19:21:25Z", @@ -918,9 +918,9 @@ "created_at": "2026-09-10T19:42:53Z", "id": 103021643946, "labels": [ - "ubuntu-24.04-arm" + "ubuntu-26.04-arm" ], - "name": "Build / ubuntu-24.04-arm / gcc", + "name": "Build / ubuntu-26.04-arm / gcc", "runner_group_name": "GitHub Actions", "runner_name": "GitHub Actions 1000054763", "started_at": "2026-09-10T19:45:59Z", @@ -1014,9 +1014,9 @@ "created_at": "2026-09-10T19:42:53Z", "id": 103021644050, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], - "name": "mbedtls-enabled / ubuntu-latest / gcc", + "name": "mbedtls-enabled / ubuntu-26.04 / gcc", "runner_group_name": "default", "runner_name": "semantic-reasoning-i401-6-2", "started_at": "2026-09-10T19:49:48Z", @@ -1414,9 +1414,9 @@ "created_at": "2026-09-10T19:59:58Z", "id": 103027282864, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], - "name": "Build / ubuntu-latest / clang", + "name": "Build / ubuntu-26.04 / clang", "runner_group_name": "default", "runner_name": "semantic-reasoning-i401-6", "started_at": "2026-09-10T20:31:06Z", @@ -1558,9 +1558,9 @@ "created_at": "2026-09-10T20:25:47Z", "id": 103035976181, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], - "name": "TSan / ubuntu-latest / gcc", + "name": "TSan / ubuntu-26.04 / gcc", "runner_group_name": "GitHub Actions", "runner_name": "GitHub Actions 1000054867", "started_at": "2026-09-10T20:52:56Z", @@ -1654,9 +1654,9 @@ "created_at": "2026-09-10T20:25:47Z", "id": 103035976299, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], - "name": "TSan-native / ubuntu-latest / gcc", + "name": "TSan-native / ubuntu-26.04 / gcc", "runner_group_name": "default", "runner_name": "i406-d2", "started_at": "2026-09-10T20:58:46Z", @@ -1750,9 +1750,9 @@ "created_at": "2026-09-10T20:25:47Z", "id": 103035976439, "labels": [ - "ubuntu-latest" + "ubuntu-26.04" ], - "name": "TSan / ubuntu-latest / clang", + "name": "TSan / ubuntu-26.04 / clang", "runner_group_name": "GitHub Actions", "runner_name": "GitHub Actions 1000054868", "started_at": "2026-09-10T20:53:12Z", diff --git a/scripts/ci/collect-size-attribution.py b/scripts/ci/collect-size-attribution.py index a994bdfbb..2b8a6d201 100644 --- a/scripts/ci/collect-size-attribution.py +++ b/scripts/ci/collect-size-attribution.py @@ -111,7 +111,10 @@ def read_policy_identity(repo: Path) -> dict[str, str]: options = (repo / "meson_options.txt").read_text(encoding="utf-8") setup = (repo / ".github/actions/setup-meson/action.yml").read_text(encoding="utf-8") required = ( - ("runs-on: ubuntu-latest", workflow), + # Pins the PR-CI profile token in ci-pr.yml. Deliberately NOT the + # attribution measurement environment, which this file pins to + # ubuntu-24.04 (see require_os_ubuntu_2404 below). + ("runs-on: ubuntu-26.04", workflow), ("meson setup builddir -Dtests=true -DmbedTLS=disabled", workflow), ("meson compile -C builddir wirelog", workflow), ("buildtype=release", meson), diff --git a/scripts/ci/test-ci-pr-build-timeouts.py b/scripts/ci/test-ci-pr-build-timeouts.py index ac5532adb..4be280e34 100644 --- a/scripts/ci/test-ci-pr-build-timeouts.py +++ b/scripts/ci/test-ci-pr-build-timeouts.py @@ -13,7 +13,7 @@ WORKFLOW = Path(__file__).resolve().parents[2] / ".github/workflows/ci-pr.yml" EXPECTED_TIMEOUTS = { - "ubuntu-latest": 60, + "ubuntu-26.04": 60, "macos-latest": 60, "windows-latest": 120, } diff --git a/scripts/ci/test-ci-run-telemetry.py b/scripts/ci/test-ci-run-telemetry.py index 0bd3624b6..6211f4944 100755 --- a/scripts/ci/test-ci-run-telemetry.py +++ b/scripts/ci/test-ci-run-telemetry.py @@ -139,7 +139,7 @@ def _did_not_run_as(self, conclusion, name=None): """A run-success job restamped the way a job that never ran is.""" run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = name or "Sanitizers / ubuntu-latest / clang" + victim = name or "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = conclusion @@ -157,7 +157,7 @@ def test_the_cli_line_names_the_same_division_as_the_report(self): run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) for job in mutated["jobs"]: - if job["name"] == "Sanitizers / ubuntu-latest / clang": + if job["name"] == "Sanitizers / ubuntu-26.04 / clang": job["conclusion"] = "startup_failure" job["started_at"] = job["created_at"] job["completed_at"] = job["created_at"] @@ -268,7 +268,7 @@ def test_a_job_that_did_not_run_cannot_stretch_the_pr_feedback(self): # never-run job's completion stamp is bookkeeping. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = "cancelled" @@ -288,7 +288,7 @@ def test_a_job_that_did_not_run_keeps_its_release_latency(self): # created), so cancelling it still leaves a measurable release latency. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = "TSan-native / ubuntu-latest / gcc" + victim = "TSan-native / ubuntu-26.04 / gcc" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = "cancelled" @@ -317,7 +317,7 @@ def test_a_job_level_conclusion_that_may_precede_dispatch_is_weighed(self): for conclusion in ("neutral", "action_required"): with self.subTest(conclusion=conclusion): mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = conclusion @@ -337,7 +337,7 @@ def test_a_conclusion_this_tool_has_not_met_is_weighed_not_assumed(self): # was written must not default to "ran" and publish a zero. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = "a_conclusion_from_the_future" @@ -359,7 +359,7 @@ def test_an_outcome_only_execution_reaches_is_never_weighed(self): for conclusion in tool.CONCLUSIONS_THAT_RAN: with self.subTest(conclusion=conclusion): mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = conclusion @@ -393,8 +393,8 @@ def test_the_summary_terms_do_not_reorder_with_the_job_list(self): # phrase follows whatever order the API returned the jobs in. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victims = ["Sanitizers / ubuntu-latest / clang", - "Sanitizers / ubuntu-latest / gcc", + victims = ["Sanitizers / ubuntu-26.04 / clang", + "Sanitizers / ubuntu-26.04 / gcc", "Sanitizers / macos-latest / clang"] for name, conclusion in zip(victims, ("startup_failure", "stale", "cancelled")): @@ -424,7 +424,7 @@ def test_an_evidence_decided_conclusion_keeps_its_evidence(self): # with nothing saying so. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = "TSan / ubuntu-latest / clang" # the last to finish + victim = "TSan / ubuntu-26.04 / clang" # the last to finish for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = "stale" @@ -439,7 +439,7 @@ def test_a_job_that_did_not_run_is_not_counted_as_executed(self): # of six blanks below it. One test decides all three. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = "cancelled" @@ -476,7 +476,7 @@ def test_skipped_jobs_have_no_durations(self): def test_first_failure_latency_is_measured(self): metric = self.report["metrics"]["first_failure_latency"] self.assertEqual(metric["status"], "measured") - self.assertEqual(metric["evidence"]["job"], "Build / ubuntu-latest / gcc") + self.assertEqual(metric["evidence"]["job"], "Build / ubuntu-26.04 / gcc") self.assertEqual(metric["seconds"], 1499) # The step matters: a size gate failing at minute 25 is #1573's case, # and the job name alone does not distinguish it from a test failure. @@ -490,7 +490,7 @@ def test_run_wall_clock_ignores_skipped_completions(self): def test_a_skipped_job_reports_no_release_latency(self): by_name = {j["name"]: j for j in self.report["jobs"]} - metric = by_name["Build / ubuntu-24.04-arm / gcc"]["metrics"][ + metric = by_name["Build / ubuntu-26.04-arm / gcc"]["metrics"][ "scheduler_release_latency"] self.assertEqual(metric["status"], "unavailable") self.assertIn("skipped", metric["reason"]) @@ -529,21 +529,21 @@ def test_the_latest_dependency_wins_when_there_are_several(self): def test_a_skipped_dependency_makes_the_latency_unavailable(self): run, jobs = fixture("run-skip-cascade") - needs = {"Build / ubuntu-latest / gcc": [ - "mbedtls-enabled / ubuntu-latest / gcc"]} + needs = {"Build / ubuntu-26.04 / gcc": [ + "mbedtls-enabled / ubuntu-26.04 / gcc"]} report = tool.analyze(run, jobs, needs) by_name = {j["name"]: j for j in report["jobs"]} - metric = by_name["Build / ubuntu-latest / gcc"]["metrics"][ + metric = by_name["Build / ubuntu-26.04 / gcc"]["metrics"][ "scheduler_release_latency"] self.assertEqual(metric["status"], "unavailable") self.assertIn("batched", metric["reason"]) def test_a_dependency_absent_from_the_run_is_named(self): run, jobs = fixture("run-skip-cascade") - needs = {"Build / ubuntu-latest / gcc": ["a job that never ran"]} + needs = {"Build / ubuntu-26.04 / gcc": ["a job that never ran"]} report = tool.analyze(run, jobs, needs) by_name = {j["name"]: j for j in report["jobs"]} - metric = by_name["Build / ubuntu-latest / gcc"]["metrics"][ + metric = by_name["Build / ubuntu-26.04 / gcc"]["metrics"][ "scheduler_release_latency"] self.assertEqual(metric["status"], "unavailable") self.assertIn("a job that never ran", metric["reason"]) @@ -590,10 +590,10 @@ class InFlightAndMissingData(unittest.TestCase): def test_queued_run_invents_nothing(self): report = tool.analyze(*fixture("run-queued")) by_name = {j["name"]: j for j in report["jobs"]} - queued = by_name["TSan / ubuntu-latest / gcc"] + queued = by_name["TSan / ubuntu-26.04 / gcc"] self.assertEqual(queued["metrics"]["queue_delay"]["status"], "unavailable") self.assertEqual(queued["metrics"]["job_wall"]["status"], "unavailable") - running = by_name["Build / ubuntu-latest / gcc"] + running = by_name["Build / ubuntu-26.04 / gcc"] self.assertEqual(running["metrics"]["queue_delay"]["seconds"], 12) self.assertEqual(running["metrics"]["job_wall"]["status"], "unavailable") self.assertEqual(report["metrics"]["run_wall_clock"]["status"], "partial") @@ -606,7 +606,7 @@ def test_missing_fields_are_reported_not_guessed(self): with self.subTest(phase=phase): self.assertEqual(metric["status"], "unavailable") self.assertEqual(no_steps["clock_offset_estimate"]["status"], "unavailable") - never_started = by_name["Build / ubuntu-latest / gcc"] + never_started = by_name["Build / ubuntu-26.04 / gcc"] self.assertEqual(never_started["metrics"]["queue_delay"]["status"], "unavailable") self.assertEqual(report["t0_source"], "created_at") @@ -665,7 +665,7 @@ def test_a_renamed_step_reports_unavailable_not_zero(self): mutated = copy.deepcopy(jobs) renamed = 0 for job in mutated["jobs"]: - if job["name"] != "Build / ubuntu-latest / gcc": + if job["name"] != "Build / ubuntu-26.04 / gcc": continue for step in job["steps"]: if step["name"] == "Test": @@ -674,7 +674,7 @@ def test_a_renamed_step_reports_unavailable_not_zero(self): self.assertEqual(renamed, 1, "fixture must contain the step to rename") report = tool.analyze(run, mutated) job = next(j for j in report["jobs"] - if j["name"] == "Build / ubuntu-latest / gcc") + if j["name"] == "Build / ubuntu-26.04 / gcc") self.assertEqual(job["phases"]["tests"]["status"], "unavailable") self.assertIn("Verify build outputs", report["unmapped_steps"]) @@ -736,13 +736,13 @@ def _with_broken_test_step(self, started, completed): run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) job = next(j for j in mutated["jobs"] - if j["name"] == "Build / ubuntu-latest / gcc") + if j["name"] == "Build / ubuntu-26.04 / gcc") for step in job["steps"]: if step["name"] == "Test": step["started_at"], step["completed_at"] = started, completed report = tool.analyze(run, mutated) return next(j for j in report["jobs"] - if j["name"] == "Build / ubuntu-latest / gcc") + if j["name"] == "Build / ubuntu-26.04 / gcc") def test_a_backwards_step_is_named_not_silently_dropped(self): job = self._with_broken_test_step("2026-09-10T19:50:00Z", @@ -761,7 +761,7 @@ def test_an_unusable_step_reaches_the_run_summary(self): run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) job = next(j for j in mutated["jobs"] - if j["name"] == "Build / ubuntu-latest / gcc") + if j["name"] == "Build / ubuntu-26.04 / gcc") for step in job["steps"]: if step["name"] == "Test": step["completed_at"] = step["started_at"] @@ -827,7 +827,7 @@ class SkippedSteps(unittest.TestCase): def test_a_skipped_step_does_not_feed_the_phase_it_matched(self): report = tool.analyze(*fixture("run-success")) job = next(j for j in report["jobs"] - if j["name"] == "Sanitizers / ubuntu-latest / gcc") + if j["name"] == "Sanitizers / ubuntu-26.04 / gcc") phase = job["phases"]["deps_install"] # The macOS install is skipped on a Linux runner; only the Linux one # actually installed anything. @@ -888,12 +888,12 @@ def test_a_timed_out_job_counts_as_the_first_failure(self): run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) job = next(j for j in mutated["jobs"] - if j["name"] == "Build / ubuntu-latest / gcc") + if j["name"] == "Build / ubuntu-26.04 / gcc") job["conclusion"] = "timed_out" report = tool.analyze(run, mutated) metric = report["metrics"]["first_failure_latency"] self.assertEqual(metric["status"], "measured") - self.assertEqual(metric["evidence"]["job"], "Build / ubuntu-latest / gcc") + self.assertEqual(metric["evidence"]["job"], "Build / ubuntu-26.04 / gcc") def test_a_cancelled_job_is_not_a_failure_but_is_acknowledged(self): run, jobs = fixture("run-success") @@ -912,7 +912,7 @@ def test_a_timeout_says_why_no_step_is_named(self): run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) job = next(j for j in mutated["jobs"] - if j["name"] == "Build / ubuntu-latest / gcc") + if j["name"] == "Build / ubuntu-26.04 / gcc") job["conclusion"] = "timed_out" for step in job["steps"]: step["conclusion"] = "cancelled" @@ -953,7 +953,7 @@ def test_a_job_missing_from_the_graph_is_not_called_a_root(self): by_name = {j["name"]: j for j in report["jobs"]} # Absence from a partial graph is an extractor gap, not rootness; the # two were conflated and 17 of 18 jobs claimed to be roots. - absent = by_name["Sanitizers / ubuntu-latest / gcc"]["metrics"][ + absent = by_name["Sanitizers / ubuntu-26.04 / gcc"]["metrics"][ "scheduler_release_latency"] self.assertIn("absent from the supplied dependency graph", absent["reason"]) @@ -998,7 +998,7 @@ def test_the_reusable_lint_workflow_is_expanded_into_its_jobs(self): # The caller's dependents attach to the callee's sink. In the # reordered graph the tsan leg is lint's direct dependent. self.assertIn("lint / uncrustify check", - self.graph["TSan / ubuntu-latest / ${{ matrix.compiler }}"]) + self.graph["TSan / ubuntu-26.04 / ${{ matrix.compiler }}"]) def test_every_job_has_an_entry_and_roots_are_explicit(self): # A job missing from the graph must be a gap, never mistaken for a @@ -1033,7 +1033,7 @@ def test_every_node_resolves_against_a_real_run(self): def test_matrix_placeholders_match_expanded_names(self): pattern = tool.name_pattern("Build / ${{ matrix.os }} / ${{ m }}") self.assertTrue(pattern.match("Build / windows-latest / msvc")) - self.assertFalse(pattern.match("Sanitizers / ubuntu-latest / gcc")) + self.assertFalse(pattern.match("Sanitizers / ubuntu-26.04 / gcc")) def test_an_unreadable_needs_form_is_refused_not_read_as_a_root(self): # Block lists and wrapped flow lists are legal YAML this parser does @@ -1058,12 +1058,12 @@ def test_a_literal_node_wins_over_a_matrix_pattern(self): # Otherwise correctness depends on which job happens to be declared # first, and #1574 reorders jobs. graph = {"Build / ${{ matrix.os }} / ${{ matrix.compiler }}": ["Wide"], - "Build / ubuntu-latest / gcc": ["Narrow"], + "Build / ubuntu-26.04 / gcc": ["Narrow"], "Wide": [], "Narrow": []} - jobs = ["Build / ubuntu-latest / gcc", "Build / macos-latest / clang", + jobs = ["Build / ubuntu-26.04 / gcc", "Build / macos-latest / clang", "Wide", "Narrow"] edges, drift = tool.match_graph_to_jobs(graph, jobs) - self.assertEqual(edges["Build / ubuntu-latest / gcc"], ["Narrow"]) + self.assertEqual(edges["Build / ubuntu-26.04 / gcc"], ["Narrow"]) self.assertEqual(edges["Build / macos-latest / clang"], ["Wide"]) self.assertEqual(drift["nodes_without_job"], []) @@ -1083,13 +1083,13 @@ def test_a_dependency_never_claims_a_job_that_resolved_elsewhere(self): graph = {"Sanitizers / ${{ matrix.os }} / ${{ matrix.compiler }}": [], "Sanitizers / tsan / ${{ matrix.compiler }}": ["Sanitizers / ${{ matrix.os }} / ${{ matrix.compiler }}"]} - jobs = ["Sanitizers / ubuntu-latest / gcc", "Sanitizers / tsan / gcc"] + jobs = ["Sanitizers / ubuntu-26.04 / gcc", "Sanitizers / tsan / gcc"] edges, _ = tool.match_graph_to_jobs(graph, jobs) for job, deps in edges.items(): with self.subTest(job=job): self.assertNotIn(job, deps, "a job cannot depend on itself") self.assertEqual(edges["Sanitizers / tsan / gcc"], - ["Sanitizers / ubuntu-latest / gcc"]) + ["Sanitizers / ubuntu-26.04 / gcc"]) def test_drift_is_reported_when_the_run_and_the_graph_disagree(self): edges, drift = tool.match_graph_to_jobs( @@ -1111,7 +1111,7 @@ def _analyze(self, name): def test_the_walk_follows_dependencies_not_the_nearest_finisher(self): path = self._analyze("run-success")["critical_path"]["observed"] chain = path["evidence"]["chain"] - self.assertEqual(chain[-1], "TSan / ubuntu-latest / clang") + self.assertEqual(chain[-1], "TSan / ubuntu-26.04 / clang") # TSan's real dependency is the lint sink, so the walk hops there; # the build matrix jobs finish nearby but are not a dependency of # TSan, and taking one would produce a plausible but wrong path. @@ -1127,9 +1127,9 @@ def test_the_observed_path_counts_queueing_and_the_graph_path_does_not(self): # different node sets and must not be equal. self.assertGreater(paths["graph"]["seconds"], paths["observed"]["seconds"]) - self.assertIn("Build / ubuntu-latest / gcc", + self.assertIn("Build / ubuntu-26.04 / gcc", paths["graph"]["evidence"]["chain"]) - self.assertNotIn("Build / ubuntu-latest / gcc", + self.assertNotIn("Build / ubuntu-26.04 / gcc", paths["observed"]["evidence"]["chain"]) def test_a_skip_cascade_whose_chain_avoids_it_stays_measured(self): @@ -1140,7 +1140,7 @@ def test_a_skip_cascade_whose_chain_avoids_it_stays_measured(self): # would attach a caveat that does not apply to the value shown. paths = self._analyze("run-skip-cascade")["critical_path"] self.assertEqual(paths["observed"]["status"], "measured") - self.assertNotIn("Build / ubuntu-24.04-arm / gcc", + self.assertNotIn("Build / ubuntu-26.04-arm / gcc", paths["observed"]["evidence"]["chain"]) def _with_skipped(self, job_name): @@ -1181,7 +1181,7 @@ def test_the_observed_walk_does_not_step_through_a_cancelled_job(self): # The walk routes around it rather than stopping: the same job still # ends the path, reached through the other dependency that did run. self.assertEqual(path["evidence"]["chain"][-1], - "TSan / ubuntu-latest / clang") + "TSan / ubuntu-26.04 / clang") self.assertIn("Detect build-triggering changes", path["evidence"]["chain"]) @@ -1198,7 +1198,7 @@ def test_the_observed_walk_stops_rather_than_inventing_a_hop(self): def test_a_job_outside_the_graph_stops_the_walk_as_partial(self): run, jobs = fixture("run-success") graph = tool.extract_graph(self.WORKFLOW) - del graph["TSan / ubuntu-latest / ${{ matrix.compiler }}"] + del graph["TSan / ubuntu-26.04 / ${{ matrix.compiler }}"] edges, drift = tool.match_graph_to_jobs( graph, [j["name"] for j in jobs["jobs"]]) report = tool.analyze(run, jobs, edges, drift) @@ -1264,7 +1264,7 @@ def test_an_untimed_job_off_the_graph_chain_still_qualifies_the_total(self): # A leaf the longest path legitimately routes around. Weighted at # zero during selection, it is precisely the node that ends up off # the winning chain, so a caveat scoped to the chain never fires. - if job["name"] == "Sanitizers / ubuntu-latest / clang": + if job["name"] == "Sanitizers / ubuntu-26.04 / clang": job["started_at"] = None graph = tool.extract_graph(self.WORKFLOW) edges, drift = tool.match_graph_to_jobs( @@ -1275,8 +1275,8 @@ def test_an_untimed_job_off_the_graph_chain_still_qualifies_the_total(self): self.assertEqual(path["status"], "partial") self.assertIn("routed around", path["reason"]) self.assertEqual(path["evidence"]["untimed_jobs_off_chain"], - ["Sanitizers / ubuntu-latest / clang"]) - self.assertNotIn("Sanitizers / ubuntu-latest / clang", + ["Sanitizers / ubuntu-26.04 / clang"]) + self.assertNotIn("Sanitizers / ubuntu-26.04 / clang", path["evidence"]["chain"]) def test_an_untimed_last_finisher_is_not_walked_past_in_silence(self): @@ -1286,7 +1286,7 @@ def test_an_untimed_last_finisher_is_not_walked_past_in_silence(self): # The observed walk starts at the last job to finish that was # timed, so untiming the actual last finisher moves the start # down one branch and the job never appears on the result. - if job["name"] == "TSan / ubuntu-latest / clang": + if job["name"] == "TSan / ubuntu-26.04 / clang": job["started_at"] = None graph = tool.extract_graph(self.WORKFLOW) edges, drift = tool.match_graph_to_jobs( @@ -1295,8 +1295,8 @@ def test_an_untimed_last_finisher_is_not_walked_past_in_silence(self): drift)["critical_path"]["observed"] self.assertEqual(path["status"], "partial") self.assertEqual(path["evidence"]["untimed_jobs_off_chain"], - ["TSan / ubuntu-latest / clang"]) - self.assertNotIn("TSan / ubuntu-latest / clang", + ["TSan / ubuntu-26.04 / clang"]) + self.assertNotIn("TSan / ubuntu-26.04 / clang", path["evidence"]["chain"]) def test_a_skipped_job_is_never_counted_as_untimed(self): @@ -1416,7 +1416,7 @@ def test_a_completed_job_is_settled_whatever_the_run_status_says(self): unreadable = copy.deepcopy(run) unreadable["status"] = "something-new" mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["started_at"] = None @@ -1517,7 +1517,7 @@ def test_a_step_that_ran_outranks_a_span_that_says_otherwise(self): # then reports is true: the step evidence establishes it ran. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = "cancelled" @@ -1536,7 +1536,7 @@ def test_a_step_of_no_duration_is_still_evidence(self): # those as unusable would discard the commonest shape there is. _, jobs = fixture("run-success") template = next(job["steps"][0] for job in jobs["jobs"] - if job["name"] == "Sanitizers / ubuntu-latest / clang") + if job["name"] == "Sanitizers / ubuntu-26.04 / clang") instant = dict(template, completed_at=template["started_at"]) self.assertEqual(tool.first_step_start({"steps": [instant]}), template["started_at"]) @@ -1548,7 +1548,7 @@ def test_a_start_stamped_at_creation_is_not_a_dispatch(self): # on a runner, and lists it as missing data besides. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = "cancelled" @@ -1579,7 +1579,7 @@ def test_a_weighed_conclusion_with_never_run_stamps_reads_as_never_run( "a_conclusion_from_the_future"): with self.subTest(conclusion=conclusion): mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = conclusion @@ -1615,7 +1615,7 @@ def test_a_start_later_than_creation_alone_proves_the_job_ran(self): # was created. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = "cancelled" @@ -1635,7 +1635,7 @@ def test_a_job_cancelled_while_it_waited_did_not_run(self): # instantly -- the misreading this whole rule exists to prevent. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = "cancelled" @@ -1653,7 +1653,7 @@ def test_a_step_still_running_is_evidence_the_job_ran(self): # stamps would discard the only evidence there is. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = "cancelled" @@ -1671,7 +1671,7 @@ def test_a_step_with_only_a_completion_is_not_evidence(self): # No start stamp to cite, so nothing says when the job began. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = "cancelled" @@ -1688,7 +1688,7 @@ def test_a_skipped_or_backwards_step_is_not_evidence(self): # report disowns two sections later. _, jobs = fixture("run-success") template = next(job["steps"][0] for job in jobs["jobs"] - if job["name"] == "Sanitizers / ubuntu-latest / clang") + if job["name"] == "Sanitizers / ubuntu-26.04 / clang") self.assertIsNone(tool.first_step_start( {"steps": [dict(template, conclusion="skipped")]})) self.assertIsNone(tool.first_step_start( @@ -1700,7 +1700,7 @@ def test_a_skipped_or_backwards_step_is_not_evidence(self): def test_the_earliest_step_is_the_one_reported(self): _, jobs = fixture("run-success") steps = next(job["steps"] for job in jobs["jobs"] - if job["name"] == "Sanitizers / ubuntu-latest / clang") + if job["name"] == "Sanitizers / ubuntu-26.04 / clang") self.assertGreater(len(steps), 1) timed = [step["started_at"] for step in steps if step.get("started_at") and step.get("completed_at") @@ -1773,7 +1773,7 @@ def test_a_cancelled_job_that_ran_still_qualifies_the_critical_path(self): # so, or a total gets published as final with that job missing. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = "cancelled" @@ -1791,7 +1791,7 @@ def test_a_cancelled_job_whose_runner_picked_it_up_is_not_excused(self): # and the caption does not cover them. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = "cancelled" @@ -1808,7 +1808,7 @@ def test_a_cancelled_job_that_lost_a_stamp_is_not_excused(self): # degraded cells from the block. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = "cancelled" @@ -1827,7 +1827,7 @@ def test_a_cancelled_job_whose_steps_ran_is_not_excused(self): # so phase seconds are printed for a job the caption would excuse. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = "cancelled" @@ -1903,7 +1903,7 @@ def test_a_finished_job_in_a_live_run_is_not_told_it_may_still_start(self): live = copy.deepcopy(run) live["status"] = "in_progress" mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["started_at"] = None @@ -1927,7 +1927,7 @@ def test_a_job_whose_steps_ran_is_not_told_it_never_started(self): # is refuted by the same row that carries it. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["started_at"] = None @@ -1966,7 +1966,7 @@ def _held_for_approval(self, run_status): run["status"] = run_status mutated = copy.deepcopy(jobs) for job in mutated["jobs"]: - if job["name"] == "Sanitizers / ubuntu-latest / clang": + if job["name"] == "Sanitizers / ubuntu-26.04 / clang": job["status"] = "waiting" job["conclusion"] = None job["started_at"] = None @@ -1984,7 +1984,7 @@ def test_a_job_held_for_approval_is_not_missing_data(self): # one-sided assertion let one of them drift back once already. for key in ("observed", "graph"): evidence = paths[key]["evidence"] - self.assertIn("Sanitizers / ubuntu-latest / clang", + self.assertIn("Sanitizers / ubuntu-26.04 / clang", evidence["unfinished_jobs_off_chain"], key) self.assertNotIn("untimed_jobs_off_chain", evidence, key) @@ -1995,7 +1995,7 @@ def test_a_job_still_waiting_in_a_finished_run_is_missing_data(self): paths = self._held_for_approval("completed") for key in ("observed", "graph"): evidence = paths[key]["evidence"] - self.assertIn("Sanitizers / ubuntu-latest / clang", + self.assertIn("Sanitizers / ubuntu-26.04 / clang", evidence["untimed_jobs_off_chain"], key) self.assertNotIn("unfinished_jobs_off_chain", evidence, key) @@ -2035,7 +2035,7 @@ def test_a_run_status_the_tool_cannot_read_is_not_read_as_running(self): # nobody can wait for. paths = self._held_for_approval("something-new") for key in ("observed", "graph"): - self.assertIn("Sanitizers / ubuntu-latest / clang", + self.assertIn("Sanitizers / ubuntu-26.04 / clang", paths[key]["evidence"]["untimed_jobs_off_chain"], key) @@ -2064,14 +2064,14 @@ def test_every_in_flight_status_takes_the_quiet_branch(self): run["status"] = "in_progress" mutated = copy.deepcopy(jobs) for job in mutated["jobs"]: - if job["name"] == "Sanitizers / ubuntu-latest / clang": + if job["name"] == "Sanitizers / ubuntu-26.04 / clang": job["status"] = status job["conclusion"] = None job["started_at"] = None job["completed_at"] = None running, untimed = tool.untimed_executed_jobs( tool.analyze(run, mutated)["jobs"], run_status="in_progress") - self.assertIn("Sanitizers / ubuntu-latest / clang", running) + self.assertIn("Sanitizers / ubuntu-26.04 / clang", running) self.assertEqual(untimed, []) def test_a_job_that_completed_without_a_start_is_not_called_unfinished(self): @@ -2098,7 +2098,7 @@ def test_a_job_that_failed_without_starting_is_still_missing_data(self): run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) for job in mutated["jobs"]: - if job["name"] == "Sanitizers / ubuntu-latest / clang": + if job["name"] == "Sanitizers / ubuntu-26.04 / clang": job["conclusion"] = "failure" job["started_at"] = None # No step evidence, so only the conclusion check can keep @@ -2108,7 +2108,7 @@ def test_a_job_that_failed_without_starting_is_still_missing_data(self): edges, drift = tool.match_graph_to_jobs( graph, [j["name"] for j in mutated["jobs"]]) path = tool.analyze(run, mutated, edges, drift)["critical_path"]["graph"] - self.assertIn("Sanitizers / ubuntu-latest / clang", + self.assertIn("Sanitizers / ubuntu-26.04 / clang", path["evidence"]["untimed_jobs_off_chain"]) @staticmethod @@ -2131,7 +2131,7 @@ def test_a_job_cancelled_before_it_started_is_not_missing_data(self): # will never acquire one. Both alarms would be false. run, jobs = fixture("run-success") mutated = self._cancelled_before_starting( - jobs, "Sanitizers / ubuntu-latest / clang") + jobs, "Sanitizers / ubuntu-26.04 / clang") graph = tool.extract_graph(self.WORKFLOW) edges, drift = tool.match_graph_to_jobs( graph, [j["name"] for j in mutated["jobs"]]) @@ -2148,21 +2148,21 @@ def test_a_status_the_tool_does_not_know_takes_the_loud_branch(self): run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) for job in mutated["jobs"]: - if job["name"] == "Sanitizers / ubuntu-latest / clang": + if job["name"] == "Sanitizers / ubuntu-26.04 / clang": job["status"] = "something-new" job["started_at"] = None graph = tool.extract_graph(self.WORKFLOW) edges, drift = tool.match_graph_to_jobs( graph, [j["name"] for j in mutated["jobs"]]) path = tool.analyze(run, mutated, edges, drift)["critical_path"]["graph"] - self.assertIn("Sanitizers / ubuntu-latest / clang", + self.assertIn("Sanitizers / ubuntu-26.04 / clang", path["evidence"]["untimed_jobs_off_chain"]) def test_a_finished_job_with_no_duration_is_still_missing_data(self): run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) for job in mutated["jobs"]: - if job["name"] == "Sanitizers / ubuntu-latest / clang": + if job["name"] == "Sanitizers / ubuntu-26.04 / clang": job["started_at"] = None # completed, never started graph = tool.extract_graph(self.WORKFLOW) edges, drift = tool.match_graph_to_jobs( @@ -2178,7 +2178,7 @@ def test_the_observed_reason_names_both_ways_it_can_be_wrong(self): run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) for job in mutated["jobs"]: - if job["name"] == "TSan / ubuntu-latest / clang": + if job["name"] == "TSan / ubuntu-26.04 / clang": job["started_at"] = None graph = tool.extract_graph(self.WORKFLOW) edges, drift = tool.match_graph_to_jobs( @@ -2318,7 +2318,7 @@ def test_labels_are_not_a_source_of_truth(self): disguised = [j for j in jobs["jobs"] if j.get("runner_name") == "semantic-reasoning-i401-6-2"] self.assertTrue(disguised, "fixture must keep the disguised runner") - self.assertIn("ubuntu-latest", disguised[0]["labels"]) + self.assertIn("ubuntu-26.04", disguised[0]["labels"]) self.assertEqual(tool.population_of(disguised[0]), "self-hosted") def test_windows_runner_is_self_hosted(self): @@ -2447,7 +2447,7 @@ def test_the_markdown_says_why_a_number_is_not_final(self): run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) for job in mutated["jobs"]: - if job["name"] == "Sanitizers / ubuntu-latest / clang": + if job["name"] == "Sanitizers / ubuntu-26.04 / clang": job["started_at"] = None graph = tool.extract_graph(self.WORKFLOW) edges, drift = tool.match_graph_to_jobs( @@ -2614,7 +2614,7 @@ def test_a_job_cancelled_after_it_ran_is_not_excused(self): # one short. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["conclusion"] = "cancelled" @@ -2635,7 +2635,7 @@ def test_a_job_excused_from_durations_is_excused_from_the_block(self): # the ones that are not absent by design. run, jobs = fixture("run-success") mutated = copy.deepcopy(jobs) - victim = "Sanitizers / ubuntu-latest / clang" + victim = "Sanitizers / ubuntu-26.04 / clang" for job in mutated["jobs"]: if job["name"] == victim: job["status"] = "completed" diff --git a/scripts/ci/test-early-size-gate.sh b/scripts/ci/test-early-size-gate.sh index f8ba86f8c..01f26ac19 100755 --- a/scripts/ci/test-early-size-gate.sh +++ b/scripts/ci/test-early-size-gate.sh @@ -148,7 +148,7 @@ assert 'main size report is uploaded through always()' grep -A6 -F 'name: Upload assert 'main artifacts have unique platform-qualified names' grep -Fq 'name: wirelog-size-monitor-${{ matrix.os }}' "$main_wf" assert 'baseline verifier trusts only canonical ubuntu artifact' grep -Fq 'wirelog-size-monitor-ubuntu-latest' "$root/scripts/ci/verify-size-baseline.py" assert 'main workflow grants no write permission' sh -c '! grep -Eq "^[[:space:]]+(actions|contents):[[:space:]]+write" "$1"' sh "$main_wf" -assert 'PR required build job identity remains unchanged' grep -Fq 'name: Build / ubuntu-latest / gcc' "$wf" +assert 'PR required build job identity remains unchanged' grep -Fq 'name: Build / ubuntu-26.04 / gcc' "$wf" # The production size gate has format-aware readers only for ELF and Mach-O. # Keep enforcing the platform-independent wiring assertions above, but do not diff --git a/scripts/ci/test-gate-skip-exit-codes.sh b/scripts/ci/test-gate-skip-exit-codes.sh index 6eca12ac6..ac3572952 100755 --- a/scripts/ci/test-gate-skip-exit-codes.sh +++ b/scripts/ci/test-gate-skip-exit-codes.sh @@ -226,7 +226,7 @@ unset coverage_padding coverage_large coverage_near # still safe -- and prefer adding a behavioural case over widening again. # -A6, not -A2: check-abi-manifest.sh's arm64 arch-skip prints five diagnostic # lines between its SKIP echo and its exit, and that branch is the one live on -# the ubuntu-24.04-arm required check. Two other branches sit at exactly the +# the ubuntu-26.04-arm required check. Two other branches sit at exactly the # old two-line limit, so the window had no margin and any added `echo` would # have silently dropped a branch out of coverage. # diff --git a/scripts/ci/test-required-gates.sh b/scripts/ci/test-required-gates.sh index 59c85a41f..1fc86be57 100755 --- a/scripts/ci/test-required-gates.sh +++ b/scripts/ci/test-required-gates.sh @@ -150,7 +150,11 @@ assert 'ci-pr.yml SBOM step sets WIRELOG_SBOM_REQUIRED' \ # The mandatory PR SBOM scan is distinct from the opportunistic scan in full # sanitizer suites. Exclude only that exact test: --no-suite sbom also drops -# the locale and snapshot contract tests. +# the locale and snapshot contract tests. Workflows still on the pinned +# setup-meson meson (1.12) use `meson test --exclude`; the PR workflow runs +# distro meson (1.10.x on ubuntu-26.04), which has no `--exclude`, so it +# builds the same "everything except sbom_snapshot" set from the configured +# test list at run time (sanitizer_excludes_snapshot_dynamic). sanitizer_excludes_snapshot() { local file=$1 job=$2 builddir=$3 awk -v job=" $job:" \ @@ -165,8 +169,21 @@ sanitizer_excludes_snapshot() { END { exit found == 1 ? 0 : 1 } ' "$root/.github/workflows/$file" } +sanitizer_excludes_snapshot_dynamic() { + local file=$1 job=$2 builddir=$3 + # Both lines must sit inside the named step: the list line excludes + # exactly sbom_snapshot by name, and the run line consumes that list. + awk -v job=" $job:" -v builddir="$builddir" ' + $0 == job { inside = 1; next } + inside && /^ [[:alnum:]_-]+:/ { exit } + inside && index($0, "meson introspect --tests " builddir) \ + && index($0, "grep -Fxv sbom_snapshot") { list = 1 } + inside && index($0, "meson test -C " builddir " $rest --print-errorlogs") { run = 1 } + END { exit (list && run) ? 0 : 1 } + ' "$root/.github/workflows/$file" +} assert 'ci-pr.yml sanitizer excludes only the duplicate SBOM snapshot' \ - sanitizer_excludes_snapshot ci-pr.yml sanitizer-matrix builddir-san + sanitizer_excludes_snapshot_dynamic ci-pr.yml sanitizer-matrix builddir-san assert 'ci-main.yml sanitizer excludes only the duplicate SBOM snapshot' \ sanitizer_excludes_snapshot ci-main.yml sanitizer-matrix builddir assert 'tier1-sanitizers.yml sanitizer excludes only the duplicate SBOM snapshot' \