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

Improve get_future_imports implementation.
[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         node = black.lib2to3_parse(
739             "from __future__ import unicode_literals as _unicode_literals"
740         )
741         self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
742         node = black.lib2to3_parse(
743             "from __future__ import unicode_literals as _lol, print"
744         )
745         self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
746
747     def test_debug_visitor(self) -> None:
748         source, _ = read_data("debug_visitor.py")
749         expected, _ = read_data("debug_visitor.out")
750         out_lines = []
751         err_lines = []
752
753         def out(msg: str, **kwargs: Any) -> None:
754             out_lines.append(msg)
755
756         def err(msg: str, **kwargs: Any) -> None:
757             err_lines.append(msg)
758
759         with patch("black.out", out), patch("black.err", err):
760             black.DebugVisitor.show(source)
761         actual = "\n".join(out_lines) + "\n"
762         log_name = ""
763         if expected != actual:
764             log_name = black.dump_to_file(*out_lines)
765         self.assertEqual(
766             expected,
767             actual,
768             f"AST print out is different. Actual version dumped to {log_name}",
769         )
770
771     def test_format_file_contents(self) -> None:
772         empty = ""
773         with self.assertRaises(black.NothingChanged):
774             black.format_file_contents(empty, line_length=ll, fast=False)
775         just_nl = "\n"
776         with self.assertRaises(black.NothingChanged):
777             black.format_file_contents(just_nl, line_length=ll, fast=False)
778         same = "l = [1, 2, 3]\n"
779         with self.assertRaises(black.NothingChanged):
780             black.format_file_contents(same, line_length=ll, fast=False)
781         different = "l = [1,2,3]"
782         expected = same
783         actual = black.format_file_contents(different, line_length=ll, fast=False)
784         self.assertEqual(expected, actual)
785         invalid = "return if you can"
786         with self.assertRaises(ValueError) as e:
787             black.format_file_contents(invalid, line_length=ll, fast=False)
788         self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
789
790     def test_endmarker(self) -> None:
791         n = black.lib2to3_parse("\n")
792         self.assertEqual(n.type, black.syms.file_input)
793         self.assertEqual(len(n.children), 1)
794         self.assertEqual(n.children[0].type, black.token.ENDMARKER)
795
796     @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
797     def test_assertFormatEqual(self) -> None:
798         out_lines = []
799         err_lines = []
800
801         def out(msg: str, **kwargs: Any) -> None:
802             out_lines.append(msg)
803
804         def err(msg: str, **kwargs: Any) -> None:
805             err_lines.append(msg)
806
807         with patch("black.out", out), patch("black.err", err):
808             with self.assertRaises(AssertionError):
809                 self.assertFormatEqual("l = [1, 2, 3]", "l = [1, 2, 3,]")
810
811         out_str = "".join(out_lines)
812         self.assertTrue("Expected tree:" in out_str)
813         self.assertTrue("Actual tree:" in out_str)
814         self.assertEqual("".join(err_lines), "")
815
816     def test_cache_broken_file(self) -> None:
817         mode = black.FileMode.AUTO_DETECT
818         with cache_dir() as workspace:
819             cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode)
820             with cache_file.open("w") as fobj:
821                 fobj.write("this is not a pickle")
822             self.assertEqual(black.read_cache(black.DEFAULT_LINE_LENGTH, mode), {})
823             src = (workspace / "test.py").resolve()
824             with src.open("w") as fobj:
825                 fobj.write("print('hello')")
826             result = CliRunner().invoke(black.main, [str(src)])
827             self.assertEqual(result.exit_code, 0)
828             cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
829             self.assertIn(src, cache)
830
831     def test_cache_single_file_already_cached(self) -> None:
832         mode = black.FileMode.AUTO_DETECT
833         with cache_dir() as workspace:
834             src = (workspace / "test.py").resolve()
835             with src.open("w") as fobj:
836                 fobj.write("print('hello')")
837             black.write_cache({}, [src], black.DEFAULT_LINE_LENGTH, mode)
838             result = CliRunner().invoke(black.main, [str(src)])
839             self.assertEqual(result.exit_code, 0)
840             with src.open("r") as fobj:
841                 self.assertEqual(fobj.read(), "print('hello')")
842
843     @event_loop(close=False)
844     def test_cache_multiple_files(self) -> None:
845         mode = black.FileMode.AUTO_DETECT
846         with cache_dir() as workspace, patch(
847             "black.ProcessPoolExecutor", new=ThreadPoolExecutor
848         ):
849             one = (workspace / "one.py").resolve()
850             with one.open("w") as fobj:
851                 fobj.write("print('hello')")
852             two = (workspace / "two.py").resolve()
853             with two.open("w") as fobj:
854                 fobj.write("print('hello')")
855             black.write_cache({}, [one], black.DEFAULT_LINE_LENGTH, mode)
856             result = CliRunner().invoke(black.main, [str(workspace)])
857             self.assertEqual(result.exit_code, 0)
858             with one.open("r") as fobj:
859                 self.assertEqual(fobj.read(), "print('hello')")
860             with two.open("r") as fobj:
861                 self.assertEqual(fobj.read(), 'print("hello")\n')
862             cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
863             self.assertIn(one, cache)
864             self.assertIn(two, cache)
865
866     def test_no_cache_when_writeback_diff(self) -> None:
867         mode = black.FileMode.AUTO_DETECT
868         with cache_dir() as workspace:
869             src = (workspace / "test.py").resolve()
870             with src.open("w") as fobj:
871                 fobj.write("print('hello')")
872             result = CliRunner().invoke(black.main, [str(src), "--diff"])
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_no_cache_when_stdin(self) -> None:
878         mode = black.FileMode.AUTO_DETECT
879         with cache_dir():
880             result = CliRunner().invoke(black.main, ["-"], input="print('hello')")
881             self.assertEqual(result.exit_code, 0)
882             cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode)
883             self.assertFalse(cache_file.exists())
884
885     def test_read_cache_no_cachefile(self) -> None:
886         mode = black.FileMode.AUTO_DETECT
887         with cache_dir():
888             self.assertEqual(black.read_cache(black.DEFAULT_LINE_LENGTH, mode), {})
889
890     def test_write_cache_read_cache(self) -> None:
891         mode = black.FileMode.AUTO_DETECT
892         with cache_dir() as workspace:
893             src = (workspace / "test.py").resolve()
894             src.touch()
895             black.write_cache({}, [src], black.DEFAULT_LINE_LENGTH, mode)
896             cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
897             self.assertIn(src, cache)
898             self.assertEqual(cache[src], black.get_cache_info(src))
899
900     def test_filter_cached(self) -> None:
901         with TemporaryDirectory() as workspace:
902             path = Path(workspace)
903             uncached = (path / "uncached").resolve()
904             cached = (path / "cached").resolve()
905             cached_but_changed = (path / "changed").resolve()
906             uncached.touch()
907             cached.touch()
908             cached_but_changed.touch()
909             cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
910             todo, done = black.filter_cached(
911                 cache, {uncached, cached, cached_but_changed}
912             )
913             self.assertEqual(todo, {uncached, cached_but_changed})
914             self.assertEqual(done, {cached})
915
916     def test_write_cache_creates_directory_if_needed(self) -> None:
917         mode = black.FileMode.AUTO_DETECT
918         with cache_dir(exists=False) as workspace:
919             self.assertFalse(workspace.exists())
920             black.write_cache({}, [], black.DEFAULT_LINE_LENGTH, mode)
921             self.assertTrue(workspace.exists())
922
923     @event_loop(close=False)
924     def test_failed_formatting_does_not_get_cached(self) -> None:
925         mode = black.FileMode.AUTO_DETECT
926         with cache_dir() as workspace, patch(
927             "black.ProcessPoolExecutor", new=ThreadPoolExecutor
928         ):
929             failing = (workspace / "failing.py").resolve()
930             with failing.open("w") as fobj:
931                 fobj.write("not actually python")
932             clean = (workspace / "clean.py").resolve()
933             with clean.open("w") as fobj:
934                 fobj.write('print("hello")\n')
935             result = CliRunner().invoke(black.main, [str(workspace)])
936             self.assertEqual(result.exit_code, 123)
937             cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
938             self.assertNotIn(failing, cache)
939             self.assertIn(clean, cache)
940
941     def test_write_cache_write_fail(self) -> None:
942         mode = black.FileMode.AUTO_DETECT
943         with cache_dir(), patch.object(Path, "open") as mock:
944             mock.side_effect = OSError
945             black.write_cache({}, [], black.DEFAULT_LINE_LENGTH, mode)
946
947     @event_loop(close=False)
948     def test_check_diff_use_together(self) -> None:
949         with cache_dir():
950             # Files which will be reformatted.
951             src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
952             result = CliRunner().invoke(black.main, [str(src1), "--diff", "--check"])
953             self.assertEqual(result.exit_code, 1, result.output)
954             # Files which will not be reformatted.
955             src2 = (THIS_DIR / "data" / "composition.py").resolve()
956             result = CliRunner().invoke(black.main, [str(src2), "--diff", "--check"])
957             self.assertEqual(result.exit_code, 0, result.output)
958             # Multi file command.
959             result = CliRunner().invoke(
960                 black.main, [str(src1), str(src2), "--diff", "--check"]
961             )
962             self.assertEqual(result.exit_code, 1, result.output)
963
964     def test_no_files(self) -> None:
965         with cache_dir():
966             # Without an argument, black exits with error code 0.
967             result = CliRunner().invoke(black.main, [])
968             self.assertEqual(result.exit_code, 0)
969
970     def test_broken_symlink(self) -> None:
971         with cache_dir() as workspace:
972             symlink = workspace / "broken_link.py"
973             try:
974                 symlink.symlink_to("nonexistent.py")
975             except OSError as e:
976                 self.skipTest(f"Can't create symlinks: {e}")
977             result = CliRunner().invoke(black.main, [str(workspace.resolve())])
978             self.assertEqual(result.exit_code, 0)
979
980     def test_read_cache_line_lengths(self) -> None:
981         mode = black.FileMode.AUTO_DETECT
982         with cache_dir() as workspace:
983             path = (workspace / "file.py").resolve()
984             path.touch()
985             black.write_cache({}, [path], 1, mode)
986             one = black.read_cache(1, mode)
987             self.assertIn(path, one)
988             two = black.read_cache(2, mode)
989             self.assertNotIn(path, two)
990
991     def test_single_file_force_pyi(self) -> None:
992         reg_mode = black.FileMode.AUTO_DETECT
993         pyi_mode = black.FileMode.PYI
994         contents, expected = read_data("force_pyi")
995         with cache_dir() as workspace:
996             path = (workspace / "file.py").resolve()
997             with open(path, "w") as fh:
998                 fh.write(contents)
999             result = CliRunner().invoke(black.main, [str(path), "--pyi"])
1000             self.assertEqual(result.exit_code, 0)
1001             with open(path, "r") as fh:
1002                 actual = fh.read()
1003             # verify cache with --pyi is separate
1004             pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, pyi_mode)
1005             self.assertIn(path, pyi_cache)
1006             normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1007             self.assertNotIn(path, normal_cache)
1008         self.assertEqual(actual, expected)
1009
1010     @event_loop(close=False)
1011     def test_multi_file_force_pyi(self) -> None:
1012         reg_mode = black.FileMode.AUTO_DETECT
1013         pyi_mode = black.FileMode.PYI
1014         contents, expected = read_data("force_pyi")
1015         with cache_dir() as workspace:
1016             paths = [
1017                 (workspace / "file1.py").resolve(),
1018                 (workspace / "file2.py").resolve(),
1019             ]
1020             for path in paths:
1021                 with open(path, "w") as fh:
1022                     fh.write(contents)
1023             result = CliRunner().invoke(black.main, [str(p) for p in paths] + ["--pyi"])
1024             self.assertEqual(result.exit_code, 0)
1025             for path in paths:
1026                 with open(path, "r") as fh:
1027                     actual = fh.read()
1028                 self.assertEqual(actual, expected)
1029             # verify cache with --pyi is separate
1030             pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, pyi_mode)
1031             normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1032             for path in paths:
1033                 self.assertIn(path, pyi_cache)
1034                 self.assertNotIn(path, normal_cache)
1035
1036     def test_pipe_force_pyi(self) -> None:
1037         source, expected = read_data("force_pyi")
1038         result = CliRunner().invoke(black.main, ["-", "-q", "--pyi"], input=source)
1039         self.assertEqual(result.exit_code, 0)
1040         actual = result.output
1041         self.assertFormatEqual(actual, expected)
1042
1043     def test_single_file_force_py36(self) -> None:
1044         reg_mode = black.FileMode.AUTO_DETECT
1045         py36_mode = black.FileMode.PYTHON36
1046         source, expected = read_data("force_py36")
1047         with cache_dir() as workspace:
1048             path = (workspace / "file.py").resolve()
1049             with open(path, "w") as fh:
1050                 fh.write(source)
1051             result = CliRunner().invoke(black.main, [str(path), "--py36"])
1052             self.assertEqual(result.exit_code, 0)
1053             with open(path, "r") as fh:
1054                 actual = fh.read()
1055             # verify cache with --py36 is separate
1056             py36_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, py36_mode)
1057             self.assertIn(path, py36_cache)
1058             normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1059             self.assertNotIn(path, normal_cache)
1060         self.assertEqual(actual, expected)
1061
1062     @event_loop(close=False)
1063     def test_multi_file_force_py36(self) -> None:
1064         reg_mode = black.FileMode.AUTO_DETECT
1065         py36_mode = black.FileMode.PYTHON36
1066         source, expected = read_data("force_py36")
1067         with cache_dir() as workspace:
1068             paths = [
1069                 (workspace / "file1.py").resolve(),
1070                 (workspace / "file2.py").resolve(),
1071             ]
1072             for path in paths:
1073                 with open(path, "w") as fh:
1074                     fh.write(source)
1075             result = CliRunner().invoke(
1076                 black.main, [str(p) for p in paths] + ["--py36"]
1077             )
1078             self.assertEqual(result.exit_code, 0)
1079             for path in paths:
1080                 with open(path, "r") as fh:
1081                     actual = fh.read()
1082                 self.assertEqual(actual, expected)
1083             # verify cache with --py36 is separate
1084             pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, py36_mode)
1085             normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1086             for path in paths:
1087                 self.assertIn(path, pyi_cache)
1088                 self.assertNotIn(path, normal_cache)
1089
1090     def test_pipe_force_py36(self) -> None:
1091         source, expected = read_data("force_py36")
1092         result = CliRunner().invoke(black.main, ["-", "-q", "--py36"], input=source)
1093         self.assertEqual(result.exit_code, 0)
1094         actual = result.output
1095         self.assertFormatEqual(actual, expected)
1096
1097     def test_include_exclude(self) -> None:
1098         path = THIS_DIR / "data" / "include_exclude_tests"
1099         include = re.compile(r"\.pyi?$")
1100         exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1101         report = black.Report()
1102         sources: List[Path] = []
1103         expected = [
1104             Path(path / "b/dont_exclude/a.py"),
1105             Path(path / "b/dont_exclude/a.pyi"),
1106         ]
1107         this_abs = THIS_DIR.resolve()
1108         sources.extend(
1109             black.gen_python_files_in_dir(path, this_abs, include, exclude, report)
1110         )
1111         self.assertEqual(sorted(expected), sorted(sources))
1112
1113     def test_empty_include(self) -> None:
1114         path = THIS_DIR / "data" / "include_exclude_tests"
1115         report = black.Report()
1116         empty = re.compile(r"")
1117         sources: List[Path] = []
1118         expected = [
1119             Path(path / "b/exclude/a.pie"),
1120             Path(path / "b/exclude/a.py"),
1121             Path(path / "b/exclude/a.pyi"),
1122             Path(path / "b/dont_exclude/a.pie"),
1123             Path(path / "b/dont_exclude/a.py"),
1124             Path(path / "b/dont_exclude/a.pyi"),
1125             Path(path / "b/.definitely_exclude/a.pie"),
1126             Path(path / "b/.definitely_exclude/a.py"),
1127             Path(path / "b/.definitely_exclude/a.pyi"),
1128         ]
1129         this_abs = THIS_DIR.resolve()
1130         sources.extend(
1131             black.gen_python_files_in_dir(
1132                 path, this_abs, empty, re.compile(black.DEFAULT_EXCLUDES), report
1133             )
1134         )
1135         self.assertEqual(sorted(expected), sorted(sources))
1136
1137     def test_empty_exclude(self) -> None:
1138         path = THIS_DIR / "data" / "include_exclude_tests"
1139         report = black.Report()
1140         empty = re.compile(r"")
1141         sources: List[Path] = []
1142         expected = [
1143             Path(path / "b/dont_exclude/a.py"),
1144             Path(path / "b/dont_exclude/a.pyi"),
1145             Path(path / "b/exclude/a.py"),
1146             Path(path / "b/exclude/a.pyi"),
1147             Path(path / "b/.definitely_exclude/a.py"),
1148             Path(path / "b/.definitely_exclude/a.pyi"),
1149         ]
1150         this_abs = THIS_DIR.resolve()
1151         sources.extend(
1152             black.gen_python_files_in_dir(
1153                 path, this_abs, re.compile(black.DEFAULT_INCLUDES), empty, report
1154             )
1155         )
1156         self.assertEqual(sorted(expected), sorted(sources))
1157
1158     def test_invalid_include_exclude(self) -> None:
1159         for option in ["--include", "--exclude"]:
1160             result = CliRunner().invoke(black.main, ["-", option, "**()(!!*)"])
1161             self.assertEqual(result.exit_code, 2)
1162
1163     def test_preserves_line_endings(self) -> None:
1164         with TemporaryDirectory() as workspace:
1165             test_file = Path(workspace) / "test.py"
1166             for nl in ["\n", "\r\n"]:
1167                 contents = nl.join(["def f(  ):", "    pass"])
1168                 test_file.write_bytes(contents.encode())
1169                 ff(test_file, write_back=black.WriteBack.YES)
1170                 updated_contents: bytes = test_file.read_bytes()
1171                 self.assertIn(nl.encode(), updated_contents)  # type: ignore
1172                 if nl == "\n":
1173                     self.assertNotIn(b"\r\n", updated_contents)  # type: ignore
1174
1175     def test_assert_equivalent_different_asts(self) -> None:
1176         with self.assertRaises(AssertionError):
1177             black.assert_equivalent("{}", "None")
1178
1179     def test_symlink_out_of_root_directory(self) -> None:
1180         path = MagicMock()
1181         root = THIS_DIR
1182         child = MagicMock()
1183         include = re.compile(black.DEFAULT_INCLUDES)
1184         exclude = re.compile(black.DEFAULT_EXCLUDES)
1185         report = black.Report()
1186         # `child` should behave like a symlink which resolved path is clearly
1187         # outside of the `root` directory.
1188         path.iterdir.return_value = [child]
1189         child.resolve.return_value = Path("/a/b/c")
1190         child.is_symlink.return_value = True
1191         try:
1192             list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1193         except ValueError as ve:
1194             self.fail("`get_python_files_in_dir()` failed: {ve}")
1195         path.iterdir.assert_called_once()
1196         child.resolve.assert_called_once()
1197         child.is_symlink.assert_called_once()
1198         # `child` should behave like a strange file which resolved path is clearly
1199         # outside of the `root` directory.
1200         child.is_symlink.return_value = False
1201         with self.assertRaises(ValueError):
1202             list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1203         path.iterdir.assert_called()
1204         self.assertEqual(path.iterdir.call_count, 2)
1205         child.resolve.assert_called()
1206         self.assertEqual(child.resolve.call_count, 2)
1207         child.is_symlink.assert_called()
1208         self.assertEqual(child.is_symlink.call_count, 2)
1209
1210     def test_shhh_click(self) -> None:
1211         try:
1212             from click import _unicodefun  # type: ignore
1213         except ModuleNotFoundError:
1214             self.skipTest("Incompatible Click version")
1215         if not hasattr(_unicodefun, "_verify_python3_env"):
1216             self.skipTest("Incompatible Click version")
1217         # First, let's see if Click is crashing with a preferred ASCII charset.
1218         with patch("locale.getpreferredencoding") as gpe:
1219             gpe.return_value = "ASCII"
1220             with self.assertRaises(RuntimeError):
1221                 _unicodefun._verify_python3_env()
1222         # Now, let's silence Click...
1223         black.patch_click()
1224         # ...and confirm it's silent.
1225         with patch("locale.getpreferredencoding") as gpe:
1226             gpe.return_value = "ASCII"
1227             try:
1228                 _unicodefun._verify_python3_env()
1229             except RuntimeError as re:
1230                 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1231
1232
1233 if __name__ == "__main__":
1234     unittest.main(module="test_black")