Skip to content

test: Update test cases to get full coverage #153

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
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
5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ ignore = [
"SIM108", # Use ternary operator
]

[tool.ruff.lint.per-file-ignores]
"tests/test_*.py" = [
"ARG001", # Pytest fixtures are passed as arguments
]

[tool.ruff.format]
docstring-code-format = true

Expand Down
17 changes: 7 additions & 10 deletions src/lazy_loader/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,16 +106,13 @@ def __init__(self, frame_data, *args, message, **kwargs):
super().__init__(*args, **kwargs)

def __getattr__(self, x):
if x in ("__class__", "__file__", "__frame_data", "__message"):
super().__getattr__(x)
else:
fd = self.__frame_data
raise ModuleNotFoundError(
f"{self.__message}\n\n"
"This error is lazily reported, having originally occurred in\n"
f" File {fd['filename']}, line {fd['lineno']}, in {fd['function']}\n\n"
f"----> {''.join(fd['code_context'] or '').strip()}"
)
fd = self.__frame_data
raise ModuleNotFoundError(
f"{self.__message}\n\n"
"This error is lazily reported, having originally occurred in\n"
f" File {fd['filename']}, line {fd['lineno']}, in {fd['function']}\n\n"
f"----> {''.join(fd['code_context'] or '').strip()}"
)


def load(fullname, *, require=None, error_on_import=False, suppress_warning=False):
Expand Down
2 changes: 1 addition & 1 deletion tests/fake_pkg/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import lazy_loader as lazy

__getattr__, __lazy_dir__, __all__ = lazy.attach(
__name__, submod_attrs={"some_func": ["some_func"]}
__name__, submod_attrs={"some_func": ["some_func", "aux_func"]}
)
2 changes: 1 addition & 1 deletion tests/fake_pkg/__init__.pyi
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from .some_func import some_func
from .some_func import aux_func, some_func
4 changes: 4 additions & 0 deletions tests/fake_pkg/some_func.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
def some_func():
"""Function with same name as submodule."""


def aux_func():
"""Auxiliary function."""
83 changes: 62 additions & 21 deletions tests/test_lazy_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,39 @@
import lazy_loader as lazy


@pytest.fixture
def clean_fake_pkg():
yield
sys.modules.pop("tests.fake_pkg.some_func", None)
sys.modules.pop("tests.fake_pkg", None)
sys.modules.pop("tests", None)


@pytest.mark.parametrize("attempt", [1, 2])
def test_cleanup_fixture(clean_fake_pkg, attempt):
assert "tests.fake_pkg" not in sys.modules
assert "tests.fake_pkg.some_func" not in sys.modules
from tests import fake_pkg

assert "tests.fake_pkg" in sys.modules
assert "tests.fake_pkg.some_func" not in sys.modules
assert isinstance(fake_pkg.some_func, types.FunctionType)
assert "tests.fake_pkg.some_func" in sys.modules


def test_lazy_import_basics():
math = lazy.load("math")
anything_not_real = lazy.load("anything_not_real")

# Now test that accessing attributes does what it should
assert math.sin(math.pi) == pytest.approx(0, 1e-6)
# poor-mans pytest.raises for testing errors on attribute access
try:
with pytest.raises(ModuleNotFoundError):
anything_not_real.pi
raise AssertionError() # Should not get here
except ModuleNotFoundError:
pass
assert isinstance(anything_not_real, lazy.DelayedImportErrorModule)
# see if it changes for second access
try:
with pytest.raises(ModuleNotFoundError):
anything_not_real.pi
raise AssertionError() # Should not get here
except ModuleNotFoundError:
pass


def test_lazy_import_subpackages():
Expand Down Expand Up @@ -68,11 +82,8 @@ def test_lazy_import_nonbuiltins():
if not isinstance(np, lazy.DelayedImportErrorModule):
assert np.sin(np.pi) == pytest.approx(0, 1e-6)
if isinstance(sp, lazy.DelayedImportErrorModule):
try:
with pytest.raises(ModuleNotFoundError):
sp.pi
raise AssertionError()
except ModuleNotFoundError:
pass


def test_lazy_attach():
Expand Down Expand Up @@ -103,6 +114,26 @@ def test_lazy_attach():
if v is not None:
assert locls[k] == v

# Exercise __getattr__, though it will just error
with pytest.raises(ImportError):
locls["__getattr__"]("mysubmodule")

# Attribute is supposed to be imported, error on submodule load
with pytest.raises(ImportError):
locls["__getattr__"]("some_var_or_func")

# Attribute is unknown, raise AttributeError
with pytest.raises(AttributeError):
locls["__getattr__"]("unknown_attr")


def test_lazy_attach_noattrs():
name = "mymod"
submods = ["mysubmodule", "anothersubmodule"]
_, _, all_ = lazy.attach(name, submods)

assert all_ == sorted(submods)


def test_lazy_attach_returns_copies():
_get, _dir, _all = lazy.attach(
Expand All @@ -127,18 +158,24 @@ def test_lazy_attach_returns_copies():
assert _all == [*expected, "modify_returned_all"]


def test_attach_same_module_and_attr_name():
from tests import fake_pkg
@pytest.mark.parametrize("eager_import", [False, True])
def test_attach_same_module_and_attr_name(clean_fake_pkg, eager_import):
env = {}
if eager_import:
env["EAGER_IMPORT"] = "1"

# Grab attribute twice, to ensure that importing it does not
# override function by module
assert isinstance(fake_pkg.some_func, types.FunctionType)
assert isinstance(fake_pkg.some_func, types.FunctionType)
with mock.patch.dict(os.environ, env):
from tests import fake_pkg

# Ensure imports from submodule still work
from tests.fake_pkg.some_func import some_func
# Grab attribute twice, to ensure that importing it does not
# override function by module
assert isinstance(fake_pkg.some_func, types.FunctionType)
assert isinstance(fake_pkg.some_func, types.FunctionType)

assert isinstance(some_func, types.FunctionType)
# Ensure imports from submodule still work
from tests.fake_pkg.some_func import some_func

assert isinstance(some_func, types.FunctionType)


FAKE_STUB = """
Expand Down Expand Up @@ -196,6 +233,10 @@ def test_require_kwarg():
math = lazy.load("math", require="somepkg >= 2.0")
assert isinstance(math, lazy.DelayedImportErrorModule)

# Eager failure
with pytest.raises(ModuleNotFoundError):
lazy.load("math", require="somepkg >= 2.0", error_on_import=True)

# When a module can be loaded but the version can't be checked,
# raise a ValueError
with pytest.raises(ValueError):
Expand Down