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

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