Skip to content

Add Python 3.14 to the test matrix - #1348

Open
brianegge wants to merge 2 commits into
morganstanley:mainfrom
brianegge:ci-python-3.14
Open

Add Python 3.14 to the test matrix#1348
brianegge wants to merge 2 commits into
morganstanley:mainfrom
brianegge:ci-python-3.14

Conversation

@brianegge

Copy link
Copy Markdown
Contributor

Summary

Extends the PR test matrix to cover Python 3.14, mirroring the python-compat job in morganstanley/hobbes, which already spans ['3.10', '3.11', '3.12', '3.13', '3.14'].

Testplan turns out to need essentially nothing to run on 3.14 — this PR is the matrix entry, the classifier, and one small code fix.

Changes

  • .github/workflows/test_pr.yml — add '3.14' to the python-version matrix.
  • pyproject.toml — add the Programming Language :: Python :: 3.14 classifier.
  • testplan/common/exporters/__init__.py — move the return in run_exporter out of its finally block.

Job count

To stop the matrix growing, [all] on ubuntu is now excluded for 3.13 as it already is for 3.11 and 3.12. [all] therefore keeps running against the oldest and newest supported interpreter (3.10 and 3.14); Windows keeps [all] on every version. Net change is 10 → 12 jobs.

The finally fix

run_exporter ended with return exp_result inside its finally: block. Python 3.14 emits a SyntaxWarning for this construct, and it was the only such warning in the codebase. It is also a latent bug independent of the version bump: returning from finally silently discards any in-flight BaseException, so a KeyboardInterrupt raised while an exporter was running was swallowed rather than propagated. The try/except Exception clauses above already handle every ordinary error path, so normal behaviour is unchanged.

Verification

Tested locally on macOS (3.12/3.13/3.14) and in Linux containers (python:3.13-slim / python:3.14-slim):

Check Result
pip install -e ".[all]" --group test on 3.12 / 3.13 / 3.14 clean on all three
Resolved dependency versions, 3.13 vs 3.14 identical — same package set, same versions, nothing fell back
tests/unit on Linux 3.13 1025 passed, 14 skipped
tests/unit on Linux 3.14 1025 passed, 14 skipped
tests/unit on macOS 3.12 / 3.13 / 3.14 1024 passed each, same 2 pre-existing macOS-only failures on every version
SyntaxWarning scan over testplan/ on 3.14 clean after this change
Removed-API scan (distutils, ByteString, typing.Text, …) no usages

multiprocessing start method

Worth recording since it is the main behavioural change in 3.14: the default start method on Linux moves from fork to forkserver, which I confirmed in-container (3.13 → fork, 3.14 → forkserver). testplan/monitor/resource.py builds multiprocessing.Process objects with bound-method targets and has comments that explicitly assume fork semantics, so this looked like a likely breakage — but tests/functional/testplan/test_resource_monitor.py passes as-is under forkserver. No change was needed; flagging it only so the assumption is on record.

Not covered

Windows on 3.14 is not something I could verify locally — that one is on CI to confirm.

constraints.txt is deliberately untouched. The uv-export pre-commit hook re-resolves it on every commit and currently produces ~150 lines of unrelated dependency churn, because releaseherald is pinned to a moving main rev. That drift is independent of this change and does not belong in this PR.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GWfe48Svf5j2TtgrryJfBK

Extend the PR test matrix to cover Python 3.14 and declare it in the
package classifiers, mirroring the python-compat job in morganstanley/hobbes
which already spans 3.10 through 3.14.

To keep the job count in check, the `[all]` extras combination on ubuntu is
excluded for 3.13 as it already is for 3.11 and 3.12, so `[all]` continues to
run against the oldest and newest supported interpreter (3.10 and 3.14).
Windows keeps `[all]` on every version. Net change is +2 jobs (10 -> 12).

Also move the `return` in `run_exporter` out of its `finally` block. Python
3.14 emits a SyntaxWarning for this construct -- it was the only such warning
in the codebase -- and returning from `finally` silently discarded any
BaseException (e.g. KeyboardInterrupt) raised while an exporter was running.

Verified against 3.14 locally: dependencies resolve to the same versions as
on 3.13 with no fallbacks, and tests/unit is at parity (1024 passed on 3.12,
3.13 and 3.14 alike).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWfe48Svf5j2TtgrryJfBK
…gge.rst

`test_basic_loose` asserted that the test process has no child processes at
all once the plan has torn down. That holds only while nothing else in the
interpreter has touched multiprocessing: the `resource_tracker`, and under
forkserver the server process itself, are children of the interpreter and
live for its lifetime.

Python 3.14 changes the default start method on Linux from fork to
forkserver, so those helpers are now routinely present. `doit test` runs the
whole suite in a single pytest process, so `test_resource_monitor.py` -- which
uses `multiprocessing.Manager()` and `Process` -- leaves behind a
resource_tracker and a forkserver zombie, and every `test_basic_loose`
parametrisation that runs afterwards then fails with `assert 2 == 0`.

The assertion is about the App driver's own processes being reaped, so it now
compares against a snapshot taken before the plan runs rather than requiring
an empty child list.

Verified on Linux in python:3.14-slim and python:3.13-slim containers. Running
test_resource_monitor.py and test_bad_app.py in one pytest process, as CI
does, went from 5 failed to 8 passed on 3.14 and stays at 8 passed on 3.13;
test_bad_app.py alone is 7 passed on both.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GWfe48Svf5j2TtgrryJfBK
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant