Skip to content

Commit 8e61907

Browse files
committed
Default test matrix moved from ubuntu-24.04-arm and ubuntu-slim to ubuntu-26.04-arm and ubuntu-26.04
1 parent f69aab0 commit 8e61907

8 files changed

Lines changed: 138 additions & 95 deletions

File tree

changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- **Breaking:** the `update-dep-graph` job moved from `autofix.yaml` into the release engine, its only firing moment being a release push. A required check or `needs:` edge naming it under Autofix must follow.
1010
- **Breaking:** manual version-bump commits now read `` [changelog] Bump minor version to `vX.Y.0` ``: every version-machinery commit carries the `[changelog] ` prefix, and anything matching the old unprefixed titles must follow.
1111
- New `lint-deps` command and release-lane job, blocking a release whose dependencies do not all resolve from PyPI: git branches, forks, local paths, direct URLs and private indexes. Exempt a package with `[tool.repomatic] lint-deps.allow`.
12+
- **Breaking:** the default test matrix moved from `ubuntu-24.04-arm` and `ubuntu-slim` to `ubuntu-26.04-arm` and `ubuntu-26.04`, which also moves the PR Linux slot and the `3.14t` smoke test. Pin the old images back with `test-matrix.replace.os = { "ubuntu-26.04-arm" = "ubuntu-24.04-arm" }`.
1213
- New `runner-images` job and CLI command, opening an issue that lists GitHub's open runner-image announcements and flags the ones retiring an image the repo runs on. Opt out with `runner-images = false`.
1314
- Autofix jobs no longer run on version-bump pushes: the 16 remaining jobs only re-checked machine-generated, ship-gated commits, and drift stays covered by the next push and the weekly sweep.
1415
- The tool runner now retries a download up to 3 times on transient network failures, instead of failing the job on a one-off TLS or truncation error.

