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

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