Coverage for tests/test_imports.py: 100%

75 statements  

« prev     ^ index     » next       coverage.py v7.4.0, created at 2024-02-05 19:37 +0100

1import re 

2from pathlib import Path 

3 

4from canonical_imports._core import main 

5from click.testing import CliRunner 

6from inline_snapshot import snapshot 

7 

8 

9def check( 

10 files, file_args=None, args=[], no=[], changed_files={}, stdout="", stderr="" 

11): 

12 runner = CliRunner(mix_stderr=False) 

13 

14 def normalize(text): 

15 if " \n" in text: 

16 text = text.replace("\n", "\u23CE\n") 

17 text = re.sub( 

18 r"^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d", "<date_time>", text, re.MULTILINE 

19 ) 

20 return text 

21 

22 with runner.isolated_filesystem(): 

23 for name, content in files.items(): 

24 d = Path(name) 

25 d.parent.mkdir(exist_ok=True, parents=True) 

26 if isinstance(content, str): 

27 d.write_text(content) 

28 elif isinstance(content, bytes): 

29 d.write_bytes(content) 

30 else: 

31 assert False 

32 

33 no = [f"--no={e}" for e in no] 

34 

35 if file_args is None: 

36 file_args = list(files.keys()) 

37 

38 result = runner.invoke(main, file_args + no + args, catch_exceptions=False) 

39 assert normalize(result.stderr) == stderr 

40 assert normalize(result.stdout) == stdout 

41 

42 assert result.exit_code == 0 

43 

44 changed = {} 

45 for name, content in files.items(): 

46 d = Path(name) 

47 if isinstance(content, str): 

48 new_content = d.read_text() 

49 elif isinstance(content, bytes): 

50 new_content = d.read_bytes() 

51 else: 

52 assert False 

53 

54 if new_content != content: 

55 changed[name] = new_content 

56 

57 assert changed == changed_files 

58 

59 

60def test_simple_indirect(): 

61 check( 

62 files={ 

63 "m/__init__.py": "", 

64 "m/a.py": "from .b import f", 

65 "m/b.py": "from .c import f", 

66 "m/c.py": "def f():pass", 

67 }, 

68 args=["-w"], 

69 changed_files=snapshot({"m/a.py": "from .c import f"}), 

70 stdout=snapshot( 

71 """\ 

72fix: m/a.py 

73""" 

74 ), 

75 stderr=snapshot(""), 

76 ) 

77 

78 

79def test_unicode_problem(): 

80 check( 

81 files={ 

82 "m/__init__.py": "", 

83 "m/a.py": "from .b import f", 

84 "m/b.py": b'print("b\xf6se")\n', 

85 }, 

86 args=["-w"], 

87 changed_files=snapshot({}), 

88 stdout=snapshot( 

89 """\ 

90<date_time> [error ] could not decode m/b.py 

91""" 

92 ), 

93 stderr=snapshot(""), 

94 ) 

95 

96 

97def test_preview(): 

98 check( 

99 files={ 

100 "m/__init__.py": "", 

101 "m/a.py": "from .b import f", 

102 "m/b.py": "from .c import f", 

103 "m/c.py": "def f():pass", 

104 }, 

105 changed_files=snapshot({}), 

106 stdout=snapshot( 

107 """\ 

108 

109 m/a.py ⏎ 

110 

111 

112 @@ -1 +1 @@ ⏎ 

113 

114 -from .b import f ⏎ 

115 +from .c import f ⏎ 

116 

117 

118────────────────────────────────────────────────────────────────────────────────⏎ 

119""" 

120 ), 

121 stderr=snapshot(""), 

122 ) 

123 

124 

125def test_no_init_module(): 

126 check( 

127 files={ 

128 "m/__init__.py": "", 

129 "m/a.py": "from .q import f", 

130 "m/q/__init__.py": "from .b import f", 

131 "m/q/b.py": "def f():pass", 

132 }, 

133 args=["-w"], 

134 changed_files=snapshot({"m/a.py": "from .q.b import f"}), 

135 stdout=snapshot( 

136 """\ 

137fix: m/a.py 

138""" 

139 ), 

140 stderr=snapshot(""), 

141 ) 

142 

