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

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