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

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