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

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