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

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