1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
|
import argparse
from dataclasses import dataclass
from functools import cached_property
from importlib import import_module
from types import ModuleType
import pytest
C_EXT_MARK = pytest.mark.c_extension
@dataclass(frozen=True)
class PropcacheImplementation:
"""A facade for accessing importable propcache module variants.
An instance essentially represents a c-extension or a pure-python module.
The actual underlying module is accessed dynamically through a property and
is cached.
It also has a text tag depending on what variant it is, and a string
representation suitable for use in Pytest's test IDs via parametrization.
"""
is_pure_python: bool
"""A flag showing whether this is a pure-python module or a C-extension."""
@cached_property
def tag(self) -> str:
"""Return a text representation of the pure-python attribute."""
return "pure-python" if self.is_pure_python else "c-extension"
@cached_property
def imported_module(self) -> ModuleType:
"""Return a loaded importable containing a propcache variant."""
importable_module = "_helpers_py" if self.is_pure_python else "_helpers_c"
return import_module(f"propcache.{importable_module}")
def __str__(self) -> str:
"""Render the implementation facade instance as a string."""
return f"{self.tag}-module"
@pytest.fixture(
scope="session",
params=(
pytest.param(
PropcacheImplementation(is_pure_python=False),
marks=C_EXT_MARK,
),
PropcacheImplementation(is_pure_python=True),
),
ids=str,
)
def propcache_implementation(request: pytest.FixtureRequest) -> PropcacheImplementation:
"""Return a propcache variant facade."""
return request.param # type: ignore[no-any-return]
@pytest.fixture(scope="session")
def propcache_module(
propcache_implementation: PropcacheImplementation,
) -> ModuleType:
"""Return a pre-imported module containing a propcache variant."""
return propcache_implementation.imported_module
def pytest_addoption(
parser: pytest.Parser,
pluginmanager: pytest.PytestPluginManager,
) -> None:
"""Define a new ``--c-extensions`` flag.
This lets the callers deselect tests executed against the C-extension
version of the ``propcache`` implementation.
"""
del pluginmanager
parser.addoption(
"--c-extensions", # disabled with `--no-c-extensions`
action=argparse.BooleanOptionalAction,
default=True,
dest="c_extensions",
help="Test C-extensions (on by default)",
)
def pytest_collection_modifyitems(
session: pytest.Session,
config: pytest.Config,
items: list[pytest.Item],
) -> None:
"""Deselect tests against C-extensions when requested via CLI."""
test_c_extensions = config.getoption("--c-extensions") is True
if test_c_extensions:
return
selected_tests: list[pytest.Item] = []
deselected_tests: list[pytest.Item] = []
for item in items:
c_ext = item.get_closest_marker(C_EXT_MARK.name) is not None
target_items_list = deselected_tests if c_ext else selected_tests
target_items_list.append(item)
config.hook.pytest_deselected(items=deselected_tests)
items[:] = selected_tests
def pytest_configure(config: pytest.Config) -> None:
"""Declare the C-extension marker in config."""
config.addinivalue_line(
"markers",
f"{C_EXT_MARK.name}: tests running against the C-extension implementation.",
)
|