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

6638dc4f119f83ab4c2cd3e2e14272362f803813
[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
947             # Files which will not be reformatted.
948             src2 = (THIS_DIR / "data" / "composition.py").resolve()
949             result = CliRunner().invoke(black.main, [str(src2), "--diff", "--check"])
950             self.assertEqual(result.exit_code, 0, result.output)
951
952             # Multi file command.
953             result = CliRunner().invoke(
954                 black.main, [str(src1), str(src2), "--diff", "--check"]
955             )
956             self.assertEqual(result.exit_code, 1, result.output)
957
958     def test_no_files(self) -> None:
959         with cache_dir():
960             # Without an argument, black exits with error code 0.
961             result = CliRunner().invoke(black.main, [])
962             self.assertEqual(result.exit_code, 0)
963
964     def test_broken_symlink(self) -> None:
965         with cache_dir() as workspace:
966             symlink = workspace / "broken_link.py"
967             try:
968                 symlink.symlink_to("nonexistent.py")
969             except OSError as e:
970                 self.skipTest(f"Can't create symlinks: {e}")
971             result = CliRunner().invoke(black.main, [str(workspace.resolve())])
972             self.assertEqual(result.exit_code, 0)
973
974     def test_read_cache_line_lengths(self) -> None:
975         mode = black.FileMode.AUTO_DETECT
976         with cache_dir() as workspace:
977             path = (workspace / "file.py").resolve()
978             path.touch()
979             black.write_cache({}, [path], 1, mode)
980             one = black.read_cache(1, mode)
981             self.assertIn(path, one)
982             two = black.read_cache(2, mode)
983             self.assertNotIn(path, two)
984
985     def test_single_file_force_pyi(self) -> None:
986         reg_mode = black.FileMode.AUTO_DETECT
987         pyi_mode = black.FileMode.PYI
988         contents, expected = read_data("force_pyi")
989         with cache_dir() as workspace:
990             path = (workspace / "file.py").resolve()
991             with open(path, "w") as fh:
992                 fh.write(contents)
993             result = CliRunner().invoke(black.main, [str(path), "--pyi"])
994             self.assertEqual(result.exit_code, 0)
995             with open(path, "r") as fh:
996                 actual = fh.read()
997             # verify cache with --pyi is separate
998             pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, pyi_mode)
999             self.assertIn(path, pyi_cache)
1000             normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1001             self.assertNotIn(path, normal_cache)
1002         self.assertEqual(actual, expected)
1003
1004     @event_loop(close=False)
1005     def test_multi_file_force_pyi(self) -> None:
1006         reg_mode = black.FileMode.AUTO_DETECT
1007         pyi_mode = black.FileMode.PYI
1008         contents, expected = read_data("force_pyi")
1009         with cache_dir() as workspace:
1010             paths = [
1011                 (workspace / "file1.py").resolve(),
1012                 (workspace / "file2.py").resolve(),
1013             ]
1014             for path in paths:
1015                 with open(path, "w") as fh:
1016                     fh.write(contents)
1017             result = CliRunner().invoke(black.main, [str(p) for p in paths] + ["--pyi"])
1018             self.assertEqual(result.exit_code, 0)
1019             for path in paths:
1020                 with open(path, "r") as fh:
1021                     actual = fh.read()
1022                 self.assertEqual(actual, expected)
1023             # verify cache with --pyi is separate
1024             pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, pyi_mode)
1025             normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1026             for path in paths:
1027                 self.assertIn(path, pyi_cache)
1028                 self.assertNotIn(path, normal_cache)
1029
1030     def test_pipe_force_pyi(self) -> None:
1031         source, expected = read_data("force_pyi")
1032         result = CliRunner().invoke(black.main, ["-", "-q", "--pyi"], input=source)
1033         self.assertEqual(result.exit_code, 0)
1034         actual = result.output
1035         self.assertFormatEqual(actual, expected)
1036
1037     def test_single_file_force_py36(self) -> None:
1038         reg_mode = black.FileMode.AUTO_DETECT
1039         py36_mode = black.FileMode.PYTHON36
1040         source, expected = read_data("force_py36")
1041         with cache_dir() as workspace:
1042             path = (workspace / "file.py").resolve()
1043             with open(path, "w") as fh:
1044                 fh.write(source)
1045             result = CliRunner().invoke(black.main, [str(path), "--py36"])
1046             self.assertEqual(result.exit_code, 0)
1047             with open(path, "r") as fh:
1048                 actual = fh.read()
1049             # verify cache with --py36 is separate
1050             py36_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, py36_mode)
1051             self.assertIn(path, py36_cache)
1052             normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1053             self.assertNotIn(path, normal_cache)
1054         self.assertEqual(actual, expected)
1055
1056     @event_loop(close=False)
1057     def test_multi_file_force_py36(self) -> None:
1058         reg_mode = black.FileMode.AUTO_DETECT
1059         py36_mode = black.FileMode.PYTHON36
1060         source, expected = read_data("force_py36")
1061         with cache_dir() as workspace:
1062             paths = [
1063                 (workspace / "file1.py").resolve(),
1064                 (workspace / "file2.py").resolve(),
1065             ]
1066             for path in paths:
1067                 with open(path, "w") as fh:
1068                     fh.write(source)
1069             result = CliRunner().invoke(
1070                 black.main, [str(p) for p in paths] + ["--py36"]
1071             )
1072             self.assertEqual(result.exit_code, 0)
1073             for path in paths:
1074                 with open(path, "r") as fh:
1075                     actual = fh.read()
1076                 self.assertEqual(actual, expected)
1077             # verify cache with --py36 is separate
1078             pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, py36_mode)
1079             normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1080             for path in paths:
1081                 self.assertIn(path, pyi_cache)
1082                 self.assertNotIn(path, normal_cache)
1083
1084     def test_pipe_force_py36(self) -> None:
1085         source, expected = read_data("force_py36")
1086         result = CliRunner().invoke(black.main, ["-", "-q", "--py36"], input=source)
1087         self.assertEqual(result.exit_code, 0)
1088         actual = result.output
1089         self.assertFormatEqual(actual, expected)
1090
1091     def test_include_exclude(self) -> None:
1092         path = THIS_DIR / "data" / "include_exclude_tests"
1093         include = re.compile(r"\.pyi?$")
1094         exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1095         report = black.Report()
1096         sources: List[Path] = []
1097         expected = [
1098             Path(path / "b/dont_exclude/a.py"),
1099             Path(path / "b/dont_exclude/a.pyi"),
1100         ]
1101         this_abs = THIS_DIR.resolve()
1102         sources.extend(
1103             black.gen_python_files_in_dir(path, this_abs, include, exclude, report)
1104         )
1105         self.assertEqual(sorted(expected), sorted(sources))
1106
1107     def test_empty_include(self) -> None:
1108         path = THIS_DIR / "data" / "include_exclude_tests"
1109         report = black.Report()
1110         empty = re.compile(r"")
1111         sources: List[Path] = []
1112         expected = [
1113             Path(path / "b/exclude/a.pie"),
1114             Path(path / "b/exclude/a.py"),
1115             Path(path / "b/exclude/a.pyi"),
1116             Path(path / "b/dont_exclude/a.pie"),
1117             Path(path / "b/dont_exclude/a.py"),
1118             Path(path / "b/dont_exclude/a.pyi"),
1119             Path(path / "b/.definitely_exclude/a.pie"),
1120             Path(path / "b/.definitely_exclude/a.py"),
1121             Path(path / "b/.definitely_exclude/a.pyi"),
1122         ]
1123         this_abs = THIS_DIR.resolve()
1124         sources.extend(
1125             black.gen_python_files_in_dir(
1126                 path, this_abs, empty, re.compile(black.DEFAULT_EXCLUDES), report
1127             )
1128         )
1129         self.assertEqual(sorted(expected), sorted(sources))
1130
1131     def test_empty_exclude(self) -> None:
1132         path = THIS_DIR / "data" / "include_exclude_tests"
1133         report = black.Report()
1134         empty = re.compile(r"")
1135         sources: List[Path] = []
1136         expected = [
1137             Path(path / "b/dont_exclude/a.py"),
1138             Path(path / "b/dont_exclude/a.pyi"),
1139             Path(path / "b/exclude/a.py"),
1140             Path(path / "b/exclude/a.pyi"),
1141             Path(path / "b/.definitely_exclude/a.py"),
1142             Path(path / "b/.definitely_exclude/a.pyi"),
1143         ]
1144         this_abs = THIS_DIR.resolve()
1145         sources.extend(
1146             black.gen_python_files_in_dir(
1147                 path, this_abs, re.compile(black.DEFAULT_INCLUDES), empty, report
1148             )
1149         )
1150         self.assertEqual(sorted(expected), sorted(sources))
1151
1152     def test_invalid_include_exclude(self) -> None:
1153         for option in ["--include", "--exclude"]:
1154             result = CliRunner().invoke(black.main, ["-", option, "**()(!!*)"])
1155             self.assertEqual(result.exit_code, 2)
1156
1157     def test_preserves_line_endings(self) -> None:
1158         with TemporaryDirectory() as workspace:
1159             test_file = Path(workspace) / "test.py"
1160             for nl in ["\n", "\r\n"]:
1161                 contents = nl.join(["def f(  ):", "    pass"])
1162                 test_file.write_bytes(contents.encode())
1163                 ff(test_file, write_back=black.WriteBack.YES)
1164                 updated_contents: bytes = test_file.read_bytes()
1165                 self.assertIn(nl.encode(), updated_contents)  # type: ignore
1166                 if nl == "\n":
1167                     self.assertNotIn(b"\r\n", updated_contents)  # type: ignore
1168
1169     def test_assert_equivalent_different_asts(self) -> None:
1170         with self.assertRaises(AssertionError):
1171             black.assert_equivalent("{}", "None")
1172
1173     def test_symlink_out_of_root_directory(self) -> None:
1174         # prepare argumens
1175         path = MagicMock()
1176         root = THIS_DIR
1177         child = MagicMock()
1178         include = re.compile(black.DEFAULT_INCLUDES)
1179         exclude = re.compile(black.DEFAULT_EXCLUDES)
1180         report = black.Report()
1181
1182         # set the behavior of mock arguments
1183         # child should behave like a symlink which resolved path is clearly
1184         # outside of the root directory
1185         path.iterdir.return_value = [child]
1186         child.resolve.return_value = Path("/a/b/c")
1187         child.is_symlink.return_value = True
1188
1189         # call the method
1190         # it should not raise any error
1191         list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1192
1193         # check the call of the methods of the mock objects
1194         path.iterdir.assert_called_once()
1195         child.resolve.assert_called_once()
1196         child.is_symlink.assert_called_once()
1197
1198         # set the behavior of mock arguments
1199         # child should behave like a strange file which resolved path is clearly
1200         # outside of the root directory
1201         child.is_symlink.return_value = False
1202
1203         # call the method
1204         # it should raise a ValueError
1205         with self.assertRaises(ValueError):
1206             list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1207
1208         # check the call of the methods of the mock objects
1209         path.iterdir.assert_called()
1210         self.assertEqual(path.iterdir.call_count, 2)
1211         child.resolve.assert_called()
1212         self.assertEqual(child.resolve.call_count, 2)
1213         child.is_symlink.assert_called()
1214         self.assertEqual(child.is_symlink.call_count, 2)
1215
1216
1217 if __name__ == "__main__":
1218     unittest.main(module="test_black")