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

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