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

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