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

e17f43b88d439b7988003dea63b9cd07112d0046
[etc/vim.git] / tests / test_black.py
1 #!/usr/bin/env python3
2 import multiprocessing
3 import asyncio
4 import logging
5 from concurrent.futures import ThreadPoolExecutor
6 from contextlib import contextmanager
7 from dataclasses import replace
8 from functools import partial
9 import inspect
10 from io import BytesIO, TextIOWrapper
11 import os
12 from pathlib import Path
13 from platform import system
14 import regex as re
15 import sys
16 from tempfile import TemporaryDirectory
17 import types
18 from typing import (
19     Any,
20     BinaryIO,
21     Callable,
22     Dict,
23     Generator,
24     List,
25     Tuple,
26     Iterator,
27     TypeVar,
28 )
29 import unittest
30 from unittest.mock import patch, MagicMock
31
32 import click
33 from click import unstyle
34 from click.testing import CliRunner
35
36 import black
37 from black import Feature, TargetVersion
38
39 try:
40     import blackd
41     from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
42     from aiohttp import web
43 except ImportError:
44     has_blackd_deps = False
45 else:
46     has_blackd_deps = True
47
48 from pathspec import PathSpec
49
50 # Import other test classes
51 from .test_primer import PrimerCLITests  # noqa: F401
52
53
54 DEFAULT_MODE = black.FileMode(experimental_string_processing=True)
55 ff = partial(black.format_file_in_place, mode=DEFAULT_MODE, fast=True)
56 fs = partial(black.format_str, mode=DEFAULT_MODE)
57 THIS_FILE = Path(__file__)
58 THIS_DIR = THIS_FILE.parent
59 PROJECT_ROOT = THIS_DIR.parent
60 DETERMINISTIC_HEADER = "[Deterministic header]"
61 EMPTY_LINE = "# EMPTY LINE WITH WHITESPACE" + " (this comment will be removed)"
62 PY36_VERSIONS = {
63     TargetVersion.PY36,
64     TargetVersion.PY37,
65     TargetVersion.PY38,
66     TargetVersion.PY39,
67 }
68 PY36_ARGS = [f"--target-version={version.name.lower()}" for version in PY36_VERSIONS]
69 T = TypeVar("T")
70 R = TypeVar("R")
71
72
73 def dump_to_stderr(*output: str) -> str:
74     return "\n" + "\n".join(output) + "\n"
75
76
77 def read_data(name: str, data: bool = True) -> Tuple[str, str]:
78     """read_data('test_name') -> 'input', 'output'"""
79     if not name.endswith((".py", ".pyi", ".out", ".diff")):
80         name += ".py"
81     _input: List[str] = []
82     _output: List[str] = []
83     base_dir = THIS_DIR / "data" if data else PROJECT_ROOT
84     with open(base_dir / name, "r", encoding="utf8") as test:
85         lines = test.readlines()
86     result = _input
87     for line in lines:
88         line = line.replace(EMPTY_LINE, "")
89         if line.rstrip() == "# output":
90             result = _output
91             continue
92
93         result.append(line)
94     if _input and not _output:
95         # If there's no output marker, treat the entire file as already pre-formatted.
96         _output = _input[:]
97     return "".join(_input).strip() + "\n", "".join(_output).strip() + "\n"
98
99
100 @contextmanager
101 def cache_dir(exists: bool = True) -> Iterator[Path]:
102     with TemporaryDirectory() as workspace:
103         cache_dir = Path(workspace)
104         if not exists:
105             cache_dir = cache_dir / "new"
106         with patch("black.CACHE_DIR", cache_dir):
107             yield cache_dir
108
109
110 @contextmanager
111 def event_loop() -> Iterator[None]:
112     policy = asyncio.get_event_loop_policy()
113     loop = policy.new_event_loop()
114     asyncio.set_event_loop(loop)
115     try:
116         yield
117
118     finally:
119         loop.close()
120
121
122 @contextmanager
123 def skip_if_exception(e: str) -> Iterator[None]:
124     try:
125         yield
126     except Exception as exc:
127         if exc.__class__.__name__ == e:
128             unittest.skip(f"Encountered expected exception {exc}, skipping")
129         else:
130             raise
131
132
133 class FakeContext(click.Context):
134     """A fake click Context for when calling functions that need it."""
135
136     def __init__(self) -> None:
137         self.default_map: Dict[str, Any] = {}
138
139
140 class FakeParameter(click.Parameter):
141     """A fake click Parameter for when calling functions that need it."""
142
143     def __init__(self) -> None:
144         pass
145
146
147 class BlackRunner(CliRunner):
148     """Modify CliRunner so that stderr is not merged with stdout.
149
150     This is a hack that can be removed once we depend on Click 7.x"""
151
152     def __init__(self) -> None:
153         self.stderrbuf = BytesIO()
154         self.stdoutbuf = BytesIO()
155         self.stdout_bytes = b""
156         self.stderr_bytes = b""
157         super().__init__()
158
159     @contextmanager
160     def isolation(self, *args: Any, **kwargs: Any) -> Generator[BinaryIO, None, None]:
161         with super().isolation(*args, **kwargs) as output:
162             try:
163                 hold_stderr = sys.stderr
164                 sys.stderr = TextIOWrapper(self.stderrbuf, encoding=self.charset)
165                 yield output
166             finally:
167                 self.stdout_bytes = sys.stdout.buffer.getvalue()  # type: ignore
168                 self.stderr_bytes = sys.stderr.buffer.getvalue()  # type: ignore
169                 sys.stderr = hold_stderr
170
171
172 class BlackTestCase(unittest.TestCase):
173     maxDiff = None
174     _diffThreshold = 2 ** 20
175
176     def assertFormatEqual(self, expected: str, actual: str) -> None:
177         if actual != expected and not os.environ.get("SKIP_AST_PRINT"):
178             bdv: black.DebugVisitor[Any]
179             black.out("Expected tree:", fg="green")
180             try:
181                 exp_node = black.lib2to3_parse(expected)
182                 bdv = black.DebugVisitor()
183                 list(bdv.visit(exp_node))
184             except Exception as ve:
185                 black.err(str(ve))
186             black.out("Actual tree:", fg="red")
187             try:
188                 exp_node = black.lib2to3_parse(actual)
189                 bdv = black.DebugVisitor()
190                 list(bdv.visit(exp_node))
191             except Exception as ve:
192                 black.err(str(ve))
193         self.assertMultiLineEqual(expected, actual)
194
195     def invokeBlack(
196         self, args: List[str], exit_code: int = 0, ignore_config: bool = True
197     ) -> None:
198         runner = BlackRunner()
199         if ignore_config:
200             args = ["--verbose", "--config", str(THIS_DIR / "empty.toml"), *args]
201         result = runner.invoke(black.main, args)
202         self.assertEqual(
203             result.exit_code,
204             exit_code,
205             msg=(
206                 f"Failed with args: {args}\n"
207                 f"stdout: {runner.stdout_bytes.decode()!r}\n"
208                 f"stderr: {runner.stderr_bytes.decode()!r}\n"
209                 f"exception: {result.exception}"
210             ),
211         )
212
213     @patch("black.dump_to_file", dump_to_stderr)
214     def checkSourceFile(self, name: str, mode: black.FileMode = DEFAULT_MODE) -> None:
215         path = THIS_DIR.parent / name
216         source, expected = read_data(str(path), data=False)
217         actual = fs(source, mode=mode)
218         self.assertFormatEqual(expected, actual)
219         black.assert_equivalent(source, actual)
220         black.assert_stable(source, actual, mode)
221         self.assertFalse(ff(path))
222
223     @patch("black.dump_to_file", dump_to_stderr)
224     def test_empty(self) -> None:
225         source = expected = ""
226         actual = fs(source)
227         self.assertFormatEqual(expected, actual)
228         black.assert_equivalent(source, actual)
229         black.assert_stable(source, actual, DEFAULT_MODE)
230
231     def test_empty_ff(self) -> None:
232         expected = ""
233         tmp_file = Path(black.dump_to_file())
234         try:
235             self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES))
236             with open(tmp_file, encoding="utf8") as f:
237                 actual = f.read()
238         finally:
239             os.unlink(tmp_file)
240         self.assertFormatEqual(expected, actual)
241
242     def test_self(self) -> None:
243         self.checkSourceFile("tests/test_black.py")
244
245     def test_black(self) -> None:
246         self.checkSourceFile("src/black/__init__.py")
247
248     def test_pygram(self) -> None:
249         self.checkSourceFile("src/blib2to3/pygram.py")
250
251     def test_pytree(self) -> None:
252         self.checkSourceFile("src/blib2to3/pytree.py")
253
254     def test_conv(self) -> None:
255         self.checkSourceFile("src/blib2to3/pgen2/conv.py")
256
257     def test_driver(self) -> None:
258         self.checkSourceFile("src/blib2to3/pgen2/driver.py")
259
260     def test_grammar(self) -> None:
261         self.checkSourceFile("src/blib2to3/pgen2/grammar.py")
262
263     def test_literals(self) -> None:
264         self.checkSourceFile("src/blib2to3/pgen2/literals.py")
265
266     def test_parse(self) -> None:
267         self.checkSourceFile("src/blib2to3/pgen2/parse.py")
268
269     def test_pgen(self) -> None:
270         self.checkSourceFile("src/blib2to3/pgen2/pgen.py")
271
272     def test_tokenize(self) -> None:
273         self.checkSourceFile("src/blib2to3/pgen2/tokenize.py")
274
275     def test_token(self) -> None:
276         self.checkSourceFile("src/blib2to3/pgen2/token.py")
277
278     def test_setup(self) -> None:
279         self.checkSourceFile("setup.py")
280
281     def test_piping(self) -> None:
282         source, expected = read_data("src/black/__init__", data=False)
283         result = BlackRunner().invoke(
284             black.main,
285             ["-", "--fast", f"--line-length={black.DEFAULT_LINE_LENGTH}"],
286             input=BytesIO(source.encode("utf8")),
287         )
288         self.assertEqual(result.exit_code, 0)
289         self.assertFormatEqual(expected, result.output)
290         black.assert_equivalent(source, result.output)
291         black.assert_stable(source, result.output, DEFAULT_MODE)
292
293     def test_piping_diff(self) -> None:
294         diff_header = re.compile(
295             r"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d\d\d\d "
296             r"\+\d\d\d\d"
297         )
298         source, _ = read_data("expression.py")
299         expected, _ = read_data("expression.diff")
300         config = THIS_DIR / "data" / "empty_pyproject.toml"
301         args = [
302             "-",
303             "--fast",
304             f"--line-length={black.DEFAULT_LINE_LENGTH}",
305             "--diff",
306             f"--config={config}",
307         ]
308         result = BlackRunner().invoke(
309             black.main, args, input=BytesIO(source.encode("utf8"))
310         )
311         self.assertEqual(result.exit_code, 0)
312         actual = diff_header.sub(DETERMINISTIC_HEADER, result.output)
313         actual = actual.rstrip() + "\n"  # the diff output has a trailing space
314         self.assertEqual(expected, actual)
315
316     def test_piping_diff_with_color(self) -> None:
317         source, _ = read_data("expression.py")
318         config = THIS_DIR / "data" / "empty_pyproject.toml"
319         args = [
320             "-",
321             "--fast",
322             f"--line-length={black.DEFAULT_LINE_LENGTH}",
323             "--diff",
324             "--color",
325             f"--config={config}",
326         ]
327         result = BlackRunner().invoke(
328             black.main, args, input=BytesIO(source.encode("utf8"))
329         )
330         actual = result.output
331         # Again, the contents are checked in a different test, so only look for colors.
332         self.assertIn("\033[1;37m", actual)
333         self.assertIn("\033[36m", actual)
334         self.assertIn("\033[32m", actual)
335         self.assertIn("\033[31m", actual)
336         self.assertIn("\033[0m", actual)
337
338     @patch("black.dump_to_file", dump_to_stderr)
339     def test_function(self) -> None:
340         source, expected = read_data("function")
341         actual = fs(source)
342         self.assertFormatEqual(expected, actual)
343         black.assert_equivalent(source, actual)
344         black.assert_stable(source, actual, DEFAULT_MODE)
345
346     @patch("black.dump_to_file", dump_to_stderr)
347     def test_function2(self) -> None:
348         source, expected = read_data("function2")
349         actual = fs(source)
350         self.assertFormatEqual(expected, actual)
351         black.assert_equivalent(source, actual)
352         black.assert_stable(source, actual, DEFAULT_MODE)
353
354     @patch("black.dump_to_file", dump_to_stderr)
355     def _test_wip(self) -> None:
356         source, expected = read_data("wip")
357         sys.settrace(tracefunc)
358         mode = replace(
359             DEFAULT_MODE,
360             experimental_string_processing=False,
361             target_versions={black.TargetVersion.PY38},
362         )
363         actual = fs(source, mode=mode)
364         sys.settrace(None)
365         self.assertFormatEqual(expected, actual)
366         black.assert_equivalent(source, actual)
367         black.assert_stable(source, actual, black.FileMode())
368
369     @patch("black.dump_to_file", dump_to_stderr)
370     def test_function_trailing_comma(self) -> None:
371         source, expected = read_data("function_trailing_comma")
372         actual = fs(source)
373         self.assertFormatEqual(expected, actual)
374         black.assert_equivalent(source, actual)
375         black.assert_stable(source, actual, DEFAULT_MODE)
376
377     @unittest.expectedFailure
378     @patch("black.dump_to_file", dump_to_stderr)
379     def test_trailing_comma_optional_parens_stability1(self) -> None:
380         source, _expected = read_data("trailing_comma_optional_parens1")
381         actual = fs(source)
382         black.assert_stable(source, actual, DEFAULT_MODE)
383
384     @unittest.expectedFailure
385     @patch("black.dump_to_file", dump_to_stderr)
386     def test_trailing_comma_optional_parens_stability2(self) -> None:
387         source, _expected = read_data("trailing_comma_optional_parens2")
388         actual = fs(source)
389         black.assert_stable(source, actual, DEFAULT_MODE)
390
391     @unittest.expectedFailure
392     @patch("black.dump_to_file", dump_to_stderr)
393     def test_trailing_comma_optional_parens_stability3(self) -> None:
394         source, _expected = read_data("trailing_comma_optional_parens3")
395         actual = fs(source)
396         black.assert_stable(source, actual, DEFAULT_MODE)
397
398     @patch("black.dump_to_file", dump_to_stderr)
399     def test_expression(self) -> None:
400         source, expected = read_data("expression")
401         actual = fs(source)
402         self.assertFormatEqual(expected, actual)
403         black.assert_equivalent(source, actual)
404         black.assert_stable(source, actual, DEFAULT_MODE)
405
406     @patch("black.dump_to_file", dump_to_stderr)
407     def test_pep_572(self) -> None:
408         source, expected = read_data("pep_572")
409         actual = fs(source)
410         self.assertFormatEqual(expected, actual)
411         black.assert_stable(source, actual, DEFAULT_MODE)
412         if sys.version_info >= (3, 8):
413             black.assert_equivalent(source, actual)
414
415     def test_pep_572_version_detection(self) -> None:
416         source, _ = read_data("pep_572")
417         root = black.lib2to3_parse(source)
418         features = black.get_features_used(root)
419         self.assertIn(black.Feature.ASSIGNMENT_EXPRESSIONS, features)
420         versions = black.detect_target_versions(root)
421         self.assertIn(black.TargetVersion.PY38, versions)
422
423     def test_expression_ff(self) -> None:
424         source, expected = read_data("expression")
425         tmp_file = Path(black.dump_to_file(source))
426         try:
427             self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
428             with open(tmp_file, encoding="utf8") as f:
429                 actual = f.read()
430         finally:
431             os.unlink(tmp_file)
432         self.assertFormatEqual(expected, actual)
433         with patch("black.dump_to_file", dump_to_stderr):
434             black.assert_equivalent(source, actual)
435             black.assert_stable(source, actual, DEFAULT_MODE)
436
437     def test_expression_diff(self) -> None:
438         source, _ = read_data("expression.py")
439         expected, _ = read_data("expression.diff")
440         tmp_file = Path(black.dump_to_file(source))
441         diff_header = re.compile(
442             rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
443             r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
444         )
445         try:
446             result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
447             self.assertEqual(result.exit_code, 0)
448         finally:
449             os.unlink(tmp_file)
450         actual = result.output
451         actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
452         actual = actual.rstrip() + "\n"  # the diff output has a trailing space
453         if expected != actual:
454             dump = black.dump_to_file(actual)
455             msg = (
456                 "Expected diff isn't equal to the actual. If you made changes to"
457                 " expression.py and this is an anticipated difference, overwrite"
458                 f" tests/data/expression.diff with {dump}"
459             )
460             self.assertEqual(expected, actual, msg)
461
462     def test_expression_diff_with_color(self) -> None:
463         source, _ = read_data("expression.py")
464         expected, _ = read_data("expression.diff")
465         tmp_file = Path(black.dump_to_file(source))
466         try:
467             result = BlackRunner().invoke(
468                 black.main, ["--diff", "--color", str(tmp_file)]
469             )
470         finally:
471             os.unlink(tmp_file)
472         actual = result.output
473         # We check the contents of the diff in `test_expression_diff`. All
474         # we need to check here is that color codes exist in the result.
475         self.assertIn("\033[1;37m", actual)
476         self.assertIn("\033[36m", actual)
477         self.assertIn("\033[32m", actual)
478         self.assertIn("\033[31m", actual)
479         self.assertIn("\033[0m", actual)
480
481     @patch("black.dump_to_file", dump_to_stderr)
482     def test_fstring(self) -> None:
483         source, expected = read_data("fstring")
484         actual = fs(source)
485         self.assertFormatEqual(expected, actual)
486         black.assert_equivalent(source, actual)
487         black.assert_stable(source, actual, DEFAULT_MODE)
488
489     @patch("black.dump_to_file", dump_to_stderr)
490     def test_pep_570(self) -> None:
491         source, expected = read_data("pep_570")
492         actual = fs(source)
493         self.assertFormatEqual(expected, actual)
494         black.assert_stable(source, actual, DEFAULT_MODE)
495         if sys.version_info >= (3, 8):
496             black.assert_equivalent(source, actual)
497
498     def test_detect_pos_only_arguments(self) -> None:
499         source, _ = read_data("pep_570")
500         root = black.lib2to3_parse(source)
501         features = black.get_features_used(root)
502         self.assertIn(black.Feature.POS_ONLY_ARGUMENTS, features)
503         versions = black.detect_target_versions(root)
504         self.assertIn(black.TargetVersion.PY38, versions)
505
506     @patch("black.dump_to_file", dump_to_stderr)
507     def test_string_quotes(self) -> None:
508         source, expected = read_data("string_quotes")
509         actual = fs(source)
510         self.assertFormatEqual(expected, actual)
511         black.assert_equivalent(source, actual)
512         black.assert_stable(source, actual, DEFAULT_MODE)
513         mode = replace(DEFAULT_MODE, string_normalization=False)
514         not_normalized = fs(source, mode=mode)
515         self.assertFormatEqual(source.replace("\\\n", ""), not_normalized)
516         black.assert_equivalent(source, not_normalized)
517         black.assert_stable(source, not_normalized, mode=mode)
518
519     @patch("black.dump_to_file", dump_to_stderr)
520     def test_docstring(self) -> None:
521         source, expected = read_data("docstring")
522         actual = fs(source)
523         self.assertFormatEqual(expected, actual)
524         black.assert_equivalent(source, actual)
525         black.assert_stable(source, actual, DEFAULT_MODE)
526
527     @patch("black.dump_to_file", dump_to_stderr)
528     def test_docstring_no_string_normalization(self) -> None:
529         """Like test_docstring but with string normalization off."""
530         source, expected = read_data("docstring_no_string_normalization")
531         mode = replace(DEFAULT_MODE, string_normalization=False)
532         actual = fs(source, mode=mode)
533         self.assertFormatEqual(expected, actual)
534         black.assert_equivalent(source, actual)
535         black.assert_stable(source, actual, mode)
536
537     def test_long_strings(self) -> None:
538         """Tests for splitting long strings."""
539         source, expected = read_data("long_strings")
540         actual = fs(source)
541         self.assertFormatEqual(expected, actual)
542         black.assert_equivalent(source, actual)
543         black.assert_stable(source, actual, DEFAULT_MODE)
544
545     def test_long_strings_flag_disabled(self) -> None:
546         """Tests for turning off the string processing logic."""
547         source, expected = read_data("long_strings_flag_disabled")
548         mode = replace(DEFAULT_MODE, experimental_string_processing=False)
549         actual = fs(source, mode=mode)
550         self.assertFormatEqual(expected, actual)
551         black.assert_stable(expected, actual, mode)
552
553     @patch("black.dump_to_file", dump_to_stderr)
554     def test_long_strings__edge_case(self) -> None:
555         """Edge-case tests for splitting long strings."""
556         source, expected = read_data("long_strings__edge_case")
557         actual = fs(source)
558         self.assertFormatEqual(expected, actual)
559         black.assert_equivalent(source, actual)
560         black.assert_stable(source, actual, DEFAULT_MODE)
561
562     @patch("black.dump_to_file", dump_to_stderr)
563     def test_long_strings__regression(self) -> None:
564         """Regression tests for splitting long strings."""
565         source, expected = read_data("long_strings__regression")
566         actual = fs(source)
567         self.assertFormatEqual(expected, actual)
568         black.assert_equivalent(source, actual)
569         black.assert_stable(source, actual, DEFAULT_MODE)
570
571     @patch("black.dump_to_file", dump_to_stderr)
572     def test_slices(self) -> None:
573         source, expected = read_data("slices")
574         actual = fs(source)
575         self.assertFormatEqual(expected, actual)
576         black.assert_equivalent(source, actual)
577         black.assert_stable(source, actual, DEFAULT_MODE)
578
579     @patch("black.dump_to_file", dump_to_stderr)
580     def test_percent_precedence(self) -> None:
581         source, expected = read_data("percent_precedence")
582         actual = fs(source)
583         self.assertFormatEqual(expected, actual)
584         black.assert_equivalent(source, actual)
585         black.assert_stable(source, actual, DEFAULT_MODE)
586
587     @patch("black.dump_to_file", dump_to_stderr)
588     def test_comments(self) -> None:
589         source, expected = read_data("comments")
590         actual = fs(source)
591         self.assertFormatEqual(expected, actual)
592         black.assert_equivalent(source, actual)
593         black.assert_stable(source, actual, DEFAULT_MODE)
594
595     @patch("black.dump_to_file", dump_to_stderr)
596     def test_comments2(self) -> None:
597         source, expected = read_data("comments2")
598         actual = fs(source)
599         self.assertFormatEqual(expected, actual)
600         black.assert_equivalent(source, actual)
601         black.assert_stable(source, actual, DEFAULT_MODE)
602
603     @patch("black.dump_to_file", dump_to_stderr)
604     def test_comments3(self) -> None:
605         source, expected = read_data("comments3")
606         actual = fs(source)
607         self.assertFormatEqual(expected, actual)
608         black.assert_equivalent(source, actual)
609         black.assert_stable(source, actual, DEFAULT_MODE)
610
611     @patch("black.dump_to_file", dump_to_stderr)
612     def test_comments4(self) -> None:
613         source, expected = read_data("comments4")
614         actual = fs(source)
615         self.assertFormatEqual(expected, actual)
616         black.assert_equivalent(source, actual)
617         black.assert_stable(source, actual, DEFAULT_MODE)
618
619     @patch("black.dump_to_file", dump_to_stderr)
620     def test_comments5(self) -> None:
621         source, expected = read_data("comments5")
622         actual = fs(source)
623         self.assertFormatEqual(expected, actual)
624         black.assert_equivalent(source, actual)
625         black.assert_stable(source, actual, DEFAULT_MODE)
626
627     @patch("black.dump_to_file", dump_to_stderr)
628     def test_comments6(self) -> None:
629         source, expected = read_data("comments6")
630         actual = fs(source)
631         self.assertFormatEqual(expected, actual)
632         black.assert_equivalent(source, actual)
633         black.assert_stable(source, actual, DEFAULT_MODE)
634
635     @patch("black.dump_to_file", dump_to_stderr)
636     def test_comments7(self) -> None:
637         source, expected = read_data("comments7")
638         mode = replace(DEFAULT_MODE, target_versions={black.TargetVersion.PY38})
639         actual = fs(source, mode=mode)
640         self.assertFormatEqual(expected, actual)
641         black.assert_equivalent(source, actual)
642         black.assert_stable(source, actual, DEFAULT_MODE)
643
644     @patch("black.dump_to_file", dump_to_stderr)
645     def test_comment_after_escaped_newline(self) -> None:
646         source, expected = read_data("comment_after_escaped_newline")
647         actual = fs(source)
648         self.assertFormatEqual(expected, actual)
649         black.assert_equivalent(source, actual)
650         black.assert_stable(source, actual, DEFAULT_MODE)
651
652     @patch("black.dump_to_file", dump_to_stderr)
653     def test_cantfit(self) -> None:
654         source, expected = read_data("cantfit")
655         actual = fs(source)
656         self.assertFormatEqual(expected, actual)
657         black.assert_equivalent(source, actual)
658         black.assert_stable(source, actual, DEFAULT_MODE)
659
660     @patch("black.dump_to_file", dump_to_stderr)
661     def test_import_spacing(self) -> None:
662         source, expected = read_data("import_spacing")
663         actual = fs(source)
664         self.assertFormatEqual(expected, actual)
665         black.assert_equivalent(source, actual)
666         black.assert_stable(source, actual, DEFAULT_MODE)
667
668     @patch("black.dump_to_file", dump_to_stderr)
669     def test_composition(self) -> None:
670         source, expected = read_data("composition")
671         actual = fs(source)
672         self.assertFormatEqual(expected, actual)
673         black.assert_equivalent(source, actual)
674         black.assert_stable(source, actual, DEFAULT_MODE)
675
676     @patch("black.dump_to_file", dump_to_stderr)
677     def test_composition_no_trailing_comma(self) -> None:
678         source, expected = read_data("composition_no_trailing_comma")
679         mode = replace(DEFAULT_MODE, target_versions={black.TargetVersion.PY38})
680         actual = fs(source, mode=mode)
681         self.assertFormatEqual(expected, actual)
682         black.assert_equivalent(source, actual)
683         black.assert_stable(source, actual, DEFAULT_MODE)
684
685     @patch("black.dump_to_file", dump_to_stderr)
686     def test_empty_lines(self) -> None:
687         source, expected = read_data("empty_lines")
688         actual = fs(source)
689         self.assertFormatEqual(expected, actual)
690         black.assert_equivalent(source, actual)
691         black.assert_stable(source, actual, DEFAULT_MODE)
692
693     @patch("black.dump_to_file", dump_to_stderr)
694     def test_remove_parens(self) -> None:
695         source, expected = read_data("remove_parens")
696         actual = fs(source)
697         self.assertFormatEqual(expected, actual)
698         black.assert_equivalent(source, actual)
699         black.assert_stable(source, actual, DEFAULT_MODE)
700
701     @patch("black.dump_to_file", dump_to_stderr)
702     def test_string_prefixes(self) -> None:
703         source, expected = read_data("string_prefixes")
704         actual = fs(source)
705         self.assertFormatEqual(expected, actual)
706         black.assert_equivalent(source, actual)
707         black.assert_stable(source, actual, DEFAULT_MODE)
708
709     @patch("black.dump_to_file", dump_to_stderr)
710     def test_numeric_literals(self) -> None:
711         source, expected = read_data("numeric_literals")
712         mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
713         actual = fs(source, mode=mode)
714         self.assertFormatEqual(expected, actual)
715         black.assert_equivalent(source, actual)
716         black.assert_stable(source, actual, mode)
717
718     @patch("black.dump_to_file", dump_to_stderr)
719     def test_numeric_literals_ignoring_underscores(self) -> None:
720         source, expected = read_data("numeric_literals_skip_underscores")
721         mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
722         actual = fs(source, mode=mode)
723         self.assertFormatEqual(expected, actual)
724         black.assert_equivalent(source, actual)
725         black.assert_stable(source, actual, mode)
726
727     @patch("black.dump_to_file", dump_to_stderr)
728     def test_numeric_literals_py2(self) -> None:
729         source, expected = read_data("numeric_literals_py2")
730         actual = fs(source)
731         self.assertFormatEqual(expected, actual)
732         black.assert_stable(source, actual, DEFAULT_MODE)
733
734     @patch("black.dump_to_file", dump_to_stderr)
735     def test_python2(self) -> None:
736         source, expected = read_data("python2")
737         actual = fs(source)
738         self.assertFormatEqual(expected, actual)
739         black.assert_equivalent(source, actual)
740         black.assert_stable(source, actual, DEFAULT_MODE)
741
742     @patch("black.dump_to_file", dump_to_stderr)
743     def test_python2_print_function(self) -> None:
744         source, expected = read_data("python2_print_function")
745         mode = replace(DEFAULT_MODE, target_versions={TargetVersion.PY27})
746         actual = fs(source, mode=mode)
747         self.assertFormatEqual(expected, actual)
748         black.assert_equivalent(source, actual)
749         black.assert_stable(source, actual, mode)
750
751     @patch("black.dump_to_file", dump_to_stderr)
752     def test_python2_unicode_literals(self) -> None:
753         source, expected = read_data("python2_unicode_literals")
754         actual = fs(source)
755         self.assertFormatEqual(expected, actual)
756         black.assert_equivalent(source, actual)
757         black.assert_stable(source, actual, DEFAULT_MODE)
758
759     @patch("black.dump_to_file", dump_to_stderr)
760     def test_stub(self) -> None:
761         mode = replace(DEFAULT_MODE, is_pyi=True)
762         source, expected = read_data("stub.pyi")
763         actual = fs(source, mode=mode)
764         self.assertFormatEqual(expected, actual)
765         black.assert_stable(source, actual, mode)
766
767     @patch("black.dump_to_file", dump_to_stderr)
768     def test_async_as_identifier(self) -> None:
769         source_path = (THIS_DIR / "data" / "async_as_identifier.py").resolve()
770         source, expected = read_data("async_as_identifier")
771         actual = fs(source)
772         self.assertFormatEqual(expected, actual)
773         major, minor = sys.version_info[:2]
774         if major < 3 or (major <= 3 and minor < 7):
775             black.assert_equivalent(source, actual)
776         black.assert_stable(source, actual, DEFAULT_MODE)
777         # ensure black can parse this when the target is 3.6
778         self.invokeBlack([str(source_path), "--target-version", "py36"])
779         # but not on 3.7, because async/await is no longer an identifier
780         self.invokeBlack([str(source_path), "--target-version", "py37"], exit_code=123)
781
782     @patch("black.dump_to_file", dump_to_stderr)
783     def test_python37(self) -> None:
784         source_path = (THIS_DIR / "data" / "python37.py").resolve()
785         source, expected = read_data("python37")
786         actual = fs(source)
787         self.assertFormatEqual(expected, actual)
788         major, minor = sys.version_info[:2]
789         if major > 3 or (major == 3 and minor >= 7):
790             black.assert_equivalent(source, actual)
791         black.assert_stable(source, actual, DEFAULT_MODE)
792         # ensure black can parse this when the target is 3.7
793         self.invokeBlack([str(source_path), "--target-version", "py37"])
794         # but not on 3.6, because we use async as a reserved keyword
795         self.invokeBlack([str(source_path), "--target-version", "py36"], exit_code=123)
796
797     @patch("black.dump_to_file", dump_to_stderr)
798     def test_python38(self) -> None:
799         source, expected = read_data("python38")
800         actual = fs(source)
801         self.assertFormatEqual(expected, actual)
802         major, minor = sys.version_info[:2]
803         if major > 3 or (major == 3 and minor >= 8):
804             black.assert_equivalent(source, actual)
805         black.assert_stable(source, actual, DEFAULT_MODE)
806
807     @patch("black.dump_to_file", dump_to_stderr)
808     def test_python39(self) -> None:
809         source, expected = read_data("python39")
810         actual = fs(source)
811         self.assertFormatEqual(expected, actual)
812         major, minor = sys.version_info[:2]
813         if major > 3 or (major == 3 and minor >= 9):
814             black.assert_equivalent(source, actual)
815         black.assert_stable(source, actual, DEFAULT_MODE)
816
817     @patch("black.dump_to_file", dump_to_stderr)
818     def test_fmtonoff(self) -> None:
819         source, expected = read_data("fmtonoff")
820         actual = fs(source)
821         self.assertFormatEqual(expected, actual)
822         black.assert_equivalent(source, actual)
823         black.assert_stable(source, actual, DEFAULT_MODE)
824
825     @patch("black.dump_to_file", dump_to_stderr)
826     def test_fmtonoff2(self) -> None:
827         source, expected = read_data("fmtonoff2")
828         actual = fs(source)
829         self.assertFormatEqual(expected, actual)
830         black.assert_equivalent(source, actual)
831         black.assert_stable(source, actual, DEFAULT_MODE)
832
833     @patch("black.dump_to_file", dump_to_stderr)
834     def test_fmtonoff3(self) -> None:
835         source, expected = read_data("fmtonoff3")
836         actual = fs(source)
837         self.assertFormatEqual(expected, actual)
838         black.assert_equivalent(source, actual)
839         black.assert_stable(source, actual, DEFAULT_MODE)
840
841     @patch("black.dump_to_file", dump_to_stderr)
842     def test_fmtonoff4(self) -> None:
843         source, expected = read_data("fmtonoff4")
844         actual = fs(source)
845         self.assertFormatEqual(expected, actual)
846         black.assert_equivalent(source, actual)
847         black.assert_stable(source, actual, DEFAULT_MODE)
848
849     @patch("black.dump_to_file", dump_to_stderr)
850     def test_remove_empty_parentheses_after_class(self) -> None:
851         source, expected = read_data("class_blank_parentheses")
852         actual = fs(source)
853         self.assertFormatEqual(expected, actual)
854         black.assert_equivalent(source, actual)
855         black.assert_stable(source, actual, DEFAULT_MODE)
856
857     @patch("black.dump_to_file", dump_to_stderr)
858     def test_new_line_between_class_and_code(self) -> None:
859         source, expected = read_data("class_methods_new_line")
860         actual = fs(source)
861         self.assertFormatEqual(expected, actual)
862         black.assert_equivalent(source, actual)
863         black.assert_stable(source, actual, DEFAULT_MODE)
864
865     @patch("black.dump_to_file", dump_to_stderr)
866     def test_bracket_match(self) -> None:
867         source, expected = read_data("bracketmatch")
868         actual = fs(source)
869         self.assertFormatEqual(expected, actual)
870         black.assert_equivalent(source, actual)
871         black.assert_stable(source, actual, DEFAULT_MODE)
872
873     @patch("black.dump_to_file", dump_to_stderr)
874     def test_tuple_assign(self) -> None:
875         source, expected = read_data("tupleassign")
876         actual = fs(source)
877         self.assertFormatEqual(expected, actual)
878         black.assert_equivalent(source, actual)
879         black.assert_stable(source, actual, DEFAULT_MODE)
880
881     @patch("black.dump_to_file", dump_to_stderr)
882     def test_beginning_backslash(self) -> None:
883         source, expected = read_data("beginning_backslash")
884         actual = fs(source)
885         self.assertFormatEqual(expected, actual)
886         black.assert_equivalent(source, actual)
887         black.assert_stable(source, actual, DEFAULT_MODE)
888
889     def test_tab_comment_indentation(self) -> None:
890         contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
891         contents_spc = "if 1:\n    if 2:\n        pass\n    # comment\n    pass\n"
892         self.assertFormatEqual(contents_spc, fs(contents_spc))
893         self.assertFormatEqual(contents_spc, fs(contents_tab))
894
895         contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
896         contents_spc = "if 1:\n    if 2:\n        pass\n        # comment\n    pass\n"
897         self.assertFormatEqual(contents_spc, fs(contents_spc))
898         self.assertFormatEqual(contents_spc, fs(contents_tab))
899
900         # mixed tabs and spaces (valid Python 2 code)
901         contents_tab = "if 1:\n        if 2:\n\t\tpass\n\t# comment\n        pass\n"
902         contents_spc = "if 1:\n    if 2:\n        pass\n    # comment\n    pass\n"
903         self.assertFormatEqual(contents_spc, fs(contents_spc))
904         self.assertFormatEqual(contents_spc, fs(contents_tab))
905
906         contents_tab = "if 1:\n        if 2:\n\t\tpass\n\t\t# comment\n        pass\n"
907         contents_spc = "if 1:\n    if 2:\n        pass\n        # comment\n    pass\n"
908         self.assertFormatEqual(contents_spc, fs(contents_spc))
909         self.assertFormatEqual(contents_spc, fs(contents_tab))
910
911     def test_report_verbose(self) -> None:
912         report = black.Report(verbose=True)
913         out_lines = []
914         err_lines = []
915
916         def out(msg: str, **kwargs: Any) -> None:
917             out_lines.append(msg)
918
919         def err(msg: str, **kwargs: Any) -> None:
920             err_lines.append(msg)
921
922         with patch("black.out", out), patch("black.err", err):
923             report.done(Path("f1"), black.Changed.NO)
924             self.assertEqual(len(out_lines), 1)
925             self.assertEqual(len(err_lines), 0)
926             self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
927             self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
928             self.assertEqual(report.return_code, 0)
929             report.done(Path("f2"), black.Changed.YES)
930             self.assertEqual(len(out_lines), 2)
931             self.assertEqual(len(err_lines), 0)
932             self.assertEqual(out_lines[-1], "reformatted f2")
933             self.assertEqual(
934                 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
935             )
936             report.done(Path("f3"), black.Changed.CACHED)
937             self.assertEqual(len(out_lines), 3)
938             self.assertEqual(len(err_lines), 0)
939             self.assertEqual(
940                 out_lines[-1], "f3 wasn't modified on disk since last run."
941             )
942             self.assertEqual(
943                 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
944             )
945             self.assertEqual(report.return_code, 0)
946             report.check = True
947             self.assertEqual(report.return_code, 1)
948             report.check = False
949             report.failed(Path("e1"), "boom")
950             self.assertEqual(len(out_lines), 3)
951             self.assertEqual(len(err_lines), 1)
952             self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
953             self.assertEqual(
954                 unstyle(str(report)),
955                 "1 file reformatted, 2 files left unchanged, 1 file failed to"
956                 " reformat.",
957             )
958             self.assertEqual(report.return_code, 123)
959             report.done(Path("f3"), black.Changed.YES)
960             self.assertEqual(len(out_lines), 4)
961             self.assertEqual(len(err_lines), 1)
962             self.assertEqual(out_lines[-1], "reformatted f3")
963             self.assertEqual(
964                 unstyle(str(report)),
965                 "2 files reformatted, 2 files left unchanged, 1 file failed to"
966                 " reformat.",
967             )
968             self.assertEqual(report.return_code, 123)
969             report.failed(Path("e2"), "boom")
970             self.assertEqual(len(out_lines), 4)
971             self.assertEqual(len(err_lines), 2)
972             self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
973             self.assertEqual(
974                 unstyle(str(report)),
975                 "2 files reformatted, 2 files left unchanged, 2 files failed to"
976                 " reformat.",
977             )
978             self.assertEqual(report.return_code, 123)
979             report.path_ignored(Path("wat"), "no match")
980             self.assertEqual(len(out_lines), 5)
981             self.assertEqual(len(err_lines), 2)
982             self.assertEqual(out_lines[-1], "wat ignored: no match")
983             self.assertEqual(
984                 unstyle(str(report)),
985                 "2 files reformatted, 2 files left unchanged, 2 files failed to"
986                 " reformat.",
987             )
988             self.assertEqual(report.return_code, 123)
989             report.done(Path("f4"), black.Changed.NO)
990             self.assertEqual(len(out_lines), 6)
991             self.assertEqual(len(err_lines), 2)
992             self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
993             self.assertEqual(
994                 unstyle(str(report)),
995                 "2 files reformatted, 3 files left unchanged, 2 files failed to"
996                 " reformat.",
997             )
998             self.assertEqual(report.return_code, 123)
999             report.check = True
1000             self.assertEqual(
1001                 unstyle(str(report)),
1002                 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1003                 " would fail to reformat.",
1004             )
1005             report.check = False
1006             report.diff = True
1007             self.assertEqual(
1008                 unstyle(str(report)),
1009                 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1010                 " would fail to reformat.",
1011             )
1012
1013     def test_report_quiet(self) -> None:
1014         report = black.Report(quiet=True)
1015         out_lines = []
1016         err_lines = []
1017
1018         def out(msg: str, **kwargs: Any) -> None:
1019             out_lines.append(msg)
1020
1021         def err(msg: str, **kwargs: Any) -> None:
1022             err_lines.append(msg)
1023
1024         with patch("black.out", out), patch("black.err", err):
1025             report.done(Path("f1"), black.Changed.NO)
1026             self.assertEqual(len(out_lines), 0)
1027             self.assertEqual(len(err_lines), 0)
1028             self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
1029             self.assertEqual(report.return_code, 0)
1030             report.done(Path("f2"), black.Changed.YES)
1031             self.assertEqual(len(out_lines), 0)
1032             self.assertEqual(len(err_lines), 0)
1033             self.assertEqual(
1034                 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
1035             )
1036             report.done(Path("f3"), black.Changed.CACHED)
1037             self.assertEqual(len(out_lines), 0)
1038             self.assertEqual(len(err_lines), 0)
1039             self.assertEqual(
1040                 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
1041             )
1042             self.assertEqual(report.return_code, 0)
1043             report.check = True
1044             self.assertEqual(report.return_code, 1)
1045             report.check = False
1046             report.failed(Path("e1"), "boom")
1047             self.assertEqual(len(out_lines), 0)
1048             self.assertEqual(len(err_lines), 1)
1049             self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
1050             self.assertEqual(
1051                 unstyle(str(report)),
1052                 "1 file reformatted, 2 files left unchanged, 1 file failed to"
1053                 " reformat.",
1054             )
1055             self.assertEqual(report.return_code, 123)
1056             report.done(Path("f3"), black.Changed.YES)
1057             self.assertEqual(len(out_lines), 0)
1058             self.assertEqual(len(err_lines), 1)
1059             self.assertEqual(
1060                 unstyle(str(report)),
1061                 "2 files reformatted, 2 files left unchanged, 1 file failed to"
1062                 " reformat.",
1063             )
1064             self.assertEqual(report.return_code, 123)
1065             report.failed(Path("e2"), "boom")
1066             self.assertEqual(len(out_lines), 0)
1067             self.assertEqual(len(err_lines), 2)
1068             self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
1069             self.assertEqual(
1070                 unstyle(str(report)),
1071                 "2 files reformatted, 2 files left unchanged, 2 files failed to"
1072                 " reformat.",
1073             )
1074             self.assertEqual(report.return_code, 123)
1075             report.path_ignored(Path("wat"), "no match")
1076             self.assertEqual(len(out_lines), 0)
1077             self.assertEqual(len(err_lines), 2)
1078             self.assertEqual(
1079                 unstyle(str(report)),
1080                 "2 files reformatted, 2 files left unchanged, 2 files failed to"
1081                 " reformat.",
1082             )
1083             self.assertEqual(report.return_code, 123)
1084             report.done(Path("f4"), black.Changed.NO)
1085             self.assertEqual(len(out_lines), 0)
1086             self.assertEqual(len(err_lines), 2)
1087             self.assertEqual(
1088                 unstyle(str(report)),
1089                 "2 files reformatted, 3 files left unchanged, 2 files failed to"
1090                 " reformat.",
1091             )
1092             self.assertEqual(report.return_code, 123)
1093             report.check = True
1094             self.assertEqual(
1095                 unstyle(str(report)),
1096                 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1097                 " would fail to reformat.",
1098             )
1099             report.check = False
1100             report.diff = True
1101             self.assertEqual(
1102                 unstyle(str(report)),
1103                 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1104                 " would fail to reformat.",
1105             )
1106
1107     def test_report_normal(self) -> None:
1108         report = black.Report()
1109         out_lines = []
1110         err_lines = []
1111
1112         def out(msg: str, **kwargs: Any) -> None:
1113             out_lines.append(msg)
1114
1115         def err(msg: str, **kwargs: Any) -> None:
1116             err_lines.append(msg)
1117
1118         with patch("black.out", out), patch("black.err", err):
1119             report.done(Path("f1"), black.Changed.NO)
1120             self.assertEqual(len(out_lines), 0)
1121             self.assertEqual(len(err_lines), 0)
1122             self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
1123             self.assertEqual(report.return_code, 0)
1124             report.done(Path("f2"), black.Changed.YES)
1125             self.assertEqual(len(out_lines), 1)
1126             self.assertEqual(len(err_lines), 0)
1127             self.assertEqual(out_lines[-1], "reformatted f2")
1128             self.assertEqual(
1129                 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
1130             )
1131             report.done(Path("f3"), black.Changed.CACHED)
1132             self.assertEqual(len(out_lines), 1)
1133             self.assertEqual(len(err_lines), 0)
1134             self.assertEqual(out_lines[-1], "reformatted f2")
1135             self.assertEqual(
1136                 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
1137             )
1138             self.assertEqual(report.return_code, 0)
1139             report.check = True
1140             self.assertEqual(report.return_code, 1)
1141             report.check = False
1142             report.failed(Path("e1"), "boom")
1143             self.assertEqual(len(out_lines), 1)
1144             self.assertEqual(len(err_lines), 1)
1145             self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
1146             self.assertEqual(
1147                 unstyle(str(report)),
1148                 "1 file reformatted, 2 files left unchanged, 1 file failed to"
1149                 " reformat.",
1150             )
1151             self.assertEqual(report.return_code, 123)
1152             report.done(Path("f3"), black.Changed.YES)
1153             self.assertEqual(len(out_lines), 2)
1154             self.assertEqual(len(err_lines), 1)
1155             self.assertEqual(out_lines[-1], "reformatted f3")
1156             self.assertEqual(
1157                 unstyle(str(report)),
1158                 "2 files reformatted, 2 files left unchanged, 1 file failed to"
1159                 " reformat.",
1160             )
1161             self.assertEqual(report.return_code, 123)
1162             report.failed(Path("e2"), "boom")
1163             self.assertEqual(len(out_lines), 2)
1164             self.assertEqual(len(err_lines), 2)
1165             self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
1166             self.assertEqual(
1167                 unstyle(str(report)),
1168                 "2 files reformatted, 2 files left unchanged, 2 files failed to"
1169                 " reformat.",
1170             )
1171             self.assertEqual(report.return_code, 123)
1172             report.path_ignored(Path("wat"), "no match")
1173             self.assertEqual(len(out_lines), 2)
1174             self.assertEqual(len(err_lines), 2)
1175             self.assertEqual(
1176                 unstyle(str(report)),
1177                 "2 files reformatted, 2 files left unchanged, 2 files failed to"
1178                 " reformat.",
1179             )
1180             self.assertEqual(report.return_code, 123)
1181             report.done(Path("f4"), black.Changed.NO)
1182             self.assertEqual(len(out_lines), 2)
1183             self.assertEqual(len(err_lines), 2)
1184             self.assertEqual(
1185                 unstyle(str(report)),
1186                 "2 files reformatted, 3 files left unchanged, 2 files failed to"
1187                 " reformat.",
1188             )
1189             self.assertEqual(report.return_code, 123)
1190             report.check = True
1191             self.assertEqual(
1192                 unstyle(str(report)),
1193                 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1194                 " would fail to reformat.",
1195             )
1196             report.check = False
1197             report.diff = True
1198             self.assertEqual(
1199                 unstyle(str(report)),
1200                 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1201                 " would fail to reformat.",
1202             )
1203
1204     def test_lib2to3_parse(self) -> None:
1205         with self.assertRaises(black.InvalidInput):
1206             black.lib2to3_parse("invalid syntax")
1207
1208         straddling = "x + y"
1209         black.lib2to3_parse(straddling)
1210         black.lib2to3_parse(straddling, {TargetVersion.PY27})
1211         black.lib2to3_parse(straddling, {TargetVersion.PY36})
1212         black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
1213
1214         py2_only = "print x"
1215         black.lib2to3_parse(py2_only)
1216         black.lib2to3_parse(py2_only, {TargetVersion.PY27})
1217         with self.assertRaises(black.InvalidInput):
1218             black.lib2to3_parse(py2_only, {TargetVersion.PY36})
1219         with self.assertRaises(black.InvalidInput):
1220             black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
1221
1222         py3_only = "exec(x, end=y)"
1223         black.lib2to3_parse(py3_only)
1224         with self.assertRaises(black.InvalidInput):
1225             black.lib2to3_parse(py3_only, {TargetVersion.PY27})
1226         black.lib2to3_parse(py3_only, {TargetVersion.PY36})
1227         black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
1228
1229     def test_get_features_used_decorator(self) -> None:
1230         # Test the feature detection of new decorator syntax
1231         # since this makes some test cases of test_get_features_used()
1232         # fails if it fails, this is tested first so that a useful case
1233         # is identified
1234         simples, relaxed = read_data("decorators")
1235         # skip explanation comments at the top of the file
1236         for simple_test in simples.split("##")[1:]:
1237             node = black.lib2to3_parse(simple_test)
1238             decorator = str(node.children[0].children[0]).strip()
1239             self.assertNotIn(
1240                 Feature.RELAXED_DECORATORS,
1241                 black.get_features_used(node),
1242                 msg=(
1243                     f"decorator '{decorator}' follows python<=3.8 syntax"
1244                     "but is detected as 3.9+"
1245                     # f"The full node is\n{node!r}"
1246                 ),
1247             )
1248         # skip the '# output' comment at the top of the output part
1249         for relaxed_test in relaxed.split("##")[1:]:
1250             node = black.lib2to3_parse(relaxed_test)
1251             decorator = str(node.children[0].children[0]).strip()
1252             self.assertIn(
1253                 Feature.RELAXED_DECORATORS,
1254                 black.get_features_used(node),
1255                 msg=(
1256                     f"decorator '{decorator}' uses python3.9+ syntax"
1257                     "but is detected as python<=3.8"
1258                     # f"The full node is\n{node!r}"
1259                 ),
1260             )
1261
1262     def test_get_features_used(self) -> None:
1263         node = black.lib2to3_parse("def f(*, arg): ...\n")
1264         self.assertEqual(black.get_features_used(node), set())
1265         node = black.lib2to3_parse("def f(*, arg,): ...\n")
1266         self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
1267         node = black.lib2to3_parse("f(*arg,)\n")
1268         self.assertEqual(
1269             black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
1270         )
1271         node = black.lib2to3_parse("def f(*, arg): f'string'\n")
1272         self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
1273         node = black.lib2to3_parse("123_456\n")
1274         self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
1275         node = black.lib2to3_parse("123456\n")
1276         self.assertEqual(black.get_features_used(node), set())
1277         source, expected = read_data("function")
1278         node = black.lib2to3_parse(source)
1279         expected_features = {
1280             Feature.TRAILING_COMMA_IN_CALL,
1281             Feature.TRAILING_COMMA_IN_DEF,
1282             Feature.F_STRINGS,
1283         }
1284         self.assertEqual(black.get_features_used(node), expected_features)
1285         node = black.lib2to3_parse(expected)
1286         self.assertEqual(black.get_features_used(node), expected_features)
1287         source, expected = read_data("expression")
1288         node = black.lib2to3_parse(source)
1289         self.assertEqual(black.get_features_used(node), set())
1290         node = black.lib2to3_parse(expected)
1291         self.assertEqual(black.get_features_used(node), set())
1292
1293     def test_get_future_imports(self) -> None:
1294         node = black.lib2to3_parse("\n")
1295         self.assertEqual(set(), black.get_future_imports(node))
1296         node = black.lib2to3_parse("from __future__ import black\n")
1297         self.assertEqual({"black"}, black.get_future_imports(node))
1298         node = black.lib2to3_parse("from __future__ import multiple, imports\n")
1299         self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1300         node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
1301         self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
1302         node = black.lib2to3_parse(
1303             "from __future__ import multiple\nfrom __future__ import imports\n"
1304         )
1305         self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1306         node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
1307         self.assertEqual({"black"}, black.get_future_imports(node))
1308         node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
1309         self.assertEqual({"black"}, black.get_future_imports(node))
1310         node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
1311         self.assertEqual(set(), black.get_future_imports(node))
1312         node = black.lib2to3_parse("from some.module import black\n")
1313         self.assertEqual(set(), black.get_future_imports(node))
1314         node = black.lib2to3_parse(
1315             "from __future__ import unicode_literals as _unicode_literals"
1316         )
1317         self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
1318         node = black.lib2to3_parse(
1319             "from __future__ import unicode_literals as _lol, print"
1320         )
1321         self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
1322
1323     def test_debug_visitor(self) -> None:
1324         source, _ = read_data("debug_visitor.py")
1325         expected, _ = read_data("debug_visitor.out")
1326         out_lines = []
1327         err_lines = []
1328
1329         def out(msg: str, **kwargs: Any) -> None:
1330             out_lines.append(msg)
1331
1332         def err(msg: str, **kwargs: Any) -> None:
1333             err_lines.append(msg)
1334
1335         with patch("black.out", out), patch("black.err", err):
1336             black.DebugVisitor.show(source)
1337         actual = "\n".join(out_lines) + "\n"
1338         log_name = ""
1339         if expected != actual:
1340             log_name = black.dump_to_file(*out_lines)
1341         self.assertEqual(
1342             expected,
1343             actual,
1344             f"AST print out is different. Actual version dumped to {log_name}",
1345         )
1346
1347     def test_format_file_contents(self) -> None:
1348         empty = ""
1349         mode = DEFAULT_MODE
1350         with self.assertRaises(black.NothingChanged):
1351             black.format_file_contents(empty, mode=mode, fast=False)
1352         just_nl = "\n"
1353         with self.assertRaises(black.NothingChanged):
1354             black.format_file_contents(just_nl, mode=mode, fast=False)
1355         same = "j = [1, 2, 3]\n"
1356         with self.assertRaises(black.NothingChanged):
1357             black.format_file_contents(same, mode=mode, fast=False)
1358         different = "j = [1,2,3]"
1359         expected = same
1360         actual = black.format_file_contents(different, mode=mode, fast=False)
1361         self.assertEqual(expected, actual)
1362         invalid = "return if you can"
1363         with self.assertRaises(black.InvalidInput) as e:
1364             black.format_file_contents(invalid, mode=mode, fast=False)
1365         self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
1366
1367     def test_endmarker(self) -> None:
1368         n = black.lib2to3_parse("\n")
1369         self.assertEqual(n.type, black.syms.file_input)
1370         self.assertEqual(len(n.children), 1)
1371         self.assertEqual(n.children[0].type, black.token.ENDMARKER)
1372
1373     @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
1374     def test_assertFormatEqual(self) -> None:
1375         out_lines = []
1376         err_lines = []
1377
1378         def out(msg: str, **kwargs: Any) -> None:
1379             out_lines.append(msg)
1380
1381         def err(msg: str, **kwargs: Any) -> None:
1382             err_lines.append(msg)
1383
1384         with patch("black.out", out), patch("black.err", err):
1385             with self.assertRaises(AssertionError):
1386                 self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")
1387
1388         out_str = "".join(out_lines)
1389         self.assertTrue("Expected tree:" in out_str)
1390         self.assertTrue("Actual tree:" in out_str)
1391         self.assertEqual("".join(err_lines), "")
1392
1393     def test_cache_broken_file(self) -> None:
1394         mode = DEFAULT_MODE
1395         with cache_dir() as workspace:
1396             cache_file = black.get_cache_file(mode)
1397             with cache_file.open("w") as fobj:
1398                 fobj.write("this is not a pickle")
1399             self.assertEqual(black.read_cache(mode), {})
1400             src = (workspace / "test.py").resolve()
1401             with src.open("w") as fobj:
1402                 fobj.write("print('hello')")
1403             self.invokeBlack([str(src)])
1404             cache = black.read_cache(mode)
1405             self.assertIn(src, cache)
1406
1407     def test_cache_single_file_already_cached(self) -> None:
1408         mode = DEFAULT_MODE
1409         with cache_dir() as workspace:
1410             src = (workspace / "test.py").resolve()
1411             with src.open("w") as fobj:
1412                 fobj.write("print('hello')")
1413             black.write_cache({}, [src], mode)
1414             self.invokeBlack([str(src)])
1415             with src.open("r") as fobj:
1416                 self.assertEqual(fobj.read(), "print('hello')")
1417
1418     @event_loop()
1419     def test_cache_multiple_files(self) -> None:
1420         mode = DEFAULT_MODE
1421         with cache_dir() as workspace, patch(
1422             "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1423         ):
1424             one = (workspace / "one.py").resolve()
1425             with one.open("w") as fobj:
1426                 fobj.write("print('hello')")
1427             two = (workspace / "two.py").resolve()
1428             with two.open("w") as fobj:
1429                 fobj.write("print('hello')")
1430             black.write_cache({}, [one], mode)
1431             self.invokeBlack([str(workspace)])
1432             with one.open("r") as fobj:
1433                 self.assertEqual(fobj.read(), "print('hello')")
1434             with two.open("r") as fobj:
1435                 self.assertEqual(fobj.read(), 'print("hello")\n')
1436             cache = black.read_cache(mode)
1437             self.assertIn(one, cache)
1438             self.assertIn(two, cache)
1439
1440     def test_no_cache_when_writeback_diff(self) -> None:
1441         mode = DEFAULT_MODE
1442         with cache_dir() as workspace:
1443             src = (workspace / "test.py").resolve()
1444             with src.open("w") as fobj:
1445                 fobj.write("print('hello')")
1446             with patch("black.read_cache") as read_cache, patch(
1447                 "black.write_cache"
1448             ) as write_cache:
1449                 self.invokeBlack([str(src), "--diff"])
1450                 cache_file = black.get_cache_file(mode)
1451                 self.assertFalse(cache_file.exists())
1452                 write_cache.assert_not_called()
1453                 read_cache.assert_not_called()
1454
1455     def test_no_cache_when_writeback_color_diff(self) -> None:
1456         mode = DEFAULT_MODE
1457         with cache_dir() as workspace:
1458             src = (workspace / "test.py").resolve()
1459             with src.open("w") as fobj:
1460                 fobj.write("print('hello')")
1461             with patch("black.read_cache") as read_cache, patch(
1462                 "black.write_cache"
1463             ) as write_cache:
1464                 self.invokeBlack([str(src), "--diff", "--color"])
1465                 cache_file = black.get_cache_file(mode)
1466                 self.assertFalse(cache_file.exists())
1467                 write_cache.assert_not_called()
1468                 read_cache.assert_not_called()
1469
1470     @event_loop()
1471     def test_output_locking_when_writeback_diff(self) -> None:
1472         with cache_dir() as workspace:
1473             for tag in range(0, 4):
1474                 src = (workspace / f"test{tag}.py").resolve()
1475                 with src.open("w") as fobj:
1476                     fobj.write("print('hello')")
1477             with patch("black.Manager", wraps=multiprocessing.Manager) as mgr:
1478                 self.invokeBlack(["--diff", str(workspace)], exit_code=0)
1479                 # this isn't quite doing what we want, but if it _isn't_
1480                 # called then we cannot be using the lock it provides
1481                 mgr.assert_called()
1482
1483     @event_loop()
1484     def test_output_locking_when_writeback_color_diff(self) -> None:
1485         with cache_dir() as workspace:
1486             for tag in range(0, 4):
1487                 src = (workspace / f"test{tag}.py").resolve()
1488                 with src.open("w") as fobj:
1489                     fobj.write("print('hello')")
1490             with patch("black.Manager", wraps=multiprocessing.Manager) as mgr:
1491                 self.invokeBlack(["--diff", "--color", str(workspace)], exit_code=0)
1492                 # this isn't quite doing what we want, but if it _isn't_
1493                 # called then we cannot be using the lock it provides
1494                 mgr.assert_called()
1495
1496     def test_no_cache_when_stdin(self) -> None:
1497         mode = DEFAULT_MODE
1498         with cache_dir():
1499             result = CliRunner().invoke(
1500                 black.main, ["-"], input=BytesIO(b"print('hello')")
1501             )
1502             self.assertEqual(result.exit_code, 0)
1503             cache_file = black.get_cache_file(mode)
1504             self.assertFalse(cache_file.exists())
1505
1506     def test_read_cache_no_cachefile(self) -> None:
1507         mode = DEFAULT_MODE
1508         with cache_dir():
1509             self.assertEqual(black.read_cache(mode), {})
1510
1511     def test_write_cache_read_cache(self) -> None:
1512         mode = DEFAULT_MODE
1513         with cache_dir() as workspace:
1514             src = (workspace / "test.py").resolve()
1515             src.touch()
1516             black.write_cache({}, [src], mode)
1517             cache = black.read_cache(mode)
1518             self.assertIn(src, cache)
1519             self.assertEqual(cache[src], black.get_cache_info(src))
1520
1521     def test_filter_cached(self) -> None:
1522         with TemporaryDirectory() as workspace:
1523             path = Path(workspace)
1524             uncached = (path / "uncached").resolve()
1525             cached = (path / "cached").resolve()
1526             cached_but_changed = (path / "changed").resolve()
1527             uncached.touch()
1528             cached.touch()
1529             cached_but_changed.touch()
1530             cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
1531             todo, done = black.filter_cached(
1532                 cache, {uncached, cached, cached_but_changed}
1533             )
1534             self.assertEqual(todo, {uncached, cached_but_changed})
1535             self.assertEqual(done, {cached})
1536
1537     def test_write_cache_creates_directory_if_needed(self) -> None:
1538         mode = DEFAULT_MODE
1539         with cache_dir(exists=False) as workspace:
1540             self.assertFalse(workspace.exists())
1541             black.write_cache({}, [], mode)
1542             self.assertTrue(workspace.exists())
1543
1544     @event_loop()
1545     def test_failed_formatting_does_not_get_cached(self) -> None:
1546         mode = DEFAULT_MODE
1547         with cache_dir() as workspace, patch(
1548             "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1549         ):
1550             failing = (workspace / "failing.py").resolve()
1551             with failing.open("w") as fobj:
1552                 fobj.write("not actually python")
1553             clean = (workspace / "clean.py").resolve()
1554             with clean.open("w") as fobj:
1555                 fobj.write('print("hello")\n')
1556             self.invokeBlack([str(workspace)], exit_code=123)
1557             cache = black.read_cache(mode)
1558             self.assertNotIn(failing, cache)
1559             self.assertIn(clean, cache)
1560
1561     def test_write_cache_write_fail(self) -> None:
1562         mode = DEFAULT_MODE
1563         with cache_dir(), patch.object(Path, "open") as mock:
1564             mock.side_effect = OSError
1565             black.write_cache({}, [], mode)
1566
1567     @event_loop()
1568     @patch("black.ProcessPoolExecutor", MagicMock(side_effect=OSError))
1569     def test_works_in_mono_process_only_environment(self) -> None:
1570         with cache_dir() as workspace:
1571             for f in [
1572                 (workspace / "one.py").resolve(),
1573                 (workspace / "two.py").resolve(),
1574             ]:
1575                 f.write_text('print("hello")\n')
1576             self.invokeBlack([str(workspace)])
1577
1578     @event_loop()
1579     def test_check_diff_use_together(self) -> None:
1580         with cache_dir():
1581             # Files which will be reformatted.
1582             src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1583             self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1584             # Files which will not be reformatted.
1585             src2 = (THIS_DIR / "data" / "composition.py").resolve()
1586             self.invokeBlack([str(src2), "--diff", "--check"])
1587             # Multi file command.
1588             self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1589
1590     def test_no_files(self) -> None:
1591         with cache_dir():
1592             # Without an argument, black exits with error code 0.
1593             self.invokeBlack([])
1594
1595     def test_broken_symlink(self) -> None:
1596         with cache_dir() as workspace:
1597             symlink = workspace / "broken_link.py"
1598             try:
1599                 symlink.symlink_to("nonexistent.py")
1600             except OSError as e:
1601                 self.skipTest(f"Can't create symlinks: {e}")
1602             self.invokeBlack([str(workspace.resolve())])
1603
1604     def test_read_cache_line_lengths(self) -> None:
1605         mode = DEFAULT_MODE
1606         short_mode = replace(DEFAULT_MODE, line_length=1)
1607         with cache_dir() as workspace:
1608             path = (workspace / "file.py").resolve()
1609             path.touch()
1610             black.write_cache({}, [path], mode)
1611             one = black.read_cache(mode)
1612             self.assertIn(path, one)
1613             two = black.read_cache(short_mode)
1614             self.assertNotIn(path, two)
1615
1616     def test_tricky_unicode_symbols(self) -> None:
1617         source, expected = read_data("tricky_unicode_symbols")
1618         actual = fs(source)
1619         self.assertFormatEqual(expected, actual)
1620         black.assert_equivalent(source, actual)
1621         black.assert_stable(source, actual, DEFAULT_MODE)
1622
1623     def test_single_file_force_pyi(self) -> None:
1624         pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
1625         contents, expected = read_data("force_pyi")
1626         with cache_dir() as workspace:
1627             path = (workspace / "file.py").resolve()
1628             with open(path, "w") as fh:
1629                 fh.write(contents)
1630             self.invokeBlack([str(path), "--pyi"])
1631             with open(path, "r") as fh:
1632                 actual = fh.read()
1633             # verify cache with --pyi is separate
1634             pyi_cache = black.read_cache(pyi_mode)
1635             self.assertIn(path, pyi_cache)
1636             normal_cache = black.read_cache(DEFAULT_MODE)
1637             self.assertNotIn(path, normal_cache)
1638         self.assertFormatEqual(expected, actual)
1639         black.assert_equivalent(contents, actual)
1640         black.assert_stable(contents, actual, pyi_mode)
1641
1642     @event_loop()
1643     def test_multi_file_force_pyi(self) -> None:
1644         reg_mode = DEFAULT_MODE
1645         pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
1646         contents, expected = read_data("force_pyi")
1647         with cache_dir() as workspace:
1648             paths = [
1649                 (workspace / "file1.py").resolve(),
1650                 (workspace / "file2.py").resolve(),
1651             ]
1652             for path in paths:
1653                 with open(path, "w") as fh:
1654                     fh.write(contents)
1655             self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1656             for path in paths:
1657                 with open(path, "r") as fh:
1658                     actual = fh.read()
1659                 self.assertEqual(actual, expected)
1660             # verify cache with --pyi is separate
1661             pyi_cache = black.read_cache(pyi_mode)
1662             normal_cache = black.read_cache(reg_mode)
1663             for path in paths:
1664                 self.assertIn(path, pyi_cache)
1665                 self.assertNotIn(path, normal_cache)
1666
1667     def test_pipe_force_pyi(self) -> None:
1668         source, expected = read_data("force_pyi")
1669         result = CliRunner().invoke(
1670             black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1671         )
1672         self.assertEqual(result.exit_code, 0)
1673         actual = result.output
1674         self.assertFormatEqual(actual, expected)
1675
1676     def test_single_file_force_py36(self) -> None:
1677         reg_mode = DEFAULT_MODE
1678         py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
1679         source, expected = read_data("force_py36")
1680         with cache_dir() as workspace:
1681             path = (workspace / "file.py").resolve()
1682             with open(path, "w") as fh:
1683                 fh.write(source)
1684             self.invokeBlack([str(path), *PY36_ARGS])
1685             with open(path, "r") as fh:
1686                 actual = fh.read()
1687             # verify cache with --target-version is separate
1688             py36_cache = black.read_cache(py36_mode)
1689             self.assertIn(path, py36_cache)
1690             normal_cache = black.read_cache(reg_mode)
1691             self.assertNotIn(path, normal_cache)
1692         self.assertEqual(actual, expected)
1693
1694     @event_loop()
1695     def test_multi_file_force_py36(self) -> None:
1696         reg_mode = DEFAULT_MODE
1697         py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
1698         source, expected = read_data("force_py36")
1699         with cache_dir() as workspace:
1700             paths = [
1701                 (workspace / "file1.py").resolve(),
1702                 (workspace / "file2.py").resolve(),
1703             ]
1704             for path in paths:
1705                 with open(path, "w") as fh:
1706                     fh.write(source)
1707             self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1708             for path in paths:
1709                 with open(path, "r") as fh:
1710                     actual = fh.read()
1711                 self.assertEqual(actual, expected)
1712             # verify cache with --target-version is separate
1713             pyi_cache = black.read_cache(py36_mode)
1714             normal_cache = black.read_cache(reg_mode)
1715             for path in paths:
1716                 self.assertIn(path, pyi_cache)
1717                 self.assertNotIn(path, normal_cache)
1718
1719     def test_collections(self) -> None:
1720         source, expected = read_data("collections")
1721         actual = fs(source)
1722         self.assertFormatEqual(expected, actual)
1723         black.assert_equivalent(source, actual)
1724         black.assert_stable(source, actual, DEFAULT_MODE)
1725
1726     def test_pipe_force_py36(self) -> None:
1727         source, expected = read_data("force_py36")
1728         result = CliRunner().invoke(
1729             black.main,
1730             ["-", "-q", "--target-version=py36"],
1731             input=BytesIO(source.encode("utf8")),
1732         )
1733         self.assertEqual(result.exit_code, 0)
1734         actual = result.output
1735         self.assertFormatEqual(actual, expected)
1736
1737     def test_include_exclude(self) -> None:
1738         path = THIS_DIR / "data" / "include_exclude_tests"
1739         include = re.compile(r"\.pyi?$")
1740         exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1741         report = black.Report()
1742         gitignore = PathSpec.from_lines("gitwildmatch", [])
1743         sources: List[Path] = []
1744         expected = [
1745             Path(path / "b/dont_exclude/a.py"),
1746             Path(path / "b/dont_exclude/a.pyi"),
1747         ]
1748         this_abs = THIS_DIR.resolve()
1749         sources.extend(
1750             black.gen_python_files(
1751                 path.iterdir(), this_abs, include, exclude, None, report, gitignore
1752             )
1753         )
1754         self.assertEqual(sorted(expected), sorted(sources))
1755
1756     @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1757     def test_exclude_for_issue_1572(self) -> None:
1758         # Exclude shouldn't touch files that were explicitly given to Black through the
1759         # CLI. Exclude is supposed to only apply to the recursive discovery of files.
1760         # https://github.com/psf/black/issues/1572
1761         path = THIS_DIR / "data" / "include_exclude_tests"
1762         include = ""
1763         exclude = r"/exclude/|a\.py"
1764         src = str(path / "b/exclude/a.py")
1765         report = black.Report()
1766         expected = [Path(path / "b/exclude/a.py")]
1767         sources = list(
1768             black.get_sources(
1769                 ctx=FakeContext(),
1770                 src=(src,),
1771                 quiet=True,
1772                 verbose=False,
1773                 include=include,
1774                 exclude=exclude,
1775                 force_exclude=None,
1776                 report=report,
1777             )
1778         )
1779         self.assertEqual(sorted(expected), sorted(sources))
1780
1781     def test_gitignore_exclude(self) -> None:
1782         path = THIS_DIR / "data" / "include_exclude_tests"
1783         include = re.compile(r"\.pyi?$")
1784         exclude = re.compile(r"")
1785         report = black.Report()
1786         gitignore = PathSpec.from_lines(
1787             "gitwildmatch", ["exclude/", ".definitely_exclude"]
1788         )
1789         sources: List[Path] = []
1790         expected = [
1791             Path(path / "b/dont_exclude/a.py"),
1792             Path(path / "b/dont_exclude/a.pyi"),
1793         ]
1794         this_abs = THIS_DIR.resolve()
1795         sources.extend(
1796             black.gen_python_files(
1797                 path.iterdir(), this_abs, include, exclude, None, report, gitignore
1798             )
1799         )
1800         self.assertEqual(sorted(expected), sorted(sources))
1801
1802     def test_empty_include(self) -> None:
1803         path = THIS_DIR / "data" / "include_exclude_tests"
1804         report = black.Report()
1805         gitignore = PathSpec.from_lines("gitwildmatch", [])
1806         empty = re.compile(r"")
1807         sources: List[Path] = []
1808         expected = [
1809             Path(path / "b/exclude/a.pie"),
1810             Path(path / "b/exclude/a.py"),
1811             Path(path / "b/exclude/a.pyi"),
1812             Path(path / "b/dont_exclude/a.pie"),
1813             Path(path / "b/dont_exclude/a.py"),
1814             Path(path / "b/dont_exclude/a.pyi"),
1815             Path(path / "b/.definitely_exclude/a.pie"),
1816             Path(path / "b/.definitely_exclude/a.py"),
1817             Path(path / "b/.definitely_exclude/a.pyi"),
1818         ]
1819         this_abs = THIS_DIR.resolve()
1820         sources.extend(
1821             black.gen_python_files(
1822                 path.iterdir(),
1823                 this_abs,
1824                 empty,
1825                 re.compile(black.DEFAULT_EXCLUDES),
1826                 None,
1827                 report,
1828                 gitignore,
1829             )
1830         )
1831         self.assertEqual(sorted(expected), sorted(sources))
1832
1833     def test_empty_exclude(self) -> None:
1834         path = THIS_DIR / "data" / "include_exclude_tests"
1835         report = black.Report()
1836         gitignore = PathSpec.from_lines("gitwildmatch", [])
1837         empty = re.compile(r"")
1838         sources: List[Path] = []
1839         expected = [
1840             Path(path / "b/dont_exclude/a.py"),
1841             Path(path / "b/dont_exclude/a.pyi"),
1842             Path(path / "b/exclude/a.py"),
1843             Path(path / "b/exclude/a.pyi"),
1844             Path(path / "b/.definitely_exclude/a.py"),
1845             Path(path / "b/.definitely_exclude/a.pyi"),
1846         ]
1847         this_abs = THIS_DIR.resolve()
1848         sources.extend(
1849             black.gen_python_files(
1850                 path.iterdir(),
1851                 this_abs,
1852                 re.compile(black.DEFAULT_INCLUDES),
1853                 empty,
1854                 None,
1855                 report,
1856                 gitignore,
1857             )
1858         )
1859         self.assertEqual(sorted(expected), sorted(sources))
1860
1861     def test_invalid_include_exclude(self) -> None:
1862         for option in ["--include", "--exclude"]:
1863             self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1864
1865     def test_preserves_line_endings(self) -> None:
1866         with TemporaryDirectory() as workspace:
1867             test_file = Path(workspace) / "test.py"
1868             for nl in ["\n", "\r\n"]:
1869                 contents = nl.join(["def f(  ):", "    pass"])
1870                 test_file.write_bytes(contents.encode())
1871                 ff(test_file, write_back=black.WriteBack.YES)
1872                 updated_contents: bytes = test_file.read_bytes()
1873                 self.assertIn(nl.encode(), updated_contents)
1874                 if nl == "\n":
1875                     self.assertNotIn(b"\r\n", updated_contents)
1876
1877     def test_preserves_line_endings_via_stdin(self) -> None:
1878         for nl in ["\n", "\r\n"]:
1879             contents = nl.join(["def f(  ):", "    pass"])
1880             runner = BlackRunner()
1881             result = runner.invoke(
1882                 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1883             )
1884             self.assertEqual(result.exit_code, 0)
1885             output = runner.stdout_bytes
1886             self.assertIn(nl.encode("utf8"), output)
1887             if nl == "\n":
1888                 self.assertNotIn(b"\r\n", output)
1889
1890     def test_assert_equivalent_different_asts(self) -> None:
1891         with self.assertRaises(AssertionError):
1892             black.assert_equivalent("{}", "None")
1893
1894     def test_symlink_out_of_root_directory(self) -> None:
1895         path = MagicMock()
1896         root = THIS_DIR.resolve()
1897         child = MagicMock()
1898         include = re.compile(black.DEFAULT_INCLUDES)
1899         exclude = re.compile(black.DEFAULT_EXCLUDES)
1900         report = black.Report()
1901         gitignore = PathSpec.from_lines("gitwildmatch", [])
1902         # `child` should behave like a symlink which resolved path is clearly
1903         # outside of the `root` directory.
1904         path.iterdir.return_value = [child]
1905         child.resolve.return_value = Path("/a/b/c")
1906         child.as_posix.return_value = "/a/b/c"
1907         child.is_symlink.return_value = True
1908         try:
1909             list(
1910                 black.gen_python_files(
1911                     path.iterdir(), root, include, exclude, None, report, gitignore
1912                 )
1913             )
1914         except ValueError as ve:
1915             self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1916         path.iterdir.assert_called_once()
1917         child.resolve.assert_called_once()
1918         child.is_symlink.assert_called_once()
1919         # `child` should behave like a strange file which resolved path is clearly
1920         # outside of the `root` directory.
1921         child.is_symlink.return_value = False
1922         with self.assertRaises(ValueError):
1923             list(
1924                 black.gen_python_files(
1925                     path.iterdir(), root, include, exclude, None, report, gitignore
1926                 )
1927             )
1928         path.iterdir.assert_called()
1929         self.assertEqual(path.iterdir.call_count, 2)
1930         child.resolve.assert_called()
1931         self.assertEqual(child.resolve.call_count, 2)
1932         child.is_symlink.assert_called()
1933         self.assertEqual(child.is_symlink.call_count, 2)
1934
1935     def test_shhh_click(self) -> None:
1936         try:
1937             from click import _unicodefun  # type: ignore
1938         except ModuleNotFoundError:
1939             self.skipTest("Incompatible Click version")
1940         if not hasattr(_unicodefun, "_verify_python3_env"):
1941             self.skipTest("Incompatible Click version")
1942         # First, let's see if Click is crashing with a preferred ASCII charset.
1943         with patch("locale.getpreferredencoding") as gpe:
1944             gpe.return_value = "ASCII"
1945             with self.assertRaises(RuntimeError):
1946                 _unicodefun._verify_python3_env()
1947         # Now, let's silence Click...
1948         black.patch_click()
1949         # ...and confirm it's silent.
1950         with patch("locale.getpreferredencoding") as gpe:
1951             gpe.return_value = "ASCII"
1952             try:
1953                 _unicodefun._verify_python3_env()
1954             except RuntimeError as re:
1955                 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1956
1957     def test_root_logger_not_used_directly(self) -> None:
1958         def fail(*args: Any, **kwargs: Any) -> None:
1959             self.fail("Record created with root logger")
1960
1961         with patch.multiple(
1962             logging.root,
1963             debug=fail,
1964             info=fail,
1965             warning=fail,
1966             error=fail,
1967             critical=fail,
1968             log=fail,
1969         ):
1970             ff(THIS_FILE)
1971
1972     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1973     def test_blackd_main(self) -> None:
1974         with patch("blackd.web.run_app"):
1975             result = CliRunner().invoke(blackd.main, [])
1976             if result.exception is not None:
1977                 raise result.exception
1978             self.assertEqual(result.exit_code, 0)
1979
1980     def test_invalid_config_return_code(self) -> None:
1981         tmp_file = Path(black.dump_to_file())
1982         try:
1983             tmp_config = Path(black.dump_to_file())
1984             tmp_config.unlink()
1985             args = ["--config", str(tmp_config), str(tmp_file)]
1986             self.invokeBlack(args, exit_code=2, ignore_config=False)
1987         finally:
1988             tmp_file.unlink()
1989
1990     def test_parse_pyproject_toml(self) -> None:
1991         test_toml_file = THIS_DIR / "test.toml"
1992         config = black.parse_pyproject_toml(str(test_toml_file))
1993         self.assertEqual(config["verbose"], 1)
1994         self.assertEqual(config["check"], "no")
1995         self.assertEqual(config["diff"], "y")
1996         self.assertEqual(config["color"], True)
1997         self.assertEqual(config["line_length"], 79)
1998         self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
1999         self.assertEqual(config["exclude"], r"\.pyi?$")
2000         self.assertEqual(config["include"], r"\.py?$")
2001
2002     def test_read_pyproject_toml(self) -> None:
2003         test_toml_file = THIS_DIR / "test.toml"
2004         fake_ctx = FakeContext()
2005         black.read_pyproject_toml(fake_ctx, FakeParameter(), str(test_toml_file))
2006         config = fake_ctx.default_map
2007         self.assertEqual(config["verbose"], "1")
2008         self.assertEqual(config["check"], "no")
2009         self.assertEqual(config["diff"], "y")
2010         self.assertEqual(config["color"], "True")
2011         self.assertEqual(config["line_length"], "79")
2012         self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
2013         self.assertEqual(config["exclude"], r"\.pyi?$")
2014         self.assertEqual(config["include"], r"\.py?$")
2015
2016     def test_find_project_root(self) -> None:
2017         with TemporaryDirectory() as workspace:
2018             root = Path(workspace)
2019             test_dir = root / "test"
2020             test_dir.mkdir()
2021
2022             src_dir = root / "src"
2023             src_dir.mkdir()
2024
2025             root_pyproject = root / "pyproject.toml"
2026             root_pyproject.touch()
2027             src_pyproject = src_dir / "pyproject.toml"
2028             src_pyproject.touch()
2029             src_python = src_dir / "foo.py"
2030             src_python.touch()
2031
2032             self.assertEqual(
2033                 black.find_project_root((src_dir, test_dir)), root.resolve()
2034             )
2035             self.assertEqual(black.find_project_root((src_dir,)), src_dir.resolve())
2036             self.assertEqual(black.find_project_root((src_python,)), src_dir.resolve())
2037
2038     def test_bpo_33660_workaround(self) -> None:
2039         if system() == "Windows":
2040             return
2041
2042         # https://bugs.python.org/issue33660
2043
2044         old_cwd = Path.cwd()
2045         try:
2046             root = Path("/")
2047             os.chdir(str(root))
2048             path = Path("workspace") / "project"
2049             report = black.Report(verbose=True)
2050             normalized_path = black.normalize_path_maybe_ignore(path, root, report)
2051             self.assertEqual(normalized_path, "workspace/project")
2052         finally:
2053             os.chdir(str(old_cwd))
2054
2055
2056 class BlackDTestCase(AioHTTPTestCase):
2057     async def get_application(self) -> web.Application:
2058         return blackd.make_app()
2059
2060     # TODO: remove these decorators once the below is released
2061     # https://github.com/aio-libs/aiohttp/pull/3727
2062     @skip_if_exception("ClientOSError")
2063     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
2064     @unittest_run_loop
2065     async def test_blackd_request_needs_formatting(self) -> None:
2066         response = await self.client.post("/", data=b"print('hello world')")
2067         self.assertEqual(response.status, 200)
2068         self.assertEqual(response.charset, "utf8")
2069         self.assertEqual(await response.read(), b'print("hello world")\n')
2070
2071     @skip_if_exception("ClientOSError")
2072     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
2073     @unittest_run_loop
2074     async def test_blackd_request_no_change(self) -> None:
2075         response = await self.client.post("/", data=b'print("hello world")\n')
2076         self.assertEqual(response.status, 204)
2077         self.assertEqual(await response.read(), b"")
2078
2079     @skip_if_exception("ClientOSError")
2080     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
2081     @unittest_run_loop
2082     async def test_blackd_request_syntax_error(self) -> None:
2083         response = await self.client.post("/", data=b"what even ( is")
2084         self.assertEqual(response.status, 400)
2085         content = await response.text()
2086         self.assertTrue(
2087             content.startswith("Cannot parse"),
2088             msg=f"Expected error to start with 'Cannot parse', got {repr(content)}",
2089         )
2090
2091     @skip_if_exception("ClientOSError")
2092     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
2093     @unittest_run_loop
2094     async def test_blackd_unsupported_version(self) -> None:
2095         response = await self.client.post(
2096             "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "2"}
2097         )
2098         self.assertEqual(response.status, 501)
2099
2100     @skip_if_exception("ClientOSError")
2101     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
2102     @unittest_run_loop
2103     async def test_blackd_supported_version(self) -> None:
2104         response = await self.client.post(
2105             "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "1"}
2106         )
2107         self.assertEqual(response.status, 200)
2108
2109     @skip_if_exception("ClientOSError")
2110     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
2111     @unittest_run_loop
2112     async def test_blackd_invalid_python_variant(self) -> None:
2113         async def check(header_value: str, expected_status: int = 400) -> None:
2114             response = await self.client.post(
2115                 "/", data=b"what", headers={blackd.PYTHON_VARIANT_HEADER: header_value}
2116             )
2117             self.assertEqual(response.status, expected_status)
2118
2119         await check("lol")
2120         await check("ruby3.5")
2121         await check("pyi3.6")
2122         await check("py1.5")
2123         await check("2.8")
2124         await check("py2.8")
2125         await check("3.0")
2126         await check("pypy3.0")
2127         await check("jython3.4")
2128
2129     @skip_if_exception("ClientOSError")
2130     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
2131     @unittest_run_loop
2132     async def test_blackd_pyi(self) -> None:
2133         source, expected = read_data("stub.pyi")
2134         response = await self.client.post(
2135             "/", data=source, headers={blackd.PYTHON_VARIANT_HEADER: "pyi"}
2136         )
2137         self.assertEqual(response.status, 200)
2138         self.assertEqual(await response.text(), expected)
2139
2140     @skip_if_exception("ClientOSError")
2141     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
2142     @unittest_run_loop
2143     async def test_blackd_diff(self) -> None:
2144         diff_header = re.compile(
2145             r"(In|Out)\t\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
2146         )
2147
2148         source, _ = read_data("blackd_diff.py")
2149         expected, _ = read_data("blackd_diff.diff")
2150
2151         response = await self.client.post(
2152             "/", data=source, headers={blackd.DIFF_HEADER: "true"}
2153         )
2154         self.assertEqual(response.status, 200)
2155
2156         actual = await response.text()
2157         actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
2158         self.assertEqual(actual, expected)
2159
2160     @skip_if_exception("ClientOSError")
2161     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
2162     @unittest_run_loop
2163     async def test_blackd_python_variant(self) -> None:
2164         code = (
2165             "def f(\n"
2166             "    and_has_a_bunch_of,\n"
2167             "    very_long_arguments_too,\n"
2168             "    and_lots_of_them_as_well_lol,\n"
2169             "    **and_very_long_keyword_arguments\n"
2170             "):\n"
2171             "    pass\n"
2172         )
2173
2174         async def check(header_value: str, expected_status: int) -> None:
2175             response = await self.client.post(
2176                 "/", data=code, headers={blackd.PYTHON_VARIANT_HEADER: header_value}
2177             )
2178             self.assertEqual(
2179                 response.status, expected_status, msg=await response.text()
2180             )
2181
2182         await check("3.6", 200)
2183         await check("py3.6", 200)
2184         await check("3.6,3.7", 200)
2185         await check("3.6,py3.7", 200)
2186         await check("py36,py37", 200)
2187         await check("36", 200)
2188         await check("3.6.4", 200)
2189
2190         await check("2", 204)
2191         await check("2.7", 204)
2192         await check("py2.7", 204)
2193         await check("3.4", 204)
2194         await check("py3.4", 204)
2195         await check("py34,py36", 204)
2196         await check("34", 204)
2197
2198     @skip_if_exception("ClientOSError")
2199     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
2200     @unittest_run_loop
2201     async def test_blackd_line_length(self) -> None:
2202         response = await self.client.post(
2203             "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "7"}
2204         )
2205         self.assertEqual(response.status, 200)
2206
2207     @skip_if_exception("ClientOSError")
2208     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
2209     @unittest_run_loop
2210     async def test_blackd_invalid_line_length(self) -> None:
2211         response = await self.client.post(
2212             "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "NaN"}
2213         )
2214         self.assertEqual(response.status, 400)
2215
2216     @skip_if_exception("ClientOSError")
2217     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
2218     @unittest_run_loop
2219     async def test_blackd_response_black_version_header(self) -> None:
2220         response = await self.client.post("/")
2221         self.assertIsNotNone(response.headers.get(blackd.BLACK_VERSION_HEADER))
2222
2223
2224 with open(black.__file__, "r", encoding="utf-8") as _bf:
2225     black_source_lines = _bf.readlines()
2226
2227
2228 def tracefunc(frame: types.FrameType, event: str, arg: Any) -> Callable:
2229     """Show function calls `from black/__init__.py` as they happen.
2230
2231     Register this with `sys.settrace()` in a test you're debugging.
2232     """
2233     if event != "call":
2234         return tracefunc
2235
2236     stack = len(inspect.stack()) - 19
2237     stack *= 2
2238     filename = frame.f_code.co_filename
2239     lineno = frame.f_lineno
2240     func_sig_lineno = lineno - 1
2241     funcname = black_source_lines[func_sig_lineno].strip()
2242     while funcname.startswith("@"):
2243         func_sig_lineno += 1
2244         funcname = black_source_lines[func_sig_lineno].strip()
2245     if "black/__init__.py" in filename:
2246         print(f"{' ' * stack}{lineno}:{funcname}")
2247     return tracefunc
2248
2249
2250 if __name__ == "__main__":
2251     unittest.main(module="test_black")