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

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