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

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