From 6031bf1ba646476354088df2289e0d9dd43917f6 Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Wed, 21 Oct 2026 04:45:47 +0000 Subject: [PATCH 1/4] tests.yml and release.yml: the two Python 3.9 jobs run on ubuntu-24.04, so the `package` job and the release's 3.9 install check no longer fail in setup-python on an Ubuntu 26.04 runner `ubuntu-latest` gives out (#1094) `ubuntu-latest` has started giving out Ubuntu 26.04 runners, and setup-python has no Python 3.9 build for them (3.10 to 3.15 there, 3.8 to 3.15 for 24.04). The `package` job of tests.yml and the release's install-check row `{python: '3.9'}` now ask for `ubuntu-24.04`, each with a comment saying why. Every other job asks for `3.x` or `3.12` and stays on `ubuntu-latest`. The release row's job name now reads `install check (ubuntu-24.04, Python 3.9)`; nothing else in release.yml reads `matrix.os`, and its one `runner.os` branch is Windows's. `requires-python` stays `>=3.9`. tests/test_release_workflow.py pins the install matrix's runners to the new row, and a new test pins the `package` job to ubuntu-24.04 and no 3.9 job of tests.yml to `ubuntu-latest`. Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/release.yml | 8 ++++++-- .github/workflows/tests.yml | 10 +++++++++- tests/test_release_workflow.py | 30 +++++++++++++++++++++++++++--- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a1f241f3..b981df79 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -900,8 +900,12 @@ jobs: python: '3.x' - os: windows-latest python: '3.x' - # The oldest Python the package supports (`requires-python`). - - os: ubuntu-latest + # The oldest Python the package supports (`requires-python`), on + # ubuntu-24.04 and not ubuntu-latest (#1094): the Ubuntu 26.04 that + # `ubuntu-latest` now gives out has no 3.9 build for setup-python, + # which lists 3.10 to 3.15 there and 3.8 to 3.15 for 24.04. The + # rows above ask for `3.x`, which 26.04 has, and stay on the latest. + - os: ubuntu-24.04 python: '3.9' env: WHEEL: ${{ needs.build.outputs.wheel }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a9226d3a..04c07a3b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -268,8 +268,16 @@ jobs: # Python, one file for every system; the release workflow (#563's next # pull request) installs it on Linux, macOS and Windows before it uploads # anything. + # + # ubuntu-24.04, NOT ubuntu-latest (#1094). 3.9 is `requires-python`'s + # floor, and the Ubuntu 26.04 that `ubuntu-latest` now gives out has no 3.9 + # build for setup-python: its manifest lists 3.10 to 3.15 there, and 3.8 to + # 3.15 for 24.04. A run that landed on a 26.04 runner failed in setup-python + # with `The version '3.9' with architecture 'x64' was not found for Ubuntu + # 26.04.` Every other job asks for `3.x` or `3.12`, which 26.04 has, and + # stays on ubuntu-latest. package: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: # The same two commits the other jobs pin. - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 diff --git a/tests/test_release_workflow.py b/tests/test_release_workflow.py index 4ae93519..6cfa7010 100644 --- a/tests/test_release_workflow.py +++ b/tests/test_release_workflow.py @@ -559,9 +559,14 @@ def test_the_install_check_runs_on_three_systems_and_python_3_9(workflow): host by the recorded digest.""" install = workflow["jobs"]["install"] legs = install["strategy"]["matrix"]["include"] - assert {leg["os"] for leg in legs} == {"ubuntu-latest", "macos-latest", - "windows-latest"} - assert {"os": "ubuntu-latest", "python": "3.9"} in legs + assert {leg["os"] for leg in legs} == {"ubuntu-latest", "ubuntu-24.04", + "macos-latest", "windows-latest"} + assert {"os": "ubuntu-24.04", "python": "3.9"} in legs + # #1094: `ubuntu-latest` now gives out Ubuntu 26.04, which has no 3.9 + # build for setup-python, so the 3.9 row stays on 24.04 and every row on + # the latest image asks for a Python 26.04 has. + assert [leg["python"] for leg in legs if leg["os"] == "ubuntu-latest"] \ + == ["3.x"] assert install["strategy"]["fail-fast"] is False assert install["env"]["PIP_INDEX_URL"] == "https://pypi.org/simple/" for step in install["steps"]: @@ -576,6 +581,25 @@ def test_the_install_check_runs_on_three_systems_and_python_3_9(workflow): "from PyPI") +def test_the_package_job_runs_its_python_3_9_on_ubuntu_24_04(): + """#1094: `tests.yml`'s `package` job builds and installs on 3.9, the + floor of `requires-python`, and the Ubuntu 26.04 that `ubuntu-latest` now + gives out has no 3.9 build for setup-python, so a run that landed there + failed in setup-python. The job names 24.04, and no job of the file asks + for 3.9 on `ubuntu-latest`.""" + jobs = load_yaml(WORKFLOWS / "tests.yml")["jobs"] + package = jobs["package"] + assert package["runs-on"] == "ubuntu-24.04" + pythons = [step["with"]["python-version"] for step in package["steps"] + if step.get("uses", "").startswith("actions/setup-python@")] + assert pythons == ["3.9"] + for name, job in jobs.items(): + asked = [step["with"]["python-version"] for step in job["steps"] + if step.get("uses", "").startswith("actions/setup-python@")] + if "3.9" in asked: + assert job["runs-on"] == "ubuntu-24.04", name + + def test_the_header_says_how_to_release(workflow): """The release notes (`docs/release-notes/v1.1.0.md` and `v1.1.1.md`) point at this header for the steps, so it carries them: Brett Heap's From 3df09c6f5d811da361fba7a20e384d37b4684159 Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Wed, 21 Oct 2026 05:15:24 +0000 Subject: [PATCH 2/4] tests/test_release_workflow.py and tests.yml: the Python 3.9 guard covers both workflows, an unquoted 3.9 and a matrix's rows, and the `package` comment names the jobs that keep their runners (#1094) RV1095's two SHOULDs. tests/test_release_workflow.py: the guard looked for the string "3.9" in tests.yml alone, so it passed with an unquoted `python-version: 3.9` (the repo's YAML reader makes that the float 3.9), with release.yml's `build` on 3.9, and with a runner or Python that a matrix gives. It is now an allow list over both workflows: every setup-python step has `${{ matrix.os }}` and `${{ matrix.python }}` filled from each `include` row, a job with no matrix is one row, and every pair on `ubuntu-latest` must ask for `3.x` or `3.12`, every 3.9 pair must run on `ubuntu-24.04`, and no expression may be left unfilled. The `package` job's own assertions stay. tests.yml, the comment on `package`: "Every other job asks for `3.x` or `3.12` ... and stays on ubuntu-latest" was not true (`tests-git-bash` is on windows-latest, no tests.yml job asks for 3.12, and testpypi and pypi ask setup-python for nothing). It now says: the other jobs keep their runners, `tests` here asks for `3.x` and release.yml's `build` for `3.12` on ubuntu-latest, both of which 26.04 has, and `tests-git-bash` runs on windows-latest. The same sentence in the previous commit's message, and in the pull request body, is this one. Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/tests.yml | 5 ++-- tests/test_release_workflow.py | 54 +++++++++++++++++++++++++++++----- 2 files changed, 50 insertions(+), 9 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 04c07a3b..d4f7f712 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -274,8 +274,9 @@ jobs: # build for setup-python: its manifest lists 3.10 to 3.15 there, and 3.8 to # 3.15 for 24.04. A run that landed on a 26.04 runner failed in setup-python # with `The version '3.9' with architecture 'x64' was not found for Ubuntu - # 26.04.` Every other job asks for `3.x` or `3.12`, which 26.04 has, and - # stays on ubuntu-latest. + # 26.04.` The other jobs keep their runners: `tests` here asks for `3.x` + # and release.yml's `build` for `3.12` on ubuntu-latest, both of which + # 26.04 has, and `tests-git-bash` runs on windows-latest. package: runs-on: ubuntu-24.04 steps: diff --git a/tests/test_release_workflow.py b/tests/test_release_workflow.py index 6cfa7010..534e256f 100644 --- a/tests/test_release_workflow.py +++ b/tests/test_release_workflow.py @@ -585,19 +585,59 @@ def test_the_package_job_runs_its_python_3_9_on_ubuntu_24_04(): """#1094: `tests.yml`'s `package` job builds and installs on 3.9, the floor of `requires-python`, and the Ubuntu 26.04 that `ubuntu-latest` now gives out has no 3.9 build for setup-python, so a run that landed there - failed in setup-python. The job names 24.04, and no job of the file asks - for 3.9 on `ubuntu-latest`.""" + failed in setup-python. The job names 24.04 and asks for 3.9 once; the + next test holds every other job of both workflows to what 26.04 has.""" jobs = load_yaml(WORKFLOWS / "tests.yml")["jobs"] package = jobs["package"] assert package["runs-on"] == "ubuntu-24.04" pythons = [step["with"]["python-version"] for step in package["steps"] if step.get("uses", "").startswith("actions/setup-python@")] assert pythons == ["3.9"] - for name, job in jobs.items(): - asked = [step["with"]["python-version"] for step in job["steps"] - if step.get("uses", "").startswith("actions/setup-python@")] - if "3.9" in asked: - assert job["runs-on"] == "ubuntu-24.04", name + + +def setup_python_pairs(): + """Every (file, job, runner, Python) a setup-python step of `tests.yml` + or `release.yml` asks for, as text. A job with a matrix gives one pair + for each `include` row, with `${{ matrix.os }}` and `${{ matrix.python }}` + filled from it; a job with none gives one. `str()` makes an unquoted + `python-version: 3.9`, which `load_yaml` reads as the float 3.9, the text + Actions hands setup-python.""" + for path in (WORKFLOWS / "tests.yml", WORKFLOWS / "release.yml"): + for name, job in load_yaml(path)["jobs"].items(): + rows = job.get("strategy", {}).get("matrix", {}).get("include") + steps = [step for step in job.get("steps", []) + if step.get("uses", "").startswith( + "actions/setup-python@")] + for step in steps: + for row in rows or [{}]: + fill = {"${{ matrix.os }}": str(row.get("os")), + "${{ matrix.python }}": str(row.get("python"))} + runner = str(job["runs-on"]) + python = str(step["with"]["python-version"]) + for key, value in fill.items(): + runner = runner.replace(key, value) + python = python.replace(key, value) + yield path.name, name, runner, python + + +def test_no_setup_python_on_ubuntu_latest_asks_for_a_python_26_04_lacks(): + """#1094: the Ubuntu 26.04 that `ubuntu-latest` gives out has setup-python + builds for 3.10 to 3.15 alone, so every job of both workflows that is on + `ubuntu-latest` asks for `3.x` or `3.12`, and a 3.9 job is on 24.04. An + allow list over the expanded matrix rows, so it holds for an unquoted + 3.9, for `release.yml`'s `build` job, and for a Python or a runner that a + matrix gives.""" + pairs = list(setup_python_pairs()) + for file, job, runner, python in pairs: + where = (file, job, runner, python) + assert "${{" not in runner + python, where + if runner == "ubuntu-latest": + assert python in {"3.x", "3.12"}, where + if python.split(".")[:2] == ["3", "9"]: + assert runner == "ubuntu-24.04", where + # The two 3.9 jobs are among the pairs, so the loop is not vacuous. + assert ("tests.yml", "package", "ubuntu-24.04", "3.9") in pairs + assert ("release.yml", "install", "ubuntu-24.04", "3.9") in pairs def test_the_header_says_how_to_release(workflow): From f464631aa89fec36ad72f6826866c273697c0ed0 Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Wed, 21 Oct 2026 05:25:44 +0000 Subject: [PATCH 3/4] tests/test_release_workflow.py: the Python 3.9 guard fails on a matrix of the standard form instead of passing it unchecked, and its docstrings name what it holds (#1094) RV1095d's SHOULD (S3) and NIT (N3). S3: setup_python_pairs() expanded `strategy.matrix.include` rows only, and filled a key a row lacked with the text "None", so the `${{` check never fired on a matrix without `include`: a `tests` job with `runs-on: ${{ matrix.os }}`, `matrix: {os: [ubuntu-latest], python: ['3.9']}` and `python-version: ${{ matrix.python }}` passed. It now asserts that a job's matrix has no key but `include`, which fails closed on the standard form, and fills `${{ matrix. }}` from the keys a row has, so a reference a row does not fill stays in the text and the `${{` check catches it. The eight pairs at the head are the same. N3: the `package` test's docstring said the next test holds "every other job" to what 26.04 has; it holds every job on `ubuntu-latest`, and says so. The new test's docstring and the helper's say that a runner or Python from an `include` row is read, and a matrix of the standard form fails. Co-Authored-By: Claude Sonnet 5.5 --- tests/test_release_workflow.py | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/tests/test_release_workflow.py b/tests/test_release_workflow.py index 534e256f..2c9695e7 100644 --- a/tests/test_release_workflow.py +++ b/tests/test_release_workflow.py @@ -586,7 +586,8 @@ def test_the_package_job_runs_its_python_3_9_on_ubuntu_24_04(): floor of `requires-python`, and the Ubuntu 26.04 that `ubuntu-latest` now gives out has no 3.9 build for setup-python, so a run that landed there failed in setup-python. The job names 24.04 and asks for 3.9 once; the - next test holds every other job of both workflows to what 26.04 has.""" + next test holds every job of both workflows on `ubuntu-latest` to what + 26.04 has.""" jobs = load_yaml(WORKFLOWS / "tests.yml")["jobs"] package = jobs["package"] assert package["runs-on"] == "ubuntu-24.04" @@ -597,26 +598,30 @@ def test_the_package_job_runs_its_python_3_9_on_ubuntu_24_04(): def setup_python_pairs(): """Every (file, job, runner, Python) a setup-python step of `tests.yml` - or `release.yml` asks for, as text. A job with a matrix gives one pair - for each `include` row, with `${{ matrix.os }}` and `${{ matrix.python }}` - filled from it; a job with none gives one. `str()` makes an unquoted - `python-version: 3.9`, which `load_yaml` reads as the float 3.9, the text - Actions hands setup-python.""" + or `release.yml` asks for, as text. A job with an `include` matrix + gives one pair for each row, with each `${{ matrix. }}` the row + has filled from it; a job with no matrix gives one. A matrix with any + other key, the standard `os: [...]` form, is not expanded here, so it + fails closed. `str()` makes an unquoted `python-version: 3.9`, which + `load_yaml` reads as the float 3.9, the text Actions hands + setup-python.""" for path in (WORKFLOWS / "tests.yml", WORKFLOWS / "release.yml"): for name, job in load_yaml(path)["jobs"].items(): - rows = job.get("strategy", {}).get("matrix", {}).get("include") + matrix = job.get("strategy", {}).get("matrix", {}) + assert set(matrix) <= {"include"}, ( + path.name, name, sorted(matrix)) + rows = matrix.get("include") steps = [step for step in job.get("steps", []) if step.get("uses", "").startswith( "actions/setup-python@")] for step in steps: for row in rows or [{}]: - fill = {"${{ matrix.os }}": str(row.get("os")), - "${{ matrix.python }}": str(row.get("python"))} runner = str(job["runs-on"]) python = str(step["with"]["python-version"]) - for key, value in fill.items(): - runner = runner.replace(key, value) - python = python.replace(key, value) + for key, value in row.items(): + ref = "${{ matrix.%s }}" % key + runner = runner.replace(ref, str(value)) + python = python.replace(ref, str(value)) yield path.name, name, runner, python @@ -625,8 +630,9 @@ def test_no_setup_python_on_ubuntu_latest_asks_for_a_python_26_04_lacks(): builds for 3.10 to 3.15 alone, so every job of both workflows that is on `ubuntu-latest` asks for `3.x` or `3.12`, and a 3.9 job is on 24.04. An allow list over the expanded matrix rows, so it holds for an unquoted - 3.9, for `release.yml`'s `build` job, and for a Python or a runner that a - matrix gives.""" + 3.9, for `release.yml`'s `build` job, and for a Python or a runner that + an `include` row gives. A matrix of the standard form, with a key of its + own, fails the helper's assertion instead of passing unchecked.""" pairs = list(setup_python_pairs()) for file, job, runner, python in pairs: where = (file, job, runner, python) From 99b097fe3c1e921bc953c8d2d32ef03743240bd5 Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Wed, 21 Oct 2026 06:10:44 +0000 Subject: [PATCH 4/4] tests.yml: the `tests` job runs on ubuntu-24.04, since the Ubuntu 26.04 that `ubuntu-latest` now gives out runs the suite out of disk under pytest's basetemp (#1096) #1092's `tests` job ran on an `ubuntu-26.04` runner (job 119050702775, image version 20261012.163.1) and ended `2436 failed, 11382 passed, 380 skipped, 5579 errors in 3048.23s`, with 28325 lines of `[Errno 122] Disk quota exceeded`, and later `No space left on device`, all under pytest's basetemp, `/tmp/pytest-of-runner/pytest-0`. It is the image move of #1094, and the job passed on 24.04, so it is pinned there until the suite runs on 26.04, which #1096 tracks. .github/workflows/tests.yml: `tests` is on `ubuntu-24.04`, with a comment that gives the job, its counts and the quota errors and says it stays until #1096. The `package` comment said "`tests` here asks for `3.x` ... on ubuntu-latest", which this makes false; it now says that `tests` is on ubuntu-24.04 too, for its own reason, that `tests-git-bash` runs on windows-latest, and that release.yml's `build` asks for `3.12` on ubuntu-latest, which 26.04 has. The 3.9 reason is as it was. tests/test_release_workflow.py: a new test holds `tests` on ubuntu-24.04, and its docstring names #1096 and the quota errors. The pairs `setup_python_pairs()` yields are the same but for `tests`, from `ubuntu-latest` to `ubuntu-24.04`, and the allow-list test passes as before. tests/test_doctor_legs_members_next.py: the docstring said `tests.yml` runs the suite on ubuntu-latest alone; it says ubuntu-24.04. Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/tests.yml | 16 ++++++++++++---- tests/test_doctor_legs_members_next.py | 2 +- tests/test_release_workflow.py | 12 ++++++++++++ 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d4f7f712..8a3dd7fe 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -62,8 +62,16 @@ concurrency: cancel-in-progress: true jobs: + # ubuntu-24.04, NOT ubuntu-latest (#1096). The Ubuntu 26.04 that + # `ubuntu-latest` now gives out runs this suite out of disk: #1092's run of + # this job (job 119050702775, image `ubuntu-26.04` version 20261012.163.1) + # ended `2436 failed, 11382 passed, 380 skipped, 5579 errors in 3048.23s`, + # with 28325 lines of `[Errno 122] Disk quota exceeded`, and later `No space + # left on device`, all under pytest's basetemp in /tmp, + # `/tmp/pytest-of-runner/pytest-0`. The same job passed on 24.04. It stays + # on 24.04 until the suite runs on 26.04, which #1096 tracks. tests: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: # PINNED BY COMMIT, not by tag — the rule this repository spends a # whole README section on. A tag can be moved and a commit cannot. @@ -274,9 +282,9 @@ jobs: # build for setup-python: its manifest lists 3.10 to 3.15 there, and 3.8 to # 3.15 for 24.04. A run that landed on a 26.04 runner failed in setup-python # with `The version '3.9' with architecture 'x64' was not found for Ubuntu - # 26.04.` The other jobs keep their runners: `tests` here asks for `3.x` - # and release.yml's `build` for `3.12` on ubuntu-latest, both of which - # 26.04 has, and `tests-git-bash` runs on windows-latest. + # 26.04.` The `tests` job above is on ubuntu-24.04 too, for a reason of its + # own (#1096), and `tests-git-bash` runs on windows-latest. In release.yml, + # `build` asks for `3.12` on ubuntu-latest, which 26.04 has. package: runs-on: ubuntu-24.04 steps: diff --git a/tests/test_doctor_legs_members_next.py b/tests/test_doctor_legs_members_next.py index f58480dd..bd3749c8 100644 --- a/tests/test_doctor_legs_members_next.py +++ b/tests/test_doctor_legs_members_next.py @@ -34,7 +34,7 @@ ask `quote_arg` for it (`platform="nt"`, from any host) and compare the string; no test here pastes a line into `powershell`. A Windows-only test would not change that, because no job runs this file on Windows: `tests.yml` -runs the suite on ubuntu-latest alone (the macOS and Windows jobs were +runs the suite on ubuntu-24.04 alone (the macOS and Windows jobs were dropped, #597), and its one Windows job, `tests-git-bash`, runs three files of the launcher's and the installer's suites and no other. A `skipif(os.name != "nt")` test added here would be skipped by every runner diff --git a/tests/test_release_workflow.py b/tests/test_release_workflow.py index 2c9695e7..956f613c 100644 --- a/tests/test_release_workflow.py +++ b/tests/test_release_workflow.py @@ -596,6 +596,18 @@ def test_the_package_job_runs_its_python_3_9_on_ubuntu_24_04(): assert pythons == ["3.9"] +def test_the_tests_job_runs_the_suite_on_ubuntu_24_04(): + """#1096: `tests.yml`'s `tests` job runs the whole suite, and on the + Ubuntu 26.04 that `ubuntu-latest` now gives out it ended `2436 failed, + 11382 passed, 380 skipped, 5579 errors`, with `[Errno 122] Disk quota + exceeded` under pytest's basetemp in `/tmp` and `No space left on + device` after it. The job is pinned to 24.04, where it passed, until + the suite runs on 26.04; the change that moves it back takes this test + with it.""" + jobs = load_yaml(WORKFLOWS / "tests.yml")["jobs"] + assert jobs["tests"]["runs-on"] == "ubuntu-24.04" + + def setup_python_pairs(): """Every (file, job, runner, Python) a setup-python step of `tests.yml` or `release.yml` asks for, as text. A job with an `include` matrix