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