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

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