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

66aa360c738a62b42f1a69aeb993c43b90ec9993
[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             report.check = False
782             report.diff = True
783             self.assertEqual(
784                 unstyle(str(report)),
785                 "2 files would be reformatted, 3 files would be left unchanged, "
786                 "2 files would fail to reformat.",
787             )
788
789     def test_report_quiet(self) -> None:
790         report = black.Report(quiet=True)
791         out_lines = []
792         err_lines = []
793
794         def out(msg: str, **kwargs: Any) -> None:
795             out_lines.append(msg)
796
797         def err(msg: str, **kwargs: Any) -> None:
798             err_lines.append(msg)
799
800         with patch("black.out", out), patch("black.err", err):
801             report.done(Path("f1"), black.Changed.NO)
802             self.assertEqual(len(out_lines), 0)
803             self.assertEqual(len(err_lines), 0)
804             self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
805             self.assertEqual(report.return_code, 0)
806             report.done(Path("f2"), black.Changed.YES)
807             self.assertEqual(len(out_lines), 0)
808             self.assertEqual(len(err_lines), 0)
809             self.assertEqual(
810                 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
811             )
812             report.done(Path("f3"), black.Changed.CACHED)
813             self.assertEqual(len(out_lines), 0)
814             self.assertEqual(len(err_lines), 0)
815             self.assertEqual(
816                 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
817             )
818             self.assertEqual(report.return_code, 0)
819             report.check = True
820             self.assertEqual(report.return_code, 1)
821             report.check = False
822             report.failed(Path("e1"), "boom")
823             self.assertEqual(len(out_lines), 0)
824             self.assertEqual(len(err_lines), 1)
825             self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
826             self.assertEqual(
827                 unstyle(str(report)),
828                 "1 file reformatted, 2 files left unchanged, "
829                 "1 file failed to reformat.",
830             )
831             self.assertEqual(report.return_code, 123)
832             report.done(Path("f3"), black.Changed.YES)
833             self.assertEqual(len(out_lines), 0)
834             self.assertEqual(len(err_lines), 1)
835             self.assertEqual(
836                 unstyle(str(report)),
837                 "2 files reformatted, 2 files left unchanged, "
838                 "1 file failed to reformat.",
839             )
840             self.assertEqual(report.return_code, 123)
841             report.failed(Path("e2"), "boom")
842             self.assertEqual(len(out_lines), 0)
843             self.assertEqual(len(err_lines), 2)
844             self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
845             self.assertEqual(
846                 unstyle(str(report)),
847                 "2 files reformatted, 2 files left unchanged, "
848                 "2 files failed to reformat.",
849             )
850             self.assertEqual(report.return_code, 123)
851             report.path_ignored(Path("wat"), "no match")
852             self.assertEqual(len(out_lines), 0)
853             self.assertEqual(len(err_lines), 2)
854             self.assertEqual(
855                 unstyle(str(report)),
856                 "2 files reformatted, 2 files left unchanged, "
857                 "2 files failed to reformat.",
858             )
859             self.assertEqual(report.return_code, 123)
860             report.done(Path("f4"), black.Changed.NO)
861             self.assertEqual(len(out_lines), 0)
862             self.assertEqual(len(err_lines), 2)
863             self.assertEqual(
864                 unstyle(str(report)),
865                 "2 files reformatted, 3 files left unchanged, "
866                 "2 files failed to reformat.",
867             )
868             self.assertEqual(report.return_code, 123)
869             report.check = True
870             self.assertEqual(
871                 unstyle(str(report)),
872                 "2 files would be reformatted, 3 files would be left unchanged, "
873                 "2 files would fail to reformat.",
874             )
875             report.check = False
876             report.diff = True
877             self.assertEqual(
878                 unstyle(str(report)),
879                 "2 files would be reformatted, 3 files would be left unchanged, "
880                 "2 files would fail to reformat.",
881             )
882
883     def test_report_normal(self) -> None:
884         report = black.Report()
885         out_lines = []
886         err_lines = []
887
888         def out(msg: str, **kwargs: Any) -> None:
889             out_lines.append(msg)
890
891         def err(msg: str, **kwargs: Any) -> None:
892             err_lines.append(msg)
893
894         with patch("black.out", out), patch("black.err", err):
895             report.done(Path("f1"), black.Changed.NO)
896             self.assertEqual(len(out_lines), 0)
897             self.assertEqual(len(err_lines), 0)
898             self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
899             self.assertEqual(report.return_code, 0)
900             report.done(Path("f2"), black.Changed.YES)
901             self.assertEqual(len(out_lines), 1)
902             self.assertEqual(len(err_lines), 0)
903             self.assertEqual(out_lines[-1], "reformatted f2")
904             self.assertEqual(
905                 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
906             )
907             report.done(Path("f3"), black.Changed.CACHED)
908             self.assertEqual(len(out_lines), 1)
909             self.assertEqual(len(err_lines), 0)
910             self.assertEqual(out_lines[-1], "reformatted f2")
911             self.assertEqual(
912                 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
913             )
914             self.assertEqual(report.return_code, 0)
915             report.check = True
916             self.assertEqual(report.return_code, 1)
917             report.check = False
918             report.failed(Path("e1"), "boom")
919             self.assertEqual(len(out_lines), 1)
920             self.assertEqual(len(err_lines), 1)
921             self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
922             self.assertEqual(
923                 unstyle(str(report)),
924                 "1 file reformatted, 2 files left unchanged, "
925                 "1 file failed to reformat.",
926             )
927             self.assertEqual(report.return_code, 123)
928             report.done(Path("f3"), black.Changed.YES)
929             self.assertEqual(len(out_lines), 2)
930             self.assertEqual(len(err_lines), 1)
931             self.assertEqual(out_lines[-1], "reformatted f3")
932             self.assertEqual(
933                 unstyle(str(report)),
934                 "2 files reformatted, 2 files left unchanged, "
935                 "1 file failed to reformat.",
936             )
937             self.assertEqual(report.return_code, 123)
938             report.failed(Path("e2"), "boom")
939             self.assertEqual(len(out_lines), 2)
940             self.assertEqual(len(err_lines), 2)
941             self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
942             self.assertEqual(
943                 unstyle(str(report)),
944                 "2 files reformatted, 2 files left unchanged, "
945                 "2 files failed to reformat.",
946             )
947             self.assertEqual(report.return_code, 123)
948             report.path_ignored(Path("wat"), "no match")
949             self.assertEqual(len(out_lines), 2)
950             self.assertEqual(len(err_lines), 2)
951             self.assertEqual(
952                 unstyle(str(report)),
953                 "2 files reformatted, 2 files left unchanged, "
954                 "2 files failed to reformat.",
955             )
956             self.assertEqual(report.return_code, 123)
957             report.done(Path("f4"), black.Changed.NO)
958             self.assertEqual(len(out_lines), 2)
959             self.assertEqual(len(err_lines), 2)
960             self.assertEqual(
961                 unstyle(str(report)),
962                 "2 files reformatted, 3 files left unchanged, "
963                 "2 files failed to reformat.",
964             )
965             self.assertEqual(report.return_code, 123)
966             report.check = True
967             self.assertEqual(
968                 unstyle(str(report)),
969                 "2 files would be reformatted, 3 files would be left unchanged, "
970                 "2 files would fail to reformat.",
971             )
972             report.check = False
973             report.diff = True
974             self.assertEqual(
975                 unstyle(str(report)),
976                 "2 files would be reformatted, 3 files would be left unchanged, "
977                 "2 files would fail to reformat.",
978             )
979
980     def test_lib2to3_parse(self) -> None:
981         with self.assertRaises(black.InvalidInput):
982             black.lib2to3_parse("invalid syntax")
983
984         straddling = "x + y"
985         black.lib2to3_parse(straddling)
986         black.lib2to3_parse(straddling, {TargetVersion.PY27})
987         black.lib2to3_parse(straddling, {TargetVersion.PY36})
988         black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
989
990         py2_only = "print x"
991         black.lib2to3_parse(py2_only)
992         black.lib2to3_parse(py2_only, {TargetVersion.PY27})
993         with self.assertRaises(black.InvalidInput):
994             black.lib2to3_parse(py2_only, {TargetVersion.PY36})
995         with self.assertRaises(black.InvalidInput):
996             black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
997
998         py3_only = "exec(x, end=y)"
999         black.lib2to3_parse(py3_only)
1000         with self.assertRaises(black.InvalidInput):
1001             black.lib2to3_parse(py3_only, {TargetVersion.PY27})
1002         black.lib2to3_parse(py3_only, {TargetVersion.PY36})
1003         black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
1004
1005     def test_get_features_used(self) -> None:
1006         node = black.lib2to3_parse("def f(*, arg): ...\n")
1007         self.assertEqual(black.get_features_used(node), set())
1008         node = black.lib2to3_parse("def f(*, arg,): ...\n")
1009         self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
1010         node = black.lib2to3_parse("f(*arg,)\n")
1011         self.assertEqual(
1012             black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
1013         )
1014         node = black.lib2to3_parse("def f(*, arg): f'string'\n")
1015         self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
1016         node = black.lib2to3_parse("123_456\n")
1017         self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
1018         node = black.lib2to3_parse("123456\n")
1019         self.assertEqual(black.get_features_used(node), set())
1020         source, expected = read_data("function")
1021         node = black.lib2to3_parse(source)
1022         expected_features = {
1023             Feature.TRAILING_COMMA_IN_CALL,
1024             Feature.TRAILING_COMMA_IN_DEF,
1025             Feature.F_STRINGS,
1026         }
1027         self.assertEqual(black.get_features_used(node), expected_features)
1028         node = black.lib2to3_parse(expected)
1029         self.assertEqual(black.get_features_used(node), expected_features)
1030         source, expected = read_data("expression")
1031         node = black.lib2to3_parse(source)
1032         self.assertEqual(black.get_features_used(node), set())
1033         node = black.lib2to3_parse(expected)
1034         self.assertEqual(black.get_features_used(node), set())
1035
1036     def test_get_future_imports(self) -> None:
1037         node = black.lib2to3_parse("\n")
1038         self.assertEqual(set(), black.get_future_imports(node))
1039         node = black.lib2to3_parse("from __future__ import black\n")
1040         self.assertEqual({"black"}, black.get_future_imports(node))
1041         node = black.lib2to3_parse("from __future__ import multiple, imports\n")
1042         self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1043         node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
1044         self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
1045         node = black.lib2to3_parse(
1046             "from __future__ import multiple\nfrom __future__ import imports\n"
1047         )
1048         self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1049         node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
1050         self.assertEqual({"black"}, black.get_future_imports(node))
1051         node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
1052         self.assertEqual({"black"}, black.get_future_imports(node))
1053         node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
1054         self.assertEqual(set(), black.get_future_imports(node))
1055         node = black.lib2to3_parse("from some.module import black\n")
1056         self.assertEqual(set(), black.get_future_imports(node))
1057         node = black.lib2to3_parse(
1058             "from __future__ import unicode_literals as _unicode_literals"
1059         )
1060         self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
1061         node = black.lib2to3_parse(
1062             "from __future__ import unicode_literals as _lol, print"
1063         )
1064         self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
1065
1066     def test_debug_visitor(self) -> None:
1067         source, _ = read_data("debug_visitor.py")
1068         expected, _ = read_data("debug_visitor.out")
1069         out_lines = []
1070         err_lines = []
1071
1072         def out(msg: str, **kwargs: Any) -> None:
1073             out_lines.append(msg)
1074
1075         def err(msg: str, **kwargs: Any) -> None:
1076             err_lines.append(msg)
1077
1078         with patch("black.out", out), patch("black.err", err):
1079             black.DebugVisitor.show(source)
1080         actual = "\n".join(out_lines) + "\n"
1081         log_name = ""
1082         if expected != actual:
1083             log_name = black.dump_to_file(*out_lines)
1084         self.assertEqual(
1085             expected,
1086             actual,
1087             f"AST print out is different. Actual version dumped to {log_name}",
1088         )
1089
1090     def test_format_file_contents(self) -> None:
1091         empty = ""
1092         mode = black.FileMode()
1093         with self.assertRaises(black.NothingChanged):
1094             black.format_file_contents(empty, mode=mode, fast=False)
1095         just_nl = "\n"
1096         with self.assertRaises(black.NothingChanged):
1097             black.format_file_contents(just_nl, mode=mode, fast=False)
1098         same = "j = [1, 2, 3]\n"
1099         with self.assertRaises(black.NothingChanged):
1100             black.format_file_contents(same, mode=mode, fast=False)
1101         different = "j = [1,2,3]"
1102         expected = same
1103         actual = black.format_file_contents(different, mode=mode, fast=False)
1104         self.assertEqual(expected, actual)
1105         invalid = "return if you can"
1106         with self.assertRaises(black.InvalidInput) as e:
1107             black.format_file_contents(invalid, mode=mode, fast=False)
1108         self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
1109
1110     def test_endmarker(self) -> None:
1111         n = black.lib2to3_parse("\n")
1112         self.assertEqual(n.type, black.syms.file_input)
1113         self.assertEqual(len(n.children), 1)
1114         self.assertEqual(n.children[0].type, black.token.ENDMARKER)
1115
1116     @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
1117     def test_assertFormatEqual(self) -> None:
1118         out_lines = []
1119         err_lines = []
1120
1121         def out(msg: str, **kwargs: Any) -> None:
1122             out_lines.append(msg)
1123
1124         def err(msg: str, **kwargs: Any) -> None:
1125             err_lines.append(msg)
1126
1127         with patch("black.out", out), patch("black.err", err):
1128             with self.assertRaises(AssertionError):
1129                 self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")
1130
1131         out_str = "".join(out_lines)
1132         self.assertTrue("Expected tree:" in out_str)
1133         self.assertTrue("Actual tree:" in out_str)
1134         self.assertEqual("".join(err_lines), "")
1135
1136     def test_cache_broken_file(self) -> None:
1137         mode = black.FileMode()
1138         with cache_dir() as workspace:
1139             cache_file = black.get_cache_file(mode)
1140             with cache_file.open("w") as fobj:
1141                 fobj.write("this is not a pickle")
1142             self.assertEqual(black.read_cache(mode), {})
1143             src = (workspace / "test.py").resolve()
1144             with src.open("w") as fobj:
1145                 fobj.write("print('hello')")
1146             self.invokeBlack([str(src)])
1147             cache = black.read_cache(mode)
1148             self.assertIn(src, cache)
1149
1150     def test_cache_single_file_already_cached(self) -> None:
1151         mode = black.FileMode()
1152         with cache_dir() as workspace:
1153             src = (workspace / "test.py").resolve()
1154             with src.open("w") as fobj:
1155                 fobj.write("print('hello')")
1156             black.write_cache({}, [src], mode)
1157             self.invokeBlack([str(src)])
1158             with src.open("r") as fobj:
1159                 self.assertEqual(fobj.read(), "print('hello')")
1160
1161     @event_loop(close=False)
1162     def test_cache_multiple_files(self) -> None:
1163         mode = black.FileMode()
1164         with cache_dir() as workspace, patch(
1165             "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1166         ):
1167             one = (workspace / "one.py").resolve()
1168             with one.open("w") as fobj:
1169                 fobj.write("print('hello')")
1170             two = (workspace / "two.py").resolve()
1171             with two.open("w") as fobj:
1172                 fobj.write("print('hello')")
1173             black.write_cache({}, [one], mode)
1174             self.invokeBlack([str(workspace)])
1175             with one.open("r") as fobj:
1176                 self.assertEqual(fobj.read(), "print('hello')")
1177             with two.open("r") as fobj:
1178                 self.assertEqual(fobj.read(), 'print("hello")\n')
1179             cache = black.read_cache(mode)
1180             self.assertIn(one, cache)
1181             self.assertIn(two, cache)
1182
1183     def test_no_cache_when_writeback_diff(self) -> None:
1184         mode = black.FileMode()
1185         with cache_dir() as workspace:
1186             src = (workspace / "test.py").resolve()
1187             with src.open("w") as fobj:
1188                 fobj.write("print('hello')")
1189             self.invokeBlack([str(src), "--diff"])
1190             cache_file = black.get_cache_file(mode)
1191             self.assertFalse(cache_file.exists())
1192
1193     def test_no_cache_when_stdin(self) -> None:
1194         mode = black.FileMode()
1195         with cache_dir():
1196             result = CliRunner().invoke(
1197                 black.main, ["-"], input=BytesIO(b"print('hello')")
1198             )
1199             self.assertEqual(result.exit_code, 0)
1200             cache_file = black.get_cache_file(mode)
1201             self.assertFalse(cache_file.exists())
1202
1203     def test_read_cache_no_cachefile(self) -> None:
1204         mode = black.FileMode()
1205         with cache_dir():
1206             self.assertEqual(black.read_cache(mode), {})
1207
1208     def test_write_cache_read_cache(self) -> None:
1209         mode = black.FileMode()
1210         with cache_dir() as workspace:
1211             src = (workspace / "test.py").resolve()
1212             src.touch()
1213             black.write_cache({}, [src], mode)
1214             cache = black.read_cache(mode)
1215             self.assertIn(src, cache)
1216             self.assertEqual(cache[src], black.get_cache_info(src))
1217
1218     def test_filter_cached(self) -> None:
1219         with TemporaryDirectory() as workspace:
1220             path = Path(workspace)
1221             uncached = (path / "uncached").resolve()
1222             cached = (path / "cached").resolve()
1223             cached_but_changed = (path / "changed").resolve()
1224             uncached.touch()
1225             cached.touch()
1226             cached_but_changed.touch()
1227             cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
1228             todo, done = black.filter_cached(
1229                 cache, {uncached, cached, cached_but_changed}
1230             )
1231             self.assertEqual(todo, {uncached, cached_but_changed})
1232             self.assertEqual(done, {cached})
1233
1234     def test_write_cache_creates_directory_if_needed(self) -> None:
1235         mode = black.FileMode()
1236         with cache_dir(exists=False) as workspace:
1237             self.assertFalse(workspace.exists())
1238             black.write_cache({}, [], mode)
1239             self.assertTrue(workspace.exists())
1240
1241     @event_loop(close=False)
1242     def test_failed_formatting_does_not_get_cached(self) -> None:
1243         mode = black.FileMode()
1244         with cache_dir() as workspace, patch(
1245             "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1246         ):
1247             failing = (workspace / "failing.py").resolve()
1248             with failing.open("w") as fobj:
1249                 fobj.write("not actually python")
1250             clean = (workspace / "clean.py").resolve()
1251             with clean.open("w") as fobj:
1252                 fobj.write('print("hello")\n')
1253             self.invokeBlack([str(workspace)], exit_code=123)
1254             cache = black.read_cache(mode)
1255             self.assertNotIn(failing, cache)
1256             self.assertIn(clean, cache)
1257
1258     def test_write_cache_write_fail(self) -> None:
1259         mode = black.FileMode()
1260         with cache_dir(), patch.object(Path, "open") as mock:
1261             mock.side_effect = OSError
1262             black.write_cache({}, [], mode)
1263
1264     @event_loop(close=False)
1265     def test_check_diff_use_together(self) -> None:
1266         with cache_dir():
1267             # Files which will be reformatted.
1268             src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1269             self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1270             # Files which will not be reformatted.
1271             src2 = (THIS_DIR / "data" / "composition.py").resolve()
1272             self.invokeBlack([str(src2), "--diff", "--check"])
1273             # Multi file command.
1274             self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1275
1276     def test_no_files(self) -> None:
1277         with cache_dir():
1278             # Without an argument, black exits with error code 0.
1279             self.invokeBlack([])
1280
1281     def test_broken_symlink(self) -> None:
1282         with cache_dir() as workspace:
1283             symlink = workspace / "broken_link.py"
1284             try:
1285                 symlink.symlink_to("nonexistent.py")
1286             except OSError as e:
1287                 self.skipTest(f"Can't create symlinks: {e}")
1288             self.invokeBlack([str(workspace.resolve())])
1289
1290     def test_read_cache_line_lengths(self) -> None:
1291         mode = black.FileMode()
1292         short_mode = black.FileMode(line_length=1)
1293         with cache_dir() as workspace:
1294             path = (workspace / "file.py").resolve()
1295             path.touch()
1296             black.write_cache({}, [path], mode)
1297             one = black.read_cache(mode)
1298             self.assertIn(path, one)
1299             two = black.read_cache(short_mode)
1300             self.assertNotIn(path, two)
1301
1302     def test_tricky_unicode_symbols(self) -> None:
1303         source, expected = read_data("tricky_unicode_symbols")
1304         actual = fs(source)
1305         self.assertFormatEqual(expected, actual)
1306         black.assert_equivalent(source, actual)
1307         black.assert_stable(source, actual, black.FileMode())
1308
1309     def test_single_file_force_pyi(self) -> None:
1310         reg_mode = black.FileMode()
1311         pyi_mode = black.FileMode(is_pyi=True)
1312         contents, expected = read_data("force_pyi")
1313         with cache_dir() as workspace:
1314             path = (workspace / "file.py").resolve()
1315             with open(path, "w") as fh:
1316                 fh.write(contents)
1317             self.invokeBlack([str(path), "--pyi"])
1318             with open(path, "r") as fh:
1319                 actual = fh.read()
1320             # verify cache with --pyi is separate
1321             pyi_cache = black.read_cache(pyi_mode)
1322             self.assertIn(path, pyi_cache)
1323             normal_cache = black.read_cache(reg_mode)
1324             self.assertNotIn(path, normal_cache)
1325         self.assertEqual(actual, expected)
1326
1327     @event_loop(close=False)
1328     def test_multi_file_force_pyi(self) -> None:
1329         reg_mode = black.FileMode()
1330         pyi_mode = black.FileMode(is_pyi=True)
1331         contents, expected = read_data("force_pyi")
1332         with cache_dir() as workspace:
1333             paths = [
1334                 (workspace / "file1.py").resolve(),
1335                 (workspace / "file2.py").resolve(),
1336             ]
1337             for path in paths:
1338                 with open(path, "w") as fh:
1339                     fh.write(contents)
1340             self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1341             for path in paths:
1342                 with open(path, "r") as fh:
1343                     actual = fh.read()
1344                 self.assertEqual(actual, expected)
1345             # verify cache with --pyi is separate
1346             pyi_cache = black.read_cache(pyi_mode)
1347             normal_cache = black.read_cache(reg_mode)
1348             for path in paths:
1349                 self.assertIn(path, pyi_cache)
1350                 self.assertNotIn(path, normal_cache)
1351
1352     def test_pipe_force_pyi(self) -> None:
1353         source, expected = read_data("force_pyi")
1354         result = CliRunner().invoke(
1355             black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1356         )
1357         self.assertEqual(result.exit_code, 0)
1358         actual = result.output
1359         self.assertFormatEqual(actual, expected)
1360
1361     def test_single_file_force_py36(self) -> None:
1362         reg_mode = black.FileMode()
1363         py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1364         source, expected = read_data("force_py36")
1365         with cache_dir() as workspace:
1366             path = (workspace / "file.py").resolve()
1367             with open(path, "w") as fh:
1368                 fh.write(source)
1369             self.invokeBlack([str(path), *PY36_ARGS])
1370             with open(path, "r") as fh:
1371                 actual = fh.read()
1372             # verify cache with --target-version is separate
1373             py36_cache = black.read_cache(py36_mode)
1374             self.assertIn(path, py36_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_py36(self) -> None:
1381         reg_mode = black.FileMode()
1382         py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1383         source, expected = read_data("force_py36")
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(source)
1392             self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
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 --target-version is separate
1398             pyi_cache = black.read_cache(py36_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_collections(self) -> None:
1405         source, expected = read_data("collections")
1406         actual = fs(source)
1407         self.assertFormatEqual(expected, actual)
1408         black.assert_equivalent(source, actual)
1409         black.assert_stable(source, actual, black.FileMode())
1410
1411     def test_pipe_force_py36(self) -> None:
1412         source, expected = read_data("force_py36")
1413         result = CliRunner().invoke(
1414             black.main,
1415             ["-", "-q", "--target-version=py36"],
1416             input=BytesIO(source.encode("utf8")),
1417         )
1418         self.assertEqual(result.exit_code, 0)
1419         actual = result.output
1420         self.assertFormatEqual(actual, expected)
1421
1422     def test_include_exclude(self) -> None:
1423         path = THIS_DIR / "data" / "include_exclude_tests"
1424         include = re.compile(r"\.pyi?$")
1425         exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1426         report = black.Report()
1427         gitignore = PathSpec.from_lines("gitwildmatch", [])
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_gitignore_exclude(self) -> None:
1442         path = THIS_DIR / "data" / "include_exclude_tests"
1443         include = re.compile(r"\.pyi?$")
1444         exclude = re.compile(r"")
1445         report = black.Report()
1446         gitignore = PathSpec.from_lines(
1447             "gitwildmatch", ["exclude/", ".definitely_exclude"]
1448         )
1449         sources: List[Path] = []
1450         expected = [
1451             Path(path / "b/dont_exclude/a.py"),
1452             Path(path / "b/dont_exclude/a.pyi"),
1453         ]
1454         this_abs = THIS_DIR.resolve()
1455         sources.extend(
1456             black.gen_python_files_in_dir(
1457                 path, this_abs, include, exclude, report, gitignore
1458             )
1459         )
1460         self.assertEqual(sorted(expected), sorted(sources))
1461
1462     def test_empty_include(self) -> None:
1463         path = THIS_DIR / "data" / "include_exclude_tests"
1464         report = black.Report()
1465         gitignore = PathSpec.from_lines("gitwildmatch", [])
1466         empty = re.compile(r"")
1467         sources: List[Path] = []
1468         expected = [
1469             Path(path / "b/exclude/a.pie"),
1470             Path(path / "b/exclude/a.py"),
1471             Path(path / "b/exclude/a.pyi"),
1472             Path(path / "b/dont_exclude/a.pie"),
1473             Path(path / "b/dont_exclude/a.py"),
1474             Path(path / "b/dont_exclude/a.pyi"),
1475             Path(path / "b/.definitely_exclude/a.pie"),
1476             Path(path / "b/.definitely_exclude/a.py"),
1477             Path(path / "b/.definitely_exclude/a.pyi"),
1478         ]
1479         this_abs = THIS_DIR.resolve()
1480         sources.extend(
1481             black.gen_python_files_in_dir(
1482                 path,
1483                 this_abs,
1484                 empty,
1485                 re.compile(black.DEFAULT_EXCLUDES),
1486                 report,
1487                 gitignore,
1488             )
1489         )
1490         self.assertEqual(sorted(expected), sorted(sources))
1491
1492     def test_empty_exclude(self) -> None:
1493         path = THIS_DIR / "data" / "include_exclude_tests"
1494         report = black.Report()
1495         gitignore = PathSpec.from_lines("gitwildmatch", [])
1496         empty = re.compile(r"")
1497         sources: List[Path] = []
1498         expected = [
1499             Path(path / "b/dont_exclude/a.py"),
1500             Path(path / "b/dont_exclude/a.pyi"),
1501             Path(path / "b/exclude/a.py"),
1502             Path(path / "b/exclude/a.pyi"),
1503             Path(path / "b/.definitely_exclude/a.py"),
1504             Path(path / "b/.definitely_exclude/a.pyi"),
1505         ]
1506         this_abs = THIS_DIR.resolve()
1507         sources.extend(
1508             black.gen_python_files_in_dir(
1509                 path,
1510                 this_abs,
1511                 re.compile(black.DEFAULT_INCLUDES),
1512                 empty,
1513                 report,
1514                 gitignore,
1515             )
1516         )
1517         self.assertEqual(sorted(expected), sorted(sources))
1518
1519     def test_invalid_include_exclude(self) -> None:
1520         for option in ["--include", "--exclude"]:
1521             self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1522
1523     def test_preserves_line_endings(self) -> None:
1524         with TemporaryDirectory() as workspace:
1525             test_file = Path(workspace) / "test.py"
1526             for nl in ["\n", "\r\n"]:
1527                 contents = nl.join(["def f(  ):", "    pass"])
1528                 test_file.write_bytes(contents.encode())
1529                 ff(test_file, write_back=black.WriteBack.YES)
1530                 updated_contents: bytes = test_file.read_bytes()
1531                 self.assertIn(nl.encode(), updated_contents)
1532                 if nl == "\n":
1533                     self.assertNotIn(b"\r\n", updated_contents)
1534
1535     def test_preserves_line_endings_via_stdin(self) -> None:
1536         for nl in ["\n", "\r\n"]:
1537             contents = nl.join(["def f(  ):", "    pass"])
1538             runner = BlackRunner()
1539             result = runner.invoke(
1540                 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1541             )
1542             self.assertEqual(result.exit_code, 0)
1543             output = runner.stdout_bytes
1544             self.assertIn(nl.encode("utf8"), output)
1545             if nl == "\n":
1546                 self.assertNotIn(b"\r\n", output)
1547
1548     def test_assert_equivalent_different_asts(self) -> None:
1549         with self.assertRaises(AssertionError):
1550             black.assert_equivalent("{}", "None")
1551
1552     def test_symlink_out_of_root_directory(self) -> None:
1553         path = MagicMock()
1554         root = THIS_DIR
1555         child = MagicMock()
1556         include = re.compile(black.DEFAULT_INCLUDES)
1557         exclude = re.compile(black.DEFAULT_EXCLUDES)
1558         report = black.Report()
1559         gitignore = PathSpec.from_lines("gitwildmatch", [])
1560         # `child` should behave like a symlink which resolved path is clearly
1561         # outside of the `root` directory.
1562         path.iterdir.return_value = [child]
1563         child.resolve.return_value = Path("/a/b/c")
1564         child.as_posix.return_value = "/a/b/c"
1565         child.is_symlink.return_value = True
1566         try:
1567             list(
1568                 black.gen_python_files_in_dir(
1569                     path, root, include, exclude, report, gitignore
1570                 )
1571             )
1572         except ValueError as ve:
1573             self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1574         path.iterdir.assert_called_once()
1575         child.resolve.assert_called_once()
1576         child.is_symlink.assert_called_once()
1577         # `child` should behave like a strange file which resolved path is clearly
1578         # outside of the `root` directory.
1579         child.is_symlink.return_value = False
1580         with self.assertRaises(ValueError):
1581             list(
1582                 black.gen_python_files_in_dir(
1583                     path, root, include, exclude, report, gitignore
1584                 )
1585             )
1586         path.iterdir.assert_called()
1587         self.assertEqual(path.iterdir.call_count, 2)
1588         child.resolve.assert_called()
1589         self.assertEqual(child.resolve.call_count, 2)
1590         child.is_symlink.assert_called()
1591         self.assertEqual(child.is_symlink.call_count, 2)
1592
1593     def test_shhh_click(self) -> None:
1594         try:
1595             from click import _unicodefun  # type: ignore
1596         except ModuleNotFoundError:
1597             self.skipTest("Incompatible Click version")
1598         if not hasattr(_unicodefun, "_verify_python3_env"):
1599             self.skipTest("Incompatible Click version")
1600         # First, let's see if Click is crashing with a preferred ASCII charset.
1601         with patch("locale.getpreferredencoding") as gpe:
1602             gpe.return_value = "ASCII"
1603             with self.assertRaises(RuntimeError):
1604                 _unicodefun._verify_python3_env()
1605         # Now, let's silence Click...
1606         black.patch_click()
1607         # ...and confirm it's silent.
1608         with patch("locale.getpreferredencoding") as gpe:
1609             gpe.return_value = "ASCII"
1610             try:
1611                 _unicodefun._verify_python3_env()
1612             except RuntimeError as re:
1613                 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1614
1615     def test_root_logger_not_used_directly(self) -> None:
1616         def fail(*args: Any, **kwargs: Any) -> None:
1617             self.fail("Record created with root logger")
1618
1619         with patch.multiple(
1620             logging.root,
1621             debug=fail,
1622             info=fail,
1623             warning=fail,
1624             error=fail,
1625             critical=fail,
1626             log=fail,
1627         ):
1628             ff(THIS_FILE)
1629
1630     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1631     def test_blackd_main(self) -> None:
1632         with patch("blackd.web.run_app"):
1633             result = CliRunner().invoke(blackd.main, [])
1634             if result.exception is not None:
1635                 raise result.exception
1636             self.assertEqual(result.exit_code, 0)
1637
1638
1639 class BlackDTestCase(AioHTTPTestCase):
1640     async def get_application(self) -> web.Application:
1641         return blackd.make_app()
1642
1643     # TODO: remove these decorators once the below is released
1644     # https://github.com/aio-libs/aiohttp/pull/3727
1645     @skip_if_exception("ClientOSError")
1646     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1647     @unittest_run_loop
1648     async def test_blackd_request_needs_formatting(self) -> None:
1649         response = await self.client.post("/", data=b"print('hello world')")
1650         self.assertEqual(response.status, 200)
1651         self.assertEqual(response.charset, "utf8")
1652         self.assertEqual(await response.read(), b'print("hello world")\n')
1653
1654     @skip_if_exception("ClientOSError")
1655     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1656     @unittest_run_loop
1657     async def test_blackd_request_no_change(self) -> None:
1658         response = await self.client.post("/", data=b'print("hello world")\n')
1659         self.assertEqual(response.status, 204)
1660         self.assertEqual(await response.read(), b"")
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_request_syntax_error(self) -> None:
1666         response = await self.client.post("/", data=b"what even ( is")
1667         self.assertEqual(response.status, 400)
1668         content = await response.text()
1669         self.assertTrue(
1670             content.startswith("Cannot parse"),
1671             msg=f"Expected error to start with 'Cannot parse', got {repr(content)}",
1672         )
1673
1674     @skip_if_exception("ClientOSError")
1675     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1676     @unittest_run_loop
1677     async def test_blackd_unsupported_version(self) -> None:
1678         response = await self.client.post(
1679             "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "2"}
1680         )
1681         self.assertEqual(response.status, 501)
1682
1683     @skip_if_exception("ClientOSError")
1684     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1685     @unittest_run_loop
1686     async def test_blackd_supported_version(self) -> None:
1687         response = await self.client.post(
1688             "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "1"}
1689         )
1690         self.assertEqual(response.status, 200)
1691
1692     @skip_if_exception("ClientOSError")
1693     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1694     @unittest_run_loop
1695     async def test_blackd_invalid_python_variant(self) -> None:
1696         async def check(header_value: str, expected_status: int = 400) -> None:
1697             response = await self.client.post(
1698                 "/", data=b"what", headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1699             )
1700             self.assertEqual(response.status, expected_status)
1701
1702         await check("lol")
1703         await check("ruby3.5")
1704         await check("pyi3.6")
1705         await check("py1.5")
1706         await check("2.8")
1707         await check("py2.8")
1708         await check("3.0")
1709         await check("pypy3.0")
1710         await check("jython3.4")
1711
1712     @skip_if_exception("ClientOSError")
1713     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1714     @unittest_run_loop
1715     async def test_blackd_pyi(self) -> None:
1716         source, expected = read_data("stub.pyi")
1717         response = await self.client.post(
1718             "/", data=source, headers={blackd.PYTHON_VARIANT_HEADER: "pyi"}
1719         )
1720         self.assertEqual(response.status, 200)
1721         self.assertEqual(await response.text(), 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_diff(self) -> None:
1727         diff_header = re.compile(
1728             rf"(In|Out)\t\d\d\d\d-\d\d-\d\d "
1729             rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
1730         )
1731
1732         source, _ = read_data("blackd_diff.py")
1733         expected, _ = read_data("blackd_diff.diff")
1734
1735         response = await self.client.post(
1736             "/", data=source, headers={blackd.DIFF_HEADER: "true"}
1737         )
1738         self.assertEqual(response.status, 200)
1739
1740         actual = await response.text()
1741         actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
1742         self.assertEqual(actual, expected)
1743
1744     @skip_if_exception("ClientOSError")
1745     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1746     @unittest_run_loop
1747     async def test_blackd_python_variant(self) -> None:
1748         code = (
1749             "def f(\n"
1750             "    and_has_a_bunch_of,\n"
1751             "    very_long_arguments_too,\n"
1752             "    and_lots_of_them_as_well_lol,\n"
1753             "    **and_very_long_keyword_arguments\n"
1754             "):\n"
1755             "    pass\n"
1756         )
1757
1758         async def check(header_value: str, expected_status: int) -> None:
1759             response = await self.client.post(
1760                 "/", data=code, headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1761             )
1762             self.assertEqual(
1763                 response.status, expected_status, msg=await response.text()
1764             )
1765
1766         await check("3.6", 200)
1767         await check("py3.6", 200)
1768         await check("3.6,3.7", 200)
1769         await check("3.6,py3.7", 200)
1770         await check("py36,py37", 200)
1771         await check("36", 200)
1772         await check("3.6.4", 200)
1773
1774         await check("2", 204)
1775         await check("2.7", 204)
1776         await check("py2.7", 204)
1777         await check("3.4", 204)
1778         await check("py3.4", 204)
1779         await check("py34,py36", 204)
1780         await check("34", 204)
1781
1782     @skip_if_exception("ClientOSError")
1783     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1784     @unittest_run_loop
1785     async def test_blackd_line_length(self) -> None:
1786         response = await self.client.post(
1787             "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "7"}
1788         )
1789         self.assertEqual(response.status, 200)
1790
1791     @skip_if_exception("ClientOSError")
1792     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1793     @unittest_run_loop
1794     async def test_blackd_invalid_line_length(self) -> None:
1795         response = await self.client.post(
1796             "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "NaN"}
1797         )
1798         self.assertEqual(response.status, 400)
1799
1800     @skip_if_exception("ClientOSError")
1801     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1802     @unittest_run_loop
1803     async def test_blackd_response_black_version_header(self) -> None:
1804         response = await self.client.post("/")
1805         self.assertIsNotNone(response.headers.get(blackd.BLACK_VERSION_HEADER))
1806
1807
1808 if __name__ == "__main__":
1809     unittest.main(module="test_black")