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

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