]> git.madduck.net Git - etc/vim.git/blob - tests/test_black.py

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

update to mypy 0.620 and make tests pass again
[etc/vim.git] / tests / test_black.py
1 #!/usr/bin/env python3
2 import asyncio
3 from concurrent.futures import ThreadPoolExecutor
4 from contextlib import contextmanager
5 from functools import partial
6 from io import BytesIO, TextIOWrapper
7 import os
8 from pathlib import Path
9 import re
10 import sys
11 from tempfile import TemporaryDirectory
12 from typing import Any, BinaryIO, Generator, List, Tuple, Iterator
13 import unittest
14 from unittest.mock import patch, MagicMock
15
16 from click import unstyle
17 from click.testing import CliRunner
18
19 import black
20
21
22 ll = 88
23 ff = partial(black.format_file_in_place, line_length=ll, fast=True)
24 fs = partial(black.format_str, line_length=ll)
25 THIS_FILE = Path(__file__)
26 THIS_DIR = THIS_FILE.parent
27 EMPTY_LINE = "# EMPTY LINE WITH WHITESPACE" + " (this comment will be removed)"
28
29
30 def dump_to_stderr(*output: str) -> str:
31     return "\n" + "\n".join(output) + "\n"
32
33
34 def read_data(name: str, data: bool = True) -> Tuple[str, str]:
35     """read_data('test_name') -> 'input', 'output'"""
36     if not name.endswith((".py", ".pyi", ".out", ".diff")):
37         name += ".py"
38     _input: List[str] = []
39     _output: List[str] = []
40     base_dir = THIS_DIR / "data" if data else THIS_DIR
41     with open(base_dir / name, "r", encoding="utf8") as test:
42         lines = test.readlines()
43     result = _input
44     for line in lines:
45         line = line.replace(EMPTY_LINE, "")
46         if line.rstrip() == "# output":
47             result = _output
48             continue
49
50         result.append(line)
51     if _input and not _output:
52         # If there's no output marker, treat the entire file as already pre-formatted.
53         _output = _input[:]
54     return "".join(_input).strip() + "\n", "".join(_output).strip() + "\n"
55
56
57 @contextmanager
58 def cache_dir(exists: bool = True) -> Iterator[Path]:
59     with TemporaryDirectory() as workspace:
60         cache_dir = Path(workspace)
61         if not exists:
62             cache_dir = cache_dir / "new"
63         with patch("black.CACHE_DIR", cache_dir):
64             yield cache_dir
65
66
67 @contextmanager
68 def event_loop(close: bool) -> Iterator[None]:
69     policy = asyncio.get_event_loop_policy()
70     old_loop = policy.get_event_loop()
71     loop = policy.new_event_loop()
72     asyncio.set_event_loop(loop)
73     try:
74         yield
75
76     finally:
77         policy.set_event_loop(old_loop)
78         if close:
79             loop.close()
80
81
82 class BlackRunner(CliRunner):
83     """Modify CliRunner so that stderr is not merged with stdout.
84
85     This is a hack that can be removed once we depend on Click 7.x"""
86
87     def __init__(self, stderrbuf: BinaryIO) -> None:
88         self.stderrbuf = stderrbuf
89         super().__init__()
90
91     @contextmanager
92     def isolation(self, *args: Any, **kwargs: Any) -> Generator[BinaryIO, None, None]:
93         with super().isolation(*args, **kwargs) as output:
94             try:
95                 hold_stderr = sys.stderr
96                 sys.stderr = TextIOWrapper(self.stderrbuf, encoding=self.charset)
97                 yield output
98             finally:
99                 sys.stderr = hold_stderr
100
101
102 class BlackTestCase(unittest.TestCase):
103     maxDiff = None
104
105     def assertFormatEqual(self, expected: str, actual: str) -> None:
106         if actual != expected and not os.environ.get("SKIP_AST_PRINT"):
107             bdv: black.DebugVisitor[Any]
108             black.out("Expected tree:", fg="green")
109             try:
110                 exp_node = black.lib2to3_parse(expected)
111                 bdv = black.DebugVisitor()
112                 list(bdv.visit(exp_node))
113             except Exception as ve:
114                 black.err(str(ve))
115             black.out("Actual tree:", fg="red")
116             try:
117                 exp_node = black.lib2to3_parse(actual)
118                 bdv = black.DebugVisitor()
119                 list(bdv.visit(exp_node))
120             except Exception as ve:
121                 black.err(str(ve))
122         self.assertEqual(expected, actual)
123
124     @patch("black.dump_to_file", dump_to_stderr)
125     def test_empty(self) -> None:
126         source = expected = ""
127         actual = fs(source)
128         self.assertFormatEqual(expected, actual)
129         black.assert_equivalent(source, actual)
130         black.assert_stable(source, actual, line_length=ll)
131
132     def test_empty_ff(self) -> None:
133         expected = ""
134         tmp_file = Path(black.dump_to_file())
135         try:
136             self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES))
137             with open(tmp_file, encoding="utf8") as f:
138                 actual = f.read()
139         finally:
140             os.unlink(tmp_file)
141         self.assertFormatEqual(expected, actual)
142
143     @patch("black.dump_to_file", dump_to_stderr)
144     def test_self(self) -> None:
145         source, expected = read_data("test_black", data=False)
146         actual = fs(source)
147         self.assertFormatEqual(expected, actual)
148         black.assert_equivalent(source, actual)
149         black.assert_stable(source, actual, line_length=ll)
150         self.assertFalse(ff(THIS_FILE))
151
152     @patch("black.dump_to_file", dump_to_stderr)
153     def test_black(self) -> None:
154         source, expected = read_data("../black", data=False)
155         actual = fs(source)
156         self.assertFormatEqual(expected, actual)
157         black.assert_equivalent(source, actual)
158         black.assert_stable(source, actual, line_length=ll)
159         self.assertFalse(ff(THIS_DIR / ".." / "black.py"))
160
161     def test_piping(self) -> None:
162         source, expected = read_data("../black", data=False)
163         stderrbuf = BytesIO()
164         result = BlackRunner(stderrbuf).invoke(
165             black.main,
166             ["-", "--fast", f"--line-length={ll}"],
167             input=BytesIO(source.encode("utf8")),
168         )
169         self.assertEqual(result.exit_code, 0)
170         self.assertFormatEqual(expected, result.output)
171         black.assert_equivalent(source, result.output)
172         black.assert_stable(source, result.output, line_length=ll)
173
174     def test_piping_diff(self) -> None:
175         diff_header = re.compile(
176             rf"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d "
177             rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
178         )
179         source, _ = read_data("expression.py")
180         expected, _ = read_data("expression.diff")
181         config = THIS_DIR / "data" / "empty_pyproject.toml"
182         stderrbuf = BytesIO()
183         args = ["-", "--fast", f"--line-length={ll}", "--diff", f"--config={config}"]
184         result = BlackRunner(stderrbuf).invoke(
185             black.main, args, input=BytesIO(source.encode("utf8"))
186         )
187         self.assertEqual(result.exit_code, 0)
188         actual = diff_header.sub("[Deterministic header]", result.output)
189         actual = actual.rstrip() + "\n"  # the diff output has a trailing space
190         self.assertEqual(expected, actual)
191
192     @patch("black.dump_to_file", dump_to_stderr)
193     def test_setup(self) -> None:
194         source, expected = read_data("../setup", data=False)
195         actual = fs(source)
196         self.assertFormatEqual(expected, actual)
197         black.assert_equivalent(source, actual)
198         black.assert_stable(source, actual, line_length=ll)
199         self.assertFalse(ff(THIS_DIR / ".." / "setup.py"))
200
201     @patch("black.dump_to_file", dump_to_stderr)
202     def test_function(self) -> None:
203         source, expected = read_data("function")
204         actual = fs(source)
205         self.assertFormatEqual(expected, actual)
206         black.assert_equivalent(source, actual)
207         black.assert_stable(source, actual, line_length=ll)
208
209     @patch("black.dump_to_file", dump_to_stderr)
210     def test_function2(self) -> None:
211         source, expected = read_data("function2")
212         actual = fs(source)
213         self.assertFormatEqual(expected, actual)
214         black.assert_equivalent(source, actual)
215         black.assert_stable(source, actual, line_length=ll)
216
217     @patch("black.dump_to_file", dump_to_stderr)
218     def test_expression(self) -> None:
219         source, expected = read_data("expression")
220         actual = fs(source)
221         self.assertFormatEqual(expected, actual)
222         black.assert_equivalent(source, actual)
223         black.assert_stable(source, actual, line_length=ll)
224
225     def test_expression_ff(self) -> None:
226         source, expected = read_data("expression")
227         tmp_file = Path(black.dump_to_file(source))
228         try:
229             self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
230             with open(tmp_file, encoding="utf8") as f:
231                 actual = f.read()
232         finally:
233             os.unlink(tmp_file)
234         self.assertFormatEqual(expected, actual)
235         with patch("black.dump_to_file", dump_to_stderr):
236             black.assert_equivalent(source, actual)
237             black.assert_stable(source, actual, line_length=ll)
238
239     def test_expression_diff(self) -> None:
240         source, _ = read_data("expression.py")
241         expected, _ = read_data("expression.diff")
242         tmp_file = Path(black.dump_to_file(source))
243         diff_header = re.compile(
244             rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
245             rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
246         )
247         stderrbuf = BytesIO()
248         try:
249             result = BlackRunner(stderrbuf).invoke(
250                 black.main, ["--diff", str(tmp_file)]
251             )
252             self.assertEqual(result.exit_code, 0)
253         finally:
254             os.unlink(tmp_file)
255         actual = result.output
256         actual = diff_header.sub("[Deterministic header]", actual)
257         actual = actual.rstrip() + "\n"  # the diff output has a trailing space
258         if expected != actual:
259             dump = black.dump_to_file(actual)
260             msg = (
261                 f"Expected diff isn't equal to the actual. If you made changes "
262                 f"to expression.py and this is an anticipated difference, "
263                 f"overwrite tests/expression.diff with {dump}"
264             )
265             self.assertEqual(expected, actual, msg)
266
267     @patch("black.dump_to_file", dump_to_stderr)
268     def test_fstring(self) -> None:
269         source, expected = read_data("fstring")
270         actual = fs(source)
271         self.assertFormatEqual(expected, actual)
272         black.assert_equivalent(source, actual)
273         black.assert_stable(source, actual, line_length=ll)
274
275     @patch("black.dump_to_file", dump_to_stderr)
276     def test_string_quotes(self) -> None:
277         source, expected = read_data("string_quotes")
278         actual = fs(source)
279         self.assertFormatEqual(expected, actual)
280         black.assert_equivalent(source, actual)
281         black.assert_stable(source, actual, line_length=ll)
282         mode = black.FileMode.NO_STRING_NORMALIZATION
283         not_normalized = fs(source, mode=mode)
284         self.assertFormatEqual(source, not_normalized)
285         black.assert_equivalent(source, not_normalized)
286         black.assert_stable(source, not_normalized, line_length=ll, mode=mode)
287
288     @patch("black.dump_to_file", dump_to_stderr)
289     def test_slices(self) -> None:
290         source, expected = read_data("slices")
291         actual = fs(source)
292         self.assertFormatEqual(expected, actual)
293         black.assert_equivalent(source, actual)
294         black.assert_stable(source, actual, line_length=ll)
295
296     @patch("black.dump_to_file", dump_to_stderr)
297     def test_comments(self) -> None:
298         source, expected = read_data("comments")
299         actual = fs(source)
300         self.assertFormatEqual(expected, actual)
301         black.assert_equivalent(source, actual)
302         black.assert_stable(source, actual, line_length=ll)
303
304     @patch("black.dump_to_file", dump_to_stderr)
305     def test_comments2(self) -> None:
306         source, expected = read_data("comments2")
307         actual = fs(source)
308         self.assertFormatEqual(expected, actual)
309         black.assert_equivalent(source, actual)
310         black.assert_stable(source, actual, line_length=ll)
311
312     @patch("black.dump_to_file", dump_to_stderr)
313     def test_comments3(self) -> None:
314         source, expected = read_data("comments3")
315         actual = fs(source)
316         self.assertFormatEqual(expected, actual)
317         black.assert_equivalent(source, actual)
318         black.assert_stable(source, actual, line_length=ll)
319
320     @patch("black.dump_to_file", dump_to_stderr)
321     def test_comments4(self) -> None:
322         source, expected = read_data("comments4")
323         actual = fs(source)
324         self.assertFormatEqual(expected, actual)
325         black.assert_equivalent(source, actual)
326         black.assert_stable(source, actual, line_length=ll)
327
328     @patch("black.dump_to_file", dump_to_stderr)
329     def test_comments5(self) -> None:
330         source, expected = read_data("comments5")
331         actual = fs(source)
332         self.assertFormatEqual(expected, actual)
333         black.assert_equivalent(source, actual)
334         black.assert_stable(source, actual, line_length=ll)
335
336     @patch("black.dump_to_file", dump_to_stderr)
337     def test_cantfit(self) -> None:
338         source, expected = read_data("cantfit")
339         actual = fs(source)
340         self.assertFormatEqual(expected, actual)
341         black.assert_equivalent(source, actual)
342         black.assert_stable(source, actual, line_length=ll)
343
344     @patch("black.dump_to_file", dump_to_stderr)
345     def test_import_spacing(self) -> None:
346         source, expected = read_data("import_spacing")
347         actual = fs(source)
348         self.assertFormatEqual(expected, actual)
349         black.assert_equivalent(source, actual)
350         black.assert_stable(source, actual, line_length=ll)
351
352     @patch("black.dump_to_file", dump_to_stderr)
353     def test_composition(self) -> None:
354         source, expected = read_data("composition")
355         actual = fs(source)
356         self.assertFormatEqual(expected, actual)
357         black.assert_equivalent(source, actual)
358         black.assert_stable(source, actual, line_length=ll)
359
360     @patch("black.dump_to_file", dump_to_stderr)
361     def test_empty_lines(self) -> None:
362         source, expected = read_data("empty_lines")
363         actual = fs(source)
364         self.assertFormatEqual(expected, actual)
365         black.assert_equivalent(source, actual)
366         black.assert_stable(source, actual, line_length=ll)
367
368     @patch("black.dump_to_file", dump_to_stderr)
369     def test_string_prefixes(self) -> None:
370         source, expected = read_data("string_prefixes")
371         actual = fs(source)
372         self.assertFormatEqual(expected, actual)
373         black.assert_equivalent(source, actual)
374         black.assert_stable(source, actual, line_length=ll)
375
376     @patch("black.dump_to_file", dump_to_stderr)
377     def test_python2(self) -> None:
378         source, expected = read_data("python2")
379         actual = fs(source)
380         self.assertFormatEqual(expected, actual)
381         # black.assert_equivalent(source, actual)
382         black.assert_stable(source, actual, line_length=ll)
383
384     @patch("black.dump_to_file", dump_to_stderr)
385     def test_python2_unicode_literals(self) -> None:
386         source, expected = read_data("python2_unicode_literals")
387         actual = fs(source)
388         self.assertFormatEqual(expected, actual)
389         black.assert_stable(source, actual, line_length=ll)
390
391     @patch("black.dump_to_file", dump_to_stderr)
392     def test_stub(self) -> None:
393         mode = black.FileMode.PYI
394         source, expected = read_data("stub.pyi")
395         actual = fs(source, mode=mode)
396         self.assertFormatEqual(expected, actual)
397         black.assert_stable(source, actual, line_length=ll, mode=mode)
398
399     @patch("black.dump_to_file", dump_to_stderr)
400     def test_fmtonoff(self) -> None:
401         source, expected = read_data("fmtonoff")
402         actual = fs(source)
403         self.assertFormatEqual(expected, actual)
404         black.assert_equivalent(source, actual)
405         black.assert_stable(source, actual, line_length=ll)
406
407     @patch("black.dump_to_file", dump_to_stderr)
408     def test_fmtonoff2(self) -> None:
409         source, expected = read_data("fmtonoff2")
410         actual = fs(source)
411         self.assertFormatEqual(expected, actual)
412         black.assert_equivalent(source, actual)
413         black.assert_stable(source, actual, line_length=ll)
414
415     @patch("black.dump_to_file", dump_to_stderr)
416     def test_remove_empty_parentheses_after_class(self) -> None:
417         source, expected = read_data("class_blank_parentheses")
418         actual = fs(source)
419         self.assertFormatEqual(expected, actual)
420         black.assert_equivalent(source, actual)
421         black.assert_stable(source, actual, line_length=ll)
422
423     @patch("black.dump_to_file", dump_to_stderr)
424     def test_new_line_between_class_and_code(self) -> None:
425         source, expected = read_data("class_methods_new_line")
426         actual = fs(source)
427         self.assertFormatEqual(expected, actual)
428         black.assert_equivalent(source, actual)
429         black.assert_stable(source, actual, line_length=ll)
430
431     def test_report_verbose(self) -> None:
432         report = black.Report(verbose=True)
433         out_lines = []
434         err_lines = []
435
436         def out(msg: str, **kwargs: Any) -> None:
437             out_lines.append(msg)
438
439         def err(msg: str, **kwargs: Any) -> None:
440             err_lines.append(msg)
441
442         with patch("black.out", out), patch("black.err", err):
443             report.done(Path("f1"), black.Changed.NO)
444             self.assertEqual(len(out_lines), 1)
445             self.assertEqual(len(err_lines), 0)
446             self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
447             self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
448             self.assertEqual(report.return_code, 0)
449             report.done(Path("f2"), black.Changed.YES)
450             self.assertEqual(len(out_lines), 2)
451             self.assertEqual(len(err_lines), 0)
452             self.assertEqual(out_lines[-1], "reformatted f2")
453             self.assertEqual(
454                 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
455             )
456             report.done(Path("f3"), black.Changed.CACHED)
457             self.assertEqual(len(out_lines), 3)
458             self.assertEqual(len(err_lines), 0)
459             self.assertEqual(
460                 out_lines[-1], "f3 wasn't modified on disk since last run."
461             )
462             self.assertEqual(
463                 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
464             )
465             self.assertEqual(report.return_code, 0)
466             report.check = True
467             self.assertEqual(report.return_code, 1)
468             report.check = False
469             report.failed(Path("e1"), "boom")
470             self.assertEqual(len(out_lines), 3)
471             self.assertEqual(len(err_lines), 1)
472             self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
473             self.assertEqual(
474                 unstyle(str(report)),
475                 "1 file reformatted, 2 files left unchanged, "
476                 "1 file failed to reformat.",
477             )
478             self.assertEqual(report.return_code, 123)
479             report.done(Path("f3"), black.Changed.YES)
480             self.assertEqual(len(out_lines), 4)
481             self.assertEqual(len(err_lines), 1)
482             self.assertEqual(out_lines[-1], "reformatted f3")
483             self.assertEqual(
484                 unstyle(str(report)),
485                 "2 files reformatted, 2 files left unchanged, "
486                 "1 file failed to reformat.",
487             )
488             self.assertEqual(report.return_code, 123)
489             report.failed(Path("e2"), "boom")
490             self.assertEqual(len(out_lines), 4)
491             self.assertEqual(len(err_lines), 2)
492             self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
493             self.assertEqual(
494                 unstyle(str(report)),
495                 "2 files reformatted, 2 files left unchanged, "
496                 "2 files failed to reformat.",
497             )
498             self.assertEqual(report.return_code, 123)
499             report.path_ignored(Path("wat"), "no match")
500             self.assertEqual(len(out_lines), 5)
501             self.assertEqual(len(err_lines), 2)
502             self.assertEqual(out_lines[-1], "wat ignored: no match")
503             self.assertEqual(
504                 unstyle(str(report)),
505                 "2 files reformatted, 2 files left unchanged, "
506                 "2 files failed to reformat.",
507             )
508             self.assertEqual(report.return_code, 123)
509             report.done(Path("f4"), black.Changed.NO)
510             self.assertEqual(len(out_lines), 6)
511             self.assertEqual(len(err_lines), 2)
512             self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
513             self.assertEqual(
514                 unstyle(str(report)),
515                 "2 files reformatted, 3 files left unchanged, "
516                 "2 files failed to reformat.",
517             )
518             self.assertEqual(report.return_code, 123)
519             report.check = True
520             self.assertEqual(
521                 unstyle(str(report)),
522                 "2 files would be reformatted, 3 files would be left unchanged, "
523                 "2 files would fail to reformat.",
524             )
525
526     def test_report_quiet(self) -> None:
527         report = black.Report(quiet=True)
528         out_lines = []
529         err_lines = []
530
531         def out(msg: str, **kwargs: Any) -> None:
532             out_lines.append(msg)
533
534         def err(msg: str, **kwargs: Any) -> None:
535             err_lines.append(msg)
536
537         with patch("black.out", out), patch("black.err", err):
538             report.done(Path("f1"), black.Changed.NO)
539             self.assertEqual(len(out_lines), 0)
540             self.assertEqual(len(err_lines), 0)
541             self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
542             self.assertEqual(report.return_code, 0)
543             report.done(Path("f2"), black.Changed.YES)
544             self.assertEqual(len(out_lines), 0)
545             self.assertEqual(len(err_lines), 0)
546             self.assertEqual(
547                 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
548             )
549             report.done(Path("f3"), black.Changed.CACHED)
550             self.assertEqual(len(out_lines), 0)
551             self.assertEqual(len(err_lines), 0)
552             self.assertEqual(
553                 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
554             )
555             self.assertEqual(report.return_code, 0)
556             report.check = True
557             self.assertEqual(report.return_code, 1)
558             report.check = False
559             report.failed(Path("e1"), "boom")
560             self.assertEqual(len(out_lines), 0)
561             self.assertEqual(len(err_lines), 1)
562             self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
563             self.assertEqual(
564                 unstyle(str(report)),
565                 "1 file reformatted, 2 files left unchanged, "
566                 "1 file failed to reformat.",
567             )
568             self.assertEqual(report.return_code, 123)
569             report.done(Path("f3"), black.Changed.YES)
570             self.assertEqual(len(out_lines), 0)
571             self.assertEqual(len(err_lines), 1)
572             self.assertEqual(
573                 unstyle(str(report)),
574                 "2 files reformatted, 2 files left unchanged, "
575                 "1 file failed to reformat.",
576             )
577             self.assertEqual(report.return_code, 123)
578             report.failed(Path("e2"), "boom")
579             self.assertEqual(len(out_lines), 0)
580             self.assertEqual(len(err_lines), 2)
581             self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
582             self.assertEqual(
583                 unstyle(str(report)),
584                 "2 files reformatted, 2 files left unchanged, "
585                 "2 files failed to reformat.",
586             )
587             self.assertEqual(report.return_code, 123)
588             report.path_ignored(Path("wat"), "no match")
589             self.assertEqual(len(out_lines), 0)
590             self.assertEqual(len(err_lines), 2)
591             self.assertEqual(
592                 unstyle(str(report)),
593                 "2 files reformatted, 2 files left unchanged, "
594                 "2 files failed to reformat.",
595             )
596             self.assertEqual(report.return_code, 123)
597             report.done(Path("f4"), black.Changed.NO)
598             self.assertEqual(len(out_lines), 0)
599             self.assertEqual(len(err_lines), 2)
600             self.assertEqual(
601                 unstyle(str(report)),
602                 "2 files reformatted, 3 files left unchanged, "
603                 "2 files failed to reformat.",
604             )
605             self.assertEqual(report.return_code, 123)
606             report.check = True
607             self.assertEqual(
608                 unstyle(str(report)),
609                 "2 files would be reformatted, 3 files would be left unchanged, "
610                 "2 files would fail to reformat.",
611             )
612
613     def test_report_normal(self) -> None:
614         report = black.Report()
615         out_lines = []
616         err_lines = []
617
618         def out(msg: str, **kwargs: Any) -> None:
619             out_lines.append(msg)
620
621         def err(msg: str, **kwargs: Any) -> None:
622             err_lines.append(msg)
623
624         with patch("black.out", out), patch("black.err", err):
625             report.done(Path("f1"), black.Changed.NO)
626             self.assertEqual(len(out_lines), 0)
627             self.assertEqual(len(err_lines), 0)
628             self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
629             self.assertEqual(report.return_code, 0)
630             report.done(Path("f2"), black.Changed.YES)
631             self.assertEqual(len(out_lines), 1)
632             self.assertEqual(len(err_lines), 0)
633             self.assertEqual(out_lines[-1], "reformatted f2")
634             self.assertEqual(
635                 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
636             )
637             report.done(Path("f3"), black.Changed.CACHED)
638             self.assertEqual(len(out_lines), 1)
639             self.assertEqual(len(err_lines), 0)
640             self.assertEqual(out_lines[-1], "reformatted f2")
641             self.assertEqual(
642                 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
643             )
644             self.assertEqual(report.return_code, 0)
645             report.check = True
646             self.assertEqual(report.return_code, 1)
647             report.check = False
648             report.failed(Path("e1"), "boom")
649             self.assertEqual(len(out_lines), 1)
650             self.assertEqual(len(err_lines), 1)
651             self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
652             self.assertEqual(
653                 unstyle(str(report)),
654                 "1 file reformatted, 2 files left unchanged, "
655                 "1 file failed to reformat.",
656             )
657             self.assertEqual(report.return_code, 123)
658             report.done(Path("f3"), black.Changed.YES)
659             self.assertEqual(len(out_lines), 2)
660             self.assertEqual(len(err_lines), 1)
661             self.assertEqual(out_lines[-1], "reformatted f3")
662             self.assertEqual(
663                 unstyle(str(report)),
664                 "2 files reformatted, 2 files left unchanged, "
665                 "1 file failed to reformat.",
666             )
667             self.assertEqual(report.return_code, 123)
668             report.failed(Path("e2"), "boom")
669             self.assertEqual(len(out_lines), 2)
670             self.assertEqual(len(err_lines), 2)
671             self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
672             self.assertEqual(
673                 unstyle(str(report)),
674                 "2 files reformatted, 2 files left unchanged, "
675                 "2 files failed to reformat.",
676             )
677             self.assertEqual(report.return_code, 123)
678             report.path_ignored(Path("wat"), "no match")
679             self.assertEqual(len(out_lines), 2)
680             self.assertEqual(len(err_lines), 2)
681             self.assertEqual(
682                 unstyle(str(report)),
683                 "2 files reformatted, 2 files left unchanged, "
684                 "2 files failed to reformat.",
685             )
686             self.assertEqual(report.return_code, 123)
687             report.done(Path("f4"), black.Changed.NO)
688             self.assertEqual(len(out_lines), 2)
689             self.assertEqual(len(err_lines), 2)
690             self.assertEqual(
691                 unstyle(str(report)),
692                 "2 files reformatted, 3 files left unchanged, "
693                 "2 files failed to reformat.",
694             )
695             self.assertEqual(report.return_code, 123)
696             report.check = True
697             self.assertEqual(
698                 unstyle(str(report)),
699                 "2 files would be reformatted, 3 files would be left unchanged, "
700                 "2 files would fail to reformat.",
701             )
702
703     def test_is_python36(self) -> None:
704         node = black.lib2to3_parse("def f(*, arg): ...\n")
705         self.assertFalse(black.is_python36(node))
706         node = black.lib2to3_parse("def f(*, arg,): ...\n")
707         self.assertTrue(black.is_python36(node))
708         node = black.lib2to3_parse("def f(*, arg): f'string'\n")
709         self.assertTrue(black.is_python36(node))
710         source, expected = read_data("function")
711         node = black.lib2to3_parse(source)
712         self.assertTrue(black.is_python36(node))
713         node = black.lib2to3_parse(expected)
714         self.assertTrue(black.is_python36(node))
715         source, expected = read_data("expression")
716         node = black.lib2to3_parse(source)
717         self.assertFalse(black.is_python36(node))
718         node = black.lib2to3_parse(expected)
719         self.assertFalse(black.is_python36(node))
720
721     def test_get_future_imports(self) -> None:
722         node = black.lib2to3_parse("\n")
723         self.assertEqual(set(), black.get_future_imports(node))
724         node = black.lib2to3_parse("from __future__ import black\n")
725         self.assertEqual({"black"}, black.get_future_imports(node))
726         node = black.lib2to3_parse("from __future__ import multiple, imports\n")
727         self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
728         node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
729         self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
730         node = black.lib2to3_parse(
731             "from __future__ import multiple\nfrom __future__ import imports\n"
732         )
733         self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
734         node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
735         self.assertEqual({"black"}, black.get_future_imports(node))
736         node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
737         self.assertEqual({"black"}, black.get_future_imports(node))
738         node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
739         self.assertEqual(set(), black.get_future_imports(node))
740         node = black.lib2to3_parse("from some.module import black\n")
741         self.assertEqual(set(), black.get_future_imports(node))
742         node = black.lib2to3_parse(
743             "from __future__ import unicode_literals as _unicode_literals"
744         )
745         self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
746         node = black.lib2to3_parse(
747             "from __future__ import unicode_literals as _lol, print"
748         )
749         self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
750
751     def test_debug_visitor(self) -> None:
752         source, _ = read_data("debug_visitor.py")
753         expected, _ = read_data("debug_visitor.out")
754         out_lines = []
755         err_lines = []
756
757         def out(msg: str, **kwargs: Any) -> None:
758             out_lines.append(msg)
759
760         def err(msg: str, **kwargs: Any) -> None:
761             err_lines.append(msg)
762
763         with patch("black.out", out), patch("black.err", err):
764             black.DebugVisitor.show(source)
765         actual = "\n".join(out_lines) + "\n"
766         log_name = ""
767         if expected != actual:
768             log_name = black.dump_to_file(*out_lines)
769         self.assertEqual(
770             expected,
771             actual,
772             f"AST print out is different. Actual version dumped to {log_name}",
773         )
774
775     def test_format_file_contents(self) -> None:
776         empty = ""
777         with self.assertRaises(black.NothingChanged):
778             black.format_file_contents(empty, line_length=ll, fast=False)
779         just_nl = "\n"
780         with self.assertRaises(black.NothingChanged):
781             black.format_file_contents(just_nl, line_length=ll, fast=False)
782         same = "l = [1, 2, 3]\n"
783         with self.assertRaises(black.NothingChanged):
784             black.format_file_contents(same, line_length=ll, fast=False)
785         different = "l = [1,2,3]"
786         expected = same
787         actual = black.format_file_contents(different, line_length=ll, fast=False)
788         self.assertEqual(expected, actual)
789         invalid = "return if you can"
790         with self.assertRaises(ValueError) as e:
791             black.format_file_contents(invalid, line_length=ll, fast=False)
792         self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
793
794     def test_endmarker(self) -> None:
795         n = black.lib2to3_parse("\n")
796         self.assertEqual(n.type, black.syms.file_input)
797         self.assertEqual(len(n.children), 1)
798         self.assertEqual(n.children[0].type, black.token.ENDMARKER)
799
800     @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
801     def test_assertFormatEqual(self) -> None:
802         out_lines = []
803         err_lines = []
804
805         def out(msg: str, **kwargs: Any) -> None:
806             out_lines.append(msg)
807
808         def err(msg: str, **kwargs: Any) -> None:
809             err_lines.append(msg)
810
811         with patch("black.out", out), patch("black.err", err):
812             with self.assertRaises(AssertionError):
813                 self.assertFormatEqual("l = [1, 2, 3]", "l = [1, 2, 3,]")
814
815         out_str = "".join(out_lines)
816         self.assertTrue("Expected tree:" in out_str)
817         self.assertTrue("Actual tree:" in out_str)
818         self.assertEqual("".join(err_lines), "")
819
820     def test_cache_broken_file(self) -> None:
821         mode = black.FileMode.AUTO_DETECT
822         with cache_dir() as workspace:
823             cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode)
824             with cache_file.open("w") as fobj:
825                 fobj.write("this is not a pickle")
826             self.assertEqual(black.read_cache(black.DEFAULT_LINE_LENGTH, mode), {})
827             src = (workspace / "test.py").resolve()
828             with src.open("w") as fobj:
829                 fobj.write("print('hello')")
830             result = CliRunner().invoke(black.main, [str(src)])
831             self.assertEqual(result.exit_code, 0)
832             cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
833             self.assertIn(src, cache)
834
835     def test_cache_single_file_already_cached(self) -> None:
836         mode = black.FileMode.AUTO_DETECT
837         with cache_dir() as workspace:
838             src = (workspace / "test.py").resolve()
839             with src.open("w") as fobj:
840                 fobj.write("print('hello')")
841             black.write_cache({}, [src], black.DEFAULT_LINE_LENGTH, mode)
842             result = CliRunner().invoke(black.main, [str(src)])
843             self.assertEqual(result.exit_code, 0)
844             with src.open("r") as fobj:
845                 self.assertEqual(fobj.read(), "print('hello')")
846
847     @event_loop(close=False)
848     def test_cache_multiple_files(self) -> None:
849         mode = black.FileMode.AUTO_DETECT
850         with cache_dir() as workspace, patch(
851             "black.ProcessPoolExecutor", new=ThreadPoolExecutor
852         ):
853             one = (workspace / "one.py").resolve()
854             with one.open("w") as fobj:
855                 fobj.write("print('hello')")
856             two = (workspace / "two.py").resolve()
857             with two.open("w") as fobj:
858                 fobj.write("print('hello')")
859             black.write_cache({}, [one], black.DEFAULT_LINE_LENGTH, mode)
860             result = CliRunner().invoke(black.main, [str(workspace)])
861             self.assertEqual(result.exit_code, 0)
862             with one.open("r") as fobj:
863                 self.assertEqual(fobj.read(), "print('hello')")
864             with two.open("r") as fobj:
865                 self.assertEqual(fobj.read(), 'print("hello")\n')
866             cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
867             self.assertIn(one, cache)
868             self.assertIn(two, cache)
869
870     def test_no_cache_when_writeback_diff(self) -> None:
871         mode = black.FileMode.AUTO_DETECT
872         with cache_dir() as workspace:
873             src = (workspace / "test.py").resolve()
874             with src.open("w") as fobj:
875                 fobj.write("print('hello')")
876             result = CliRunner().invoke(black.main, [str(src), "--diff"])
877             self.assertEqual(result.exit_code, 0)
878             cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode)
879             self.assertFalse(cache_file.exists())
880
881     def test_no_cache_when_stdin(self) -> None:
882         mode = black.FileMode.AUTO_DETECT
883         with cache_dir():
884             result = CliRunner().invoke(
885                 black.main, ["-"], input=BytesIO(b"print('hello')")
886             )
887             self.assertEqual(result.exit_code, 0)
888             cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode)
889             self.assertFalse(cache_file.exists())
890
891     def test_read_cache_no_cachefile(self) -> None:
892         mode = black.FileMode.AUTO_DETECT
893         with cache_dir():
894             self.assertEqual(black.read_cache(black.DEFAULT_LINE_LENGTH, mode), {})
895
896     def test_write_cache_read_cache(self) -> None:
897         mode = black.FileMode.AUTO_DETECT
898         with cache_dir() as workspace:
899             src = (workspace / "test.py").resolve()
900             src.touch()
901             black.write_cache({}, [src], black.DEFAULT_LINE_LENGTH, mode)
902             cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
903             self.assertIn(src, cache)
904             self.assertEqual(cache[src], black.get_cache_info(src))
905
906     def test_filter_cached(self) -> None:
907         with TemporaryDirectory() as workspace:
908             path = Path(workspace)
909             uncached = (path / "uncached").resolve()
910             cached = (path / "cached").resolve()
911             cached_but_changed = (path / "changed").resolve()
912             uncached.touch()
913             cached.touch()
914             cached_but_changed.touch()
915             cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
916             todo, done = black.filter_cached(
917                 cache, {uncached, cached, cached_but_changed}
918             )
919             self.assertEqual(todo, {uncached, cached_but_changed})
920             self.assertEqual(done, {cached})
921
922     def test_write_cache_creates_directory_if_needed(self) -> None:
923         mode = black.FileMode.AUTO_DETECT
924         with cache_dir(exists=False) as workspace:
925             self.assertFalse(workspace.exists())
926             black.write_cache({}, [], black.DEFAULT_LINE_LENGTH, mode)
927             self.assertTrue(workspace.exists())
928
929     @event_loop(close=False)
930     def test_failed_formatting_does_not_get_cached(self) -> None:
931         mode = black.FileMode.AUTO_DETECT
932         with cache_dir() as workspace, patch(
933             "black.ProcessPoolExecutor", new=ThreadPoolExecutor
934         ):
935             failing = (workspace / "failing.py").resolve()
936             with failing.open("w") as fobj:
937                 fobj.write("not actually python")
938             clean = (workspace / "clean.py").resolve()
939             with clean.open("w") as fobj:
940                 fobj.write('print("hello")\n')
941             result = CliRunner().invoke(black.main, [str(workspace)])
942             self.assertEqual(result.exit_code, 123)
943             cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
944             self.assertNotIn(failing, cache)
945             self.assertIn(clean, cache)
946
947     def test_write_cache_write_fail(self) -> None:
948         mode = black.FileMode.AUTO_DETECT
949         with cache_dir(), patch.object(Path, "open") as mock:
950             mock.side_effect = OSError
951             black.write_cache({}, [], black.DEFAULT_LINE_LENGTH, mode)
952
953     @event_loop(close=False)
954     def test_check_diff_use_together(self) -> None:
955         with cache_dir():
956             # Files which will be reformatted.
957             src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
958             result = CliRunner().invoke(black.main, [str(src1), "--diff", "--check"])
959             self.assertEqual(result.exit_code, 1, result.output)
960             # Files which will not be reformatted.
961             src2 = (THIS_DIR / "data" / "composition.py").resolve()
962             result = CliRunner().invoke(black.main, [str(src2), "--diff", "--check"])
963             self.assertEqual(result.exit_code, 0, result.output)
964             # Multi file command.
965             result = CliRunner().invoke(
966                 black.main, [str(src1), str(src2), "--diff", "--check"]
967             )
968             self.assertEqual(result.exit_code, 1, result.output)
969
970     def test_no_files(self) -> None:
971         with cache_dir():
972             # Without an argument, black exits with error code 0.
973             result = CliRunner().invoke(black.main, [])
974             self.assertEqual(result.exit_code, 0)
975
976     def test_broken_symlink(self) -> None:
977         with cache_dir() as workspace:
978             symlink = workspace / "broken_link.py"
979             try:
980                 symlink.symlink_to("nonexistent.py")
981             except OSError as e:
982                 self.skipTest(f"Can't create symlinks: {e}")
983             result = CliRunner().invoke(black.main, [str(workspace.resolve())])
984             self.assertEqual(result.exit_code, 0)
985
986     def test_read_cache_line_lengths(self) -> None:
987         mode = black.FileMode.AUTO_DETECT
988         with cache_dir() as workspace:
989             path = (workspace / "file.py").resolve()
990             path.touch()
991             black.write_cache({}, [path], 1, mode)
992             one = black.read_cache(1, mode)
993             self.assertIn(path, one)
994             two = black.read_cache(2, mode)
995             self.assertNotIn(path, two)
996
997     def test_single_file_force_pyi(self) -> None:
998         reg_mode = black.FileMode.AUTO_DETECT
999         pyi_mode = black.FileMode.PYI
1000         contents, expected = read_data("force_pyi")
1001         with cache_dir() as workspace:
1002             path = (workspace / "file.py").resolve()
1003             with open(path, "w") as fh:
1004                 fh.write(contents)
1005             result = CliRunner().invoke(black.main, [str(path), "--pyi"])
1006             self.assertEqual(result.exit_code, 0)
1007             with open(path, "r") as fh:
1008                 actual = fh.read()
1009             # verify cache with --pyi is separate
1010             pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, pyi_mode)
1011             self.assertIn(path, pyi_cache)
1012             normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1013             self.assertNotIn(path, normal_cache)
1014         self.assertEqual(actual, expected)
1015
1016     @event_loop(close=False)
1017     def test_multi_file_force_pyi(self) -> None:
1018         reg_mode = black.FileMode.AUTO_DETECT
1019         pyi_mode = black.FileMode.PYI
1020         contents, expected = read_data("force_pyi")
1021         with cache_dir() as workspace:
1022             paths = [
1023                 (workspace / "file1.py").resolve(),
1024                 (workspace / "file2.py").resolve(),
1025             ]
1026             for path in paths:
1027                 with open(path, "w") as fh:
1028                     fh.write(contents)
1029             result = CliRunner().invoke(black.main, [str(p) for p in paths] + ["--pyi"])
1030             self.assertEqual(result.exit_code, 0)
1031             for path in paths:
1032                 with open(path, "r") as fh:
1033                     actual = fh.read()
1034                 self.assertEqual(actual, expected)
1035             # verify cache with --pyi is separate
1036             pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, pyi_mode)
1037             normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1038             for path in paths:
1039                 self.assertIn(path, pyi_cache)
1040                 self.assertNotIn(path, normal_cache)
1041
1042     def test_pipe_force_pyi(self) -> None:
1043         source, expected = read_data("force_pyi")
1044         result = CliRunner().invoke(
1045             black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1046         )
1047         self.assertEqual(result.exit_code, 0)
1048         actual = result.output
1049         self.assertFormatEqual(actual, expected)
1050
1051     def test_single_file_force_py36(self) -> None:
1052         reg_mode = black.FileMode.AUTO_DETECT
1053         py36_mode = black.FileMode.PYTHON36
1054         source, expected = read_data("force_py36")
1055         with cache_dir() as workspace:
1056             path = (workspace / "file.py").resolve()
1057             with open(path, "w") as fh:
1058                 fh.write(source)
1059             result = CliRunner().invoke(black.main, [str(path), "--py36"])
1060             self.assertEqual(result.exit_code, 0)
1061             with open(path, "r") as fh:
1062                 actual = fh.read()
1063             # verify cache with --py36 is separate
1064             py36_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, py36_mode)
1065             self.assertIn(path, py36_cache)
1066             normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1067             self.assertNotIn(path, normal_cache)
1068         self.assertEqual(actual, expected)
1069
1070     @event_loop(close=False)
1071     def test_multi_file_force_py36(self) -> None:
1072         reg_mode = black.FileMode.AUTO_DETECT
1073         py36_mode = black.FileMode.PYTHON36
1074         source, expected = read_data("force_py36")
1075         with cache_dir() as workspace:
1076             paths = [
1077                 (workspace / "file1.py").resolve(),
1078                 (workspace / "file2.py").resolve(),
1079             ]
1080             for path in paths:
1081                 with open(path, "w") as fh:
1082                     fh.write(source)
1083             result = CliRunner().invoke(
1084                 black.main, [str(p) for p in paths] + ["--py36"]
1085             )
1086             self.assertEqual(result.exit_code, 0)
1087             for path in paths:
1088                 with open(path, "r") as fh:
1089                     actual = fh.read()
1090                 self.assertEqual(actual, expected)
1091             # verify cache with --py36 is separate
1092             pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, py36_mode)
1093             normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1094             for path in paths:
1095                 self.assertIn(path, pyi_cache)
1096                 self.assertNotIn(path, normal_cache)
1097
1098     def test_pipe_force_py36(self) -> None:
1099         source, expected = read_data("force_py36")
1100         result = CliRunner().invoke(
1101             black.main, ["-", "-q", "--py36"], input=BytesIO(source.encode("utf8"))
1102         )
1103         self.assertEqual(result.exit_code, 0)
1104         actual = result.output
1105         self.assertFormatEqual(actual, expected)
1106
1107     def test_include_exclude(self) -> None:
1108         path = THIS_DIR / "data" / "include_exclude_tests"
1109         include = re.compile(r"\.pyi?$")
1110         exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1111         report = black.Report()
1112         sources: List[Path] = []
1113         expected = [
1114             Path(path / "b/dont_exclude/a.py"),
1115             Path(path / "b/dont_exclude/a.pyi"),
1116         ]
1117         this_abs = THIS_DIR.resolve()
1118         sources.extend(
1119             black.gen_python_files_in_dir(path, this_abs, include, exclude, report)
1120         )
1121         self.assertEqual(sorted(expected), sorted(sources))
1122
1123     def test_empty_include(self) -> None:
1124         path = THIS_DIR / "data" / "include_exclude_tests"
1125         report = black.Report()
1126         empty = re.compile(r"")
1127         sources: List[Path] = []
1128         expected = [
1129             Path(path / "b/exclude/a.pie"),
1130             Path(path / "b/exclude/a.py"),
1131             Path(path / "b/exclude/a.pyi"),
1132             Path(path / "b/dont_exclude/a.pie"),
1133             Path(path / "b/dont_exclude/a.py"),
1134             Path(path / "b/dont_exclude/a.pyi"),
1135             Path(path / "b/.definitely_exclude/a.pie"),
1136             Path(path / "b/.definitely_exclude/a.py"),
1137             Path(path / "b/.definitely_exclude/a.pyi"),
1138         ]
1139         this_abs = THIS_DIR.resolve()
1140         sources.extend(
1141             black.gen_python_files_in_dir(
1142                 path, this_abs, empty, re.compile(black.DEFAULT_EXCLUDES), report
1143             )
1144         )
1145         self.assertEqual(sorted(expected), sorted(sources))
1146
1147     def test_empty_exclude(self) -> None:
1148         path = THIS_DIR / "data" / "include_exclude_tests"
1149         report = black.Report()
1150         empty = re.compile(r"")
1151         sources: List[Path] = []
1152         expected = [
1153             Path(path / "b/dont_exclude/a.py"),
1154             Path(path / "b/dont_exclude/a.pyi"),
1155             Path(path / "b/exclude/a.py"),
1156             Path(path / "b/exclude/a.pyi"),
1157             Path(path / "b/.definitely_exclude/a.py"),
1158             Path(path / "b/.definitely_exclude/a.pyi"),
1159         ]
1160         this_abs = THIS_DIR.resolve()
1161         sources.extend(
1162             black.gen_python_files_in_dir(
1163                 path, this_abs, re.compile(black.DEFAULT_INCLUDES), empty, report
1164             )
1165         )
1166         self.assertEqual(sorted(expected), sorted(sources))
1167
1168     def test_invalid_include_exclude(self) -> None:
1169         for option in ["--include", "--exclude"]:
1170             result = CliRunner().invoke(black.main, ["-", option, "**()(!!*)"])
1171             self.assertEqual(result.exit_code, 2)
1172
1173     def test_preserves_line_endings(self) -> None:
1174         with TemporaryDirectory() as workspace:
1175             test_file = Path(workspace) / "test.py"
1176             for nl in ["\n", "\r\n"]:
1177                 contents = nl.join(["def f(  ):", "    pass"])
1178                 test_file.write_bytes(contents.encode())
1179                 ff(test_file, write_back=black.WriteBack.YES)
1180                 updated_contents: bytes = test_file.read_bytes()
1181                 self.assertIn(nl.encode(), updated_contents)
1182                 if nl == "\n":
1183                     self.assertNotIn(b"\r\n", updated_contents)
1184
1185     def test_assert_equivalent_different_asts(self) -> None:
1186         with self.assertRaises(AssertionError):
1187             black.assert_equivalent("{}", "None")
1188
1189     def test_symlink_out_of_root_directory(self) -> None:
1190         path = MagicMock()
1191         root = THIS_DIR
1192         child = MagicMock()
1193         include = re.compile(black.DEFAULT_INCLUDES)
1194         exclude = re.compile(black.DEFAULT_EXCLUDES)
1195         report = black.Report()
1196         # `child` should behave like a symlink which resolved path is clearly
1197         # outside of the `root` directory.
1198         path.iterdir.return_value = [child]
1199         child.resolve.return_value = Path("/a/b/c")
1200         child.is_symlink.return_value = True
1201         try:
1202             list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1203         except ValueError as ve:
1204             self.fail("`get_python_files_in_dir()` failed: {ve}")
1205         path.iterdir.assert_called_once()
1206         child.resolve.assert_called_once()
1207         child.is_symlink.assert_called_once()
1208         # `child` should behave like a strange file which resolved path is clearly
1209         # outside of the `root` directory.
1210         child.is_symlink.return_value = False
1211         with self.assertRaises(ValueError):
1212             list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1213         path.iterdir.assert_called()
1214         self.assertEqual(path.iterdir.call_count, 2)
1215         child.resolve.assert_called()
1216         self.assertEqual(child.resolve.call_count, 2)
1217         child.is_symlink.assert_called()
1218         self.assertEqual(child.is_symlink.call_count, 2)
1219
1220     def test_shhh_click(self) -> None:
1221         try:
1222             from click import _unicodefun  # type: ignore
1223         except ModuleNotFoundError:
1224             self.skipTest("Incompatible Click version")
1225         if not hasattr(_unicodefun, "_verify_python3_env"):
1226             self.skipTest("Incompatible Click version")
1227         # First, let's see if Click is crashing with a preferred ASCII charset.
1228         with patch("locale.getpreferredencoding") as gpe:
1229             gpe.return_value = "ASCII"
1230             with self.assertRaises(RuntimeError):
1231                 _unicodefun._verify_python3_env()
1232         # Now, let's silence Click...
1233         black.patch_click()
1234         # ...and confirm it's silent.
1235         with patch("locale.getpreferredencoding") as gpe:
1236             gpe.return_value = "ASCII"
1237             try:
1238                 _unicodefun._verify_python3_env()
1239             except RuntimeError as re:
1240                 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1241
1242
1243 if __name__ == "__main__":
1244     unittest.main(module="test_black")