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

1import os 

2import re 

3import subprocess 

4import subprocess as sp 

5import sys 

6from contextlib import contextmanager 

7from pathlib import Path 

8from tempfile import TemporaryDirectory 

9 

10from inline_snapshot import snapshot 

11 

12python_version = f"python{sys.version_info[0]}.{sys.version_info[1]}" 

13 

14 

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) 

20 

21 

22@contextmanager 

23def package(name, content): 

24 with TemporaryDirectory() as d: 

25 package_dir = Path(d) / name 

26 package_dir.mkdir() 

27 

28 write_files(package_dir, content) 

29 

30 subprocess.run( 

31 [sys.executable, "-m", "pip", "install", str(package_dir)], 

32 input=b"y", 

33 check=True, 

34 ) 

35 

36 yield 

37 

38 subprocess.run( 

39 [sys.executable, "-m", "pip", "uninstall", name], input=b"y", check=True 

40 ) 

41 

42 

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": """ 

54 

55[build-system] 

56requires = ["hatchling"] 

57build-backend = "hatchling.build" 

58 

59[project] 

60name="test-pck" 

61keywords=["lazy-imports-lite-enabled"] 

62version="0.0.1" 

63""", 

64 **package_files, 

65 } 

66 

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 

77 

78 with package("test_pck", package_files), TemporaryDirectory() as script_dir: 

79 print(sys.exec_prefix) 

80 script_dir = Path(script_dir) 

81 

82 script_file = script_dir / "script.py" 

83 script_file.write_text(script) 

84 

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 ) 

91 

92 transformed_result = sp.run( 

93 [sys.executable, str(script_file)], cwd=str(script_dir), capture_output=True 

94 ) 

95 

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) 

100 

101 assert normal_stdout == n_stdout 

102 assert transformed_stdout == ( 

103 "<equal to normal>" if n_stdout == t_stdout else t_stdout 

104 ) 

105 

106 assert normal_stderr == n_stderr 

107 assert transformed_stderr == ( 

108 "<equal to normal>" if n_stderr == t_stderr else t_stderr 

109 ) 

110 

111 

112def test_loader(): 

113 check_script( 

114 { 

115 "test_pck/__init__.py": """\ 

116from .mx import x 

117from .my import y 

118 

119def use_x(): 

120 return x 

121 

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 ) 

158 

159 

160def test_loader_keywords(): 

161 check_script( 

162 { 

163 "test_pck/__init__.py": """\ 

164from .mx import x 

165print("imported init") 

166 

167def use_x(): 

168 return x 

169 

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 ) 

197 

198 

199def test_lazy_module_attr(): 

200 check_script( 

201 { 

202 "test_pck/__init__.py": """\ 

203from .mx import x 

204from .my import y 

205 

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) 

219 

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 ) 

242 

243 

244def test_lazy_module_content(): 

245 check_script( 

246 { 

247 "test_pck/__init__.py": """\ 

248from .mx import x 

249from .my import y 

250 

251""", 

252 "test_pck/mx.py": """\ 

253x=5 

254""", 

255 "test_pck/my.py": """\ 

256y=5 

257""", 

258 }, 

259 """\ 

260import test_pck 

261 

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 ) 

280 

281 

282def test_lazy_module_content_import_from(): 

283 check_script( 

284 { 

285 "test_pck/__init__.py": """\ 

286from .mx import x 

287print("inside",globals().keys()) 

288 

289try: 

290 print("mx",mx) 

291except: 

292 print("no mx") 

293 

294print("inside",globals().keys()) 

295 

296def later(): 

297 print("later",globals().keys()) 

298""", 

299 "test_pck/mx.py": """\ 

300x=5 

301""", 

302 }, 

303 """\ 

304import test_pck 

305 

306print("outside",vars(test_pck).keys()) 

307 

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 ) 

331 

332 

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 ) 

366 

367 

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 ) 

409 

410 

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 

426 

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 ) 

438 

439 

440def test_lazy_module_setattr(): 

441 check_script( 

442 { 

443 "test_pck/__init__.py": """\ 

444from .ma import b 

445 

446def foo(): 

447 print(b()) 

448 

449""", 

450 "test_pck/ma.py": """\ 

451def b(): 

452 return 5 

453""", 

454 }, 

455 """\ 

456from test_pck import foo 

457import test_pck 

458 

459foo() 

460test_pck.b=lambda:6 

461foo() 

462 

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 ) 

474 

475def test_loader_is_used(): 

476 check_script( 

477 { 

478 "test_pck/__init__.py": """\ 

479 

480def foo(): 

481 print("foo") 

482 

483""", 

484 }, 

485 """\ 

486import test_pck 

487 

488print(type(test_pck.__spec__.loader)) 

489 

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 )