Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 17 additions & 10 deletions .github/workflows/python-test.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
# This workflow will install Python dependencies and run tests with a variety of Python versions
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python

name: Test

on:
Expand All @@ -14,12 +11,13 @@ jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: true
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
python-version: ["3.10", "3.11", "3.12", "pypy3.10"]
pytest-version: ["8"]
cython-version: ["0.29", "3"]
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ["3.11", "3.12", "3.13", "3.14"]
pytest-version: ["9"]
cython-version: ["3"]
editable-install: ["true", "false"]

steps:
- uses: actions/checkout@v6
Expand All @@ -38,6 +36,15 @@ jobs:
python -m pip install cython==${{ matrix.cython-version }}.*
python -m pip install -e .

- name: Install example-project (editable)
run: python -m pip install -e tests/example-project
if: ${{ matrix.editable-install == 'true' }}

- name: Install example-project (non-editable)
run: python -m pip install tests/example-project
if: ${{ matrix.editable-install == 'false' }}

- name: Test with pytest
run: |
pytest -vv tests src
env:
PY_IGNORE_IMPORTMISMATCH: "1"
run: pytest -vv tests
11 changes: 4 additions & 7 deletions src/pytest_cython/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
from importlib.metadata import version, PackageNotFoundError
from importlib.metadata import version

try:
__version__ = version("pytest-cython")
except PackageNotFoundError:
import warnings
warnings.warn('could not get pytest-cython version')
__version__ = '0.0.0'
__version__ = version('pytest-cython')

del version
42 changes: 2 additions & 40 deletions src/pytest_cython/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@

from typing import Any, Iterable, Union

from _pytest.nodes import Collector
from _pytest.doctest import skip, DoctestModule, DoctestItem
from _pytest.doctest import DoctestModule, DoctestItem
from _pytest.pathlib import resolve_package_path, ImportMode


Expand All @@ -33,15 +32,11 @@ def pytest_addoption(parser: pytest.Parser):
)


def pytest_collect_file(file_path: pathlib.Path, path, parent: pytest.Collector) -> pytest.Module:
def pytest_collect_file(file_path: pathlib.Path, parent: pytest.Collector) -> pytest.Module:
config = parent.config
if file_path.suffix not in CYTHON_SUFFIXES or not config.getoption('--doctest-cython'):
return

bin_path = file_path.with_suffix(EXT_SUFFIX)
if not bin_path.exists():
return

# only run test if matching .so and .pyx files exist
return _PatchedDoctestModule.from_parent(parent, path=file_path)

Expand All @@ -62,15 +57,6 @@ def collect(self) -> Iterable[DoctestItem]:
os.environ[IGNORE_IMPORTMISMATCH_KEY] = IGNORE_IMPORTMISMATCH

module = self.obj # module already imported

try:
_check_module_import(module, self.path, mode)
except Collector.CollectError:
if self.config.getvalue("doctest_ignore_import_errors"):
skip("unable to import module %r" % self.path)
else:
raise

return _add_line_numbers(module, items)


Expand All @@ -94,30 +80,6 @@ def _get_module_name(path: pathlib.Path) -> str:
return module_name


def _check_module_import(module: Any, path: pathlib.Path, mode: ImportMode) -> None:
# double check that the only difference is the extension else raise an exception

if mode is ImportMode.importlib or IGNORE_IMPORTMISMATCH == "1":
return

module_name = _get_module_name(path)
module_file = _without_suffixes(module.__file__)
import_file = _without_suffixes(path)

if module_file == import_file:
return

raise Collector.CollectError(
"import file mismatch:\n"
"imported module %r has this __file__ attribute:\n"
" %s\n"
"which is not the same as the test file we want to collect:\n"
" %s\n"
"HINT: remove __pycache__ / .pyc files and/or use a "
"unique basename for your test file modules" % (module_name, module_file, import_file)
)


def _add_line_numbers(module: Any, items: Iterable[DoctestItem]) -> Iterable[DoctestItem]:
# handle tests from Cython's internal __test__ dict generated by
# the autotestdict directive; we exclude the tests from __test__,
Expand Down
2 changes: 2 additions & 0 deletions tests/example-project/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[build-system]
requires = ["setuptools>=74.1", "Cython", "wheel"]
2 changes: 1 addition & 1 deletion tests/example-project/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
]

setup(
name='pytest-cython',
name='pytest-cython-example',
version='0.3.1',
description="Example Cython project for pytest-cython tests",
package_dir={'': 'src'},
Expand Down
26 changes: 5 additions & 21 deletions tests/test_pytest_cython.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,19 @@

import pathlib
import pytest
import shutil
from pathlib import Path

from setuptools.sandbox import run_setup

# import pytest_cython as a quite check to ensure it was installed before running tests
# Check imports to ensure packages are installed before running tests
import pytest_cython.plugin
import pypackage


ROOT_PATH = pathlib.Path(__file__).parent
PROJECT_PATH = ROOT_PATH.joinpath('example-project')
PACKAGE_PATH = PROJECT_PATH.joinpath('src', 'pypackage')

IMPORT_MODES = ["append", "prepend", "importlib"]

# TODO: Figure out if importlib can be supported.
IMPORT_MODES = ["append", "prepend"]

def get_module(basename: str, suffix='.pyx') -> pathlib.Path:
return PACKAGE_PATH.joinpath(basename + suffix)
Expand All @@ -25,21 +24,6 @@ def run_pytest(pytester: pytest.Pytester, module: pathlib.Path, import_mode) ->
return pytester.runpytest('-vv', '--doctest-cython', '--import-mode', import_mode, str(module))


@pytest.fixture(scope='module', autouse=True)
def build_example_project():
shutil.rmtree(PROJECT_PATH.joinpath('build'), True)
shutil.rmtree(PACKAGE_PATH.joinpath('__pycache__'), True)

for file in PACKAGE_PATH.glob('*.pyd'):
file.unlink()

for file in PACKAGE_PATH.glob('*.c'):
file.unlink()

setup_py = PROJECT_PATH.joinpath('setup.py')
run_setup(str(setup_py), ['build_ext', '--inplace'])


@pytest.mark.parametrize('import_mode', IMPORT_MODES)
def test_cython_ext_module(pytester, import_mode):
module = get_module('cython_ext_module')
Expand Down