Skip to content

Commit 5888228

Browse files
authored
test: Update test cases to get full coverage (#153)
* fix: Remove unused guard __getattr__() is called on missing values. Actually existing values will be returned without hitting this method. * test: __getattr__ returned by __attach__, error_on_import * test: Smoke test no attributes * test: Add auxiliary func to test module name != attr name * chore: Ignore ARG001 for pytest fixtures * test: Use pytest.raises() checks
1 parent 21e5d04 commit 5888228

File tree

6 files changed

+80
-33
lines changed

6 files changed

+80
-33
lines changed

pyproject.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ ignore = [
8989
"SIM108", # Use ternary operator
9090
]
9191

92+
[tool.ruff.lint.per-file-ignores]
93+
"tests/test_*.py" = [
94+
"ARG001", # Pytest fixtures are passed as arguments
95+
]
96+
9297
[tool.ruff.format]
9398
docstring-code-format = true
9499

src/lazy_loader/__init__.py

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -106,16 +106,13 @@ def __init__(self, frame_data, *args, message, **kwargs):
106106
super().__init__(*args, **kwargs)
107107

108108
def __getattr__(self, x):
109-
if x in ("__class__", "__file__", "__frame_data", "__message"):
110-
super().__getattr__(x)
111-
else:
112-
fd = self.__frame_data
113-
raise ModuleNotFoundError(
114-
f"{self.__message}\n\n"
115-
"This error is lazily reported, having originally occurred in\n"
116-
f" File {fd['filename']}, line {fd['lineno']}, in {fd['function']}\n\n"
117-
f"----> {''.join(fd['code_context'] or '').strip()}"
118-
)
109+
fd = self.__frame_data
110+
raise ModuleNotFoundError(
111+
f"{self.__message}\n\n"
112+
"This error is lazily reported, having originally occurred in\n"
113+
f" File {fd['filename']}, line {fd['lineno']}, in {fd['function']}\n\n"
114+
f"----> {''.join(fd['code_context'] or '').strip()}"
115+
)
119116

120117

121118
def load(fullname, *, require=None, error_on_import=False, suppress_warning=False):

tests/fake_pkg/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import lazy_loader as lazy
22

33
__getattr__, __lazy_dir__, __all__ = lazy.attach(
4-
__name__, submod_attrs={"some_func": ["some_func"]}
4+
__name__, submod_attrs={"some_func": ["some_func", "aux_func"]}
55
)

tests/fake_pkg/__init__.pyi

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
from .some_func import some_func
1+
from .some_func import aux_func, some_func

tests/fake_pkg/some_func.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
def some_func():
22
"""Function with same name as submodule."""
3+
4+
5+
def aux_func():
6+
"""Auxiliary function."""

tests/test_lazy_loader.py

Lines changed: 62 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,25 +10,39 @@
1010
import lazy_loader as lazy
1111

1212

13+
@pytest.fixture
14+
def clean_fake_pkg():
15+
yield
16+
sys.modules.pop("tests.fake_pkg.some_func", None)
17+
sys.modules.pop("tests.fake_pkg", None)
18+
sys.modules.pop("tests", None)
19+
20+
21+
@pytest.mark.parametrize("attempt", [1, 2])
22+
def test_cleanup_fixture(clean_fake_pkg, attempt):
23+
assert "tests.fake_pkg" not in sys.modules
24+
assert "tests.fake_pkg.some_func" not in sys.modules
25+
from tests import fake_pkg
26+
27+
assert "tests.fake_pkg" in sys.modules
28+
assert "tests.fake_pkg.some_func" not in sys.modules
29+
assert isinstance(fake_pkg.some_func, types.FunctionType)
30+
assert "tests.fake_pkg.some_func" in sys.modules
31+
32+
1333
def test_lazy_import_basics():
1434
math = lazy.load("math")
1535
anything_not_real = lazy.load("anything_not_real")
1636

1737
# Now test that accessing attributes does what it should
1838
assert math.sin(math.pi) == pytest.approx(0, 1e-6)
1939
# poor-mans pytest.raises for testing errors on attribute access
20-
try:
40+
with pytest.raises(ModuleNotFoundError):
2141
anything_not_real.pi
22-
raise AssertionError() # Should not get here
23-
except ModuleNotFoundError:
24-
pass
2542
assert isinstance(anything_not_real, lazy.DelayedImportErrorModule)
2643
# see if it changes for second access
27-
try:
44+
with pytest.raises(ModuleNotFoundError):
2845
anything_not_real.pi
29-
raise AssertionError() # Should not get here
30-
except ModuleNotFoundError:
31-
pass
3246

3347

3448
def test_lazy_import_subpackages():
@@ -68,11 +82,8 @@ def test_lazy_import_nonbuiltins():
6882
if not isinstance(np, lazy.DelayedImportErrorModule):
6983
assert np.sin(np.pi) == pytest.approx(0, 1e-6)
7084
if isinstance(sp, lazy.DelayedImportErrorModule):
71-
try:
85+
with pytest.raises(ModuleNotFoundError):
7286
sp.pi
73-
raise AssertionError()
74-
except ModuleNotFoundError:
75-
pass
7687

7788

7889
def test_lazy_attach():
@@ -103,6 +114,26 @@ def test_lazy_attach():
103114
if v is not None:
104115
assert locls[k] == v
105116

117+
# Exercise __getattr__, though it will just error
118+
with pytest.raises(ImportError):
119+
locls["__getattr__"]("mysubmodule")
120+
121+
# Attribute is supposed to be imported, error on submodule load
122+
with pytest.raises(ImportError):
123+
locls["__getattr__"]("some_var_or_func")
124+
125+
# Attribute is unknown, raise AttributeError
126+
with pytest.raises(AttributeError):
127+
locls["__getattr__"]("unknown_attr")
128+
129+
130+
def test_lazy_attach_noattrs():
131+
name = "mymod"
132+
submods = ["mysubmodule", "anothersubmodule"]
133+
_, _, all_ = lazy.attach(name, submods)
134+
135+
assert all_ == sorted(submods)
136+
106137

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

129160

130-
def test_attach_same_module_and_attr_name():
131-
from tests import fake_pkg
161+
@pytest.mark.parametrize("eager_import", [False, True])
162+
def test_attach_same_module_and_attr_name(clean_fake_pkg, eager_import):
163+
env = {}
164+
if eager_import:
165+
env["EAGER_IMPORT"] = "1"
132166

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

138-
# Ensure imports from submodule still work
139-
from tests.fake_pkg.some_func import some_func
170+
# Grab attribute twice, to ensure that importing it does not
171+
# override function by module
172+
assert isinstance(fake_pkg.some_func, types.FunctionType)
173+
assert isinstance(fake_pkg.some_func, types.FunctionType)
140174

141-
assert isinstance(some_func, types.FunctionType)
175+
# Ensure imports from submodule still work
176+
from tests.fake_pkg.some_func import some_func
177+
178+
assert isinstance(some_func, types.FunctionType)
142179

143180

144181
FAKE_STUB = """
@@ -196,6 +233,10 @@ def test_require_kwarg():
196233
math = lazy.load("math", require="somepkg >= 2.0")
197234
assert isinstance(math, lazy.DelayedImportErrorModule)
198235

236+
# Eager failure
237+
with pytest.raises(ModuleNotFoundError):
238+
lazy.load("math", require="somepkg >= 2.0", error_on_import=True)
239+
199240
# When a module can be loaded but the version can't be checked,
200241
# raise a ValueError
201242
with pytest.raises(ValueError):

0 commit comments

Comments
 (0)