143 check( 

144 files={ 

145 "m/__init__.py": "", 

146 "m/a.py": "from .b import f", 

147 "m/b.py": "from .q import f", 

148 "m/q/__init__.py": "from .c import f", 

149 "m/q/c.py": "def f():pass", 

150 }, 

151 args=["-w"], 

152 no=["into-init"], 

153 changed_files=snapshot({"m/a.py": "from .q import f"}), 

154 stdout=snapshot( 

155 """\ 

156fix: m/a.py 

157""" 

158 ), 

159 stderr=snapshot(""), 

160 ) 

161 

162 

163def test_double_import(): 

164 check( 

165 files={ 

166 "m/__init__.py": "", 

167 "m/a.py": "from .b import f", 

168 "m/b.py": "from .c import f\nfrom .d import f", 

169 "m/c.py": "def f():pass", 

170 "m/d.py": "def f():pass", 

171 }, 

172 args=["-w"], 

173 changed_files=snapshot({}), 

174 stdout=snapshot(""), 

175 stderr=snapshot(""), 

176 ) 

177 

178 

179def test_import_override(): 

180 check( 

181 files={ 

182 "m/__init__.py": "", 

183 "m/a.py": "from .b import f", 

184 "m/b.py": "from .c import f\nif True: f=5", 

185 "m/c.py": "def f():pass", 

186 }, 

187 args=["-w"], 

188 changed_files=snapshot({}), 

189 stdout=snapshot(""), 

190 stderr=snapshot(""), 

191 ) 

192 

193 

194def test_import_as(): 

195 check( 

196 files={ 

197 "m/__init__.py": "", 

198 "m/a.py": "from .b import g", 

199 "m/b.py": "from .c import f as g", 

200 "m/c.py": "def f():pass", 

201 }, 

202 args=["-w"], 

203 changed_files=snapshot({"m/a.py": "from .c import f as g"}), 

204 stdout=snapshot( 

205 """\ 

206fix: m/a.py 

207""" 

208 ), 

209 stderr=snapshot(""), 

210 ) 

211 

212 

213def test_multiple_imports_to_follow(): 

214 check( 

215 files={ 

216 "m/__init__.py": "", 

217 "m/a.py": "from .b import f", 

218 "m/b.py": "from .c import g\nfrom .c import f", 

219 "m/c.py": "def f():pass", 

220 }, 

221 args=["-w"], 

222 changed_files=snapshot({"m/a.py": "from .c import f"}), 

223 stdout=snapshot( 

224 """\ 

225fix: m/a.py 

226""" 

227 ), 

228 stderr=snapshot(""), 

229 ) 

230 

231 

232def test_simple_indirect_to_other_file(): 

233 check( 

234 files={ 

235 "m/__init__.py": "", 

236 "m/a.py": "from .b import f", 

237 "m/b.py": "from .c import f", 

238 "m/c.py": "def f():pass", 

239 }, 

240 args=["-w"], 

241 file_args=["m/a.py"], 

242 changed_files=snapshot({"m/a.py": "from .c import f"}), 

243 stdout=snapshot( 

244 """\ 

245fix: m/a.py 

246""" 

247 ), 

248 stderr=snapshot(""), 

249 ) 

250 

251 

252def test_different_package(): 

253 check( 

254 files={ 

255 "m/__init__.py": "", 

256 "m/a.py": "from b.b import f", 

257 "b/__init__.py": "", 

258 "b/b.py": "from .c import f", 

259 "b/c.py": "def f():pass", 

260 }, 

261 args=["-w"], 

262 changed_files=snapshot({"m/a.py": "from b.c import f"}), 

263 stdout=snapshot( 

264 """\ 

265fix: m/a.py 

266""" 

267 ), 

268 stderr=snapshot(""), 

269 ) 

270 

271 

272def test_different_unknown_package(): 

273 check( 

274 files={ 

275 "m/__init__.py": "", 

276 "m/a.py": "from b.b import f", 

277 }, 

278 args=["-w"], 

279 changed_files=snapshot({}), 

280 stdout=snapshot(""), 

281 stderr=snapshot(""), 

282 ) 

283 

284 

285def test_cycle(): 

