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..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. @@ -268,8 +276,17 @@ 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.` 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-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_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 4ae93519..956f613c 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,83 @@ 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 asks for 3.9 once; the + 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" + pythons = [step["with"]["python-version"] for step in package["steps"] + if step.get("uses", "").startswith("actions/setup-python@")] + 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 + 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(): + 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 [{}]: + runner = str(job["runs-on"]) + python = str(step["with"]["python-version"]) + 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 + + +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 + 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) + 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): """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