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

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