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

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