-
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adapt multidict C ext test implementation (#25)
* Adapt multidict implementation * migrate tests * cleanup * Revert "fix tests" This reverts commit ce4da7f. * Revert "Revert "fix tests"" This reverts commit 428a16f. * fix missing extension files under windows * fix missing extension files under windows * maybe should have been manifest * Update tests/conftest.py Co-authored-by: Sviatoslav Sydorenko (Святослав Сидоренко) <[email protected]> * windows produces .pyd * Update tests/conftest.py Co-authored-by: Sviatoslav Sydorenko (Святослав Сидоренко) <[email protected]> * naming * revert MANIFEST.in changes --------- Co-authored-by: Sviatoslav Sydorenko (Святослав Сидоренко) <[email protected]>
- Loading branch information
Showing
5 changed files
with
276 additions
and
170 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
import argparse | ||
from dataclasses import dataclass | ||
from functools import cached_property | ||
from importlib import import_module | ||
from sys import version_info as _version_info | ||
from types import ModuleType | ||
from typing import List, Type, Union | ||
|
||
import pytest | ||
|
||
C_EXT_MARK = pytest.mark.c_extension | ||
PY_38_AND_BELOW = _version_info < (3, 9) | ||
|
||
|
||
@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 | ||
|
||
|
||
@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 | ||
|
||
arg_parse_action: Union[str, Type[argparse.Action]] | ||
if PY_38_AND_BELOW: | ||
arg_parse_action = "store_true" | ||
else: | ||
arg_parse_action = argparse.BooleanOptionalAction # type: ignore[attr-defined, unused-ignore] # noqa | ||
|
||
parser.addoption( | ||
"--c-extensions", # disabled with `--no-c-extensions` | ||
action=arg_parse_action, | ||
default=True, | ||
dest="c_extensions", | ||
help="Test C-extensions (on by default)", | ||
) | ||
|
||
if PY_38_AND_BELOW: | ||
parser.addoption( | ||
"--no-c-extensions", | ||
action="store_false", | ||
dest="c_extensions", | ||
help="Skip testing 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.", | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,134 +1,123 @@ | ||
import platform | ||
from operator import not_ | ||
|
||
import pytest | ||
|
||
from propcache import _helpers, _helpers_py | ||
from propcache._helpers import cached_property | ||
|
||
IS_PYPY = platform.python_implementation() == "PyPy" | ||
def test_cached_property(propcache_module) -> None: | ||
class A: | ||
def __init__(self): | ||
self._cache = {} | ||
|
||
@propcache_module.cached_property | ||
def prop(self): | ||
return 1 | ||
|
||
class CachedPropertyMixin: | ||
cached_property = NotImplemented | ||
a = A() | ||
assert a.prop == 1 | ||
|
||
def test_cached_property(self) -> None: | ||
class A: | ||
def __init__(self): | ||
self._cache = {} | ||
|
||
@self.cached_property # type: ignore[misc] | ||
def prop(self): | ||
return 1 | ||
def test_cached_property_class(propcache_module) -> None: | ||
class A: | ||
def __init__(self): | ||
"""Init.""" | ||
# self._cache not set because its never accessed in this test | ||
|
||
a = A() | ||
assert a.prop == 1 | ||
@propcache_module.cached_property | ||
def prop(self): | ||
"""Docstring.""" | ||
|
||
def test_cached_property_class(self) -> None: | ||
class A: | ||
def __init__(self): | ||
"""Init.""" | ||
# self._cache not set because its never accessed in this test | ||
assert isinstance(A.prop, propcache_module.cached_property) | ||
assert A.prop.__doc__ == "Docstring." | ||
|
||
@self.cached_property # type: ignore[misc] | ||
def prop(self): | ||
"""Docstring.""" | ||
|
||
assert isinstance(A.prop, self.cached_property) | ||
assert A.prop.__doc__ == "Docstring." | ||
def test_cached_property_without_cache(propcache_module) -> None: | ||
class A: | ||
|
||
def test_cached_property_without_cache(self) -> None: | ||
class A: | ||
__slots__ = () | ||
|
||
__slots__ = () | ||
def __init__(self): | ||
pass | ||
|
||
def __init__(self): | ||
pass | ||
@propcache_module.cached_property | ||
def prop(self): | ||
"""Mock property.""" | ||
|
||
@self.cached_property # type: ignore[misc] | ||
def prop(self): | ||
"""Mock property.""" | ||
a = A() | ||
|
||
a = A() | ||
with pytest.raises(AttributeError): | ||
a.prop = 123 | ||
|
||
with pytest.raises(AttributeError): | ||
a.prop = 123 | ||
|
||
def test_cached_property_check_without_cache(self) -> None: | ||
class A: | ||
def test_cached_property_check_without_cache(propcache_module) -> None: | ||
class A: | ||
|
||
__slots__ = () | ||
__slots__ = () | ||
|
||
def __init__(self): | ||
pass | ||
def __init__(self): | ||
pass | ||
|
||
@self.cached_property # type: ignore[misc] | ||
def prop(self): | ||
"""Mock property.""" | ||
@propcache_module.cached_property | ||
def prop(self): | ||
"""Mock property.""" | ||
|
||
a = A() | ||
with pytest.raises((TypeError, AttributeError)): | ||
assert a.prop == 1 | ||
a = A() | ||
with pytest.raises((TypeError, AttributeError)): | ||
assert a.prop == 1 | ||
|
||
|
||
class A: | ||
def __init__(self): | ||
self._cache = {} | ||
def test_cached_property_caching(propcache_module) -> None: | ||
|
||
@cached_property | ||
def prop(self): | ||
"""Docstring.""" | ||
return 1 | ||
class A: | ||
def __init__(self): | ||
self._cache = {} | ||
|
||
@propcache_module.cached_property | ||
def prop(self): | ||
"""Docstring.""" | ||
return 1 | ||
|
||
def test_cached_property(): | ||
a = A() | ||
assert 1 == a.prop | ||
|
||
|
||
def test_cached_property_class(): | ||
assert isinstance(A.prop, cached_property) | ||
assert "Docstring." == A.prop.__doc__ | ||
|
||
|
||
class TestPyCachedProperty(CachedPropertyMixin): | ||
cached_property = _helpers_py.cached_property # type: ignore[assignment] | ||
def test_cached_property_class_docstring(propcache_module) -> None: | ||
|
||
class A: | ||
def __init__(self): | ||
"""Init.""" | ||
|
||
if ( | ||
not _helpers.NO_EXTENSIONS | ||
and not IS_PYPY | ||
and hasattr(_helpers, "cached_property_c") | ||
): | ||
@propcache_module.cached_property | ||
def prop(self): | ||
"""Docstring.""" | ||
|
||
class TestCCachedProperty(CachedPropertyMixin): | ||
cached_property = _helpers.cached_property_c # type: ignore[assignment, attr-defined, unused-ignore] # noqa: E501 | ||
assert isinstance(A.prop, propcache_module.cached_property) | ||
assert "Docstring." == A.prop.__doc__ | ||
|
||
|
||
def test_set_name(): | ||
def test_set_name(propcache_module) -> None: | ||
"""Test that the __set_name__ method is called and checked.""" | ||
|
||
class A: | ||
|
||
@cached_property | ||
@propcache_module.cached_property | ||
def prop(self): | ||
"""Docstring.""" | ||
|
||
A.prop.__set_name__(A, "prop") | ||
|
||
with pytest.raises( | ||
TypeError, match=r"Cannot assign the same cached_property to two " | ||
): | ||
match = r"Cannot assign the same cached_property to two " | ||
with pytest.raises(TypeError, match=match): | ||
A.prop.__set_name__(A, "something_else") | ||
|
||
|
||
def test_get_without_set_name(): | ||
def test_get_without_set_name(propcache_module) -> None: | ||
"""Test that get without __set_name__ fails.""" | ||
cp = cached_property(not_) | ||
cp = propcache_module.cached_property(not_) | ||
|
||
class A: | ||
"""A class.""" | ||
|
||
A.cp = cp | ||
with pytest.raises(TypeError, match=r"Cannot use cached_property instance "): | ||
_ = A().cp | ||
A.cp = cp # type: ignore[attr-defined] | ||
match = r"Cannot use cached_property instance " | ||
with pytest.raises(TypeError, match=match): | ||
_ = A().cp # type: ignore[attr-defined] |
Oops, something went wrong.