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

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