claude.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,8 @@ When releasing `kdeldycke/repomatic`, see [`docs/upstream-development.md` § Rel
390390

391391
- **Cover the shipped config broadly; probe unreleased axes narrowly; smoke-test released flavors.** Released dependencies on stable Python get the full cross-platform spread. Unreleased dependency branches and prerelease Python run on one runner as `continue-on-error` probes (`test-matrix.unstable`), never across platforms. A released free-threaded build (`3.14t`) runs **stable** on a single runner (a `python-version` variation pinned with `exclude`, left out of `unstable`), not as an `unstable` probe.
392392
- **Pin the dependency floor, and any release a workaround targets.** Add the floor of a supported range as an explicit matrix value, plus any mid-range release a shim works around: that is the version that catches the shim regressing.
393-
- **Select runners by measured speed and workload, not architecture** (read your own CI timings; the `docs/test-matrix.md` inventory has the numbers). The parallel `pytest --numprocesses=auto` suite favors `ubuntu-24.04-arm` (the test PR Linux slot), while setup-bound light jobs keep the lean `ubuntu-slim`. Where one fast runner suffices, `ubuntu-24.04-arm` is the default (fastest and cheapest tier; hosted macOS bills ~10x Linux), so `macos-26`/Windows are reserved for the OS coverage only they add. Drop the slower twin of an OS pair via `test-matrix.remove.os`.
393+
- **Select runners by measured speed and workload, not architecture** (read your own CI timings; the `docs/test-matrix.md` inventory has the numbers). The parallel `pytest --numprocesses=auto` suite favors `ubuntu-26.04-arm` (the test PR Linux slot), while setup-bound light jobs keep the lean `ubuntu-slim`. Where one fast runner suffices, `ubuntu-26.04-arm` is the default (fastest and cheapest tier; hosted macOS bills ~10x Linux), so `macos-26`/Windows are reserved for the OS coverage only they add. Drop the slower twin of an OS pair via `test-matrix.remove.os`.
394+
- **An image is stable once validated here, not once GitHub relabels it.** The Ubuntu 26.04 axes ship as stable cells while still marked *preview* upstream: that label chiefly gates `-latest` alias eligibility, and no workflow here uses a floating alias. Test axes and the runners other jobs use are separate sets (`TEST_RUNNERS_*` and `NON_MATRIX_RUNNERS` in `matrix_axes.py`), and `KNOWN_RUNNERS` is their union, so a job on `ubuntu-slim` still passes `lint-repo` while a typo fails. Release binaries stay on GA images: a preview image's capacity is unbalanced for its first weeks, and queue time would delay a publish.
394395

395396
## Agent conventions
396397

docs/test-matrix.md

Lines changed: 38 additions & 33 deletions
Large diffs are not rendered by default.

pyproject.toml

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -535,20 +535,6 @@ pypi-package-history = [ "gha-utils", "repokit" ]
535535
# marketplace at .claude-plugin/marketplace.json installs the plugin straight
536536
# from this asset, so a release missing it breaks `/plugin install`.
537537
release-assets = [ "repomatic-claude-plugin.zip" ]
538-
# Test-drive GitHub's preview Ubuntu 26.04 images on this repository only: the
539-
# shipped defaults in repomatic/matrix_axes.py stay on GA images, so no
540-
# downstream repo inherits a preview. Both cells run continue-on-error, so a
541-
# preview breaking never reddens main, and neither joins the PR matrix.
542-
# `ubuntu-26.04-arm` against `ubuntu-24.04-arm` is the comparison worth
543-
# reading: same architecture, same full image, only the base version differs.
544-
# `ubuntu-26.04` has no matching control, the matrix's x86 Linux runner being
545-
# the lean `ubuntu-slim`, so it measures compatibility rather than speed.
546-
# See docs/test-matrix.md § Preview images under evaluation.
547-
test-matrix.unstable = [
548-
{ os = "ubuntu-26.04" },
549-
{ os = "ubuntu-26.04-arm" },
550-
]
551-
test-matrix.variations.os = [ "ubuntu-26.04", "ubuntu-26.04-arm" ]
552538

553539
[tool.actionlint]
554540
# actionlint reads no pyproject section of its own: repomatic serializes this one

repomatic/lint_repo.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
from .github.matrix import stale_axis_values
4242
from .github.token import check_all_pat_permissions
4343
from .matrix_axes import (
44+
NON_MATRIX_RUNNERS,
4445
TEST_RUNNERS_FULL,
4546
TEST_RUNNERS_PR,
4647
UNSTABLE_PYTHON_VERSIONS,
@@ -104,12 +105,22 @@
104105
major series, and `Implementation :: CPython` the interpreter.
105106
"""
106107

107-
KNOWN_RUNNERS = frozenset(TEST_RUNNERS_FULL) | frozenset(TEST_RUNNERS_PR)
108-
"""Every runner image the test matrix axes draw from.
108+
KNOWN_RUNNERS = (
109+
frozenset(TEST_RUNNERS_FULL)
110+
| frozenset(TEST_RUNNERS_PR)
111+
| frozenset(NON_MATRIX_RUNNERS)
112+
)
113+
"""Every runner image this project has deliberately chosen.
109114
110115
The closest thing to a curated list of images a project should be running on,
111116
and the one place carrying measured guidance on their relative speed and cost.
112117
A job naming something outside it has been picked without that guidance.
118+
119+
The union of the test axes and {data}`~repomatic.matrix_axes.NON_MATRIX_RUNNERS`,
120+
which stopped being the same set when the test matrix moved to Ubuntu 26.04 and
121+
the light mechanical jobs stayed on the lean `ubuntu-slim`. Both are chosen
122+
deliberately, so both belong here; only an image nobody weighed should be
123+
flagged.
113124
"""
114125

115126
TEMPLATE_FILE_ARG_RE = re.compile(r"--template-file[=\s]+(?P<path>\S+)")

repomatic/matrix_axes.py

Lines changed: 57 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@
2929
from typing import Final
3030

3131
TEST_RUNNERS_FULL = (
32-
"ubuntu-24.04-arm",
33-
"ubuntu-slim",
32+
"ubuntu-26.04-arm",
33+
"ubuntu-26.04",
3434
"macos-26",
3535
"macos-26-intel",
3636
"windows-11-arm",
@@ -41,38 +41,77 @@
4141
Two variants per platform (one per architecture). See
4242
[available images](https://github.com/actions/runner-images#available-images).
4343
44+
```{note} Preview images are adopted on measurement, not on GitHub's label
45+
GitHub still marks the Ubuntu 26.04 pair *preview*, which gates their
46+
eligibility to sit behind the `-latest` aliases. This project never uses those
47+
aliases (a floating alias re-points with no commit to review, which
48+
{func}`~repomatic.lint_repo.check_runner_images` rejects outright), so that
49+
distinction does not reach it. An image is treated as stable here once it has
50+
been validated against this suite, not once a vendor relabels it. Measured over
51+
consecutive runs before the swap, `ubuntu-26.04-arm` beat `ubuntu-24.04-arm` by
52+
16% on Python 3.10 and 28% on 3.14, tied on 3.15, and failed nothing.
53+
54+
The residual risk is capacity rather than correctness: GitHub warns a preview
55+
image's capacity "will be balanced only throughout the next weeks", so queue
56+
time may be worse than the runtimes above suggest. Release binaries are built
57+
on GA images for that reason, see {data}`~repomatic.binary.NUITKA_BUILD_TARGETS`.
58+
```
59+
4460
```{note} Architecture speed is not uniform across platforms
4561
When reducing to one runner per OS, choose by measured speed, not architecture
4662
(see {doc}`/test-matrix`). Tendencies from `repomatic`'s own full test suite:
47-
ARM Linux (`ubuntu-24.04-arm`) runs two to three times as fast as the lean x86
48-
`ubuntu-slim`, the slowest tier overall;
49-
Apple-silicon `macos-26` beats `macos-26-intel` by ~2x; the two Windows images
50-
tie on compute (`windows-2025` is the PR pick). Per-job wall-clock folds in
51-
setup and upload, so isolate the test steps before blaming the image. These
52-
figures drift as images are re-provisioned, so re-confirm against your own job
53-
timings.
63+
ARM Linux runs two to three times as fast as the lean x86 `ubuntu-slim` that
64+
preceded `ubuntu-26.04` on this axis; Apple-silicon `macos-26` beats
65+
`macos-26-intel` by ~2x; the two Windows images tie on compute (`windows-2025`
66+
is the PR pick). Per-job wall-clock folds in setup and upload, so isolate the
67+
test steps before blaming the image. These figures drift as images are
68+
re-provisioned, so re-confirm against your own job timings.
5469
```
5570
"""
5671

57-
TEST_RUNNERS_PR = (
72+
NON_MATRIX_RUNNERS = (
73+
"ubuntu-slim",
74+
"ubuntu-24.04",
5875
"ubuntu-24.04-arm",
76+
)
77+
"""Images used by jobs that are not test-matrix cells.
78+
79+
The test axes above answer "where is the suite exercised". These answer "what
80+
else may a job legitimately run on", and the two stopped being the same set when
81+
the matrix moved to Ubuntu 26.04 while the rest of the fleet did not:
82+
83+
- `ubuntu-slim` is the lean default for the light mechanical jobs (linters and
84+
formatters), which are setup-bound rather than compute-bound, so the faster
85+
image buys them nothing. GitHub rebases this image onto a newer Ubuntu on its
86+
own schedule, which is why it carries no version in its name.
87+
- `ubuntu-24.04` and `ubuntu-24.04-arm` host the Linux Nuitka builds and the
88+
one compute-heavy light job (`Format Markdown`, which needs `shfmt` and so
89+
cannot use the lean image).
90+
91+
{data}`~repomatic.lint_repo.KNOWN_RUNNERS` is the union of this and the test
92+
axes, so a job naming one of these is not flagged as untracked while a genuine
93+
typo still is.
94+
"""
95+
96+
TEST_RUNNERS_PR = (
97+
"ubuntu-26.04-arm",
5998
"macos-26",
6099
"windows-2025",
61100
)
62101
"""Reduced runner set for pull request test matrices.
63102
64-
One runner per platform: ARM Linux (`ubuntu-24.04-arm`) and Apple-silicon macOS
103+
One runner per platform: ARM Linux (`ubuntu-26.04-arm`) and Apple-silicon macOS
65104
(`macos-26`) are the fastest of their platform on the test workload, plus x86
66105
Windows (`windows-2025`, where the two Windows images tie on compute). x86 Linux
67106
stays covered by the full matrix ({data}`TEST_RUNNERS_FULL`).
68107
69108
```{note} Why ARM Linux for the PR slot
70109
The suite runs `pytest --numprocesses=auto`, so it scales with cores and favors
71-
ARM: `ubuntu-24.04-arm` runs it two to three times faster than the lean
72-
`ubuntu-slim`, for quicker PR feedback. That ratio is the heavy test suite's,
73-
not a portable property: setup-bound light jobs (which run prebuilt
74-
single-threaded binaries) barely move between runners, so they keep the lean
75-
`ubuntu-slim` default. See {doc}`/test-matrix` for the measurements.
110+
ARM, by two to three times over the lean x86 image, for quicker PR feedback.
111+
That ratio is the heavy test suite's, not a portable property: setup-bound light
112+
jobs (which run prebuilt single-threaded binaries) barely move between runners,
113+
so they keep the lean `ubuntu-slim` default ({data}`NON_MATRIX_RUNNERS`). See
114+
{doc}`/test-matrix` for the measurements.
76115
```
77116
"""
78117

@@ -109,7 +148,7 @@
109148
{data}`SINGLE_RUNNER_PYTHON_VERSIONS`, which are released and run stable.
110149
"""
111150

112-
SINGLE_RUNNER_PYTHON_VERSIONS: Final[dict[str, str]] = {"3.14t": "ubuntu-24.04-arm"}
151+
SINGLE_RUNNER_PYTHON_VERSIONS: Final[dict[str, str]] = {"3.14t": "ubuntu-26.04-arm"}
113152
"""Released Python build flavors smoke-tested on a single runner, mapped to it.
114153
115154
A free-threaded build (the `t` suffix, made officially supported in 3.14 by
@@ -118,7 +157,7 @@
118157
full cross-platform spread ({data}`TEST_PYTHON_FULL`), so the library logic is
119158
covered everywhere; the flavor only needs one runner to catch a
120159
free-threading-specific break. These run *stable* (expected to pass), unlike the
121-
unreleased {data}`UNSTABLE_PYTHON_VERSIONS`. The runner is `ubuntu-24.04-arm`,
160+
unreleased {data}`UNSTABLE_PYTHON_VERSIONS`. The runner is `ubuntu-26.04-arm`,
122161
the default single-runner pick: the fastest measured on compute-bound parallel
123162
work and the cheapest tier, and free-threading targets server workloads where
124163
Linux/ARM is the norm (see {doc}`/test-matrix`).

tests/test_lint_repo.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1165,7 +1165,9 @@ def test_pypi_trusted_publisher_repository_mismatch():
11651165
("runner", "stale"),
11661166
[
11671167
pytest.param("macos-15-intel", True, id="renamed-runner"),
1168-
pytest.param("ubuntu-slim", False, id="live-runner"),
1168+
# Must be a value the test axes actually carry: `ubuntu-slim` is a known
1169+
# runner but no longer a matrix cell, so excluding it would be stale.
1170+
pytest.param("ubuntu-26.04-arm", False, id="live-runner"),
11691171
],
11701172
)
11711173
def test_check_test_matrix_excludes(tmp_path, monkeypatch, runner, stale):
@@ -1283,7 +1285,11 @@ def test_python_matrix_built_at_runtime_is_skipped(tmp_path, monkeypatch):
12831285
("runner", "expect_fail", "needle"),
12841286
[
12851287
pytest.param("ubuntu-latest", True, "repoints", id="floating-alias"),
1286-
pytest.param("ubuntu-24.04", True, "not one of the images", id="unknown-image"),
1288+
# A real GitHub image, but one this project runs nothing on: neither a
1289+
# test axis nor a NON_MATRIX_RUNNERS entry.
1290+
pytest.param("ubuntu-22.04", True, "not one of the images", id="unknown-image"),
1291+
# Known without being a test axis, which is the case NON_MATRIX_RUNNERS
1292+
# exists to cover: every light mechanical job runs here.
12871293
pytest.param("ubuntu-slim", False, None, id="known-image"),
12881294
pytest.param("${{ matrix.os }}", False, None, id="expression"),
12891295
],

tests/test_metadata.py

Lines changed: 19 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -797,19 +797,14 @@ def _tracked_inventory(
797797
},
798798
]),
799799
},
800-
# The two ubuntu-26.04 runners and their unstable includes are this
801-
# repository's own `[tool.repomatic] test-matrix` probe of GitHub's preview
802-
# images, not a shipped default. Drop them here when the probe ends.
803800
"test_matrix": {
804801
"os": [
805-
"ubuntu-24.04-arm",
806-
"ubuntu-slim",
802+
"ubuntu-26.04-arm",
803+
"ubuntu-26.04",
807804
"macos-26",
808805
"macos-26-intel",
809806
"windows-11-arm",
810807
"windows-2025",
811-
"ubuntu-26.04",
812-
"ubuntu-26.04-arm",
813808
],
814809
"python-version": [
815810
"3.10",
@@ -819,17 +814,15 @@ def _tracked_inventory(
819814
"include": [
820815
{"state": "stable"},
821816
{"state": "unstable", "python-version": "3.15"},
822-
{"os": "ubuntu-24.04-arm", "python-version": "3.14t", "state": "stable"},
823-
{"os": "ubuntu-26.04", "state": "unstable"},
824-
{"os": "ubuntu-26.04-arm", "state": "unstable"},
817+
{"os": "ubuntu-26.04-arm", "python-version": "3.14t", "state": "stable"},
825818
],
826819
"exclude": [
827820
{"os": "windows-11-arm", "python-version": "3.10"},
828821
],
829822
},
830823
"test_matrix_pr": {
831824
"os": [
832-
"ubuntu-24.04-arm",
825+
"ubuntu-26.04-arm",
833826
"macos-26",
834827
"windows-2025",
835828
],
@@ -1089,7 +1082,7 @@ def test_show_test_matrix_renders_full_grid():
10891082
result = CliRunner().invoke(repomatic, ["show-test-matrix"])
10901083
assert result.exit_code == 0, result.output
10911084
assert "Python" in result.output
1092-
assert "ubuntu-24.04-arm" in result.output
1085+
assert "ubuntu-26.04-arm" in result.output
10931086
# The matrix always carries stable jobs, decorated with an emoji by default.
10941087
assert "✅ stable" in result.output
10951088

@@ -1534,7 +1527,7 @@ def test_full_include_flattens_matrix(tmp_path, monkeypatch):
15341527
[tool.repomatic]
15351528
test-matrix.include = [{ "click-version" = "released" }]
15361529
test-matrix.full-include = [
1537-
{ "os" = "ubuntu-24.04-arm", "python-version" = "3.10", "click-version" = "8.3.1" },
1530+
{ "os" = "ubuntu-26.04-arm", "python-version" = "3.10", "click-version" = "8.3.1" },
15381531
]
15391532
""",
15401533
)
@@ -1546,13 +1539,13 @@ def test_full_include_flattens_matrix(tmp_path, monkeypatch):
15461539
rows = emitted["include"]
15471540

15481541
shipped = {
1549-
"os": "ubuntu-24.04-arm",
1542+
"os": "ubuntu-26.04-arm",
15501543
"python-version": "3.10",
15511544
"click-version": "released",
15521545
"state": "stable",
15531546
}
15541547
pinned = {
1555-
"os": "ubuntu-24.04-arm",
1548+
"os": "ubuntu-26.04-arm",
15561549
"python-version": "3.10",
15571550
"click-version": "8.3.1",
15581551
"state": "stable",
@@ -1586,7 +1579,7 @@ def test_full_include_backfills_probe_defaults(tmp_path, monkeypatch):
15861579
{ "cloup-version" = "released" },
15871580
]
15881581
test-matrix.full-include = [
1589-
{ "os" = "ubuntu-24.04-arm", "python-version" = "3.14", "click-version" = "main" },
1582+
{ "os" = "ubuntu-26.04-arm", "python-version" = "3.14", "click-version" = "main" },
15901583
]
15911584
""",
15921585
)
@@ -1596,7 +1589,7 @@ def test_full_include_backfills_probe_defaults(tmp_path, monkeypatch):
15961589

15971590
probe = next(r for r in rows if r["python-version"] == "3.14t")
15981591
assert probe == {
1599-
"os": "ubuntu-24.04-arm",
1592+
"os": "ubuntu-26.04-arm",
16001593
"python-version": "3.14t",
16011594
"click-version": "released",
16021595
"cloup-version": "released",
@@ -1843,19 +1836,19 @@ def test_test_matrix_config_replace(tmp_path, monkeypatch):
18431836
version = "1.0.0"
18441837
18451838
[tool.repomatic.test-matrix.replace]
1846-
os = { "ubuntu-24.04-arm" = "ubuntu-24.04" }
1839+
os = { "ubuntu-26.04-arm" = "ubuntu-24.04" }
18471840
"""
18481841
metadata = metadata_from_pyproject(tmp_path, monkeypatch, pyproject_content)
18491842

1850-
# Full matrix: ubuntu-24.04-arm replaced with ubuntu-24.04.
1843+
# Full matrix: ubuntu-26.04-arm replaced with ubuntu-24.04.
18511844
full = metadata.test_matrix.matrix()
18521845
assert "ubuntu-24.04" in full["os"]
1853-
assert "ubuntu-24.04-arm" not in full["os"]
1846+
assert "ubuntu-26.04-arm" not in full["os"]
18541847

1855-
# PR matrix: same replacement applied (ubuntu-24.04-arm is in both sets).
1848+
# PR matrix: same replacement applied (ubuntu-26.04-arm is in both sets).
18561849
pr = metadata.test_matrix_pr.matrix()
18571850
assert "ubuntu-24.04" in pr["os"]
1858-
assert "ubuntu-24.04-arm" not in pr["os"]
1851+
assert "ubuntu-26.04-arm" not in pr["os"]
18591852

18601853

18611854
def test_test_matrix_config_remove(tmp_path, monkeypatch):
@@ -1966,17 +1959,18 @@ def test_stale_test_matrix_excludes(tmp_path, monkeypatch):
19661959
19671960
[tool.repomatic.test-matrix]
19681961
exclude = [
1969-
{os = "ubuntu-slim"},
1962+
{os = "ubuntu-26.04"},
19701963
{os = "macos-15-intel"},
19711964
{python-version = "9.99"},
19721965
]
19731966
"""
19741967
metadata = metadata_from_pyproject(tmp_path, monkeypatch, pyproject_content)
19751968

1976-
# ubuntu-slim is a live runner, so its exclude is honored. The renamed
1969+
# ubuntu-26.04 is a live axis value, so its exclude is honored. The renamed
19771970
# macos-15-intel and the bogus Python version match no axis and are flagged.
1971+
# `ubuntu-slim` would fail here too: a known runner, but no longer a cell.
19781972
stale = metadata.stale_test_matrix_excludes
1979-
assert {"os": "ubuntu-slim"} not in stale
1973+
assert {"os": "ubuntu-26.04"} not in stale
19801974
assert {"os": "macos-15-intel"} in stale
19811975
assert {"python-version": "9.99"} in stale
19821976

0 commit comments

Comments
 (0)