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

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