From 3f380d8bf907c3639f469c1b808631c59bacad84 Mon Sep 17 00:00:00 2001 From: Nyako Shigure Date: Mon, 28 Sep 2026 23:26:23 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20feat:=20support=20Pyth?= =?UTF-8?q?on=203.15=20and=20free-threaded=20wheels,=20drop=203.10?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/lint-and-fmt.yml | 2 +- .github/workflows/release.yml | 90 +++++++++++++++++++++++++++--- .github/workflows/unit-test.yml | 30 ++++------ README.md | 11 ++++ pyproject.toml | 8 +-- rust/Cargo.toml | 2 +- rust/src/lib.rs | 2 +- tests/test_basic.py | 11 +++- tests/test_runtime.py | 82 +++++++++++++++++++++++++++ 9 files changed, 204 insertions(+), 34 deletions(-) create mode 100644 tests/test_runtime.py diff --git a/.github/workflows/lint-and-fmt.yml b/.github/workflows/lint-and-fmt.yml index a0ccd78..ee74313 100644 --- a/.github/workflows/lint-and-fmt.yml +++ b/.github/workflows/lint-and-fmt.yml @@ -13,7 +13,7 @@ jobs: strategy: matrix: # Only run linter and formatter on minimum supported Python version - python-version: ['3.10'] + python-version: ['3.11'] architecture: ['x64'] name: lint and fmt - Python ${{ matrix.python-version }} on ${{ matrix.architecture }} steps: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2f707e6..4016b5b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,13 +22,14 @@ jobs: - uses: actions/checkout@v7 - uses: actions/setup-python@v7 with: - python-version: 3.x + python-version: '3.11' allow-prereleases: true - name: Build wheels uses: PyO3/maturin-action@v1 with: + maturin-version: '1.15.0' target: ${{ matrix.target }} - args: --release --out dist --find-interpreter + args: --release --locked --out dist --interpreter python3.11 sccache: 'true' manylinux: auto - name: Upload wheels @@ -47,14 +48,15 @@ jobs: - uses: actions/checkout@v7 - uses: actions/setup-python@v7 with: - python-version: 3.x + python-version: '3.11' architecture: ${{ matrix.target }} allow-prereleases: true - name: Build wheels uses: PyO3/maturin-action@v1 with: + maturin-version: '1.15.0' target: ${{ matrix.target }} - args: --release --out dist --find-interpreter + args: --release --locked --out dist --interpreter python sccache: 'true' - name: Upload wheels uses: actions/upload-artifact@v7 @@ -72,13 +74,14 @@ jobs: - uses: actions/checkout@v7 - uses: actions/setup-python@v7 with: - python-version: 3.x + python-version: '3.11' allow-prereleases: true - name: Build wheels uses: PyO3/maturin-action@v1 with: + maturin-version: '1.15.0' target: ${{ matrix.target }} - args: --release --out dist --find-interpreter + args: --release --locked --out dist --interpreter python3.11 sccache: 'true' - name: Upload wheels uses: actions/upload-artifact@v7 @@ -86,6 +89,76 @@ jobs: name: wheels-macos-${{ matrix.target }} path: dist + # abi3 wheels cannot be loaded by free-threaded CPython. Build a separate + # version-specific wheel for each native 64-bit release platform. + free-threaded: + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-24.04 + target: x86_64 + - os: ubuntu-24.04-arm + target: aarch64 + - os: windows-latest + target: x64 + - os: macos-15-intel + target: x86_64 + - os: macos-15 + target: aarch64 + runs-on: ${{ matrix.os }} + name: Python 3.15t wheel, ${{ matrix.os }} + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-python@v7 + with: + python-version: '3.15t' + allow-prereleases: true + - name: Build free-threaded wheel + uses: PyO3/maturin-action@v1 + with: + maturin-version: '1.15.0' + target: ${{ matrix.target }} + args: --release --locked --out dist --interpreter ${{ runner.os == 'Linux' && 'python3.15t' || 'python' }} + # The manylinux images provide their own CPython 3.15t interpreter. + manylinux: '2_28' + sccache: 'true' + - uses: actions/upload-artifact@v7 + with: + name: wheels-315t-${{ matrix.os }} + path: dist + if-no-files-found: error + + wheel-test: + needs: [linux, windows, macos, free-threaded] + strategy: + fail-fast: false + matrix: + os: [ubuntu-24.04, ubuntu-24.04-arm, windows-latest, macos-15-intel, macos-15] + python-version: ['3.15', '3.15t'] + runs-on: ${{ matrix.os }} + name: Release wheel test, Python ${{ matrix.python-version }}, ${{ matrix.os }} + env: + EXPECTED_PYTHON_VERSION: ${{ matrix.python-version }} + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-python@v7 + with: + python-version: ${{ matrix.python-version }} + allow-prereleases: true + - uses: actions/download-artifact@v8 + with: + pattern: wheels-* + merge-multiple: true + path: dist + - name: Install release wheel (no source fallback) + run: | + python -m pip install --upgrade pip + python -m pip install --no-index --only-binary=:all: --find-links dist codespan-reporting + python -m pip install pytest + - name: Test installed native extension + run: python -m pytest -v + sdist: runs-on: ubuntu-latest steps: @@ -93,6 +166,7 @@ jobs: - name: Build sdist uses: PyO3/maturin-action@v1 with: + maturin-version: '1.15.0' command: sdist args: --out dist - name: Upload sdist @@ -105,7 +179,7 @@ jobs: runs-on: ubuntu-latest name: Publish to PyPI if: "startsWith(github.ref, 'refs/tags/')" - needs: [linux, windows, macos, sdist] + needs: [linux, windows, macos, free-threaded, wheel-test, sdist] permissions: id-token: write @@ -123,7 +197,7 @@ jobs: runs-on: ubuntu-latest name: Publish to GitHub if: "startsWith(github.ref, 'refs/tags/')" - needs: [linux, windows, macos, sdist] + needs: [linux, windows, macos, free-threaded, wheel-test, sdist] permissions: contents: write steps: diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 50a44fc..3fea6d3 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -11,35 +11,29 @@ jobs: unit-test: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: - python-version: ['3.10', '3.11', '3.12', '3.13', '3.14'] - architecture: ['x64'] - name: unittest - Python ${{ matrix.python-version }} on ${{ matrix.architecture }} + python-version: ['3.11', '3.12', '3.13', '3.14', '3.15', '3.15t'] + name: unittest - Python ${{ matrix.python-version }} + env: + EXPECTED_PYTHON_VERSION: ${{ matrix.python-version }} steps: - name: Checkout uses: actions/checkout@v7 - - name: Install maturin - run: pipx install maturin - - name: Install python uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} - architecture: ${{ matrix.architecture }} cache: 'pip' allow-prereleases: true - - name: Install just - uses: extractions/setup-just@v4 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Install dependencies + - name: Build and install wheel run: | - just create-venv - just ci-install + python -m pip install --upgrade pip + python -m pip wheel --no-deps --wheel-dir dist . + python -m pip install --no-index --only-binary=:all: --find-links dist codespan-reporting + python -m pip install pytest - - name: unit test - run: | - just ci-test + - name: Test installed native extension + run: python -m pytest -v diff --git a/README.md b/README.md index a7f0d38..e3291af 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,17 @@ A python binding for [codespan-reporting](https://github.com/brendanzab/codespan ## Installation +Requires Python 3.11 or newer. CPython 3.15 (including the free-threaded `3.15t` +build) is tested with native wheels. Standard CPython uses `cp311-abi3` wheels; +free-threaded 3.15 uses separate `cp315-cp315t` wheels for Linux x86-64/AArch64, +Windows x86-64, and macOS x86-64/Apple Silicon. Python 3.15 is currently tested +against its release candidate; CI allows prerelease interpreters until the final +release is available. + +The extension does not require the GIL. Concurrent rendering can share files and +diagnostics, but callers must synchronize mutation of a shared `SimpleFiles` +instance (for example, calls to `add` while another thread renders it). + ```bash pip install codespan-reporting ``` diff --git a/pyproject.toml b/pyproject.toml index 1ee1f0a..b32a985 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,12 @@ [build-system] -requires = ["maturin>=1.4,<2.0"] +requires = ["maturin>=1.15,<2.0"] build-backend = "maturin" [project] name = "codespan-reporting" description = "A python binding for codespan-reporting" readme = "README.md" -requires-python = ">=3.10" +requires-python = ">=3.11" authors = [{ name = "Nyakku Shigure", email = "sigure.qaq@gmail.com" }] keywords = ["codespan-reporting"] license = "MIT" @@ -15,11 +15,11 @@ classifiers = [ "Programming Language :: Rust", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: 3.15", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", ] @@ -30,7 +30,7 @@ dev = ["pytest>=7.4.4", "ruff==0.16.8", "ty>=0.0.32", "build"] [tool.ruff] line-length = 120 -target-version = "py310" +target-version = "py311" [tool.ruff.lint] select = [ diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 8c4e82e..1946909 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -9,5 +9,5 @@ name = "codespan_reporting_pyo3" crate-type = ["cdylib"] [dependencies] -pyo3 = { version = "0.29.0", features = ["abi3-py310"] } +pyo3 = { version = "0.29.2", features = ["abi3-py311"] } codespan-reporting = "0.13.0" diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 1b9f462..e7bee01 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -6,7 +6,7 @@ mod file; /// A python binding for codespan-reporting. #[pymodule] -#[pyo3(name = "_core")] +#[pyo3(name = "_core", gil_used = false)] fn codespan_reporting_pyo3(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(emit::emit, m)?)?; m.add_class::()?; diff --git a/tests/test_basic.py b/tests/test_basic.py index 1627380..0f9c1bf 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1,5 +1,7 @@ import textwrap +import pytest + from codespan_reporting import ( Config, Diagnostic, @@ -10,7 +12,7 @@ ) -def test_basic(): +def test_basic(capfd: pytest.CaptureFixture[str]): files = SimpleFiles() file_id = files.add( @@ -61,3 +63,10 @@ def test_basic(): writer = StandardStream.Stderr config = Config() emit(writer, config, files, diagnostic) + + output = capfd.readouterr().err + assert "error[E0308]" in output + assert "FizzBuzz.fun" in output + assert "`case` clauses have incompatible types" in output + assert "expected `String`, found `Nat`" in output + assert "fizzâ‚‚" in output diff --git a/tests/test_runtime.py b/tests/test_runtime.py new file mode 100644 index 0000000..99d318d --- /dev/null +++ b/tests/test_runtime.py @@ -0,0 +1,82 @@ +import json +import os +import subprocess +import sys +import sysconfig +from concurrent.futures import ThreadPoolExecutor + +import pytest + +from codespan_reporting import Config, Diagnostic, Label, SimpleFiles, StandardStream, emit + + +def test_native_import_and_gil(): + # Use a fresh process with the default GIL policy: forcing PYTHON_GIL=0 + # would hide an extension which silently re-enables the GIL on import. + env = os.environ.copy() + env.pop("PYTHON_GIL", None) + result = subprocess.run( + [ + sys.executable, + "-Werror::RuntimeWarning", + "-c", + """ +import importlib.machinery +import json +import sys +import sysconfig + +free_threaded = bool(sysconfig.get_config_var("Py_GIL_DISABLED")) +is_gil_enabled = getattr(sys, "_is_gil_enabled", lambda: True) +assert is_gil_enabled() == (not free_threaded) +from codespan_reporting import _core +assert any(_core.__file__.endswith(suffix) for suffix in importlib.machinery.EXTENSION_SUFFIXES) +assert is_gil_enabled() == (not free_threaded) +print(json.dumps({ + "version": f"{sys.version_info.major}.{sys.version_info.minor}" + ("t" if free_threaded else ""), + "extension": _core.__file__, +})) +""", + ], + env=env, + capture_output=True, + text=True, + check=True, + ) + runtime = json.loads(result.stdout) + current_version = f"{sys.version_info.major}.{sys.version_info.minor}" + if sysconfig.get_config_var("Py_GIL_DISABLED"): + current_version += "t" + assert runtime["version"] == os.environ.get("EXPECTED_PYTHON_VERSION", current_version) + + +def test_invalid_file_id(): + diagnostic = Diagnostic.error("E001", "missing file", [Label.primary(0, 0, 1, "missing")], []) + with pytest.raises(ValueError): + emit(StandardStream.Stderr, Config(), SimpleFiles(), diagnostic) + + +def test_parallel_native_rendering(capfd: pytest.CaptureFixture[str]): + # Read-only objects may be shared. SimpleFiles.add mutates its receiver and + # callers must serialize mutation of a shared instance, as with other PyO3 + # mutable classes. Build this shared source before starting the threads. + files = SimpleFiles() + file_id = files.add("parallel.py", "value = 1\n") + config = Config() + diagnostic = Diagnostic.error("E001", "parallel diagnostic", [Label.primary(file_id, 0, 5, "value")], []) + + def render(_: int) -> None: + emit(StandardStream.Stderr, config, files, diagnostic) + local_files = SimpleFiles() + assert local_files.add("thread.py", "x\n") == 0 + local_diagnostic = Diagnostic.note("N001", "thread-local diagnostic", [Label.primary(0, 0, 1, "x")], []) + emit(StandardStream.Stderr, config, local_files, local_diagnostic) + + with ThreadPoolExecutor(max_workers=8) as executor: + list(executor.map(render, range(32))) + + output = capfd.readouterr().err + assert output.count("parallel diagnostic") == 32 + assert output.count("thread-local diagnostic") == 32 + assert output.count("parallel.py") == 32 + assert output.count("thread.py") == 32 From 272423ae588df43bfe8fd1f2c4ec01fcdc80a674 Mon Sep 17 00:00:00 2001 From: Nyako Shigure Date: Tue, 29 Sep 2026 00:45:34 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20ci:=20limit=20Python?= =?UTF-8?q?=20upgrade=20to=20existing=20test=20matrix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/release.yml | 90 +++------------------------------ .github/workflows/unit-test.yml | 28 ++++++---- README.md | 11 ---- pyproject.toml | 2 +- rust/Cargo.toml | 2 +- rust/src/lib.rs | 2 +- tests/test_basic.py | 17 +++---- tests/test_runtime.py | 82 ------------------------------ 8 files changed, 35 insertions(+), 199 deletions(-) delete mode 100644 tests/test_runtime.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4016b5b..2f707e6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,14 +22,13 @@ jobs: - uses: actions/checkout@v7 - uses: actions/setup-python@v7 with: - python-version: '3.11' + python-version: 3.x allow-prereleases: true - name: Build wheels uses: PyO3/maturin-action@v1 with: - maturin-version: '1.15.0' target: ${{ matrix.target }} - args: --release --locked --out dist --interpreter python3.11 + args: --release --out dist --find-interpreter sccache: 'true' manylinux: auto - name: Upload wheels @@ -48,15 +47,14 @@ jobs: - uses: actions/checkout@v7 - uses: actions/setup-python@v7 with: - python-version: '3.11' + python-version: 3.x architecture: ${{ matrix.target }} allow-prereleases: true - name: Build wheels uses: PyO3/maturin-action@v1 with: - maturin-version: '1.15.0' target: ${{ matrix.target }} - args: --release --locked --out dist --interpreter python + args: --release --out dist --find-interpreter sccache: 'true' - name: Upload wheels uses: actions/upload-artifact@v7 @@ -74,14 +72,13 @@ jobs: - uses: actions/checkout@v7 - uses: actions/setup-python@v7 with: - python-version: '3.11' + python-version: 3.x allow-prereleases: true - name: Build wheels uses: PyO3/maturin-action@v1 with: - maturin-version: '1.15.0' target: ${{ matrix.target }} - args: --release --locked --out dist --interpreter python3.11 + args: --release --out dist --find-interpreter sccache: 'true' - name: Upload wheels uses: actions/upload-artifact@v7 @@ -89,76 +86,6 @@ jobs: name: wheels-macos-${{ matrix.target }} path: dist - # abi3 wheels cannot be loaded by free-threaded CPython. Build a separate - # version-specific wheel for each native 64-bit release platform. - free-threaded: - strategy: - fail-fast: false - matrix: - include: - - os: ubuntu-24.04 - target: x86_64 - - os: ubuntu-24.04-arm - target: aarch64 - - os: windows-latest - target: x64 - - os: macos-15-intel - target: x86_64 - - os: macos-15 - target: aarch64 - runs-on: ${{ matrix.os }} - name: Python 3.15t wheel, ${{ matrix.os }} - steps: - - uses: actions/checkout@v7 - - uses: actions/setup-python@v7 - with: - python-version: '3.15t' - allow-prereleases: true - - name: Build free-threaded wheel - uses: PyO3/maturin-action@v1 - with: - maturin-version: '1.15.0' - target: ${{ matrix.target }} - args: --release --locked --out dist --interpreter ${{ runner.os == 'Linux' && 'python3.15t' || 'python' }} - # The manylinux images provide their own CPython 3.15t interpreter. - manylinux: '2_28' - sccache: 'true' - - uses: actions/upload-artifact@v7 - with: - name: wheels-315t-${{ matrix.os }} - path: dist - if-no-files-found: error - - wheel-test: - needs: [linux, windows, macos, free-threaded] - strategy: - fail-fast: false - matrix: - os: [ubuntu-24.04, ubuntu-24.04-arm, windows-latest, macos-15-intel, macos-15] - python-version: ['3.15', '3.15t'] - runs-on: ${{ matrix.os }} - name: Release wheel test, Python ${{ matrix.python-version }}, ${{ matrix.os }} - env: - EXPECTED_PYTHON_VERSION: ${{ matrix.python-version }} - steps: - - uses: actions/checkout@v7 - - uses: actions/setup-python@v7 - with: - python-version: ${{ matrix.python-version }} - allow-prereleases: true - - uses: actions/download-artifact@v8 - with: - pattern: wheels-* - merge-multiple: true - path: dist - - name: Install release wheel (no source fallback) - run: | - python -m pip install --upgrade pip - python -m pip install --no-index --only-binary=:all: --find-links dist codespan-reporting - python -m pip install pytest - - name: Test installed native extension - run: python -m pytest -v - sdist: runs-on: ubuntu-latest steps: @@ -166,7 +93,6 @@ jobs: - name: Build sdist uses: PyO3/maturin-action@v1 with: - maturin-version: '1.15.0' command: sdist args: --out dist - name: Upload sdist @@ -179,7 +105,7 @@ jobs: runs-on: ubuntu-latest name: Publish to PyPI if: "startsWith(github.ref, 'refs/tags/')" - needs: [linux, windows, macos, free-threaded, wheel-test, sdist] + needs: [linux, windows, macos, sdist] permissions: id-token: write @@ -197,7 +123,7 @@ jobs: runs-on: ubuntu-latest name: Publish to GitHub if: "startsWith(github.ref, 'refs/tags/')" - needs: [linux, windows, macos, free-threaded, wheel-test, sdist] + needs: [linux, windows, macos, sdist] permissions: contents: write steps: diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 3fea6d3..2c209cc 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -11,29 +11,35 @@ jobs: unit-test: runs-on: ubuntu-latest strategy: - fail-fast: false matrix: python-version: ['3.11', '3.12', '3.13', '3.14', '3.15', '3.15t'] - name: unittest - Python ${{ matrix.python-version }} - env: - EXPECTED_PYTHON_VERSION: ${{ matrix.python-version }} + architecture: ['x64'] + name: unittest - Python ${{ matrix.python-version }} on ${{ matrix.architecture }} steps: - name: Checkout uses: actions/checkout@v7 + - name: Install maturin + run: pipx install maturin + - name: Install python uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} + architecture: ${{ matrix.architecture }} cache: 'pip' allow-prereleases: true - - name: Build and install wheel + - name: Install just + uses: extractions/setup-just@v4 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Install dependencies run: | - python -m pip install --upgrade pip - python -m pip wheel --no-deps --wheel-dir dist . - python -m pip install --no-index --only-binary=:all: --find-links dist codespan-reporting - python -m pip install pytest + just create-venv + .venv/bin/python -m pip install ".[dev]" - - name: Test installed native extension - run: python -m pytest -v + - name: unit test + run: | + just ci-test diff --git a/README.md b/README.md index e3291af..a7f0d38 100644 --- a/README.md +++ b/README.md @@ -14,17 +14,6 @@ A python binding for [codespan-reporting](https://github.com/brendanzab/codespan ## Installation -Requires Python 3.11 or newer. CPython 3.15 (including the free-threaded `3.15t` -build) is tested with native wheels. Standard CPython uses `cp311-abi3` wheels; -free-threaded 3.15 uses separate `cp315-cp315t` wheels for Linux x86-64/AArch64, -Windows x86-64, and macOS x86-64/Apple Silicon. Python 3.15 is currently tested -against its release candidate; CI allows prerelease interpreters until the final -release is available. - -The extension does not require the GIL. Concurrent rendering can share files and -diagnostics, but callers must synchronize mutation of a shared `SimpleFiles` -instance (for example, calls to `add` while another thread renders it). - ```bash pip install codespan-reporting ``` diff --git a/pyproject.toml b/pyproject.toml index b32a985..a9a09e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["maturin>=1.15,<2.0"] +requires = ["maturin>=1.14,<2.0"] build-backend = "maturin" [project] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 1946909..8c4e82e 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -9,5 +9,5 @@ name = "codespan_reporting_pyo3" crate-type = ["cdylib"] [dependencies] -pyo3 = { version = "0.29.2", features = ["abi3-py311"] } +pyo3 = { version = "0.29.0", features = ["abi3-py310"] } codespan-reporting = "0.13.0" diff --git a/rust/src/lib.rs b/rust/src/lib.rs index e7bee01..1b9f462 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -6,7 +6,7 @@ mod file; /// A python binding for codespan-reporting. #[pymodule] -#[pyo3(name = "_core", gil_used = false)] +#[pyo3(name = "_core")] fn codespan_reporting_pyo3(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(emit::emit, m)?)?; m.add_class::()?; diff --git a/tests/test_basic.py b/tests/test_basic.py index 0f9c1bf..baf0c06 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1,7 +1,7 @@ +import sys +import sysconfig import textwrap -import pytest - from codespan_reporting import ( Config, Diagnostic, @@ -12,7 +12,11 @@ ) -def test_basic(capfd: pytest.CaptureFixture[str]): +def test_basic(): + # Importing the native extension must not silently re-enable the GIL. + if sys.version_info >= (3, 13) and sysconfig.get_config_var("Py_GIL_DISABLED"): + assert not sys._is_gil_enabled() + files = SimpleFiles() file_id = files.add( @@ -63,10 +67,3 @@ def test_basic(capfd: pytest.CaptureFixture[str]): writer = StandardStream.Stderr config = Config() emit(writer, config, files, diagnostic) - - output = capfd.readouterr().err - assert "error[E0308]" in output - assert "FizzBuzz.fun" in output - assert "`case` clauses have incompatible types" in output - assert "expected `String`, found `Nat`" in output - assert "fizzâ‚‚" in output diff --git a/tests/test_runtime.py b/tests/test_runtime.py deleted file mode 100644 index 99d318d..0000000 --- a/tests/test_runtime.py +++ /dev/null @@ -1,82 +0,0 @@ -import json -import os -import subprocess -import sys -import sysconfig -from concurrent.futures import ThreadPoolExecutor - -import pytest - -from codespan_reporting import Config, Diagnostic, Label, SimpleFiles, StandardStream, emit - - -def test_native_import_and_gil(): - # Use a fresh process with the default GIL policy: forcing PYTHON_GIL=0 - # would hide an extension which silently re-enables the GIL on import. - env = os.environ.copy() - env.pop("PYTHON_GIL", None) - result = subprocess.run( - [ - sys.executable, - "-Werror::RuntimeWarning", - "-c", - """ -import importlib.machinery -import json -import sys -import sysconfig - -free_threaded = bool(sysconfig.get_config_var("Py_GIL_DISABLED")) -is_gil_enabled = getattr(sys, "_is_gil_enabled", lambda: True) -assert is_gil_enabled() == (not free_threaded) -from codespan_reporting import _core -assert any(_core.__file__.endswith(suffix) for suffix in importlib.machinery.EXTENSION_SUFFIXES) -assert is_gil_enabled() == (not free_threaded) -print(json.dumps({ - "version": f"{sys.version_info.major}.{sys.version_info.minor}" + ("t" if free_threaded else ""), - "extension": _core.__file__, -})) -""", - ], - env=env, - capture_output=True, - text=True, - check=True, - ) - runtime = json.loads(result.stdout) - current_version = f"{sys.version_info.major}.{sys.version_info.minor}" - if sysconfig.get_config_var("Py_GIL_DISABLED"): - current_version += "t" - assert runtime["version"] == os.environ.get("EXPECTED_PYTHON_VERSION", current_version) - - -def test_invalid_file_id(): - diagnostic = Diagnostic.error("E001", "missing file", [Label.primary(0, 0, 1, "missing")], []) - with pytest.raises(ValueError): - emit(StandardStream.Stderr, Config(), SimpleFiles(), diagnostic) - - -def test_parallel_native_rendering(capfd: pytest.CaptureFixture[str]): - # Read-only objects may be shared. SimpleFiles.add mutates its receiver and - # callers must serialize mutation of a shared instance, as with other PyO3 - # mutable classes. Build this shared source before starting the threads. - files = SimpleFiles() - file_id = files.add("parallel.py", "value = 1\n") - config = Config() - diagnostic = Diagnostic.error("E001", "parallel diagnostic", [Label.primary(file_id, 0, 5, "value")], []) - - def render(_: int) -> None: - emit(StandardStream.Stderr, config, files, diagnostic) - local_files = SimpleFiles() - assert local_files.add("thread.py", "x\n") == 0 - local_diagnostic = Diagnostic.note("N001", "thread-local diagnostic", [Label.primary(0, 0, 1, "x")], []) - emit(StandardStream.Stderr, config, local_files, local_diagnostic) - - with ThreadPoolExecutor(max_workers=8) as executor: - list(executor.map(render, range(32))) - - output = capfd.readouterr().err - assert output.count("parallel diagnostic") == 32 - assert output.count("thread-local diagnostic") == 32 - assert output.count("parallel.py") == 32 - assert output.count("thread.py") == 32 From 3bb8efffb5f605c9a51ea56ff651a91bbc0a31da Mon Sep 17 00:00:00 2001 From: Nyako Shigure Date: Tue, 29 Sep 2026 02:18:33 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20ci:=20omit=20free-thre?= =?UTF-8?q?aded=20Python=20from=20version=20upgrade?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/unit-test.yml | 4 ++-- pyproject.toml | 2 +- tests/test_basic.py | 6 ------ 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/.github/workflows/unit-test.yml b/.github/workflows/unit-test.yml index 2c209cc..bc520ac 100644 --- a/.github/workflows/unit-test.yml +++ b/.github/workflows/unit-test.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.11', '3.12', '3.13', '3.14', '3.15', '3.15t'] + python-version: ['3.11', '3.12', '3.13', '3.14', '3.15'] architecture: ['x64'] name: unittest - Python ${{ matrix.python-version }} on ${{ matrix.architecture }} steps: @@ -38,7 +38,7 @@ jobs: - name: Install dependencies run: | just create-venv - .venv/bin/python -m pip install ".[dev]" + just ci-install - name: unit test run: | diff --git a/pyproject.toml b/pyproject.toml index a9a09e6..0f5f058 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["maturin>=1.14,<2.0"] +requires = ["maturin>=1.4,<2.0"] build-backend = "maturin" [project] diff --git a/tests/test_basic.py b/tests/test_basic.py index baf0c06..1627380 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -1,5 +1,3 @@ -import sys -import sysconfig import textwrap from codespan_reporting import ( @@ -13,10 +11,6 @@ def test_basic(): - # Importing the native extension must not silently re-enable the GIL. - if sys.version_info >= (3, 13) and sysconfig.get_config_var("Py_GIL_DISABLED"): - assert not sys._is_gil_enabled() - files = SimpleFiles() file_id = files.add(