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