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

madduck's git repository

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

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

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

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

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

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