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

madduck's git repository

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

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

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

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

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

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