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
« prev ^ index » next coverage.py v7.4.0, created at 2024-02-05 19:37 +0100
1import re
2from pathlib import Path
4from canonical_imports._core import main
5from click.testing import CliRunner
6from inline_snapshot import snapshot
9def check(
10 files, file_args=None, args=[], no=[], changed_files={}, stdout="", stderr=""
11):
12 runner = CliRunner(mix_stderr=False)
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
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
33 no = [f"--no={e}" for e in no]
35 if file_args is None:
36 file_args = list(files.keys())
38 result = runner.invoke(main, file_args + no + args, catch_exceptions=False)
39 assert normalize(result.stderr) == stderr
40 assert normalize(result.stdout) == stdout
42 assert result.exit_code == 0
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
54 if new_content != content:
55 changed[name] = new_content
57 assert changed == changed_files
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 )
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 )
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 )
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 )
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 )
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 )
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 )
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 )
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 )
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 )
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 )
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 )
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 )
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 )
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 )
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 )
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 )
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 )
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 )
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 )