286 check( 

287 files={ 

288 "m/__init__.py": "", 

289 "m/a.py": "from .b import f", 

290 "m/b.py": "from .a import f", 

291 }, 

292 args=["-w"], 

293 changed_files=snapshot({}), 

294 stdout=snapshot(""), 

295 stderr=snapshot(""), 

296 ) 

297 

298 

299def test_import_module(): 

300 check( 

301 files={ 

302 "m/__init__.py": "from .b import f", 

303 "m/a.py": "from . import f", 

304 "m/b.py": "def f():pass", 

305 }, 

306 args=["-w"], 

307 changed_files=snapshot({"m/a.py": "from .b import f"}), 

308 stdout=snapshot( 

309 """\ 

310fix: m/a.py 

311""" 

312 ), 

313 stderr=snapshot(""), 

314 ) 

315 

316 

317def test_invalid_syntax(): 

318 check( 

319 files={ 

320 "m/__init__.py": "", 

321 "m/a.py": "from .b import f", 

322 "m/b.py": "5///4", 

323 }, 

324 args=["-w"], 

325 file_args=["m/a.py"], 

326 changed_files=snapshot({}), 

327 stdout=snapshot( 

328 """\ 

329<date_time> [error ] could not parse m/b.py 

330""" 

331 ), 

332 stderr=snapshot(""), 

333 ) 

334 

335 

336def test_module_not_found(): 

337 check( 

338 files={ 

339 "m/__init__.py": "", 

340 "m/a.py": "from .b import f", 

341 }, 

342 args=["-w"], 

343 changed_files=snapshot({}), 

344 stdout=snapshot(""), 

345 stderr=snapshot(""), 

346 ) 

347 

348 

349def test_private_indirect(): 

350 check( 

351 files={ 

352 "m/__init__.py": "", 

353 "m/a.py": "from .b import f", 

354 "m/b.py": "from ._c import f", 

355 "m/_c.py": "def f():pass", 

356 }, 

357 args=["-w"], 

358 no=["public-private"], 

359 changed_files=snapshot({}), 

360 stdout=snapshot(""), 

361 stderr=snapshot(""), 

362 ) 

363 

364 check( 

365 files={ 

366 "m/__init__.py": "", 

367 "m/a.py": "from ._b import f", 

368 "m/_b.py": "from .c import f", 

369 "m/c.py": "def f():pass", 

370 }, 

371 args=["-w"], 

372 no=["public-private"], 

373 changed_files=snapshot({"m/a.py": "from .c import f"}), 

374 stdout=snapshot( 

375 """\ 

376fix: m/a.py 

377""" 

378 ), 

379 stderr=snapshot(""), 

380 ) 

381 

382 

383def test_preserve_style(): 

384 check( 

385 files={ 

386 "m/__init__.py": "", 

387 "m/a.py": """\ 

388from .b import f 

389from m.b import f 

390""", 

391 "m/b.py": "from .c import f", 

392 "m/c.py": "def f():pass", 

393 }, 

394 args=["-w"], 

395 changed_files=snapshot( 

396 { 

397 "m/a.py": """\ 

398from .c import f 

399from m.c import f 

400""" 

401 } 

402 ), 

403 stdout=snapshot( 

404 """\ 

405fix: m/a.py 

406""" 

407 ), 

408 stderr=snapshot(""), 

409 ) 

410 

411 

412def test_scan_directories(): 

413 check( 

414 files={ 

415 "m/__init__.py": "", 

416 "m/a.py": """\ 

417from .b import f 

418""", 

419 "m/dir/__init__.py": "", 

420 "m/dir/a.py": """\ 

421from ..b import f 

422""", 

423 "m/b.py": "from .c import f", 

424 "m/c.py": "def f():pass", 

425 }, 

426 args=["-w"], 

427 file_args=["m"], 

428 changed_files=snapshot( 

429 { 

430 "m/a.py": """\ 

431from .c import f 

432""", 

433 "m/dir/a.py": """\ 

434from ..c import f 

435""", 

436 } 

437 ), 

438 stdout=snapshot( 

439 """\ 

440fix: m/a.py 

441fix: m/dir/a.py 

442""" 

443 ), 

444 stderr=snapshot(""), 

445 )