Coverage for tests/test_loader.py: 100%
71 statements
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-31 08:58 +0100
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-31 08:58 +0100
1import os
2import re
3import subprocess
4import subprocess as sp
5import sys
6from contextlib import contextmanager
7from pathlib import Path
8from tempfile import TemporaryDirectory
10from inline_snapshot import snapshot
12python_version = f"python{sys.version_info[0]}.{sys.version_info[1]}"
15def write_files(dir, content):
16 for path, text in content.items():
17 path = dir / path
18 path.parent.mkdir(exist_ok=True, parents=True)
19 path.write_text(text)
22@contextmanager
23def package(name, content):
24 with TemporaryDirectory() as d:
25 package_dir = Path(d) / name
26 package_dir.mkdir()
28 write_files(package_dir, content)
30 subprocess.run(
31 [sys.executable, "-m", "pip", "install", str(package_dir)],
32 input=b"y",
33 check=True,
34 )
36 yield
38 subprocess.run(
39 [sys.executable, "-m", "pip", "uninstall", name], input=b"y", check=True
40 )
43def check_script(
44 package_files,
45 script,
46 *,
47 transformed_stdout="",
48 transformed_stderr="",
49 normal_stdout="",
50 normal_stderr="",
51):
52 package_files = {
53 "pyproject.toml": """
55[build-system]
56requires = ["hatchling"]
57build-backend = "hatchling.build"
59[project]
60name="test-pck"
61keywords=["lazy-imports-lite-enabled"]
62version="0.0.1"
63""",
64 **package_files,
65 }
67 def normalize_output(output: bytes):
68 text = output.decode()
69 text = text.replace(sys.exec_prefix, "<exec_prefix>")
70 text = re.sub("at 0x[0-9a-f]*>", "at <hex_value>>", text)
71 text = re.sub("line [0-9]*", "line <n>", text)
72 text = text.replace(python_version, "<python_version>")
73 text = text.replace(str(script_dir), "<script_dir>")
74 if " \n" in text:
75 text = text.replace("\n", "⏎\n")
76 return text
78 with package("test_pck", package_files), TemporaryDirectory() as script_dir:
79 print(sys.exec_prefix)
80 script_dir = Path(script_dir)
82 script_file = script_dir / "script.py"
83 script_file.write_text(script)
85 normal_result = sp.run(
86 [sys.executable, str(script_file)],
87 cwd=str(script_dir),
88 env={**os.environ, "LAZY_IMPORTS_LITE_DISABLE": "True"},
89 capture_output=True,
90 )
92 transformed_result = sp.run(
93 [sys.executable, str(script_file)], cwd=str(script_dir), capture_output=True
94 )
96 n_stdout = normalize_output(normal_result.stdout)
97 t_stdout = normalize_output(transformed_result.stdout)
98 n_stderr = normalize_output(normal_result.stderr)
99 t_stderr = normalize_output(transformed_result.stderr)
101 assert normal_stdout == n_stdout
102 assert transformed_stdout == (
103 "<equal to normal>" if n_stdout == t_stdout else t_stdout
104 )
106 assert normal_stderr == n_stderr
107 assert transformed_stderr == (
108 "<equal to normal>" if n_stderr == t_stderr else t_stderr
109 )
112def test_loader():
113 check_script(
114 {
115 "test_pck/__init__.py": """\
116from .mx import x
117from .my import y
119def use_x():
120 return x
122def use_y():
123 return y
124""",
125 "test_pck/mx.py": """\
126print('imported mx')
127x=5
128""",
129 "test_pck/my.py": """\
130print('imported my')
131y=5
132""",
133 },
134 """\
135from test_pck import use_x, use_y
136print("y:",use_y())
137print("x:",use_x())
138""",
139 transformed_stdout=snapshot(
140 """\
141imported my
142y: 5
143imported mx
144x: 5
145"""
146 ),
147 transformed_stderr=snapshot("<equal to normal>"),
148 normal_stdout=snapshot(
149 """\
150imported mx
151imported my
152y: 5
153x: 5
154"""
155 ),
156 normal_stderr=snapshot(""),
157 )
160def test_loader_keywords():
161 check_script(
162 {
163 "test_pck/__init__.py": """\
164from .mx import x
165print("imported init")
167def use_x():
168 return x
170""",
171 "test_pck/mx.py": """\
172print('imported mx')
173x=5
174""",
175 },
176 """\
177from test_pck import use_x
178print("x:",use_x())
179""",
180 transformed_stdout=snapshot(
181 """\
182imported init
183imported mx
184x: 5
185"""
186 ),
187 transformed_stderr=snapshot("<equal to normal>"),
188 normal_stdout=snapshot(
189 """\
190imported mx
191imported init
192x: 5
193"""
194 ),
195 normal_stderr=snapshot(""),
196 )
199def test_lazy_module_attr():
200 check_script(
201 {
202 "test_pck/__init__.py": """\
203from .mx import x
204from .my import y
206""",
207 "test_pck/mx.py": """\
208print('imported mx')
209x=5
210""",
211 "test_pck/my.py": """\
212print('imported my')
213y=5
214""",
215 },
216 """\
217from test_pck import y
218print("y:",y)
220from test_pck import x
221print("x:",x)
222""",
223 transformed_stdout=snapshot(
224 """\
225imported my
226y: 5
227imported mx
228x: 5
229"""
230 ),
231 transformed_stderr=snapshot("<equal to normal>"),
232 normal_stdout=snapshot(
233 """\
234imported mx
235imported my
236y: 5
237x: 5
238"""
239 ),
240 normal_stderr=snapshot(""),
241 )
244def test_lazy_module_content():
245 check_script(
246 {
247 "test_pck/__init__.py": """\
248from .mx import x
249from .my import y
251""",
252 "test_pck/mx.py": """\
253x=5
254""",
255 "test_pck/my.py": """\
256y=5
257""",
258 },
259 """\
260import test_pck
262print(test_pck)
263print(vars(test_pck).keys())
264""",
265 transformed_stdout=snapshot(
266 """\
267<module 'test_pck' from '<exec_prefix>/lib/<python_version>/site-packages/test_pck/__init__.py'>
268dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__path__', '__file__', '__cached__', '__builtins__', 'x', 'y'])
269"""
270 ),
271 transformed_stderr=snapshot("<equal to normal>"),
272 normal_stdout=snapshot(
273 """\
274<module 'test_pck' from '<exec_prefix>/lib/<python_version>/site-packages/test_pck/__init__.py'>
275dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__path__', '__file__', '__cached__', '__builtins__', 'mx', 'x', 'my', 'y'])
276"""
277 ),
278 normal_stderr=snapshot(""),
279 )
282def test_lazy_module_content_import_from():
283 check_script(
284 {
285 "test_pck/__init__.py": """\
286from .mx import x
287print("inside",globals().keys())
289try:
290 print("mx",mx)
291except:
292 print("no mx")
294print("inside",globals().keys())
296def later():
297 print("later",globals().keys())
298""",
299 "test_pck/mx.py": """\
300x=5
301""",
302 },
303 """\
304import test_pck
306print("outside",vars(test_pck).keys())
308test_pck.later()
309""",
310 transformed_stdout=snapshot(
311 """\
312inside dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__path__', '__file__', '__cached__', '__builtins__', 'x'])
313mx <module 'test_pck.mx' from '<exec_prefix>/lib/<python_version>/site-packages/test_pck/mx.py'>
314inside dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__path__', '__file__', '__cached__', '__builtins__', 'x', 'mx'])
315outside dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__path__', '__file__', '__cached__', '__builtins__', 'x', 'mx', 'later'])
316later dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__path__', '__file__', '__cached__', '__builtins__', 'x', 'mx', 'later'])
317"""
318 ),
319 transformed_stderr=snapshot("<equal to normal>"),
320 normal_stdout=snapshot(
321 """\
322inside dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__path__', '__file__', '__cached__', '__builtins__', 'mx', 'x'])
323mx <module 'test_pck.mx' from '<exec_prefix>/lib/<python_version>/site-packages/test_pck/mx.py'>
324inside dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__path__', '__file__', '__cached__', '__builtins__', 'mx', 'x'])
325outside dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__path__', '__file__', '__cached__', '__builtins__', 'mx', 'x', 'later'])
326later dict_keys(['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__path__', '__file__', '__cached__', '__builtins__', 'mx', 'x', 'later'])
327"""
328 ),
329 normal_stderr=snapshot(""),
330 )
333def test_import_module_with_error():
334 check_script(
335 {
336 "test_pck/__init__.py": """\
337import test_pck.m
338print(test_pck.m.v)
339""",
340 "test_pck/m.py": """\
341raise ValueError()
342""",
343 },
344 """\
345try:
346 from test_pck import v
347except BaseException as e:
348 while e:
349 print(f"{type(e).__name__}: {e}")
350 e=e.__cause__ if e.__suppress_context__ else e.__context__
351""",
352 transformed_stdout=snapshot(
353 """\
354LazyImportError: Deferred importing of module 'test_pck.m' caused an error⏎
355ValueError: ⏎
356"""
357 ),
358 transformed_stderr=snapshot("<equal to normal>"),
359 normal_stdout=snapshot(
360 """\
361ValueError: ⏎
362"""
363 ),
364 normal_stderr=snapshot(""),
365 )
368def test_load_chain_of_modules_with_error():
369 check_script(
370 {
371 "test_pck/__init__.py": """\
372from .m import v
373print(v)
374""",
375 "test_pck/m/__init__.py": """\
376from .x import v
377print(v)
378""",
379 "test_pck/m/x.py": """\
380from .y import v
381print(v)
382""",
383 "test_pck/m/y.py": """\
384raise ValueError()
385""",
386 },
387 """\
388try:
389 from test_pck import v
390except BaseException as e:
391 while e:
392 print(f"{type(e).__name__}: {e}")
393 e=e.__cause__ if e.__suppress_context__ else e.__context__
394""",
395 transformed_stdout=snapshot(
396 """\
397LazyImportError: Deferred importing of module '.y' in 'test_pck.m' caused an error⏎
398ValueError: ⏎
399"""
400 ),
401 transformed_stderr=snapshot("<equal to normal>"),
402 normal_stdout=snapshot(
403 """\
404ValueError: ⏎
405"""
406 ),
407 normal_stderr=snapshot(""),
408 )
411def test_lazy_module_import_from_empty_init():
412 check_script(
413 {
414 "test_pck/__init__.py": """\
415""",
416 "test_pck/ma.py": """\
417a=5
418""",
419 "test_pck/mb.py": """\
420from test_pck import ma
421a=ma.a
422""",
423 },
424 """\
425from test_pck import mb
427print(mb.a)
428""",
429 transformed_stdout=snapshot("<equal to normal>"),
430 transformed_stderr=snapshot("<equal to normal>"),
431 normal_stdout=snapshot(
432 """\
4335
434"""
435 ),
436 normal_stderr=snapshot(""),
437 )
440def test_lazy_module_setattr():
441 check_script(
442 {
443 "test_pck/__init__.py": """\
444from .ma import b
446def foo():
447 print(b())
449""",
450 "test_pck/ma.py": """\
451def b():
452 return 5
453""",
454 },
455 """\
456from test_pck import foo
457import test_pck
459foo()
460test_pck.b=lambda:6
461foo()
463""",
464 transformed_stdout=snapshot("<equal to normal>"),
465 transformed_stderr=snapshot("<equal to normal>"),
466 normal_stdout=snapshot(
467 """\
4685
4696
470"""
471 ),
472 normal_stderr=snapshot(""),
473 )
475def test_loader_is_used():
476 check_script(
477 {
478 "test_pck/__init__.py": """\
480def foo():
481 print("foo")
483""",
484 },
485 """\
486import test_pck
488print(type(test_pck.__spec__.loader))
490""",
491 transformed_stdout=snapshot("""\
492<class 'lazy_imports_lite._loader.LazyLoader'>
493"""),
494 transformed_stderr=snapshot("<equal to normal>"),
495 normal_stdout=snapshot("""\
496<class '_frozen_importlib_external.SourceFileLoader'>
497"""),
498 normal_stderr=snapshot(""),
499 )