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

7927b368b5d0fc7d7141875db1933175b261b240
[etc/vim.git] / tests / test_black.py
1 #!/usr/bin/env python3
2 import multiprocessing
3 import asyncio
4 import logging
5 from concurrent.futures import ThreadPoolExecutor
6 from contextlib import contextmanager
7 from dataclasses import replace
8 from functools import partial
9 import inspect
10 from io import BytesIO, TextIOWrapper
11 import os
12 from pathlib import Path
13 from platform import system
14 import regex as re
15 import sys
16 from tempfile import TemporaryDirectory
17 import types
18 from typing import (
19     Any,
20     BinaryIO,
21     Callable,
22     Dict,
23     Generator,
24     List,
25     Iterator,
26     TypeVar,
27 )
28 import unittest
29 from unittest.mock import patch, MagicMock
30
31 import click
32 from click import unstyle
33 from click.testing import CliRunner
34
35 import black
36 from black import Feature, TargetVersion
37
38 from pathspec import PathSpec
39
40 # Import other test classes
41 from tests.util import THIS_DIR, read_data, DETERMINISTIC_HEADER
42 from .test_primer import PrimerCLITests  # noqa: F401
43
44
45 DEFAULT_MODE = black.FileMode(experimental_string_processing=True)
46 ff = partial(black.format_file_in_place, mode=DEFAULT_MODE, fast=True)
47 fs = partial(black.format_str, mode=DEFAULT_MODE)
48 THIS_FILE = Path(__file__)
49 PY36_VERSIONS = {
50     TargetVersion.PY36,
51     TargetVersion.PY37,
52     TargetVersion.PY38,
53     TargetVersion.PY39,
54 }
55 PY36_ARGS = [f"--target-version={version.name.lower()}" for version in PY36_VERSIONS]
56 T = TypeVar("T")
57 R = TypeVar("R")
58
59
60 def dump_to_stderr(*output: str) -> str:
61     return "\n" + "\n".join(output) + "\n"
62
63
64 @contextmanager
65 def cache_dir(exists: bool = True) -> Iterator[Path]:
66     with TemporaryDirectory() as workspace:
67         cache_dir = Path(workspace)
68         if not exists:
69             cache_dir = cache_dir / "new"
70         with patch("black.CACHE_DIR", cache_dir):
71             yield cache_dir
72
73
74 @contextmanager
75 def event_loop() -> Iterator[None]:
76     policy = asyncio.get_event_loop_policy()
77     loop = policy.new_event_loop()
78     asyncio.set_event_loop(loop)
79     try:
80         yield
81
82     finally:
83         loop.close()
84
85
86 class FakeContext(click.Context):
87     """A fake click Context for when calling functions that need it."""
88
89     def __init__(self) -> None:
90         self.default_map: Dict[str, Any] = {}
91
92
93 class FakeParameter(click.Parameter):
94     """A fake click Parameter for when calling functions that need it."""
95
96     def __init__(self) -> None:
97         pass
98
99
100 class BlackRunner(CliRunner):
101     """Modify CliRunner so that stderr is not merged with stdout.
102
103     This is a hack that can be removed once we depend on Click 7.x"""
104
105     def __init__(self) -> None:
106         self.stderrbuf = BytesIO()
107         self.stdoutbuf = BytesIO()
108         self.stdout_bytes = b""
109         self.stderr_bytes = b""
110         super().__init__()
111
112     @contextmanager
113     def isolation(self, *args: Any, **kwargs: Any) -> Generator[BinaryIO, None, None]:
114         with super().isolation(*args, **kwargs) as output:
115             try:
116                 hold_stderr = sys.stderr
117                 sys.stderr = TextIOWrapper(self.stderrbuf, encoding=self.charset)
118                 yield output
119             finally:
120                 self.stdout_bytes = sys.stdout.buffer.getvalue()  # type: ignore
121                 self.stderr_bytes = sys.stderr.buffer.getvalue()  # type: ignore
122                 sys.stderr = hold_stderr
123
124
125 class BlackTestCase(unittest.TestCase):
126     maxDiff = None
127     _diffThreshold = 2 ** 20
128
129     def assertFormatEqual(self, expected: str, actual: str) -> None:
130         if actual != expected and not os.environ.get("SKIP_AST_PRINT"):
131             bdv: black.DebugVisitor[Any]
132             black.out("Expected tree:", fg="green")
133             try:
134                 exp_node = black.lib2to3_parse(expected)
135                 bdv = black.DebugVisitor()
136                 list(bdv.visit(exp_node))
137             except Exception as ve:
138                 black.err(str(ve))
139             black.out("Actual tree:", fg="red")
140             try:
141                 exp_node = black.lib2to3_parse(actual)
142                 bdv = black.DebugVisitor()
143                 list(bdv.visit(exp_node))
144             except Exception as ve:
145                 black.err(str(ve))
146         self.assertMultiLineEqual(expected, actual)
147
148     def invokeBlack(
149         self, args: List[str], exit_code: int = 0, ignore_config: bool = True
150     ) -> None:
151         runner = BlackRunner()
152         if ignore_config:
153             args = ["--verbose", "--config", str(THIS_DIR / "empty.toml"), *args]
154         result = runner.invoke(black.main, args)
155         self.assertEqual(
156             result.exit_code,
157             exit_code,
158             msg=(
159                 f"Failed with args: {args}\n"
160                 f"stdout: {runner.stdout_bytes.decode()!r}\n"
161                 f"stderr: {runner.stderr_bytes.decode()!r}\n"
162                 f"exception: {result.exception}"
163             ),
164         )
165
166     @patch("black.dump_to_file", dump_to_stderr)
167     def checkSourceFile(self, name: str, mode: black.FileMode = DEFAULT_MODE) -> None:
168         path = THIS_DIR.parent / name
169         source, expected = read_data(str(path), data=False)
170         actual = fs(source, mode=mode)
171         self.assertFormatEqual(expected, actual)
172         black.assert_equivalent(source, actual)
173         black.assert_stable(source, actual, mode)
174         self.assertFalse(ff(path))
175
176     @patch("black.dump_to_file", dump_to_stderr)
177     def test_empty(self) -> None:
178         source = expected = ""
179         actual = fs(source)
180         self.assertFormatEqual(expected, actual)
181         black.assert_equivalent(source, actual)
182         black.assert_stable(source, actual, DEFAULT_MODE)
183
184     def test_empty_ff(self) -> None:
185         expected = ""
186         tmp_file = Path(black.dump_to_file())
187         try:
188             self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES))
189             with open(tmp_file, encoding="utf8") as f:
190                 actual = f.read()
191         finally:
192             os.unlink(tmp_file)
193         self.assertFormatEqual(expected, actual)
194
195     def test_run_on_test_black(self) -> None:
196         self.checkSourceFile("tests/test_black.py")
197
198     def test_run_on_test_blackd(self) -> None:
199         self.checkSourceFile("tests/test_blackd.py")
200
201     def test_black(self) -> None:
202         self.checkSourceFile("src/black/__init__.py")
203
204     def test_pygram(self) -> None:
205         self.checkSourceFile("src/blib2to3/pygram.py")
206
207     def test_pytree(self) -> None:
208         self.checkSourceFile("src/blib2to3/pytree.py")
209
210     def test_conv(self) -> None:
211         self.checkSourceFile("src/blib2to3/pgen2/conv.py")
212
213     def test_driver(self) -> None:
214         self.checkSourceFile("src/blib2to3/pgen2/driver.py")
215
216     def test_grammar(self) -> None:
217         self.checkSourceFile("src/blib2to3/pgen2/grammar.py")
218
219     def test_literals(self) -> None:
220         self.checkSourceFile("src/blib2to3/pgen2/literals.py")
221
222     def test_parse(self) -> None:
223         self.checkSourceFile("src/blib2to3/pgen2/parse.py")
224
225     def test_pgen(self) -> None:
226         self.checkSourceFile("src/blib2to3/pgen2/pgen.py")
227
228     def test_tokenize(self) -> None:
229         self.checkSourceFile("src/blib2to3/pgen2/tokenize.py")
230
231     def test_token(self) -> None:
232         self.checkSourceFile("src/blib2to3/pgen2/token.py")
233
234     def test_setup(self) -> None:
235         self.checkSourceFile("setup.py")
236
237     def test_piping(self) -> None:
238         source, expected = read_data("src/black/__init__", data=False)
239         result = BlackRunner().invoke(
240             black.main,
241             ["-", "--fast", f"--line-length={black.DEFAULT_LINE_LENGTH}"],
242             input=BytesIO(source.encode("utf8")),
243         )
244         self.assertEqual(result.exit_code, 0)
245         self.assertFormatEqual(expected, result.output)
246         black.assert_equivalent(source, result.output)
247         black.assert_stable(source, result.output, DEFAULT_MODE)
248
249     def test_piping_diff(self) -> None:
250         diff_header = re.compile(
251             r"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d\d\d\d "
252             r"\+\d\d\d\d"
253         )
254         source, _ = read_data("expression.py")
255         expected, _ = read_data("expression.diff")
256         config = THIS_DIR / "data" / "empty_pyproject.toml"
257         args = [
258             "-",
259             "--fast",
260             f"--line-length={black.DEFAULT_LINE_LENGTH}",
261             "--diff",
262             f"--config={config}",
263         ]
264         result = BlackRunner().invoke(
265             black.main, args, input=BytesIO(source.encode("utf8"))
266         )
267         self.assertEqual(result.exit_code, 0)
268         actual = diff_header.sub(DETERMINISTIC_HEADER, result.output)
269         actual = actual.rstrip() + "\n"  # the diff output has a trailing space
270         self.assertEqual(expected, actual)
271
272     def test_piping_diff_with_color(self) -> None:
273         source, _ = read_data("expression.py")
274         config = THIS_DIR / "data" / "empty_pyproject.toml"
275         args = [
276             "-",
277             "--fast",
278             f"--line-length={black.DEFAULT_LINE_LENGTH}",
279             "--diff",
280             "--color",
281             f"--config={config}",
282         ]
283         result = BlackRunner().invoke(
284             black.main, args, input=BytesIO(source.encode("utf8"))
285         )
286         actual = result.output
287         # Again, the contents are checked in a different test, so only look for colors.
288         self.assertIn("\033[1;37m", actual)
289         self.assertIn("\033[36m", actual)
290         self.assertIn("\033[32m", actual)
291         self.assertIn("\033[31m", actual)
292         self.assertIn("\033[0m", actual)
293
294     @patch("black.dump_to_file", dump_to_stderr)
295     def test_function(self) -> None:
296         source, expected = read_data("function")
297         actual = fs(source)
298         self.assertFormatEqual(expected, actual)
299         black.assert_equivalent(source, actual)
300         black.assert_stable(source, actual, DEFAULT_MODE)
301
302     @patch("black.dump_to_file", dump_to_stderr)
303     def test_function2(self) -> None:
304         source, expected = read_data("function2")
305         actual = fs(source)
306         self.assertFormatEqual(expected, actual)
307         black.assert_equivalent(source, actual)
308         black.assert_stable(source, actual, DEFAULT_MODE)
309
310     @patch("black.dump_to_file", dump_to_stderr)
311     def _test_wip(self) -> None:
312         source, expected = read_data("wip")
313         sys.settrace(tracefunc)
314         mode = replace(
315             DEFAULT_MODE,
316             experimental_string_processing=False,
317             target_versions={black.TargetVersion.PY38},
318         )
319         actual = fs(source, mode=mode)
320         sys.settrace(None)
321         self.assertFormatEqual(expected, actual)
322         black.assert_equivalent(source, actual)
323         black.assert_stable(source, actual, black.FileMode())
324
325     @patch("black.dump_to_file", dump_to_stderr)
326     def test_function_trailing_comma(self) -> None:
327         source, expected = read_data("function_trailing_comma")
328         actual = fs(source)
329         self.assertFormatEqual(expected, actual)
330         black.assert_equivalent(source, actual)
331         black.assert_stable(source, actual, DEFAULT_MODE)
332
333     @unittest.expectedFailure
334     @patch("black.dump_to_file", dump_to_stderr)
335     def test_trailing_comma_optional_parens_stability1(self) -> None:
336         source, _expected = read_data("trailing_comma_optional_parens1")
337         actual = fs(source)
338         black.assert_stable(source, actual, DEFAULT_MODE)
339
340     @unittest.expectedFailure
341     @patch("black.dump_to_file", dump_to_stderr)
342     def test_trailing_comma_optional_parens_stability2(self) -> None:
343         source, _expected = read_data("trailing_comma_optional_parens2")
344         actual = fs(source)
345         black.assert_stable(source, actual, DEFAULT_MODE)
346
347     @unittest.expectedFailure
348     @patch("black.dump_to_file", dump_to_stderr)
349     def test_trailing_comma_optional_parens_stability3(self) -> None:
350         source, _expected = read_data("trailing_comma_optional_parens3")
351         actual = fs(source)
352         black.assert_stable(source, actual, DEFAULT_MODE)
353
354     @patch("black.dump_to_file", dump_to_stderr)
355     def test_expression(self) -> None:
356         source, expected = read_data("expression")
357         actual = fs(source)
358         self.assertFormatEqual(expected, actual)
359         black.assert_equivalent(source, actual)
360         black.assert_stable(source, actual, DEFAULT_MODE)
361
362     @patch("black.dump_to_file", dump_to_stderr)
363     def test_pep_572(self) -> None:
364         source, expected = read_data("pep_572")
365         actual = fs(source)
366         self.assertFormatEqual(expected, actual)
367         black.assert_stable(source, actual, DEFAULT_MODE)
368         if sys.version_info >= (3, 8):
369             black.assert_equivalent(source, actual)
370
371     def test_pep_572_version_detection(self) -> None:
372         source, _ = read_data("pep_572")
373         root = black.lib2to3_parse(source)
374         features = black.get_features_used(root)
375         self.assertIn(black.Feature.ASSIGNMENT_EXPRESSIONS, features)
376         versions = black.detect_target_versions(root)
377         self.assertIn(black.TargetVersion.PY38, versions)
378
379     def test_expression_ff(self) -> None:
380         source, expected = read_data("expression")
381         tmp_file = Path(black.dump_to_file(source))
382         try:
383             self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
384             with open(tmp_file, encoding="utf8") as f:
385                 actual = f.read()
386         finally:
387             os.unlink(tmp_file)
388         self.assertFormatEqual(expected, actual)
389         with patch("black.dump_to_file", dump_to_stderr):
390             black.assert_equivalent(source, actual)
391             black.assert_stable(source, actual, DEFAULT_MODE)
392
393     def test_expression_diff(self) -> None:
394         source, _ = read_data("expression.py")
395         expected, _ = read_data("expression.diff")
396         tmp_file = Path(black.dump_to_file(source))
397         diff_header = re.compile(
398             rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
399             r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
400         )
401         try:
402             result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
403             self.assertEqual(result.exit_code, 0)
404         finally:
405             os.unlink(tmp_file)
406         actual = result.output
407         actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
408         actual = actual.rstrip() + "\n"  # the diff output has a trailing space
409         if expected != actual:
410             dump = black.dump_to_file(actual)
411             msg = (
412                 "Expected diff isn't equal to the actual. If you made changes to"
413                 " expression.py and this is an anticipated difference, overwrite"
414                 f" tests/data/expression.diff with {dump}"
415             )
416             self.assertEqual(expected, actual, msg)
417
418     def test_expression_diff_with_color(self) -> None:
419         source, _ = read_data("expression.py")
420         expected, _ = read_data("expression.diff")
421         tmp_file = Path(black.dump_to_file(source))
422         try:
423             result = BlackRunner().invoke(
424                 black.main, ["--diff", "--color", str(tmp_file)]
425             )
426         finally:
427             os.unlink(tmp_file)
428         actual = result.output
429         # We check the contents of the diff in `test_expression_diff`. All
430         # we need to check here is that color codes exist in the result.
431         self.assertIn("\033[1;37m", actual)
432         self.assertIn("\033[36m", actual)
433         self.assertIn("\033[32m", actual)
434         self.assertIn("\033[31m", actual)
435         self.assertIn("\033[0m", actual)
436
437     @patch("black.dump_to_file", dump_to_stderr)
438     def test_fstring(self) -> None:
439         source, expected = read_data("fstring")
440         actual = fs(source)
441         self.assertFormatEqual(expected, actual)
442         black.assert_equivalent(source, actual)
443         black.assert_stable(source, actual, DEFAULT_MODE)
444
445     @patch("black.dump_to_file", dump_to_stderr)
446     def test_pep_570(self) -> None:
447         source, expected = read_data("pep_570")
448         actual = fs(source)
449         self.assertFormatEqual(expected, actual)
450         black.assert_stable(source, actual, DEFAULT_MODE)
451         if sys.version_info >= (3, 8):
452             black.assert_equivalent(source, actual)
453
454     def test_detect_pos_only_arguments(self) -> None:
455         source, _ = read_data("pep_570")
456         root = black.lib2to3_parse(source)
457         features = black.get_features_used(root)
458         self.assertIn(black.Feature.POS_ONLY_ARGUMENTS, features)
459         versions = black.detect_target_versions(root)
460         self.assertIn(black.TargetVersion.PY38, versions)
461
462     @patch("black.dump_to_file", dump_to_stderr)
463     def test_string_quotes(self) -> None:
464         source, expected = read_data("string_quotes")
465         actual = fs(source)
466         self.assertFormatEqual(expected, actual)
467         black.assert_equivalent(source, actual)
468         black.assert_stable(source, actual, DEFAULT_MODE)
469         mode = replace(DEFAULT_MODE, string_normalization=False)
470         not_normalized = fs(source, mode=mode)
471         self.assertFormatEqual(source.replace("\\\n", ""), not_normalized)
472         black.assert_equivalent(source, not_normalized)
473         black.assert_stable(source, not_normalized, mode=mode)
474
475     @patch("black.dump_to_file", dump_to_stderr)
476     def test_docstring(self) -> None:
477         source, expected = read_data("docstring")
478         actual = fs(source)
479         self.assertFormatEqual(expected, actual)
480         black.assert_equivalent(source, actual)
481         black.assert_stable(source, actual, DEFAULT_MODE)
482
483     @patch("black.dump_to_file", dump_to_stderr)
484     def test_docstring_no_string_normalization(self) -> None:
485         """Like test_docstring but with string normalization off."""
486         source, expected = read_data("docstring_no_string_normalization")
487         mode = replace(DEFAULT_MODE, string_normalization=False)
488         actual = fs(source, mode=mode)
489         self.assertFormatEqual(expected, actual)
490         black.assert_equivalent(source, actual)
491         black.assert_stable(source, actual, mode)
492
493     def test_long_strings(self) -> None:
494         """Tests for splitting long strings."""
495         source, expected = read_data("long_strings")
496         actual = fs(source)
497         self.assertFormatEqual(expected, actual)
498         black.assert_equivalent(source, actual)
499         black.assert_stable(source, actual, DEFAULT_MODE)
500
501     def test_long_strings_flag_disabled(self) -> None:
502         """Tests for turning off the string processing logic."""
503         source, expected = read_data("long_strings_flag_disabled")
504         mode = replace(DEFAULT_MODE, experimental_string_processing=False)
505         actual = fs(source, mode=mode)
506         self.assertFormatEqual(expected, actual)
507         black.assert_stable(expected, actual, mode)
508
509     @patch("black.dump_to_file", dump_to_stderr)
510     def test_long_strings__edge_case(self) -> None:
511         """Edge-case tests for splitting long strings."""
512         source, expected = read_data("long_strings__edge_case")
513         actual = fs(source)
514         self.assertFormatEqual(expected, actual)
515         black.assert_equivalent(source, actual)
516         black.assert_stable(source, actual, DEFAULT_MODE)
517
518     @patch("black.dump_to_file", dump_to_stderr)
519     def test_long_strings__regression(self) -> None:
520         """Regression tests for splitting long strings."""
521         source, expected = read_data("long_strings__regression")
522         actual = fs(source)
523         self.assertFormatEqual(expected, actual)
524         black.assert_equivalent(source, actual)
525         black.assert_stable(source, actual, DEFAULT_MODE)
526
527     @patch("black.dump_to_file", dump_to_stderr)
528     def test_slices(self) -> None:
529         source, expected = read_data("slices")
530         actual = fs(source)
531         self.assertFormatEqual(expected, actual)
532         black.assert_equivalent(source, actual)
533         black.assert_stable(source, actual, DEFAULT_MODE)
534
535     @patch("black.dump_to_file", dump_to_stderr)
536     def test_percent_precedence(self) -> None:
537         source, expected = read_data("percent_precedence")
538         actual = fs(source)
539         self.assertFormatEqual(expected, actual)
540         black.assert_equivalent(source, actual)
541         black.assert_stable(source, actual, DEFAULT_MODE)
542
543     @patch("black.dump_to_file", dump_to_stderr)
544     def test_comments(self) -> None:
545         source, expected = read_data("comments")
546         actual = fs(source)
547         self.assertFormatEqual(expected, actual)
548         black.assert_equivalent(source, actual)
549         black.assert_stable(source, actual, DEFAULT_MODE)
550
551     @patch("black.dump_to_file", dump_to_stderr)
552     def test_comments2(self) -> None:
553         source, expected = read_data("comments2")
554         actual = fs(source)
555         self.assertFormatEqual(expected, actual)
556         black.assert_equivalent(source, actual)
557         black.assert_stable(source, actual, DEFAULT_MODE)
558
559     @patch("black.dump_to_file", dump_to_stderr)
560     def test_comments3(self) -> None:
561         source, expected = read_data("comments3")
562         actual = fs(source)
563         self.assertFormatEqual(expected, actual)
564         black.assert_equivalent(source, actual)
565         black.assert_stable(source, actual, DEFAULT_MODE)
566
567     @patch("black.dump_to_file", dump_to_stderr)
568     def test_comments4(self) -> None:
569         source, expected = read_data("comments4")
570         actual = fs(source)
571         self.assertFormatEqual(expected, actual)
572         black.assert_equivalent(source, actual)
573         black.assert_stable(source, actual, DEFAULT_MODE)
574
575     @patch("black.dump_to_file", dump_to_stderr)
576     def test_comments5(self) -> None:
577         source, expected = read_data("comments5")
578         actual = fs(source)
579         self.assertFormatEqual(expected, actual)
580         black.assert_equivalent(source, actual)
581         black.assert_stable(source, actual, DEFAULT_MODE)
582
583     @patch("black.dump_to_file", dump_to_stderr)
584     def test_comments6(self) -> None:
585         source, expected = read_data("comments6")
586         actual = fs(source)
587         self.assertFormatEqual(expected, actual)
588         black.assert_equivalent(source, actual)
589         black.assert_stable(source, actual, DEFAULT_MODE)
590
591     @patch("black.dump_to_file", dump_to_stderr)
592     def test_comments7(self) -> None:
593         source, expected = read_data("comments7")
594         mode = replace(DEFAULT_MODE, target_versions={black.TargetVersion.PY38})
595         actual = fs(source, mode=mode)
596         self.assertFormatEqual(expected, actual)
597         black.assert_equivalent(source, actual)
598         black.assert_stable(source, actual, DEFAULT_MODE)
599
600     @patch("black.dump_to_file", dump_to_stderr)
601     def test_comment_after_escaped_newline(self) -> None:
602         source, expected = read_data("comment_after_escaped_newline")
603         actual = fs(source)
604         self.assertFormatEqual(expected, actual)
605         black.assert_equivalent(source, actual)
606         black.assert_stable(source, actual, DEFAULT_MODE)
607
608     @patch("black.dump_to_file", dump_to_stderr)
609     def test_cantfit(self) -> None:
610         source, expected = read_data("cantfit")
611         actual = fs(source)
612         self.assertFormatEqual(expected, actual)
613         black.assert_equivalent(source, actual)
614         black.assert_stable(source, actual, DEFAULT_MODE)
615
616     @patch("black.dump_to_file", dump_to_stderr)
617     def test_import_spacing(self) -> None:
618         source, expected = read_data("import_spacing")
619         actual = fs(source)
620         self.assertFormatEqual(expected, actual)
621         black.assert_equivalent(source, actual)
622         black.assert_stable(source, actual, DEFAULT_MODE)
623
624     @patch("black.dump_to_file", dump_to_stderr)
625     def test_composition(self) -> None:
626         source, expected = read_data("composition")
627         actual = fs(source)
628         self.assertFormatEqual(expected, actual)
629         black.assert_equivalent(source, actual)
630         black.assert_stable(source, actual, DEFAULT_MODE)
631
632     @patch("black.dump_to_file", dump_to_stderr)
633     def test_composition_no_trailing_comma(self) -> None:
634         source, expected = read_data("composition_no_trailing_comma")
635         mode = replace(DEFAULT_MODE, target_versions={black.TargetVersion.PY38})
636         actual = fs(source, mode=mode)
637         self.assertFormatEqual(expected, actual)
638         black.assert_equivalent(source, actual)
639         black.assert_stable(source, actual, DEFAULT_MODE)
640
641     @patch("black.dump_to_file", dump_to_stderr)
642     def test_empty_lines(self) -> None:
643         source, expected = read_data("empty_lines")
644         actual = fs(source)
645         self.assertFormatEqual(expected, actual)
646         black.assert_equivalent(source, actual)
647         black.assert_stable(source, actual, DEFAULT_MODE)
648
649     @patch("black.dump_to_file", dump_to_stderr)
650     def test_remove_parens(self) -> None:
651         source, expected = read_data("remove_parens")
652         actual = fs(source)
653         self.assertFormatEqual(expected, actual)
654         black.assert_equivalent(source, actual)
655         black.assert_stable(source, actual, DEFAULT_MODE)
656
657     @patch("black.dump_to_file", dump_to_stderr)
658     def test_string_prefixes(self) -> None:
659         source, expected = read_data("string_prefixes")
660         actual = fs(source)
661         self.assertFormatEqual(expected, actual)
662         black.assert_equivalent(source, actual)
663         black.assert_stable(source, actual, DEFAULT_MODE)
664
665     @patch("black.dump_to_file", dump_to_stderr)
666     def test_numeric_literals(self) -> None:
667         source, expected = read_data("numeric_literals")
668         mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
669         actual = fs(source, mode=mode)
670         self.assertFormatEqual(expected, actual)
671         black.assert_equivalent(source, actual)
672         black.assert_stable(source, actual, mode)
673
674     @patch("black.dump_to_file", dump_to_stderr)
675     def test_numeric_literals_ignoring_underscores(self) -> None:
676         source, expected = read_data("numeric_literals_skip_underscores")
677         mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
678         actual = fs(source, mode=mode)
679         self.assertFormatEqual(expected, actual)
680         black.assert_equivalent(source, actual)
681         black.assert_stable(source, actual, mode)
682
683     @patch("black.dump_to_file", dump_to_stderr)
684     def test_numeric_literals_py2(self) -> None:
685         source, expected = read_data("numeric_literals_py2")
686         actual = fs(source)
687         self.assertFormatEqual(expected, actual)
688         black.assert_stable(source, actual, DEFAULT_MODE)
689
690     @patch("black.dump_to_file", dump_to_stderr)
691     def test_python2(self) -> None:
692         source, expected = read_data("python2")
693         actual = fs(source)
694         self.assertFormatEqual(expected, actual)
695         black.assert_equivalent(source, actual)
696         black.assert_stable(source, actual, DEFAULT_MODE)
697
698     @patch("black.dump_to_file", dump_to_stderr)
699     def test_python2_print_function(self) -> None:
700         source, expected = read_data("python2_print_function")
701         mode = replace(DEFAULT_MODE, target_versions={TargetVersion.PY27})
702         actual = fs(source, mode=mode)
703         self.assertFormatEqual(expected, actual)
704         black.assert_equivalent(source, actual)
705         black.assert_stable(source, actual, mode)
706
707     @patch("black.dump_to_file", dump_to_stderr)
708     def test_python2_unicode_literals(self) -> None:
709         source, expected = read_data("python2_unicode_literals")
710         actual = fs(source)
711         self.assertFormatEqual(expected, actual)
712         black.assert_equivalent(source, actual)
713         black.assert_stable(source, actual, DEFAULT_MODE)
714
715     @patch("black.dump_to_file", dump_to_stderr)
716     def test_stub(self) -> None:
717         mode = replace(DEFAULT_MODE, is_pyi=True)
718         source, expected = read_data("stub.pyi")
719         actual = fs(source, mode=mode)
720         self.assertFormatEqual(expected, actual)
721         black.assert_stable(source, actual, mode)
722
723     @patch("black.dump_to_file", dump_to_stderr)
724     def test_async_as_identifier(self) -> None:
725         source_path = (THIS_DIR / "data" / "async_as_identifier.py").resolve()
726         source, expected = read_data("async_as_identifier")
727         actual = fs(source)
728         self.assertFormatEqual(expected, actual)
729         major, minor = sys.version_info[:2]
730         if major < 3 or (major <= 3 and minor < 7):
731             black.assert_equivalent(source, actual)
732         black.assert_stable(source, actual, DEFAULT_MODE)
733         # ensure black can parse this when the target is 3.6
734         self.invokeBlack([str(source_path), "--target-version", "py36"])
735         # but not on 3.7, because async/await is no longer an identifier
736         self.invokeBlack([str(source_path), "--target-version", "py37"], exit_code=123)
737
738     @patch("black.dump_to_file", dump_to_stderr)
739     def test_python37(self) -> None:
740         source_path = (THIS_DIR / "data" / "python37.py").resolve()
741         source, expected = read_data("python37")
742         actual = fs(source)
743         self.assertFormatEqual(expected, actual)
744         major, minor = sys.version_info[:2]
745         if major > 3 or (major == 3 and minor >= 7):
746             black.assert_equivalent(source, actual)
747         black.assert_stable(source, actual, DEFAULT_MODE)
748         # ensure black can parse this when the target is 3.7
749         self.invokeBlack([str(source_path), "--target-version", "py37"])
750         # but not on 3.6, because we use async as a reserved keyword
751         self.invokeBlack([str(source_path), "--target-version", "py36"], exit_code=123)
752
753     @patch("black.dump_to_file", dump_to_stderr)
754     def test_python38(self) -> None:
755         source, expected = read_data("python38")
756         actual = fs(source)
757         self.assertFormatEqual(expected, actual)
758         major, minor = sys.version_info[:2]
759         if major > 3 or (major == 3 and minor >= 8):
760             black.assert_equivalent(source, actual)
761         black.assert_stable(source, actual, DEFAULT_MODE)
762
763     @patch("black.dump_to_file", dump_to_stderr)
764     def test_python39(self) -> None:
765         source, expected = read_data("python39")
766         actual = fs(source)
767         self.assertFormatEqual(expected, actual)
768         major, minor = sys.version_info[:2]
769         if major > 3 or (major == 3 and minor >= 9):
770             black.assert_equivalent(source, actual)
771         black.assert_stable(source, actual, DEFAULT_MODE)
772
773     @patch("black.dump_to_file", dump_to_stderr)
774     def test_fmtonoff(self) -> None:
775         source, expected = read_data("fmtonoff")
776         actual = fs(source)
777         self.assertFormatEqual(expected, actual)
778         black.assert_equivalent(source, actual)
779         black.assert_stable(source, actual, DEFAULT_MODE)
780
781     @patch("black.dump_to_file", dump_to_stderr)
782     def test_fmtonoff2(self) -> None:
783         source, expected = read_data("fmtonoff2")
784         actual = fs(source)
785         self.assertFormatEqual(expected, actual)
786         black.assert_equivalent(source, actual)
787         black.assert_stable(source, actual, DEFAULT_MODE)
788
789     @patch("black.dump_to_file", dump_to_stderr)
790     def test_fmtonoff3(self) -> None:
791         source, expected = read_data("fmtonoff3")
792         actual = fs(source)
793         self.assertFormatEqual(expected, actual)
794         black.assert_equivalent(source, actual)
795         black.assert_stable(source, actual, DEFAULT_MODE)
796
797     @patch("black.dump_to_file", dump_to_stderr)
798     def test_fmtonoff4(self) -> None:
799         source, expected = read_data("fmtonoff4")
800         actual = fs(source)
801         self.assertFormatEqual(expected, actual)
802         black.assert_equivalent(source, actual)
803         black.assert_stable(source, actual, DEFAULT_MODE)
804
805     @patch("black.dump_to_file", dump_to_stderr)
806     def test_remove_empty_parentheses_after_class(self) -> None:
807         source, expected = read_data("class_blank_parentheses")
808         actual = fs(source)
809         self.assertFormatEqual(expected, actual)
810         black.assert_equivalent(source, actual)
811         black.assert_stable(source, actual, DEFAULT_MODE)
812
813     @patch("black.dump_to_file", dump_to_stderr)
814     def test_new_line_between_class_and_code(self) -> None:
815         source, expected = read_data("class_methods_new_line")
816         actual = fs(source)
817         self.assertFormatEqual(expected, actual)
818         black.assert_equivalent(source, actual)
819         black.assert_stable(source, actual, DEFAULT_MODE)
820
821     @patch("black.dump_to_file", dump_to_stderr)
822     def test_bracket_match(self) -> None:
823         source, expected = read_data("bracketmatch")
824         actual = fs(source)
825         self.assertFormatEqual(expected, actual)
826         black.assert_equivalent(source, actual)
827         black.assert_stable(source, actual, DEFAULT_MODE)
828
829     @patch("black.dump_to_file", dump_to_stderr)
830     def test_tuple_assign(self) -> None:
831         source, expected = read_data("tupleassign")
832         actual = fs(source)
833         self.assertFormatEqual(expected, actual)
834         black.assert_equivalent(source, actual)
835         black.assert_stable(source, actual, DEFAULT_MODE)
836
837     @patch("black.dump_to_file", dump_to_stderr)
838     def test_beginning_backslash(self) -> None:
839         source, expected = read_data("beginning_backslash")
840         actual = fs(source)
841         self.assertFormatEqual(expected, actual)
842         black.assert_equivalent(source, actual)
843         black.assert_stable(source, actual, DEFAULT_MODE)
844
845     def test_tab_comment_indentation(self) -> None:
846         contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
847         contents_spc = "if 1:\n    if 2:\n        pass\n    # comment\n    pass\n"
848         self.assertFormatEqual(contents_spc, fs(contents_spc))
849         self.assertFormatEqual(contents_spc, fs(contents_tab))
850
851         contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
852         contents_spc = "if 1:\n    if 2:\n        pass\n        # comment\n    pass\n"
853         self.assertFormatEqual(contents_spc, fs(contents_spc))
854         self.assertFormatEqual(contents_spc, fs(contents_tab))
855
856         # mixed tabs and spaces (valid Python 2 code)
857         contents_tab = "if 1:\n        if 2:\n\t\tpass\n\t# comment\n        pass\n"
858         contents_spc = "if 1:\n    if 2:\n        pass\n    # comment\n    pass\n"
859         self.assertFormatEqual(contents_spc, fs(contents_spc))
860         self.assertFormatEqual(contents_spc, fs(contents_tab))
861
862         contents_tab = "if 1:\n        if 2:\n\t\tpass\n\t\t# comment\n        pass\n"
863         contents_spc = "if 1:\n    if 2:\n        pass\n        # comment\n    pass\n"
864         self.assertFormatEqual(contents_spc, fs(contents_spc))
865         self.assertFormatEqual(contents_spc, fs(contents_tab))
866
867     def test_report_verbose(self) -> None:
868         report = black.Report(verbose=True)
869         out_lines = []
870         err_lines = []
871
872         def out(msg: str, **kwargs: Any) -> None:
873             out_lines.append(msg)
874
875         def err(msg: str, **kwargs: Any) -> None:
876             err_lines.append(msg)
877
878         with patch("black.out", out), patch("black.err", err):
879             report.done(Path("f1"), black.Changed.NO)
880             self.assertEqual(len(out_lines), 1)
881             self.assertEqual(len(err_lines), 0)
882             self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
883             self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
884             self.assertEqual(report.return_code, 0)
885             report.done(Path("f2"), black.Changed.YES)
886             self.assertEqual(len(out_lines), 2)
887             self.assertEqual(len(err_lines), 0)
888             self.assertEqual(out_lines[-1], "reformatted f2")
889             self.assertEqual(
890                 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
891             )
892             report.done(Path("f3"), black.Changed.CACHED)
893             self.assertEqual(len(out_lines), 3)
894             self.assertEqual(len(err_lines), 0)
895             self.assertEqual(
896                 out_lines[-1], "f3 wasn't modified on disk since last run."
897             )
898             self.assertEqual(
899                 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
900             )
901             self.assertEqual(report.return_code, 0)
902             report.check = True
903             self.assertEqual(report.return_code, 1)
904             report.check = False
905             report.failed(Path("e1"), "boom")
906             self.assertEqual(len(out_lines), 3)
907             self.assertEqual(len(err_lines), 1)
908             self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
909             self.assertEqual(
910                 unstyle(str(report)),
911                 "1 file reformatted, 2 files left unchanged, 1 file failed to"
912                 " reformat.",
913             )
914             self.assertEqual(report.return_code, 123)
915             report.done(Path("f3"), black.Changed.YES)
916             self.assertEqual(len(out_lines), 4)
917             self.assertEqual(len(err_lines), 1)
918             self.assertEqual(out_lines[-1], "reformatted f3")
919             self.assertEqual(
920                 unstyle(str(report)),
921                 "2 files reformatted, 2 files left unchanged, 1 file failed to"
922                 " reformat.",
923             )
924             self.assertEqual(report.return_code, 123)
925             report.failed(Path("e2"), "boom")
926             self.assertEqual(len(out_lines), 4)
927             self.assertEqual(len(err_lines), 2)
928             self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
929             self.assertEqual(
930                 unstyle(str(report)),
931                 "2 files reformatted, 2 files left unchanged, 2 files failed to"
932                 " reformat.",
933             )
934             self.assertEqual(report.return_code, 123)
935             report.path_ignored(Path("wat"), "no match")
936             self.assertEqual(len(out_lines), 5)
937             self.assertEqual(len(err_lines), 2)
938             self.assertEqual(out_lines[-1], "wat ignored: no match")
939             self.assertEqual(
940                 unstyle(str(report)),
941                 "2 files reformatted, 2 files left unchanged, 2 files failed to"
942                 " reformat.",
943             )
944             self.assertEqual(report.return_code, 123)
945             report.done(Path("f4"), black.Changed.NO)
946             self.assertEqual(len(out_lines), 6)
947             self.assertEqual(len(err_lines), 2)
948             self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
949             self.assertEqual(
950                 unstyle(str(report)),
951                 "2 files reformatted, 3 files left unchanged, 2 files failed to"
952                 " reformat.",
953             )
954             self.assertEqual(report.return_code, 123)
955             report.check = True
956             self.assertEqual(
957                 unstyle(str(report)),
958                 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
959                 " would fail to reformat.",
960             )
961             report.check = False
962             report.diff = 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
969     def test_report_quiet(self) -> None:
970         report = black.Report(quiet=True)
971         out_lines = []
972         err_lines = []
973
974         def out(msg: str, **kwargs: Any) -> None:
975             out_lines.append(msg)
976
977         def err(msg: str, **kwargs: Any) -> None:
978             err_lines.append(msg)
979
980         with patch("black.out", out), patch("black.err", err):
981             report.done(Path("f1"), black.Changed.NO)
982             self.assertEqual(len(out_lines), 0)
983             self.assertEqual(len(err_lines), 0)
984             self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
985             self.assertEqual(report.return_code, 0)
986             report.done(Path("f2"), black.Changed.YES)
987             self.assertEqual(len(out_lines), 0)
988             self.assertEqual(len(err_lines), 0)
989             self.assertEqual(
990                 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
991             )
992             report.done(Path("f3"), black.Changed.CACHED)
993             self.assertEqual(len(out_lines), 0)
994             self.assertEqual(len(err_lines), 0)
995             self.assertEqual(
996                 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
997             )
998             self.assertEqual(report.return_code, 0)
999             report.check = True
1000             self.assertEqual(report.return_code, 1)
1001             report.check = False
1002             report.failed(Path("e1"), "boom")
1003             self.assertEqual(len(out_lines), 0)
1004             self.assertEqual(len(err_lines), 1)
1005             self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
1006             self.assertEqual(
1007                 unstyle(str(report)),
1008                 "1 file reformatted, 2 files left unchanged, 1 file failed to"
1009                 " reformat.",
1010             )
1011             self.assertEqual(report.return_code, 123)
1012             report.done(Path("f3"), black.Changed.YES)
1013             self.assertEqual(len(out_lines), 0)
1014             self.assertEqual(len(err_lines), 1)
1015             self.assertEqual(
1016                 unstyle(str(report)),
1017                 "2 files reformatted, 2 files left unchanged, 1 file failed to"
1018                 " reformat.",
1019             )
1020             self.assertEqual(report.return_code, 123)
1021             report.failed(Path("e2"), "boom")
1022             self.assertEqual(len(out_lines), 0)
1023             self.assertEqual(len(err_lines), 2)
1024             self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
1025             self.assertEqual(
1026                 unstyle(str(report)),
1027                 "2 files reformatted, 2 files left unchanged, 2 files failed to"
1028                 " reformat.",
1029             )
1030             self.assertEqual(report.return_code, 123)
1031             report.path_ignored(Path("wat"), "no match")
1032             self.assertEqual(len(out_lines), 0)
1033             self.assertEqual(len(err_lines), 2)
1034             self.assertEqual(
1035                 unstyle(str(report)),
1036                 "2 files reformatted, 2 files left unchanged, 2 files failed to"
1037                 " reformat.",
1038             )
1039             self.assertEqual(report.return_code, 123)
1040             report.done(Path("f4"), black.Changed.NO)
1041             self.assertEqual(len(out_lines), 0)
1042             self.assertEqual(len(err_lines), 2)
1043             self.assertEqual(
1044                 unstyle(str(report)),
1045                 "2 files reformatted, 3 files left unchanged, 2 files failed to"
1046                 " reformat.",
1047             )
1048             self.assertEqual(report.return_code, 123)
1049             report.check = True
1050             self.assertEqual(
1051                 unstyle(str(report)),
1052                 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1053                 " would fail to reformat.",
1054             )
1055             report.check = False
1056             report.diff = True
1057             self.assertEqual(
1058                 unstyle(str(report)),
1059                 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1060                 " would fail to reformat.",
1061             )
1062
1063     def test_report_normal(self) -> None:
1064         report = black.Report()
1065         out_lines = []
1066         err_lines = []
1067
1068         def out(msg: str, **kwargs: Any) -> None:
1069             out_lines.append(msg)
1070
1071         def err(msg: str, **kwargs: Any) -> None:
1072             err_lines.append(msg)
1073
1074         with patch("black.out", out), patch("black.err", err):
1075             report.done(Path("f1"), black.Changed.NO)
1076             self.assertEqual(len(out_lines), 0)
1077             self.assertEqual(len(err_lines), 0)
1078             self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
1079             self.assertEqual(report.return_code, 0)
1080             report.done(Path("f2"), black.Changed.YES)
1081             self.assertEqual(len(out_lines), 1)
1082             self.assertEqual(len(err_lines), 0)
1083             self.assertEqual(out_lines[-1], "reformatted f2")
1084             self.assertEqual(
1085                 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
1086             )
1087             report.done(Path("f3"), black.Changed.CACHED)
1088             self.assertEqual(len(out_lines), 1)
1089             self.assertEqual(len(err_lines), 0)
1090             self.assertEqual(out_lines[-1], "reformatted f2")
1091             self.assertEqual(
1092                 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
1093             )
1094             self.assertEqual(report.return_code, 0)
1095             report.check = True
1096             self.assertEqual(report.return_code, 1)
1097             report.check = False
1098             report.failed(Path("e1"), "boom")
1099             self.assertEqual(len(out_lines), 1)
1100             self.assertEqual(len(err_lines), 1)
1101             self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
1102             self.assertEqual(
1103                 unstyle(str(report)),
1104                 "1 file reformatted, 2 files left unchanged, 1 file failed to"
1105                 " reformat.",
1106             )
1107             self.assertEqual(report.return_code, 123)
1108             report.done(Path("f3"), black.Changed.YES)
1109             self.assertEqual(len(out_lines), 2)
1110             self.assertEqual(len(err_lines), 1)
1111             self.assertEqual(out_lines[-1], "reformatted f3")
1112             self.assertEqual(
1113                 unstyle(str(report)),
1114                 "2 files reformatted, 2 files left unchanged, 1 file failed to"
1115                 " reformat.",
1116             )
1117             self.assertEqual(report.return_code, 123)
1118             report.failed(Path("e2"), "boom")
1119             self.assertEqual(len(out_lines), 2)
1120             self.assertEqual(len(err_lines), 2)
1121             self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
1122             self.assertEqual(
1123                 unstyle(str(report)),
1124                 "2 files reformatted, 2 files left unchanged, 2 files failed to"
1125                 " reformat.",
1126             )
1127             self.assertEqual(report.return_code, 123)
1128             report.path_ignored(Path("wat"), "no match")
1129             self.assertEqual(len(out_lines), 2)
1130             self.assertEqual(len(err_lines), 2)
1131             self.assertEqual(
1132                 unstyle(str(report)),
1133                 "2 files reformatted, 2 files left unchanged, 2 files failed to"
1134                 " reformat.",
1135             )
1136             self.assertEqual(report.return_code, 123)
1137             report.done(Path("f4"), black.Changed.NO)
1138             self.assertEqual(len(out_lines), 2)
1139             self.assertEqual(len(err_lines), 2)
1140             self.assertEqual(
1141                 unstyle(str(report)),
1142                 "2 files reformatted, 3 files left unchanged, 2 files failed to"
1143                 " reformat.",
1144             )
1145             self.assertEqual(report.return_code, 123)
1146             report.check = True
1147             self.assertEqual(
1148                 unstyle(str(report)),
1149                 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1150                 " would fail to reformat.",
1151             )
1152             report.check = False
1153             report.diff = True
1154             self.assertEqual(
1155                 unstyle(str(report)),
1156                 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1157                 " would fail to reformat.",
1158             )
1159
1160     def test_lib2to3_parse(self) -> None:
1161         with self.assertRaises(black.InvalidInput):
1162             black.lib2to3_parse("invalid syntax")
1163
1164         straddling = "x + y"
1165         black.lib2to3_parse(straddling)
1166         black.lib2to3_parse(straddling, {TargetVersion.PY27})
1167         black.lib2to3_parse(straddling, {TargetVersion.PY36})
1168         black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
1169
1170         py2_only = "print x"
1171         black.lib2to3_parse(py2_only)
1172         black.lib2to3_parse(py2_only, {TargetVersion.PY27})
1173         with self.assertRaises(black.InvalidInput):
1174             black.lib2to3_parse(py2_only, {TargetVersion.PY36})
1175         with self.assertRaises(black.InvalidInput):
1176             black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
1177
1178         py3_only = "exec(x, end=y)"
1179         black.lib2to3_parse(py3_only)
1180         with self.assertRaises(black.InvalidInput):
1181             black.lib2to3_parse(py3_only, {TargetVersion.PY27})
1182         black.lib2to3_parse(py3_only, {TargetVersion.PY36})
1183         black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
1184
1185     def test_get_features_used_decorator(self) -> None:
1186         # Test the feature detection of new decorator syntax
1187         # since this makes some test cases of test_get_features_used()
1188         # fails if it fails, this is tested first so that a useful case
1189         # is identified
1190         simples, relaxed = read_data("decorators")
1191         # skip explanation comments at the top of the file
1192         for simple_test in simples.split("##")[1:]:
1193             node = black.lib2to3_parse(simple_test)
1194             decorator = str(node.children[0].children[0]).strip()
1195             self.assertNotIn(
1196                 Feature.RELAXED_DECORATORS,
1197                 black.get_features_used(node),
1198                 msg=(
1199                     f"decorator '{decorator}' follows python<=3.8 syntax"
1200                     "but is detected as 3.9+"
1201                     # f"The full node is\n{node!r}"
1202                 ),
1203             )
1204         # skip the '# output' comment at the top of the output part
1205         for relaxed_test in relaxed.split("##")[1:]:
1206             node = black.lib2to3_parse(relaxed_test)
1207             decorator = str(node.children[0].children[0]).strip()
1208             self.assertIn(
1209                 Feature.RELAXED_DECORATORS,
1210                 black.get_features_used(node),
1211                 msg=(
1212                     f"decorator '{decorator}' uses python3.9+ syntax"
1213                     "but is detected as python<=3.8"
1214                     # f"The full node is\n{node!r}"
1215                 ),
1216             )
1217
1218     def test_get_features_used(self) -> None:
1219         node = black.lib2to3_parse("def f(*, arg): ...\n")
1220         self.assertEqual(black.get_features_used(node), set())
1221         node = black.lib2to3_parse("def f(*, arg,): ...\n")
1222         self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
1223         node = black.lib2to3_parse("f(*arg,)\n")
1224         self.assertEqual(
1225             black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
1226         )
1227         node = black.lib2to3_parse("def f(*, arg): f'string'\n")
1228         self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
1229         node = black.lib2to3_parse("123_456\n")
1230         self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
1231         node = black.lib2to3_parse("123456\n")
1232         self.assertEqual(black.get_features_used(node), set())
1233         source, expected = read_data("function")
1234         node = black.lib2to3_parse(source)
1235         expected_features = {
1236             Feature.TRAILING_COMMA_IN_CALL,
1237             Feature.TRAILING_COMMA_IN_DEF,
1238             Feature.F_STRINGS,
1239         }
1240         self.assertEqual(black.get_features_used(node), expected_features)
1241         node = black.lib2to3_parse(expected)
1242         self.assertEqual(black.get_features_used(node), expected_features)
1243         source, expected = read_data("expression")
1244         node = black.lib2to3_parse(source)
1245         self.assertEqual(black.get_features_used(node), set())
1246         node = black.lib2to3_parse(expected)
1247         self.assertEqual(black.get_features_used(node), set())
1248
1249     def test_get_future_imports(self) -> None:
1250         node = black.lib2to3_parse("\n")
1251         self.assertEqual(set(), black.get_future_imports(node))
1252         node = black.lib2to3_parse("from __future__ import black\n")
1253         self.assertEqual({"black"}, black.get_future_imports(node))
1254         node = black.lib2to3_parse("from __future__ import multiple, imports\n")
1255         self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1256         node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
1257         self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
1258         node = black.lib2to3_parse(
1259             "from __future__ import multiple\nfrom __future__ import imports\n"
1260         )
1261         self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1262         node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
1263         self.assertEqual({"black"}, black.get_future_imports(node))
1264         node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
1265         self.assertEqual({"black"}, black.get_future_imports(node))
1266         node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
1267         self.assertEqual(set(), black.get_future_imports(node))
1268         node = black.lib2to3_parse("from some.module import black\n")
1269         self.assertEqual(set(), black.get_future_imports(node))
1270         node = black.lib2to3_parse(
1271             "from __future__ import unicode_literals as _unicode_literals"
1272         )
1273         self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
1274         node = black.lib2to3_parse(
1275             "from __future__ import unicode_literals as _lol, print"
1276         )
1277         self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
1278
1279     def test_debug_visitor(self) -> None:
1280         source, _ = read_data("debug_visitor.py")
1281         expected, _ = read_data("debug_visitor.out")
1282         out_lines = []
1283         err_lines = []
1284
1285         def out(msg: str, **kwargs: Any) -> None:
1286             out_lines.append(msg)
1287
1288         def err(msg: str, **kwargs: Any) -> None:
1289             err_lines.append(msg)
1290
1291         with patch("black.out", out), patch("black.err", err):
1292             black.DebugVisitor.show(source)
1293         actual = "\n".join(out_lines) + "\n"
1294         log_name = ""
1295         if expected != actual:
1296             log_name = black.dump_to_file(*out_lines)
1297         self.assertEqual(
1298             expected,
1299             actual,
1300             f"AST print out is different. Actual version dumped to {log_name}",
1301         )
1302
1303     def test_format_file_contents(self) -> None:
1304         empty = ""
1305         mode = DEFAULT_MODE
1306         with self.assertRaises(black.NothingChanged):
1307             black.format_file_contents(empty, mode=mode, fast=False)
1308         just_nl = "\n"
1309         with self.assertRaises(black.NothingChanged):
1310             black.format_file_contents(just_nl, mode=mode, fast=False)
1311         same = "j = [1, 2, 3]\n"
1312         with self.assertRaises(black.NothingChanged):
1313             black.format_file_contents(same, mode=mode, fast=False)
1314         different = "j = [1,2,3]"
1315         expected = same
1316         actual = black.format_file_contents(different, mode=mode, fast=False)
1317         self.assertEqual(expected, actual)
1318         invalid = "return if you can"
1319         with self.assertRaises(black.InvalidInput) as e:
1320             black.format_file_contents(invalid, mode=mode, fast=False)
1321         self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
1322
1323     def test_endmarker(self) -> None:
1324         n = black.lib2to3_parse("\n")
1325         self.assertEqual(n.type, black.syms.file_input)
1326         self.assertEqual(len(n.children), 1)
1327         self.assertEqual(n.children[0].type, black.token.ENDMARKER)
1328
1329     @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
1330     def test_assertFormatEqual(self) -> None:
1331         out_lines = []
1332         err_lines = []
1333
1334         def out(msg: str, **kwargs: Any) -> None:
1335             out_lines.append(msg)
1336
1337         def err(msg: str, **kwargs: Any) -> None:
1338             err_lines.append(msg)
1339
1340         with patch("black.out", out), patch("black.err", err):
1341             with self.assertRaises(AssertionError):
1342                 self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")
1343
1344         out_str = "".join(out_lines)
1345         self.assertTrue("Expected tree:" in out_str)
1346         self.assertTrue("Actual tree:" in out_str)
1347         self.assertEqual("".join(err_lines), "")
1348
1349     def test_cache_broken_file(self) -> None:
1350         mode = DEFAULT_MODE
1351         with cache_dir() as workspace:
1352             cache_file = black.get_cache_file(mode)
1353             with cache_file.open("w") as fobj:
1354                 fobj.write("this is not a pickle")
1355             self.assertEqual(black.read_cache(mode), {})
1356             src = (workspace / "test.py").resolve()
1357             with src.open("w") as fobj:
1358                 fobj.write("print('hello')")
1359             self.invokeBlack([str(src)])
1360             cache = black.read_cache(mode)
1361             self.assertIn(src, cache)
1362
1363     def test_cache_single_file_already_cached(self) -> None:
1364         mode = DEFAULT_MODE
1365         with cache_dir() as workspace:
1366             src = (workspace / "test.py").resolve()
1367             with src.open("w") as fobj:
1368                 fobj.write("print('hello')")
1369             black.write_cache({}, [src], mode)
1370             self.invokeBlack([str(src)])
1371             with src.open("r") as fobj:
1372                 self.assertEqual(fobj.read(), "print('hello')")
1373
1374     @event_loop()
1375     def test_cache_multiple_files(self) -> None:
1376         mode = DEFAULT_MODE
1377         with cache_dir() as workspace, patch(
1378             "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1379         ):
1380             one = (workspace / "one.py").resolve()
1381             with one.open("w") as fobj:
1382                 fobj.write("print('hello')")
1383             two = (workspace / "two.py").resolve()
1384             with two.open("w") as fobj:
1385                 fobj.write("print('hello')")
1386             black.write_cache({}, [one], mode)
1387             self.invokeBlack([str(workspace)])
1388             with one.open("r") as fobj:
1389                 self.assertEqual(fobj.read(), "print('hello')")
1390             with two.open("r") as fobj:
1391                 self.assertEqual(fobj.read(), 'print("hello")\n')
1392             cache = black.read_cache(mode)
1393             self.assertIn(one, cache)
1394             self.assertIn(two, cache)
1395
1396     def test_no_cache_when_writeback_diff(self) -> None:
1397         mode = DEFAULT_MODE
1398         with cache_dir() as workspace:
1399             src = (workspace / "test.py").resolve()
1400             with src.open("w") as fobj:
1401                 fobj.write("print('hello')")
1402             with patch("black.read_cache") as read_cache, patch(
1403                 "black.write_cache"
1404             ) as write_cache:
1405                 self.invokeBlack([str(src), "--diff"])
1406                 cache_file = black.get_cache_file(mode)
1407                 self.assertFalse(cache_file.exists())
1408                 write_cache.assert_not_called()
1409                 read_cache.assert_not_called()
1410
1411     def test_no_cache_when_writeback_color_diff(self) -> None:
1412         mode = DEFAULT_MODE
1413         with cache_dir() as workspace:
1414             src = (workspace / "test.py").resolve()
1415             with src.open("w") as fobj:
1416                 fobj.write("print('hello')")
1417             with patch("black.read_cache") as read_cache, patch(
1418                 "black.write_cache"
1419             ) as write_cache:
1420                 self.invokeBlack([str(src), "--diff", "--color"])
1421                 cache_file = black.get_cache_file(mode)
1422                 self.assertFalse(cache_file.exists())
1423                 write_cache.assert_not_called()
1424                 read_cache.assert_not_called()
1425
1426     @event_loop()
1427     def test_output_locking_when_writeback_diff(self) -> None:
1428         with cache_dir() as workspace:
1429             for tag in range(0, 4):
1430                 src = (workspace / f"test{tag}.py").resolve()
1431                 with src.open("w") as fobj:
1432                     fobj.write("print('hello')")
1433             with patch("black.Manager", wraps=multiprocessing.Manager) as mgr:
1434                 self.invokeBlack(["--diff", str(workspace)], exit_code=0)
1435                 # this isn't quite doing what we want, but if it _isn't_
1436                 # called then we cannot be using the lock it provides
1437                 mgr.assert_called()
1438
1439     @event_loop()
1440     def test_output_locking_when_writeback_color_diff(self) -> None:
1441         with cache_dir() as workspace:
1442             for tag in range(0, 4):
1443                 src = (workspace / f"test{tag}.py").resolve()
1444                 with src.open("w") as fobj:
1445                     fobj.write("print('hello')")
1446             with patch("black.Manager", wraps=multiprocessing.Manager) as mgr:
1447                 self.invokeBlack(["--diff", "--color", str(workspace)], exit_code=0)
1448                 # this isn't quite doing what we want, but if it _isn't_
1449                 # called then we cannot be using the lock it provides
1450                 mgr.assert_called()
1451
1452     def test_no_cache_when_stdin(self) -> None:
1453         mode = DEFAULT_MODE
1454         with cache_dir():
1455             result = CliRunner().invoke(
1456                 black.main, ["-"], input=BytesIO(b"print('hello')")
1457             )
1458             self.assertEqual(result.exit_code, 0)
1459             cache_file = black.get_cache_file(mode)
1460             self.assertFalse(cache_file.exists())
1461
1462     def test_read_cache_no_cachefile(self) -> None:
1463         mode = DEFAULT_MODE
1464         with cache_dir():
1465             self.assertEqual(black.read_cache(mode), {})
1466
1467     def test_write_cache_read_cache(self) -> None:
1468         mode = DEFAULT_MODE
1469         with cache_dir() as workspace:
1470             src = (workspace / "test.py").resolve()
1471             src.touch()
1472             black.write_cache({}, [src], mode)
1473             cache = black.read_cache(mode)
1474             self.assertIn(src, cache)
1475             self.assertEqual(cache[src], black.get_cache_info(src))
1476
1477     def test_filter_cached(self) -> None:
1478         with TemporaryDirectory() as workspace:
1479             path = Path(workspace)
1480             uncached = (path / "uncached").resolve()
1481             cached = (path / "cached").resolve()
1482             cached_but_changed = (path / "changed").resolve()
1483             uncached.touch()
1484             cached.touch()
1485             cached_but_changed.touch()
1486             cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
1487             todo, done = black.filter_cached(
1488                 cache, {uncached, cached, cached_but_changed}
1489             )
1490             self.assertEqual(todo, {uncached, cached_but_changed})
1491             self.assertEqual(done, {cached})
1492
1493     def test_write_cache_creates_directory_if_needed(self) -> None:
1494         mode = DEFAULT_MODE
1495         with cache_dir(exists=False) as workspace:
1496             self.assertFalse(workspace.exists())
1497             black.write_cache({}, [], mode)
1498             self.assertTrue(workspace.exists())
1499
1500     @event_loop()
1501     def test_failed_formatting_does_not_get_cached(self) -> None:
1502         mode = DEFAULT_MODE
1503         with cache_dir() as workspace, patch(
1504             "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1505         ):
1506             failing = (workspace / "failing.py").resolve()
1507             with failing.open("w") as fobj:
1508                 fobj.write("not actually python")
1509             clean = (workspace / "clean.py").resolve()
1510             with clean.open("w") as fobj:
1511                 fobj.write('print("hello")\n')
1512             self.invokeBlack([str(workspace)], exit_code=123)
1513             cache = black.read_cache(mode)
1514             self.assertNotIn(failing, cache)
1515             self.assertIn(clean, cache)
1516
1517     def test_write_cache_write_fail(self) -> None:
1518         mode = DEFAULT_MODE
1519         with cache_dir(), patch.object(Path, "open") as mock:
1520             mock.side_effect = OSError
1521             black.write_cache({}, [], mode)
1522
1523     @event_loop()
1524     @patch("black.ProcessPoolExecutor", MagicMock(side_effect=OSError))
1525     def test_works_in_mono_process_only_environment(self) -> None:
1526         with cache_dir() as workspace:
1527             for f in [
1528                 (workspace / "one.py").resolve(),
1529                 (workspace / "two.py").resolve(),
1530             ]:
1531                 f.write_text('print("hello")\n')
1532             self.invokeBlack([str(workspace)])
1533
1534     @event_loop()
1535     def test_check_diff_use_together(self) -> None:
1536         with cache_dir():
1537             # Files which will be reformatted.
1538             src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1539             self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1540             # Files which will not be reformatted.
1541             src2 = (THIS_DIR / "data" / "composition.py").resolve()
1542             self.invokeBlack([str(src2), "--diff", "--check"])
1543             # Multi file command.
1544             self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1545
1546     def test_no_files(self) -> None:
1547         with cache_dir():
1548             # Without an argument, black exits with error code 0.
1549             self.invokeBlack([])
1550
1551     def test_broken_symlink(self) -> None:
1552         with cache_dir() as workspace:
1553             symlink = workspace / "broken_link.py"
1554             try:
1555                 symlink.symlink_to("nonexistent.py")
1556             except OSError as e:
1557                 self.skipTest(f"Can't create symlinks: {e}")
1558             self.invokeBlack([str(workspace.resolve())])
1559
1560     def test_read_cache_line_lengths(self) -> None:
1561         mode = DEFAULT_MODE
1562         short_mode = replace(DEFAULT_MODE, line_length=1)
1563         with cache_dir() as workspace:
1564             path = (workspace / "file.py").resolve()
1565             path.touch()
1566             black.write_cache({}, [path], mode)
1567             one = black.read_cache(mode)
1568             self.assertIn(path, one)
1569             two = black.read_cache(short_mode)
1570             self.assertNotIn(path, two)
1571
1572     def test_tricky_unicode_symbols(self) -> None:
1573         source, expected = read_data("tricky_unicode_symbols")
1574         actual = fs(source)
1575         self.assertFormatEqual(expected, actual)
1576         black.assert_equivalent(source, actual)
1577         black.assert_stable(source, actual, DEFAULT_MODE)
1578
1579     def test_single_file_force_pyi(self) -> None:
1580         pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
1581         contents, expected = read_data("force_pyi")
1582         with cache_dir() as workspace:
1583             path = (workspace / "file.py").resolve()
1584             with open(path, "w") as fh:
1585                 fh.write(contents)
1586             self.invokeBlack([str(path), "--pyi"])
1587             with open(path, "r") as fh:
1588                 actual = fh.read()
1589             # verify cache with --pyi is separate
1590             pyi_cache = black.read_cache(pyi_mode)
1591             self.assertIn(path, pyi_cache)
1592             normal_cache = black.read_cache(DEFAULT_MODE)
1593             self.assertNotIn(path, normal_cache)
1594         self.assertFormatEqual(expected, actual)
1595         black.assert_equivalent(contents, actual)
1596         black.assert_stable(contents, actual, pyi_mode)
1597
1598     @event_loop()
1599     def test_multi_file_force_pyi(self) -> None:
1600         reg_mode = DEFAULT_MODE
1601         pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
1602         contents, expected = read_data("force_pyi")
1603         with cache_dir() as workspace:
1604             paths = [
1605                 (workspace / "file1.py").resolve(),
1606                 (workspace / "file2.py").resolve(),
1607             ]
1608             for path in paths:
1609                 with open(path, "w") as fh:
1610                     fh.write(contents)
1611             self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1612             for path in paths:
1613                 with open(path, "r") as fh:
1614                     actual = fh.read()
1615                 self.assertEqual(actual, expected)
1616             # verify cache with --pyi is separate
1617             pyi_cache = black.read_cache(pyi_mode)
1618             normal_cache = black.read_cache(reg_mode)
1619             for path in paths:
1620                 self.assertIn(path, pyi_cache)
1621                 self.assertNotIn(path, normal_cache)
1622
1623     def test_pipe_force_pyi(self) -> None:
1624         source, expected = read_data("force_pyi")
1625         result = CliRunner().invoke(
1626             black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1627         )
1628         self.assertEqual(result.exit_code, 0)
1629         actual = result.output
1630         self.assertFormatEqual(actual, expected)
1631
1632     def test_single_file_force_py36(self) -> None:
1633         reg_mode = DEFAULT_MODE
1634         py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
1635         source, expected = read_data("force_py36")
1636         with cache_dir() as workspace:
1637             path = (workspace / "file.py").resolve()
1638             with open(path, "w") as fh:
1639                 fh.write(source)
1640             self.invokeBlack([str(path), *PY36_ARGS])
1641             with open(path, "r") as fh:
1642                 actual = fh.read()
1643             # verify cache with --target-version is separate
1644             py36_cache = black.read_cache(py36_mode)
1645             self.assertIn(path, py36_cache)
1646             normal_cache = black.read_cache(reg_mode)
1647             self.assertNotIn(path, normal_cache)
1648         self.assertEqual(actual, expected)
1649
1650     @event_loop()
1651     def test_multi_file_force_py36(self) -> None:
1652         reg_mode = DEFAULT_MODE
1653         py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
1654         source, expected = read_data("force_py36")
1655         with cache_dir() as workspace:
1656             paths = [
1657                 (workspace / "file1.py").resolve(),
1658                 (workspace / "file2.py").resolve(),
1659             ]
1660             for path in paths:
1661                 with open(path, "w") as fh:
1662                     fh.write(source)
1663             self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1664             for path in paths:
1665                 with open(path, "r") as fh:
1666                     actual = fh.read()
1667                 self.assertEqual(actual, expected)
1668             # verify cache with --target-version is separate
1669             pyi_cache = black.read_cache(py36_mode)
1670             normal_cache = black.read_cache(reg_mode)
1671             for path in paths:
1672                 self.assertIn(path, pyi_cache)
1673                 self.assertNotIn(path, normal_cache)
1674
1675     def test_collections(self) -> None:
1676         source, expected = read_data("collections")
1677         actual = fs(source)
1678         self.assertFormatEqual(expected, actual)
1679         black.assert_equivalent(source, actual)
1680         black.assert_stable(source, actual, DEFAULT_MODE)
1681
1682     def test_pipe_force_py36(self) -> None:
1683         source, expected = read_data("force_py36")
1684         result = CliRunner().invoke(
1685             black.main,
1686             ["-", "-q", "--target-version=py36"],
1687             input=BytesIO(source.encode("utf8")),
1688         )
1689         self.assertEqual(result.exit_code, 0)
1690         actual = result.output
1691         self.assertFormatEqual(actual, expected)
1692
1693     def test_include_exclude(self) -> None:
1694         path = THIS_DIR / "data" / "include_exclude_tests"
1695         include = re.compile(r"\.pyi?$")
1696         exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1697         report = black.Report()
1698         gitignore = PathSpec.from_lines("gitwildmatch", [])
1699         sources: List[Path] = []
1700         expected = [
1701             Path(path / "b/dont_exclude/a.py"),
1702             Path(path / "b/dont_exclude/a.pyi"),
1703         ]
1704         this_abs = THIS_DIR.resolve()
1705         sources.extend(
1706             black.gen_python_files(
1707                 path.iterdir(), this_abs, include, exclude, None, report, gitignore
1708             )
1709         )
1710         self.assertEqual(sorted(expected), sorted(sources))
1711
1712     @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1713     def test_exclude_for_issue_1572(self) -> None:
1714         # Exclude shouldn't touch files that were explicitly given to Black through the
1715         # CLI. Exclude is supposed to only apply to the recursive discovery of files.
1716         # https://github.com/psf/black/issues/1572
1717         path = THIS_DIR / "data" / "include_exclude_tests"
1718         include = ""
1719         exclude = r"/exclude/|a\.py"
1720         src = str(path / "b/exclude/a.py")
1721         report = black.Report()
1722         expected = [Path(path / "b/exclude/a.py")]
1723         sources = list(
1724             black.get_sources(
1725                 ctx=FakeContext(),
1726                 src=(src,),
1727                 quiet=True,
1728                 verbose=False,
1729                 include=include,
1730                 exclude=exclude,
1731                 force_exclude=None,
1732                 report=report,
1733             )
1734         )
1735         self.assertEqual(sorted(expected), sorted(sources))
1736
1737     def test_gitignore_exclude(self) -> None:
1738         path = THIS_DIR / "data" / "include_exclude_tests"
1739         include = re.compile(r"\.pyi?$")
1740         exclude = re.compile(r"")
1741         report = black.Report()
1742         gitignore = PathSpec.from_lines(
1743             "gitwildmatch", ["exclude/", ".definitely_exclude"]
1744         )
1745         sources: List[Path] = []
1746         expected = [
1747             Path(path / "b/dont_exclude/a.py"),
1748             Path(path / "b/dont_exclude/a.pyi"),
1749         ]
1750         this_abs = THIS_DIR.resolve()
1751         sources.extend(
1752             black.gen_python_files(
1753                 path.iterdir(), this_abs, include, exclude, None, report, gitignore
1754             )
1755         )
1756         self.assertEqual(sorted(expected), sorted(sources))
1757
1758     def test_empty_include(self) -> None:
1759         path = THIS_DIR / "data" / "include_exclude_tests"
1760         report = black.Report()
1761         gitignore = PathSpec.from_lines("gitwildmatch", [])
1762         empty = re.compile(r"")
1763         sources: List[Path] = []
1764         expected = [
1765             Path(path / "b/exclude/a.pie"),
1766             Path(path / "b/exclude/a.py"),
1767             Path(path / "b/exclude/a.pyi"),
1768             Path(path / "b/dont_exclude/a.pie"),
1769             Path(path / "b/dont_exclude/a.py"),
1770             Path(path / "b/dont_exclude/a.pyi"),
1771             Path(path / "b/.definitely_exclude/a.pie"),
1772             Path(path / "b/.definitely_exclude/a.py"),
1773             Path(path / "b/.definitely_exclude/a.pyi"),
1774         ]
1775         this_abs = THIS_DIR.resolve()
1776         sources.extend(
1777             black.gen_python_files(
1778                 path.iterdir(),
1779                 this_abs,
1780                 empty,
1781                 re.compile(black.DEFAULT_EXCLUDES),
1782                 None,
1783                 report,
1784                 gitignore,
1785             )
1786         )
1787         self.assertEqual(sorted(expected), sorted(sources))
1788
1789     def test_empty_exclude(self) -> None:
1790         path = THIS_DIR / "data" / "include_exclude_tests"
1791         report = black.Report()
1792         gitignore = PathSpec.from_lines("gitwildmatch", [])
1793         empty = re.compile(r"")
1794         sources: List[Path] = []
1795         expected = [
1796             Path(path / "b/dont_exclude/a.py"),
1797             Path(path / "b/dont_exclude/a.pyi"),
1798             Path(path / "b/exclude/a.py"),
1799             Path(path / "b/exclude/a.pyi"),
1800             Path(path / "b/.definitely_exclude/a.py"),
1801             Path(path / "b/.definitely_exclude/a.pyi"),
1802         ]
1803         this_abs = THIS_DIR.resolve()
1804         sources.extend(
1805             black.gen_python_files(
1806                 path.iterdir(),
1807                 this_abs,
1808                 re.compile(black.DEFAULT_INCLUDES),
1809                 empty,
1810                 None,
1811                 report,
1812                 gitignore,
1813             )
1814         )
1815         self.assertEqual(sorted(expected), sorted(sources))
1816
1817     def test_invalid_include_exclude(self) -> None:
1818         for option in ["--include", "--exclude"]:
1819             self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1820
1821     def test_preserves_line_endings(self) -> None:
1822         with TemporaryDirectory() as workspace:
1823             test_file = Path(workspace) / "test.py"
1824             for nl in ["\n", "\r\n"]:
1825                 contents = nl.join(["def f(  ):", "    pass"])
1826                 test_file.write_bytes(contents.encode())
1827                 ff(test_file, write_back=black.WriteBack.YES)
1828                 updated_contents: bytes = test_file.read_bytes()
1829                 self.assertIn(nl.encode(), updated_contents)
1830                 if nl == "\n":
1831                     self.assertNotIn(b"\r\n", updated_contents)
1832
1833     def test_preserves_line_endings_via_stdin(self) -> None:
1834         for nl in ["\n", "\r\n"]:
1835             contents = nl.join(["def f(  ):", "    pass"])
1836             runner = BlackRunner()
1837             result = runner.invoke(
1838                 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1839             )
1840             self.assertEqual(result.exit_code, 0)
1841             output = runner.stdout_bytes
1842             self.assertIn(nl.encode("utf8"), output)
1843             if nl == "\n":
1844                 self.assertNotIn(b"\r\n", output)
1845
1846     def test_assert_equivalent_different_asts(self) -> None:
1847         with self.assertRaises(AssertionError):
1848             black.assert_equivalent("{}", "None")
1849
1850     def test_symlink_out_of_root_directory(self) -> None:
1851         path = MagicMock()
1852         root = THIS_DIR.resolve()
1853         child = MagicMock()
1854         include = re.compile(black.DEFAULT_INCLUDES)
1855         exclude = re.compile(black.DEFAULT_EXCLUDES)
1856         report = black.Report()
1857         gitignore = PathSpec.from_lines("gitwildmatch", [])
1858         # `child` should behave like a symlink which resolved path is clearly
1859         # outside of the `root` directory.
1860         path.iterdir.return_value = [child]
1861         child.resolve.return_value = Path("/a/b/c")
1862         child.as_posix.return_value = "/a/b/c"
1863         child.is_symlink.return_value = True
1864         try:
1865             list(
1866                 black.gen_python_files(
1867                     path.iterdir(), root, include, exclude, None, report, gitignore
1868                 )
1869             )
1870         except ValueError as ve:
1871             self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1872         path.iterdir.assert_called_once()
1873         child.resolve.assert_called_once()
1874         child.is_symlink.assert_called_once()
1875         # `child` should behave like a strange file which resolved path is clearly
1876         # outside of the `root` directory.
1877         child.is_symlink.return_value = False
1878         with self.assertRaises(ValueError):
1879             list(
1880                 black.gen_python_files(
1881                     path.iterdir(), root, include, exclude, None, report, gitignore
1882                 )
1883             )
1884         path.iterdir.assert_called()
1885         self.assertEqual(path.iterdir.call_count, 2)
1886         child.resolve.assert_called()
1887         self.assertEqual(child.resolve.call_count, 2)
1888         child.is_symlink.assert_called()
1889         self.assertEqual(child.is_symlink.call_count, 2)
1890
1891     def test_shhh_click(self) -> None:
1892         try:
1893             from click import _unicodefun  # type: ignore
1894         except ModuleNotFoundError:
1895             self.skipTest("Incompatible Click version")
1896         if not hasattr(_unicodefun, "_verify_python3_env"):
1897             self.skipTest("Incompatible Click version")
1898         # First, let's see if Click is crashing with a preferred ASCII charset.
1899         with patch("locale.getpreferredencoding") as gpe:
1900             gpe.return_value = "ASCII"
1901             with self.assertRaises(RuntimeError):
1902                 _unicodefun._verify_python3_env()
1903         # Now, let's silence Click...
1904         black.patch_click()
1905         # ...and confirm it's silent.
1906         with patch("locale.getpreferredencoding") as gpe:
1907             gpe.return_value = "ASCII"
1908             try:
1909                 _unicodefun._verify_python3_env()
1910             except RuntimeError as re:
1911                 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1912
1913     def test_root_logger_not_used_directly(self) -> None:
1914         def fail(*args: Any, **kwargs: Any) -> None:
1915             self.fail("Record created with root logger")
1916
1917         with patch.multiple(
1918             logging.root,
1919             debug=fail,
1920             info=fail,
1921             warning=fail,
1922             error=fail,
1923             critical=fail,
1924             log=fail,
1925         ):
1926             ff(THIS_FILE)
1927
1928     def test_invalid_config_return_code(self) -> None:
1929         tmp_file = Path(black.dump_to_file())
1930         try:
1931             tmp_config = Path(black.dump_to_file())
1932             tmp_config.unlink()
1933             args = ["--config", str(tmp_config), str(tmp_file)]
1934             self.invokeBlack(args, exit_code=2, ignore_config=False)
1935         finally:
1936             tmp_file.unlink()
1937
1938     def test_parse_pyproject_toml(self) -> None:
1939         test_toml_file = THIS_DIR / "test.toml"
1940         config = black.parse_pyproject_toml(str(test_toml_file))
1941         self.assertEqual(config["verbose"], 1)
1942         self.assertEqual(config["check"], "no")
1943         self.assertEqual(config["diff"], "y")
1944         self.assertEqual(config["color"], True)
1945         self.assertEqual(config["line_length"], 79)
1946         self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
1947         self.assertEqual(config["exclude"], r"\.pyi?$")
1948         self.assertEqual(config["include"], r"\.py?$")
1949
1950     def test_read_pyproject_toml(self) -> None:
1951         test_toml_file = THIS_DIR / "test.toml"
1952         fake_ctx = FakeContext()
1953         black.read_pyproject_toml(fake_ctx, FakeParameter(), str(test_toml_file))
1954         config = fake_ctx.default_map
1955         self.assertEqual(config["verbose"], "1")
1956         self.assertEqual(config["check"], "no")
1957         self.assertEqual(config["diff"], "y")
1958         self.assertEqual(config["color"], "True")
1959         self.assertEqual(config["line_length"], "79")
1960         self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
1961         self.assertEqual(config["exclude"], r"\.pyi?$")
1962         self.assertEqual(config["include"], r"\.py?$")
1963
1964     def test_find_project_root(self) -> None:
1965         with TemporaryDirectory() as workspace:
1966             root = Path(workspace)
1967             test_dir = root / "test"
1968             test_dir.mkdir()
1969
1970             src_dir = root / "src"
1971             src_dir.mkdir()
1972
1973             root_pyproject = root / "pyproject.toml"
1974             root_pyproject.touch()
1975             src_pyproject = src_dir / "pyproject.toml"
1976             src_pyproject.touch()
1977             src_python = src_dir / "foo.py"
1978             src_python.touch()
1979
1980             self.assertEqual(
1981                 black.find_project_root((src_dir, test_dir)), root.resolve()
1982             )
1983             self.assertEqual(black.find_project_root((src_dir,)), src_dir.resolve())
1984             self.assertEqual(black.find_project_root((src_python,)), src_dir.resolve())
1985
1986     def test_bpo_33660_workaround(self) -> None:
1987         if system() == "Windows":
1988             return
1989
1990         # https://bugs.python.org/issue33660
1991
1992         old_cwd = Path.cwd()
1993         try:
1994             root = Path("/")
1995             os.chdir(str(root))
1996             path = Path("workspace") / "project"
1997             report = black.Report(verbose=True)
1998             normalized_path = black.normalize_path_maybe_ignore(path, root, report)
1999             self.assertEqual(normalized_path, "workspace/project")
2000         finally:
2001             os.chdir(str(old_cwd))
2002
2003
2004 with open(black.__file__, "r", encoding="utf-8") as _bf:
2005     black_source_lines = _bf.readlines()
2006
2007
2008 def tracefunc(frame: types.FrameType, event: str, arg: Any) -> Callable:
2009     """Show function calls `from black/__init__.py` as they happen.
2010
2011     Register this with `sys.settrace()` in a test you're debugging.
2012     """
2013     if event != "call":
2014         return tracefunc
2015
2016     stack = len(inspect.stack()) - 19
2017     stack *= 2
2018     filename = frame.f_code.co_filename
2019     lineno = frame.f_lineno
2020     func_sig_lineno = lineno - 1
2021     funcname = black_source_lines[func_sig_lineno].strip()
2022     while funcname.startswith("@"):
2023         func_sig_lineno += 1
2024         funcname = black_source_lines[func_sig_lineno].strip()
2025     if "black/__init__.py" in filename:
2026         print(f"{' ' * stack}{lineno}:{funcname}")
2027     return tracefunc
2028
2029
2030 if __name__ == "__main__":
2031     unittest.main(module="test_black")