From 0a09620e980ff7b06b3e17ccfe3c0fde2888f89d Mon Sep 17 00:00:00 2001 From: crusaderky Date: Fri, 26 Sep 2025 11:55:12 +0100 Subject: [PATCH 1/3] Free-threading: run with pytest-run-paralell --- .github/workflows/tests.yaml | 2 ++ pyproject.toml | 7 ++++++- tests/test_ext_init.py | 1 + tests/test_leak.py | 3 +++ uv.lock | 35 ++++++++++++++++++++++++++++------- 5 files changed, 40 insertions(+), 8 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index aba1a0d5..8ec3f175 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -35,6 +35,8 @@ jobs: python-version: ${{ matrix.python }} allow-prereleases: true - run: uv run --locked tox run -e ${{ matrix.tox || format('py{0}', matrix.python) }} + - if: endsWith(matrix.python, 't') + run: uv run --locked tox run -e ${{ format('py{0}', matrix.python) }} -- --parallel-threads=4 typing: runs-on: ubuntu-latest steps: diff --git a/pyproject.toml b/pyproject.toml index b5e351b8..d3c9a990 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,6 +12,7 @@ classifiers = [ "Intended Audience :: Developers", "Operating System :: OS Independent", "Programming Language :: Python", + "Programming Language :: Python :: Free Threading :: 2 - Beta", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Text Processing :: Markup :: HTML", "Typing :: Typed", @@ -48,6 +49,7 @@ pre-commit = [ ] tests = [ "pytest", + "pytest-run-parallel; python_full_version >= '3.13'", ] typing = [ "mypy", @@ -67,7 +69,10 @@ testpaths = ["tests"] filterwarnings = [ "error", ] - +markers = [ + # Needed when pytest-run-parallel is not installed + "thread_unsafe: mark test as not safe to run in multiple threads", +] [tool.coverage.run] branch = true source = ["markupsafe", "tests"] diff --git a/tests/test_ext_init.py b/tests/test_ext_init.py index f1f6a808..3e535dc3 100644 --- a/tests/test_ext_init.py +++ b/tests/test_ext_init.py @@ -10,6 +10,7 @@ _speedups = None # type: ignore[assignment] +@pytest.mark.thread_unsafe(reason="Tampers with sys.modules") @pytest.mark.skipif(_speedups is None, reason="speedups unavailable") def test_ext_init() -> None: """Test that the extension module uses multi-phase init by checking that diff --git a/tests/test_leak.py b/tests/test_leak.py index b786e072..c9de4751 100644 --- a/tests/test_leak.py +++ b/tests/test_leak.py @@ -2,9 +2,12 @@ import gc +import pytest + from markupsafe import escape +@pytest.mark.thread_unsafe(reason="Tests gc.get_objects()") def test_markup_leaks() -> None: counts = set() # Try to start with a "clean" count. Works for PyPy but not 3.13 JIT. diff --git a/uv.lock b/uv.lock index 4d63ac7f..0110dc46 100644 --- a/uv.lock +++ b/uv.lock @@ -1,8 +1,9 @@ version = 1 -revision = 2 +revision = 3 requires-python = ">=3.9" resolution-markers = [ - "python_full_version >= '3.12'", + "python_full_version >= '3.13'", + "python_full_version == '3.12.*'", "python_full_version == '3.11.*'", "python_full_version == '3.10.*'", "python_full_version < '3.10'", @@ -25,7 +26,8 @@ name = "alabaster" version = "1.0.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.12'", + "python_full_version >= '3.13'", + "python_full_version == '3.12.*'", "python_full_version == '3.11.*'", "python_full_version == '3.10.*'", ] @@ -189,7 +191,8 @@ name = "click" version = "8.3.0" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.12'", + "python_full_version >= '3.13'", + "python_full_version == '3.12.*'", "python_full_version == '3.11.*'", "python_full_version == '3.10.*'", ] @@ -390,6 +393,7 @@ pre-commit = [ ] tests = [ { name = "pytest" }, + { name = "pytest-run-parallel", marker = "python_full_version >= '3.13'" }, ] typing = [ { name = "mypy" }, @@ -416,7 +420,10 @@ pre-commit = [ { name = "pre-commit" }, { name = "pre-commit-uv" }, ] -tests = [{ name = "pytest" }] +tests = [ + { name = "pytest" }, + { name = "pytest-run-parallel", marker = "python_full_version >= '3.13'" }, +] typing = [ { name = "mypy" }, { name = "pyright" }, @@ -626,6 +633,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, ] +[[package]] +name = "pytest-run-parallel" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pytest", marker = "python_full_version >= '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d0/0c/2dd2d1f2f014e8d9c9f365eb4138c52eeb5b96fa8574f15c1d9436842a48/pytest_run_parallel-0.7.0.tar.gz", hash = "sha256:05088a808d26975f095739a06efc9e8ba4749c194457f9927903eaacdd1e05ce", size = 50185, upload-time = "2025-09-25T13:57:29.686Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/64/d676a217242e263a62bf30588654dba53252fb40df15d8a7023026dae109/pytest_run_parallel-0.7.0-py3-none-any.whl", hash = "sha256:0d8b981a2ac895df25c4bc27b89e8df0bbddbead57cbfdb0aed743db8533a8c0", size = 18884, upload-time = "2025-09-25T13:57:28.545Z" }, +] + [[package]] name = "pyyaml" version = "6.0.2" @@ -815,7 +834,8 @@ name = "sphinx" version = "8.2.3" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.12'", + "python_full_version >= '3.13'", + "python_full_version == '3.12.*'", "python_full_version == '3.11.*'", ] dependencies = [ @@ -869,7 +889,8 @@ name = "sphinx-autobuild" version = "2025.8.25" source = { registry = "https://pypi.org/simple" } resolution-markers = [ - "python_full_version >= '3.12'", + "python_full_version >= '3.13'", + "python_full_version == '3.12.*'", "python_full_version == '3.11.*'", ] dependencies = [ From 5c1e0ba949068dda2e4d76ade644df1729e1fb0e Mon Sep 17 00:00:00 2001 From: David Lord Date: Sat, 27 Sep 2025 10:09:57 -0700 Subject: [PATCH 2/3] add parallel env to tox --- .github/workflows/tests.yaml | 2 +- pyproject.toml | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 8ec3f175..b7dbad48 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -36,7 +36,7 @@ jobs: allow-prereleases: true - run: uv run --locked tox run -e ${{ matrix.tox || format('py{0}', matrix.python) }} - if: endsWith(matrix.python, 't') - run: uv run --locked tox run -e ${{ format('py{0}', matrix.python) }} -- --parallel-threads=4 + run: uv run --locked tox run -e parallel typing: runs-on: ubuntu-latest steps: diff --git a/pyproject.toml b/pyproject.toml index d3c9a990..bd02beb8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -129,8 +129,8 @@ tag-only = [ [tool.tox] env_list = [ - "py3.14", "py3.14t", "py3.13", "py3.13t", - "py3.12", "py3.11", "py3.10", "py3.9", + "py3.14", "py3.14t", "parallel", + "py3.13", "py3.13t", "py3.12", "py3.11", "py3.10", "py3.9", "pypy3.11", "style", "typing", @@ -150,6 +150,15 @@ commands = [[ {replace = "posargs", default = [], extend = true}, ]] +[tool.tox.env.parallel] +description = "check for free threading issues" +base_python = ["3.14t"] +commands = [[ + "pytest", "-v", "--tb=short", "--basetemp=env_tmp_dir", + "--parallel-threads=8", + {replace = "posargs", default = [], extend = true}, +]] + [tool.tox.env.style] description = "run all pre-commit hooks on all files" dependency_groups = ["pre-commit"] From f0b96109c3b39e93275d4f9ec2771de51cbb311b Mon Sep 17 00:00:00 2001 From: David Lord Date: Sat, 27 Sep 2025 10:36:11 -0700 Subject: [PATCH 3/3] remove classifier --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index bd02beb8..e7804ffd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,6 @@ classifiers = [ "Intended Audience :: Developers", "Operating System :: OS Independent", "Programming Language :: Python", - "Programming Language :: Python :: Free Threading :: 2 - Beta", "Topic :: Internet :: WWW/HTTP :: Dynamic Content", "Topic :: Text Processing :: Markup :: HTML", "Typing :: Typed",