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

Remove no longer used appveyor (#1401)
[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 PROJECT_ROOT = THIS_DIR.parent
39 DETERMINISTIC_HEADER = "[Deterministic header]"
40 EMPTY_LINE = "# EMPTY LINE WITH WHITESPACE" + " (this comment will be removed)"
41 PY36_ARGS = [
42     f"--target-version={version.name.lower()}" for version in black.PY36_VERSIONS
43 ]
44 T = TypeVar("T")
45 R = TypeVar("R")
46
47
48 def dump_to_stderr(*output: str) -> str:
49     return "\n" + "\n".join(output) + "\n"
50
51
52 def read_data(name: str, data: bool = True) -> Tuple[str, str]:
53     """read_data('test_name') -> 'input', 'output'"""
54     if not name.endswith((".py", ".pyi", ".out", ".diff")):
55         name += ".py"
56     _input: List[str] = []
57     _output: List[str] = []
58     base_dir = THIS_DIR / "data" if data else PROJECT_ROOT
59     with open(base_dir / name, "r", encoding="utf8") as test:
60         lines = test.readlines()
61     result = _input
62     for line in lines:
63         line = line.replace(EMPTY_LINE, "")
64         if line.rstrip() == "# output":
65             result = _output
66             continue
67
68         result.append(line)
69     if _input and not _output:
70         # If there's no output marker, treat the entire file as already pre-formatted.
71         _output = _input[:]
72     return "".join(_input).strip() + "\n", "".join(_output).strip() + "\n"
73
74
75 @contextmanager
76 def cache_dir(exists: bool = True) -> Iterator[Path]:
77     with TemporaryDirectory() as workspace:
78         cache_dir = Path(workspace)
79         if not exists:
80             cache_dir = cache_dir / "new"
81         with patch("black.CACHE_DIR", cache_dir):
82             yield cache_dir
83
84
85 @contextmanager
86 def event_loop() -> Iterator[None]:
87     policy = asyncio.get_event_loop_policy()
88     loop = policy.new_event_loop()
89     asyncio.set_event_loop(loop)
90     try:
91         yield
92
93     finally:
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("src/black/__init__.py")
207
208     def test_pygram(self) -> None:
209         self.checkSourceFile("src/blib2to3/pygram.py")
210
211     def test_pytree(self) -> None:
212         self.checkSourceFile("src/blib2to3/pytree.py")
213
214     def test_conv(self) -> None:
215         self.checkSourceFile("src/blib2to3/pgen2/conv.py")
216
217     def test_driver(self) -> None:
218         self.checkSourceFile("src/blib2to3/pgen2/driver.py")
219
220     def test_grammar(self) -> None:
221         self.checkSourceFile("src/blib2to3/pgen2/grammar.py")
222
223     def test_literals(self) -> None:
224         self.checkSourceFile("src/blib2to3/pgen2/literals.py")
225
226     def test_parse(self) -> None:
227         self.checkSourceFile("src/blib2to3/pgen2/parse.py")
228
229     def test_pgen(self) -> None:
230         self.checkSourceFile("src/blib2to3/pgen2/pgen.py")
231
232     def test_tokenize(self) -> None:
233         self.checkSourceFile("src/blib2to3/pgen2/tokenize.py")
234
235     def test_token(self) -> None:
236         self.checkSourceFile("src/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("src/black/__init__", 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()
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()
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     @event_loop()
1367     @patch("black.ProcessPoolExecutor", MagicMock(side_effect=OSError))
1368     def test_works_in_mono_process_only_environment(self) -> None:
1369         with cache_dir() as workspace:
1370             for f in [
1371                 (workspace / "one.py").resolve(),
1372                 (workspace / "two.py").resolve(),
1373             ]:
1374                 f.write_text('print("hello")\n')
1375             self.invokeBlack([str(workspace)])
1376
1377     @event_loop()
1378     def test_check_diff_use_together(self) -> None:
1379         with cache_dir():
1380             # Files which will be reformatted.
1381             src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1382             self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1383             # Files which will not be reformatted.
1384             src2 = (THIS_DIR / "data" / "composition.py").resolve()
1385             self.invokeBlack([str(src2), "--diff", "--check"])
1386             # Multi file command.
1387             self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1388
1389     def test_no_files(self) -> None:
1390         with cache_dir():
1391             # Without an argument, black exits with error code 0.
1392             self.invokeBlack([])
1393
1394     def test_broken_symlink(self) -> None:
1395         with cache_dir() as workspace:
1396             symlink = workspace / "broken_link.py"
1397             try:
1398                 symlink.symlink_to("nonexistent.py")
1399             except OSError as e:
1400                 self.skipTest(f"Can't create symlinks: {e}")
1401             self.invokeBlack([str(workspace.resolve())])
1402
1403     def test_read_cache_line_lengths(self) -> None:
1404         mode = black.FileMode()
1405         short_mode = black.FileMode(line_length=1)
1406         with cache_dir() as workspace:
1407             path = (workspace / "file.py").resolve()
1408             path.touch()
1409             black.write_cache({}, [path], mode)
1410             one = black.read_cache(mode)
1411             self.assertIn(path, one)
1412             two = black.read_cache(short_mode)
1413             self.assertNotIn(path, two)
1414
1415     def test_tricky_unicode_symbols(self) -> None:
1416         source, expected = read_data("tricky_unicode_symbols")
1417         actual = fs(source)
1418         self.assertFormatEqual(expected, actual)
1419         black.assert_equivalent(source, actual)
1420         black.assert_stable(source, actual, black.FileMode())
1421
1422     def test_single_file_force_pyi(self) -> None:
1423         reg_mode = black.FileMode()
1424         pyi_mode = black.FileMode(is_pyi=True)
1425         contents, expected = read_data("force_pyi")
1426         with cache_dir() as workspace:
1427             path = (workspace / "file.py").resolve()
1428             with open(path, "w") as fh:
1429                 fh.write(contents)
1430             self.invokeBlack([str(path), "--pyi"])
1431             with open(path, "r") as fh:
1432                 actual = fh.read()
1433             # verify cache with --pyi is separate
1434             pyi_cache = black.read_cache(pyi_mode)
1435             self.assertIn(path, pyi_cache)
1436             normal_cache = black.read_cache(reg_mode)
1437             self.assertNotIn(path, normal_cache)
1438         self.assertEqual(actual, expected)
1439
1440     @event_loop()
1441     def test_multi_file_force_pyi(self) -> None:
1442         reg_mode = black.FileMode()
1443         pyi_mode = black.FileMode(is_pyi=True)
1444         contents, expected = read_data("force_pyi")
1445         with cache_dir() as workspace:
1446             paths = [
1447                 (workspace / "file1.py").resolve(),
1448                 (workspace / "file2.py").resolve(),
1449             ]
1450             for path in paths:
1451                 with open(path, "w") as fh:
1452                     fh.write(contents)
1453             self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1454             for path in paths:
1455                 with open(path, "r") as fh:
1456                     actual = fh.read()
1457                 self.assertEqual(actual, expected)
1458             # verify cache with --pyi is separate
1459             pyi_cache = black.read_cache(pyi_mode)
1460             normal_cache = black.read_cache(reg_mode)
1461             for path in paths:
1462                 self.assertIn(path, pyi_cache)
1463                 self.assertNotIn(path, normal_cache)
1464
1465     def test_pipe_force_pyi(self) -> None:
1466         source, expected = read_data("force_pyi")
1467         result = CliRunner().invoke(
1468             black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1469         )
1470         self.assertEqual(result.exit_code, 0)
1471         actual = result.output
1472         self.assertFormatEqual(actual, expected)
1473
1474     def test_single_file_force_py36(self) -> None:
1475         reg_mode = black.FileMode()
1476         py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1477         source, expected = read_data("force_py36")
1478         with cache_dir() as workspace:
1479             path = (workspace / "file.py").resolve()
1480             with open(path, "w") as fh:
1481                 fh.write(source)
1482             self.invokeBlack([str(path), *PY36_ARGS])
1483             with open(path, "r") as fh:
1484                 actual = fh.read()
1485             # verify cache with --target-version is separate
1486             py36_cache = black.read_cache(py36_mode)
1487             self.assertIn(path, py36_cache)
1488             normal_cache = black.read_cache(reg_mode)
1489             self.assertNotIn(path, normal_cache)
1490         self.assertEqual(actual, expected)
1491
1492     @event_loop()
1493     def test_multi_file_force_py36(self) -> None:
1494         reg_mode = black.FileMode()
1495         py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1496         source, expected = read_data("force_py36")
1497         with cache_dir() as workspace:
1498             paths = [
1499                 (workspace / "file1.py").resolve(),
1500                 (workspace / "file2.py").resolve(),
1501             ]
1502             for path in paths:
1503                 with open(path, "w") as fh:
1504                     fh.write(source)
1505             self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1506             for path in paths:
1507                 with open(path, "r") as fh:
1508                     actual = fh.read()
1509                 self.assertEqual(actual, expected)
1510             # verify cache with --target-version is separate
1511             pyi_cache = black.read_cache(py36_mode)
1512             normal_cache = black.read_cache(reg_mode)
1513             for path in paths:
1514                 self.assertIn(path, pyi_cache)
1515                 self.assertNotIn(path, normal_cache)
1516
1517     def test_collections(self) -> None:
1518         source, expected = read_data("collections")
1519         actual = fs(source)
1520         self.assertFormatEqual(expected, actual)
1521         black.assert_equivalent(source, actual)
1522         black.assert_stable(source, actual, black.FileMode())
1523
1524     def test_pipe_force_py36(self) -> None:
1525         source, expected = read_data("force_py36")
1526         result = CliRunner().invoke(
1527             black.main,
1528             ["-", "-q", "--target-version=py36"],
1529             input=BytesIO(source.encode("utf8")),
1530         )
1531         self.assertEqual(result.exit_code, 0)
1532         actual = result.output
1533         self.assertFormatEqual(actual, expected)
1534
1535     def test_include_exclude(self) -> None:
1536         path = THIS_DIR / "data" / "include_exclude_tests"
1537         include = re.compile(r"\.pyi?$")
1538         exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1539         report = black.Report()
1540         gitignore = PathSpec.from_lines("gitwildmatch", [])
1541         sources: List[Path] = []
1542         expected = [
1543             Path(path / "b/dont_exclude/a.py"),
1544             Path(path / "b/dont_exclude/a.pyi"),
1545         ]
1546         this_abs = THIS_DIR.resolve()
1547         sources.extend(
1548             black.gen_python_files(
1549                 path.iterdir(), this_abs, include, [exclude], report, gitignore
1550             )
1551         )
1552         self.assertEqual(sorted(expected), sorted(sources))
1553
1554     def test_gitignore_exclude(self) -> None:
1555         path = THIS_DIR / "data" / "include_exclude_tests"
1556         include = re.compile(r"\.pyi?$")
1557         exclude = re.compile(r"")
1558         report = black.Report()
1559         gitignore = PathSpec.from_lines(
1560             "gitwildmatch", ["exclude/", ".definitely_exclude"]
1561         )
1562         sources: List[Path] = []
1563         expected = [
1564             Path(path / "b/dont_exclude/a.py"),
1565             Path(path / "b/dont_exclude/a.pyi"),
1566         ]
1567         this_abs = THIS_DIR.resolve()
1568         sources.extend(
1569             black.gen_python_files(
1570                 path.iterdir(), this_abs, include, [exclude], report, gitignore
1571             )
1572         )
1573         self.assertEqual(sorted(expected), sorted(sources))
1574
1575     def test_empty_include(self) -> None:
1576         path = THIS_DIR / "data" / "include_exclude_tests"
1577         report = black.Report()
1578         gitignore = PathSpec.from_lines("gitwildmatch", [])
1579         empty = re.compile(r"")
1580         sources: List[Path] = []
1581         expected = [
1582             Path(path / "b/exclude/a.pie"),
1583             Path(path / "b/exclude/a.py"),
1584             Path(path / "b/exclude/a.pyi"),
1585             Path(path / "b/dont_exclude/a.pie"),
1586             Path(path / "b/dont_exclude/a.py"),
1587             Path(path / "b/dont_exclude/a.pyi"),
1588             Path(path / "b/.definitely_exclude/a.pie"),
1589             Path(path / "b/.definitely_exclude/a.py"),
1590             Path(path / "b/.definitely_exclude/a.pyi"),
1591         ]
1592         this_abs = THIS_DIR.resolve()
1593         sources.extend(
1594             black.gen_python_files(
1595                 path.iterdir(),
1596                 this_abs,
1597                 empty,
1598                 [re.compile(black.DEFAULT_EXCLUDES)],
1599                 report,
1600                 gitignore,
1601             )
1602         )
1603         self.assertEqual(sorted(expected), sorted(sources))
1604
1605     def test_empty_exclude(self) -> None:
1606         path = THIS_DIR / "data" / "include_exclude_tests"
1607         report = black.Report()
1608         gitignore = PathSpec.from_lines("gitwildmatch", [])
1609         empty = re.compile(r"")
1610         sources: List[Path] = []
1611         expected = [
1612             Path(path / "b/dont_exclude/a.py"),
1613             Path(path / "b/dont_exclude/a.pyi"),
1614             Path(path / "b/exclude/a.py"),
1615             Path(path / "b/exclude/a.pyi"),
1616             Path(path / "b/.definitely_exclude/a.py"),
1617             Path(path / "b/.definitely_exclude/a.pyi"),
1618         ]
1619         this_abs = THIS_DIR.resolve()
1620         sources.extend(
1621             black.gen_python_files(
1622                 path.iterdir(),
1623                 this_abs,
1624                 re.compile(black.DEFAULT_INCLUDES),
1625                 [empty],
1626                 report,
1627                 gitignore,
1628             )
1629         )
1630         self.assertEqual(sorted(expected), sorted(sources))
1631
1632     def test_invalid_include_exclude(self) -> None:
1633         for option in ["--include", "--exclude"]:
1634             self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1635
1636     def test_preserves_line_endings(self) -> None:
1637         with TemporaryDirectory() as workspace:
1638             test_file = Path(workspace) / "test.py"
1639             for nl in ["\n", "\r\n"]:
1640                 contents = nl.join(["def f(  ):", "    pass"])
1641                 test_file.write_bytes(contents.encode())
1642                 ff(test_file, write_back=black.WriteBack.YES)
1643                 updated_contents: bytes = test_file.read_bytes()
1644                 self.assertIn(nl.encode(), updated_contents)
1645                 if nl == "\n":
1646                     self.assertNotIn(b"\r\n", updated_contents)
1647
1648     def test_preserves_line_endings_via_stdin(self) -> None:
1649         for nl in ["\n", "\r\n"]:
1650             contents = nl.join(["def f(  ):", "    pass"])
1651             runner = BlackRunner()
1652             result = runner.invoke(
1653                 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1654             )
1655             self.assertEqual(result.exit_code, 0)
1656             output = runner.stdout_bytes
1657             self.assertIn(nl.encode("utf8"), output)
1658             if nl == "\n":
1659                 self.assertNotIn(b"\r\n", output)
1660
1661     def test_assert_equivalent_different_asts(self) -> None:
1662         with self.assertRaises(AssertionError):
1663             black.assert_equivalent("{}", "None")
1664
1665     def test_symlink_out_of_root_directory(self) -> None:
1666         path = MagicMock()
1667         root = THIS_DIR.resolve()
1668         child = MagicMock()
1669         include = re.compile(black.DEFAULT_INCLUDES)
1670         exclude = re.compile(black.DEFAULT_EXCLUDES)
1671         report = black.Report()
1672         gitignore = PathSpec.from_lines("gitwildmatch", [])
1673         # `child` should behave like a symlink which resolved path is clearly
1674         # outside of the `root` directory.
1675         path.iterdir.return_value = [child]
1676         child.resolve.return_value = Path("/a/b/c")
1677         child.as_posix.return_value = "/a/b/c"
1678         child.is_symlink.return_value = True
1679         try:
1680             list(
1681                 black.gen_python_files(
1682                     path.iterdir(), root, include, exclude, report, gitignore
1683                 )
1684             )
1685         except ValueError as ve:
1686             self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1687         path.iterdir.assert_called_once()
1688         child.resolve.assert_called_once()
1689         child.is_symlink.assert_called_once()
1690         # `child` should behave like a strange file which resolved path is clearly
1691         # outside of the `root` directory.
1692         child.is_symlink.return_value = False
1693         with self.assertRaises(ValueError):
1694             list(
1695                 black.gen_python_files(
1696                     path.iterdir(), root, include, exclude, report, gitignore
1697                 )
1698             )
1699         path.iterdir.assert_called()
1700         self.assertEqual(path.iterdir.call_count, 2)
1701         child.resolve.assert_called()
1702         self.assertEqual(child.resolve.call_count, 2)
1703         child.is_symlink.assert_called()
1704         self.assertEqual(child.is_symlink.call_count, 2)
1705
1706     def test_shhh_click(self) -> None:
1707         try:
1708             from click import _unicodefun  # type: ignore
1709         except ModuleNotFoundError:
1710             self.skipTest("Incompatible Click version")
1711         if not hasattr(_unicodefun, "_verify_python3_env"):
1712             self.skipTest("Incompatible Click version")
1713         # First, let's see if Click is crashing with a preferred ASCII charset.
1714         with patch("locale.getpreferredencoding") as gpe:
1715             gpe.return_value = "ASCII"
1716             with self.assertRaises(RuntimeError):
1717                 _unicodefun._verify_python3_env()
1718         # Now, let's silence Click...
1719         black.patch_click()
1720         # ...and confirm it's silent.
1721         with patch("locale.getpreferredencoding") as gpe:
1722             gpe.return_value = "ASCII"
1723             try:
1724                 _unicodefun._verify_python3_env()
1725             except RuntimeError as re:
1726                 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1727
1728     def test_root_logger_not_used_directly(self) -> None:
1729         def fail(*args: Any, **kwargs: Any) -> None:
1730             self.fail("Record created with root logger")
1731
1732         with patch.multiple(
1733             logging.root,
1734             debug=fail,
1735             info=fail,
1736             warning=fail,
1737             error=fail,
1738             critical=fail,
1739             log=fail,
1740         ):
1741             ff(THIS_FILE)
1742
1743     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1744     def test_blackd_main(self) -> None:
1745         with patch("blackd.web.run_app"):
1746             result = CliRunner().invoke(blackd.main, [])
1747             if result.exception is not None:
1748                 raise result.exception
1749             self.assertEqual(result.exit_code, 0)
1750
1751     def test_invalid_config_return_code(self) -> None:
1752         tmp_file = Path(black.dump_to_file())
1753         try:
1754             tmp_config = Path(black.dump_to_file())
1755             tmp_config.unlink()
1756             args = ["--config", str(tmp_config), str(tmp_file)]
1757             self.invokeBlack(args, exit_code=2, ignore_config=False)
1758         finally:
1759             tmp_file.unlink()
1760
1761
1762 class BlackDTestCase(AioHTTPTestCase):
1763     async def get_application(self) -> web.Application:
1764         return blackd.make_app()
1765
1766     # TODO: remove these decorators once the below is released
1767     # https://github.com/aio-libs/aiohttp/pull/3727
1768     @skip_if_exception("ClientOSError")
1769     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1770     @unittest_run_loop
1771     async def test_blackd_request_needs_formatting(self) -> None:
1772         response = await self.client.post("/", data=b"print('hello world')")
1773         self.assertEqual(response.status, 200)
1774         self.assertEqual(response.charset, "utf8")
1775         self.assertEqual(await response.read(), b'print("hello world")\n')
1776
1777     @skip_if_exception("ClientOSError")
1778     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1779     @unittest_run_loop
1780     async def test_blackd_request_no_change(self) -> None:
1781         response = await self.client.post("/", data=b'print("hello world")\n')
1782         self.assertEqual(response.status, 204)
1783         self.assertEqual(await response.read(), b"")
1784
1785     @skip_if_exception("ClientOSError")
1786     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1787     @unittest_run_loop
1788     async def test_blackd_request_syntax_error(self) -> None:
1789         response = await self.client.post("/", data=b"what even ( is")
1790         self.assertEqual(response.status, 400)
1791         content = await response.text()
1792         self.assertTrue(
1793             content.startswith("Cannot parse"),
1794             msg=f"Expected error to start with 'Cannot parse', got {repr(content)}",
1795         )
1796
1797     @skip_if_exception("ClientOSError")
1798     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1799     @unittest_run_loop
1800     async def test_blackd_unsupported_version(self) -> None:
1801         response = await self.client.post(
1802             "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "2"}
1803         )
1804         self.assertEqual(response.status, 501)
1805
1806     @skip_if_exception("ClientOSError")
1807     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1808     @unittest_run_loop
1809     async def test_blackd_supported_version(self) -> None:
1810         response = await self.client.post(
1811             "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "1"}
1812         )
1813         self.assertEqual(response.status, 200)
1814
1815     @skip_if_exception("ClientOSError")
1816     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1817     @unittest_run_loop
1818     async def test_blackd_invalid_python_variant(self) -> None:
1819         async def check(header_value: str, expected_status: int = 400) -> None:
1820             response = await self.client.post(
1821                 "/", data=b"what", headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1822             )
1823             self.assertEqual(response.status, expected_status)
1824
1825         await check("lol")
1826         await check("ruby3.5")
1827         await check("pyi3.6")
1828         await check("py1.5")
1829         await check("2.8")
1830         await check("py2.8")
1831         await check("3.0")
1832         await check("pypy3.0")
1833         await check("jython3.4")
1834
1835     @skip_if_exception("ClientOSError")
1836     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1837     @unittest_run_loop
1838     async def test_blackd_pyi(self) -> None:
1839         source, expected = read_data("stub.pyi")
1840         response = await self.client.post(
1841             "/", data=source, headers={blackd.PYTHON_VARIANT_HEADER: "pyi"}
1842         )
1843         self.assertEqual(response.status, 200)
1844         self.assertEqual(await response.text(), expected)
1845
1846     @skip_if_exception("ClientOSError")
1847     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1848     @unittest_run_loop
1849     async def test_blackd_diff(self) -> None:
1850         diff_header = re.compile(
1851             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"
1852         )
1853
1854         source, _ = read_data("blackd_diff.py")
1855         expected, _ = read_data("blackd_diff.diff")
1856
1857         response = await self.client.post(
1858             "/", data=source, headers={blackd.DIFF_HEADER: "true"}
1859         )
1860         self.assertEqual(response.status, 200)
1861
1862         actual = await response.text()
1863         actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
1864         self.assertEqual(actual, expected)
1865
1866     @skip_if_exception("ClientOSError")
1867     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1868     @unittest_run_loop
1869     async def test_blackd_python_variant(self) -> None:
1870         code = (
1871             "def f(\n"
1872             "    and_has_a_bunch_of,\n"
1873             "    very_long_arguments_too,\n"
1874             "    and_lots_of_them_as_well_lol,\n"
1875             "    **and_very_long_keyword_arguments\n"
1876             "):\n"
1877             "    pass\n"
1878         )
1879
1880         async def check(header_value: str, expected_status: int) -> None:
1881             response = await self.client.post(
1882                 "/", data=code, headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1883             )
1884             self.assertEqual(
1885                 response.status, expected_status, msg=await response.text()
1886             )
1887
1888         await check("3.6", 200)
1889         await check("py3.6", 200)
1890         await check("3.6,3.7", 200)
1891         await check("3.6,py3.7", 200)
1892         await check("py36,py37", 200)
1893         await check("36", 200)
1894         await check("3.6.4", 200)
1895
1896         await check("2", 204)
1897         await check("2.7", 204)
1898         await check("py2.7", 204)
1899         await check("3.4", 204)
1900         await check("py3.4", 204)
1901         await check("py34,py36", 204)
1902         await check("34", 204)
1903
1904     @skip_if_exception("ClientOSError")
1905     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1906     @unittest_run_loop
1907     async def test_blackd_line_length(self) -> None:
1908         response = await self.client.post(
1909             "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "7"}
1910         )
1911         self.assertEqual(response.status, 200)
1912
1913     @skip_if_exception("ClientOSError")
1914     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1915     @unittest_run_loop
1916     async def test_blackd_invalid_line_length(self) -> None:
1917         response = await self.client.post(
1918             "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "NaN"}
1919         )
1920         self.assertEqual(response.status, 400)
1921
1922     @skip_if_exception("ClientOSError")
1923     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1924     @unittest_run_loop
1925     async def test_blackd_response_black_version_header(self) -> None:
1926         response = await self.client.post("/")
1927         self.assertIsNotNone(response.headers.get(blackd.BLACK_VERSION_HEADER))
1928
1929
1930 if __name__ == "__main__":
1931     unittest.main(module="test_black")