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.
  12 from concurrent.futures import ThreadPoolExecutor
 
  13 from contextlib import contextmanager, redirect_stderr
 
  14 from dataclasses import replace
 
  15 from io import BytesIO
 
  16 from pathlib import Path
 
  17 from platform import system
 
  18 from tempfile import TemporaryDirectory
 
  30 from unittest.mock import MagicMock, patch
 
  35 from click import unstyle
 
  36 from click.testing import CliRunner
 
  37 from pathspec import PathSpec
 
  41 from black import Feature, TargetVersion
 
  42 from black import re_compile_maybe_verbose as compile_pattern
 
  43 from black.cache import get_cache_dir, get_cache_file
 
  44 from black.debug import DebugVisitor
 
  45 from black.output import color_diff, diff
 
  46 from black.report import Report
 
  48 # Import other test classes
 
  49 from tests.util import (
 
  67 THIS_FILE = Path(__file__)
 
  68 EMPTY_CONFIG = THIS_DIR / "data" / "empty_pyproject.toml"
 
  69 PY36_ARGS = [f"--target-version={version.name.lower()}" for version in PY36_VERSIONS]
 
  70 DEFAULT_EXCLUDE = black.re_compile_maybe_verbose(black.const.DEFAULT_EXCLUDES)
 
  71 DEFAULT_INCLUDE = black.re_compile_maybe_verbose(black.const.DEFAULT_INCLUDES)
 
  75 # Match the time output in a diff, but nothing else
 
  76 DIFF_TIME = re.compile(r"\t[\d\-:+\. ]+")
 
  80 def cache_dir(exists: bool = True) -> Iterator[Path]:
 
  81     with TemporaryDirectory() as workspace:
 
  82         cache_dir = Path(workspace)
 
  84             cache_dir = cache_dir / "new"
 
  85         with patch("black.cache.CACHE_DIR", cache_dir):
 
  90 def event_loop() -> Iterator[None]:
 
  91     policy = asyncio.get_event_loop_policy()
 
  92     loop = policy.new_event_loop()
 
  93     asyncio.set_event_loop(loop)
 
 101 class FakeContext(click.Context):
 
 102     """A fake click Context for when calling functions that need it."""
 
 104     def __init__(self) -> None:
 
 105         self.default_map: Dict[str, Any] = {}
 
 106         # Dummy root, since most of the tests don't care about it
 
 107         self.obj: Dict[str, Any] = {"root": PROJECT_ROOT}
 
 110 class FakeParameter(click.Parameter):
 
 111     """A fake click Parameter for when calling functions that need it."""
 
 113     def __init__(self) -> None:
 
 117 class BlackRunner(CliRunner):
 
 118     """Make sure STDOUT and STDERR are kept separate when testing Black via its CLI."""
 
 120     def __init__(self) -> None:
 
 121         super().__init__(mix_stderr=False)
 
 125     args: List[str], exit_code: int = 0, ignore_config: bool = True
 
 127     runner = BlackRunner()
 
 129         args = ["--verbose", "--config", str(THIS_DIR / "empty.toml"), *args]
 
 130     result = runner.invoke(black.main, args, catch_exceptions=False)
 
 131     assert result.stdout_bytes is not None
 
 132     assert result.stderr_bytes is not None
 
 134         f"Failed with args: {args}\n"
 
 135         f"stdout: {result.stdout_bytes.decode()!r}\n"
 
 136         f"stderr: {result.stderr_bytes.decode()!r}\n"
 
 137         f"exception: {result.exception}"
 
 139     assert result.exit_code == exit_code, msg
 
 142 class BlackTestCase(BlackBaseTestCase):
 
 143     invokeBlack = staticmethod(invokeBlack)
 
 145     def test_empty_ff(self) -> None:
 
 147         tmp_file = Path(black.dump_to_file())
 
 149             self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES))
 
 150             with open(tmp_file, encoding="utf8") as f:
 
 154         self.assertFormatEqual(expected, actual)
 
 156     def test_experimental_string_processing_warns(self) -> None:
 
 158             black.mode.Deprecated, black.Mode, experimental_string_processing=True
 
 161     def test_piping(self) -> None:
 
 162         source, expected = read_data_from_file(PROJECT_ROOT / "src/black/__init__.py")
 
 163         result = BlackRunner().invoke(
 
 168                 f"--line-length={black.DEFAULT_LINE_LENGTH}",
 
 169                 f"--config={EMPTY_CONFIG}",
 
 171             input=BytesIO(source.encode("utf8")),
 
 173         self.assertEqual(result.exit_code, 0)
 
 174         self.assertFormatEqual(expected, result.output)
 
 175         if source != result.output:
 
 176             black.assert_equivalent(source, result.output)
 
 177             black.assert_stable(source, result.output, DEFAULT_MODE)
 
 179     def test_piping_diff(self) -> None:
 
 180         diff_header = re.compile(
 
 181             r"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d\d\d\d "
 
 184         source, _ = read_data("simple_cases", "expression.py")
 
 185         expected, _ = read_data("simple_cases", "expression.diff")
 
 189             f"--line-length={black.DEFAULT_LINE_LENGTH}",
 
 191             f"--config={EMPTY_CONFIG}",
 
 193         result = BlackRunner().invoke(
 
 194             black.main, args, input=BytesIO(source.encode("utf8"))
 
 196         self.assertEqual(result.exit_code, 0)
 
 197         actual = diff_header.sub(DETERMINISTIC_HEADER, result.output)
 
 198         actual = actual.rstrip() + "\n"  # the diff output has a trailing space
 
 199         self.assertEqual(expected, actual)
 
 201     def test_piping_diff_with_color(self) -> None:
 
 202         source, _ = read_data("simple_cases", "expression.py")
 
 206             f"--line-length={black.DEFAULT_LINE_LENGTH}",
 
 209             f"--config={EMPTY_CONFIG}",
 
 211         result = BlackRunner().invoke(
 
 212             black.main, args, input=BytesIO(source.encode("utf8"))
 
 214         actual = result.output
 
 215         # Again, the contents are checked in a different test, so only look for colors.
 
 216         self.assertIn("\033[1m", actual)
 
 217         self.assertIn("\033[36m", actual)
 
 218         self.assertIn("\033[32m", actual)
 
 219         self.assertIn("\033[31m", actual)
 
 220         self.assertIn("\033[0m", actual)
 
 222     @patch("black.dump_to_file", dump_to_stderr)
 
 223     def _test_wip(self) -> None:
 
 224         source, expected = read_data("miscellaneous", "wip")
 
 225         sys.settrace(tracefunc)
 
 228             experimental_string_processing=False,
 
 229             target_versions={black.TargetVersion.PY38},
 
 231         actual = fs(source, mode=mode)
 
 233         self.assertFormatEqual(expected, actual)
 
 234         black.assert_equivalent(source, actual)
 
 235         black.assert_stable(source, actual, black.FileMode())
 
 237     def test_pep_572_version_detection(self) -> None:
 
 238         source, _ = read_data("py_38", "pep_572")
 
 239         root = black.lib2to3_parse(source)
 
 240         features = black.get_features_used(root)
 
 241         self.assertIn(black.Feature.ASSIGNMENT_EXPRESSIONS, features)
 
 242         versions = black.detect_target_versions(root)
 
 243         self.assertIn(black.TargetVersion.PY38, versions)
 
 245     def test_expression_ff(self) -> None:
 
 246         source, expected = read_data("simple_cases", "expression.py")
 
 247         tmp_file = Path(black.dump_to_file(source))
 
 249             self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
 
 250             with open(tmp_file, encoding="utf8") as f:
 
 254         self.assertFormatEqual(expected, actual)
 
 255         with patch("black.dump_to_file", dump_to_stderr):
 
 256             black.assert_equivalent(source, actual)
 
 257             black.assert_stable(source, actual, DEFAULT_MODE)
 
 259     def test_expression_diff(self) -> None:
 
 260         source, _ = read_data("simple_cases", "expression.py")
 
 261         expected, _ = read_data("simple_cases", "expression.diff")
 
 262         tmp_file = Path(black.dump_to_file(source))
 
 263         diff_header = re.compile(
 
 264             rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
 
 265             r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
 
 268             result = BlackRunner().invoke(
 
 269                 black.main, ["--diff", str(tmp_file), f"--config={EMPTY_CONFIG}"]
 
 271             self.assertEqual(result.exit_code, 0)
 
 274         actual = result.output
 
 275         actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
 
 276         if expected != actual:
 
 277             dump = black.dump_to_file(actual)
 
 279                 "Expected diff isn't equal to the actual. If you made changes to"
 
 280                 " expression.py and this is an anticipated difference, overwrite"
 
 281                 f" tests/data/expression.diff with {dump}"
 
 283             self.assertEqual(expected, actual, msg)
 
 285     def test_expression_diff_with_color(self) -> None:
 
 286         source, _ = read_data("simple_cases", "expression.py")
 
 287         expected, _ = read_data("simple_cases", "expression.diff")
 
 288         tmp_file = Path(black.dump_to_file(source))
 
 290             result = BlackRunner().invoke(
 
 292                 ["--diff", "--color", str(tmp_file), f"--config={EMPTY_CONFIG}"],
 
 296         actual = result.output
 
 297         # We check the contents of the diff in `test_expression_diff`. All
 
 298         # we need to check here is that color codes exist in the result.
 
 299         self.assertIn("\033[1m", actual)
 
 300         self.assertIn("\033[36m", actual)
 
 301         self.assertIn("\033[32m", actual)
 
 302         self.assertIn("\033[31m", actual)
 
 303         self.assertIn("\033[0m", actual)
 
 305     def test_detect_pos_only_arguments(self) -> None:
 
 306         source, _ = read_data("py_38", "pep_570")
 
 307         root = black.lib2to3_parse(source)
 
 308         features = black.get_features_used(root)
 
 309         self.assertIn(black.Feature.POS_ONLY_ARGUMENTS, features)
 
 310         versions = black.detect_target_versions(root)
 
 311         self.assertIn(black.TargetVersion.PY38, versions)
 
 313     @patch("black.dump_to_file", dump_to_stderr)
 
 314     def test_string_quotes(self) -> None:
 
 315         source, expected = read_data("miscellaneous", "string_quotes")
 
 316         mode = black.Mode(preview=True)
 
 317         assert_format(source, expected, mode)
 
 318         mode = replace(mode, string_normalization=False)
 
 319         not_normalized = fs(source, mode=mode)
 
 320         self.assertFormatEqual(source.replace("\\\n", ""), not_normalized)
 
 321         black.assert_equivalent(source, not_normalized)
 
 322         black.assert_stable(source, not_normalized, mode=mode)
 
 324     def test_skip_magic_trailing_comma(self) -> None:
 
 325         source, _ = read_data("simple_cases", "expression")
 
 326         expected, _ = read_data(
 
 327             "miscellaneous", "expression_skip_magic_trailing_comma.diff"
 
 329         tmp_file = Path(black.dump_to_file(source))
 
 330         diff_header = re.compile(
 
 331             rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
 
 332             r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
 
 335             result = BlackRunner().invoke(
 
 336                 black.main, ["-C", "--diff", str(tmp_file), f"--config={EMPTY_CONFIG}"]
 
 338             self.assertEqual(result.exit_code, 0)
 
 341         actual = result.output
 
 342         actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
 
 343         actual = actual.rstrip() + "\n"  # the diff output has a trailing space
 
 344         if expected != actual:
 
 345             dump = black.dump_to_file(actual)
 
 347                 "Expected diff isn't equal to the actual. If you made changes to"
 
 348                 " expression.py and this is an anticipated difference, overwrite"
 
 349                 f" tests/data/expression_skip_magic_trailing_comma.diff with {dump}"
 
 351             self.assertEqual(expected, actual, msg)
 
 353     @patch("black.dump_to_file", dump_to_stderr)
 
 354     def test_async_as_identifier(self) -> None:
 
 355         source_path = get_case_path("miscellaneous", "async_as_identifier")
 
 356         source, expected = read_data_from_file(source_path)
 
 358         self.assertFormatEqual(expected, actual)
 
 359         major, minor = sys.version_info[:2]
 
 360         if major < 3 or (major <= 3 and minor < 7):
 
 361             black.assert_equivalent(source, actual)
 
 362         black.assert_stable(source, actual, DEFAULT_MODE)
 
 363         # ensure black can parse this when the target is 3.6
 
 364         self.invokeBlack([str(source_path), "--target-version", "py36"])
 
 365         # but not on 3.7, because async/await is no longer an identifier
 
 366         self.invokeBlack([str(source_path), "--target-version", "py37"], exit_code=123)
 
 368     @patch("black.dump_to_file", dump_to_stderr)
 
 369     def test_python37(self) -> None:
 
 370         source_path = get_case_path("py_37", "python37")
 
 371         source, expected = read_data_from_file(source_path)
 
 373         self.assertFormatEqual(expected, actual)
 
 374         major, minor = sys.version_info[:2]
 
 375         if major > 3 or (major == 3 and minor >= 7):
 
 376             black.assert_equivalent(source, actual)
 
 377         black.assert_stable(source, actual, DEFAULT_MODE)
 
 378         # ensure black can parse this when the target is 3.7
 
 379         self.invokeBlack([str(source_path), "--target-version", "py37"])
 
 380         # but not on 3.6, because we use async as a reserved keyword
 
 381         self.invokeBlack([str(source_path), "--target-version", "py36"], exit_code=123)
 
 383     def test_tab_comment_indentation(self) -> None:
 
 384         contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
 
 385         contents_spc = "if 1:\n    if 2:\n        pass\n    # comment\n    pass\n"
 
 386         self.assertFormatEqual(contents_spc, fs(contents_spc))
 
 387         self.assertFormatEqual(contents_spc, fs(contents_tab))
 
 389         contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
 
 390         contents_spc = "if 1:\n    if 2:\n        pass\n        # comment\n    pass\n"
 
 391         self.assertFormatEqual(contents_spc, fs(contents_spc))
 
 392         self.assertFormatEqual(contents_spc, fs(contents_tab))
 
 394         # mixed tabs and spaces (valid Python 2 code)
 
 395         contents_tab = "if 1:\n        if 2:\n\t\tpass\n\t# comment\n        pass\n"
 
 396         contents_spc = "if 1:\n    if 2:\n        pass\n    # comment\n    pass\n"
 
 397         self.assertFormatEqual(contents_spc, fs(contents_spc))
 
 398         self.assertFormatEqual(contents_spc, fs(contents_tab))
 
 400         contents_tab = "if 1:\n        if 2:\n\t\tpass\n\t\t# comment\n        pass\n"
 
 401         contents_spc = "if 1:\n    if 2:\n        pass\n        # comment\n    pass\n"
 
 402         self.assertFormatEqual(contents_spc, fs(contents_spc))
 
 403         self.assertFormatEqual(contents_spc, fs(contents_tab))
 
 405     def test_report_verbose(self) -> None:
 
 406         report = Report(verbose=True)
 
 410         def out(msg: str, **kwargs: Any) -> None:
 
 411             out_lines.append(msg)
 
 413         def err(msg: str, **kwargs: Any) -> None:
 
 414             err_lines.append(msg)
 
 416         with patch("black.output._out", out), patch("black.output._err", err):
 
 417             report.done(Path("f1"), black.Changed.NO)
 
 418             self.assertEqual(len(out_lines), 1)
 
 419             self.assertEqual(len(err_lines), 0)
 
 420             self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
 
 421             self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
 
 422             self.assertEqual(report.return_code, 0)
 
 423             report.done(Path("f2"), black.Changed.YES)
 
 424             self.assertEqual(len(out_lines), 2)
 
 425             self.assertEqual(len(err_lines), 0)
 
 426             self.assertEqual(out_lines[-1], "reformatted f2")
 
 428                 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
 
 430             report.done(Path("f3"), black.Changed.CACHED)
 
 431             self.assertEqual(len(out_lines), 3)
 
 432             self.assertEqual(len(err_lines), 0)
 
 434                 out_lines[-1], "f3 wasn't modified on disk since last run."
 
 437                 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
 
 439             self.assertEqual(report.return_code, 0)
 
 441             self.assertEqual(report.return_code, 1)
 
 443             report.failed(Path("e1"), "boom")
 
 444             self.assertEqual(len(out_lines), 3)
 
 445             self.assertEqual(len(err_lines), 1)
 
 446             self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
 
 448                 unstyle(str(report)),
 
 449                 "1 file reformatted, 2 files left unchanged, 1 file failed to"
 
 452             self.assertEqual(report.return_code, 123)
 
 453             report.done(Path("f3"), black.Changed.YES)
 
 454             self.assertEqual(len(out_lines), 4)
 
 455             self.assertEqual(len(err_lines), 1)
 
 456             self.assertEqual(out_lines[-1], "reformatted f3")
 
 458                 unstyle(str(report)),
 
 459                 "2 files reformatted, 2 files left unchanged, 1 file failed to"
 
 462             self.assertEqual(report.return_code, 123)
 
 463             report.failed(Path("e2"), "boom")
 
 464             self.assertEqual(len(out_lines), 4)
 
 465             self.assertEqual(len(err_lines), 2)
 
 466             self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
 
 468                 unstyle(str(report)),
 
 469                 "2 files reformatted, 2 files left unchanged, 2 files failed to"
 
 472             self.assertEqual(report.return_code, 123)
 
 473             report.path_ignored(Path("wat"), "no match")
 
 474             self.assertEqual(len(out_lines), 5)
 
 475             self.assertEqual(len(err_lines), 2)
 
 476             self.assertEqual(out_lines[-1], "wat ignored: no match")
 
 478                 unstyle(str(report)),
 
 479                 "2 files reformatted, 2 files left unchanged, 2 files failed to"
 
 482             self.assertEqual(report.return_code, 123)
 
 483             report.done(Path("f4"), black.Changed.NO)
 
 484             self.assertEqual(len(out_lines), 6)
 
 485             self.assertEqual(len(err_lines), 2)
 
 486             self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
 
 488                 unstyle(str(report)),
 
 489                 "2 files reformatted, 3 files left unchanged, 2 files failed to"
 
 492             self.assertEqual(report.return_code, 123)
 
 495                 unstyle(str(report)),
 
 496                 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
 
 497                 " would fail to reformat.",
 
 502                 unstyle(str(report)),
 
 503                 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
 
 504                 " would fail to reformat.",
 
 507     def test_report_quiet(self) -> None:
 
 508         report = Report(quiet=True)
 
 512         def out(msg: str, **kwargs: Any) -> None:
 
 513             out_lines.append(msg)
 
 515         def err(msg: str, **kwargs: Any) -> None:
 
 516             err_lines.append(msg)
 
 518         with patch("black.output._out", out), patch("black.output._err", err):
 
 519             report.done(Path("f1"), black.Changed.NO)
 
 520             self.assertEqual(len(out_lines), 0)
 
 521             self.assertEqual(len(err_lines), 0)
 
 522             self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
 
 523             self.assertEqual(report.return_code, 0)
 
 524             report.done(Path("f2"), black.Changed.YES)
 
 525             self.assertEqual(len(out_lines), 0)
 
 526             self.assertEqual(len(err_lines), 0)
 
 528                 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
 
 530             report.done(Path("f3"), black.Changed.CACHED)
 
 531             self.assertEqual(len(out_lines), 0)
 
 532             self.assertEqual(len(err_lines), 0)
 
 534                 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
 
 536             self.assertEqual(report.return_code, 0)
 
 538             self.assertEqual(report.return_code, 1)
 
 540             report.failed(Path("e1"), "boom")
 
 541             self.assertEqual(len(out_lines), 0)
 
 542             self.assertEqual(len(err_lines), 1)
 
 543             self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
 
 545                 unstyle(str(report)),
 
 546                 "1 file reformatted, 2 files left unchanged, 1 file failed to"
 
 549             self.assertEqual(report.return_code, 123)
 
 550             report.done(Path("f3"), black.Changed.YES)
 
 551             self.assertEqual(len(out_lines), 0)
 
 552             self.assertEqual(len(err_lines), 1)
 
 554                 unstyle(str(report)),
 
 555                 "2 files reformatted, 2 files left unchanged, 1 file failed to"
 
 558             self.assertEqual(report.return_code, 123)
 
 559             report.failed(Path("e2"), "boom")
 
 560             self.assertEqual(len(out_lines), 0)
 
 561             self.assertEqual(len(err_lines), 2)
 
 562             self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
 
 564                 unstyle(str(report)),
 
 565                 "2 files reformatted, 2 files left unchanged, 2 files failed to"
 
 568             self.assertEqual(report.return_code, 123)
 
 569             report.path_ignored(Path("wat"), "no match")
 
 570             self.assertEqual(len(out_lines), 0)
 
 571             self.assertEqual(len(err_lines), 2)
 
 573                 unstyle(str(report)),
 
 574                 "2 files reformatted, 2 files left unchanged, 2 files failed to"
 
 577             self.assertEqual(report.return_code, 123)
 
 578             report.done(Path("f4"), black.Changed.NO)
 
 579             self.assertEqual(len(out_lines), 0)
 
 580             self.assertEqual(len(err_lines), 2)
 
 582                 unstyle(str(report)),
 
 583                 "2 files reformatted, 3 files left unchanged, 2 files failed to"
 
 586             self.assertEqual(report.return_code, 123)
 
 589                 unstyle(str(report)),
 
 590                 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
 
 591                 " would fail to reformat.",
 
 596                 unstyle(str(report)),
 
 597                 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
 
 598                 " would fail to reformat.",
 
 601     def test_report_normal(self) -> None:
 
 602         report = black.Report()
 
 606         def out(msg: str, **kwargs: Any) -> None:
 
 607             out_lines.append(msg)
 
 609         def err(msg: str, **kwargs: Any) -> None:
 
 610             err_lines.append(msg)
 
 612         with patch("black.output._out", out), patch("black.output._err", err):
 
 613             report.done(Path("f1"), black.Changed.NO)
 
 614             self.assertEqual(len(out_lines), 0)
 
 615             self.assertEqual(len(err_lines), 0)
 
 616             self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
 
 617             self.assertEqual(report.return_code, 0)
 
 618             report.done(Path("f2"), black.Changed.YES)
 
 619             self.assertEqual(len(out_lines), 1)
 
 620             self.assertEqual(len(err_lines), 0)
 
 621             self.assertEqual(out_lines[-1], "reformatted f2")
 
 623                 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
 
 625             report.done(Path("f3"), black.Changed.CACHED)
 
 626             self.assertEqual(len(out_lines), 1)
 
 627             self.assertEqual(len(err_lines), 0)
 
 628             self.assertEqual(out_lines[-1], "reformatted f2")
 
 630                 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
 
 632             self.assertEqual(report.return_code, 0)
 
 634             self.assertEqual(report.return_code, 1)
 
 636             report.failed(Path("e1"), "boom")
 
 637             self.assertEqual(len(out_lines), 1)
 
 638             self.assertEqual(len(err_lines), 1)
 
 639             self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
 
 641                 unstyle(str(report)),
 
 642                 "1 file reformatted, 2 files left unchanged, 1 file failed to"
 
 645             self.assertEqual(report.return_code, 123)
 
 646             report.done(Path("f3"), black.Changed.YES)
 
 647             self.assertEqual(len(out_lines), 2)
 
 648             self.assertEqual(len(err_lines), 1)
 
 649             self.assertEqual(out_lines[-1], "reformatted f3")
 
 651                 unstyle(str(report)),
 
 652                 "2 files reformatted, 2 files left unchanged, 1 file failed to"
 
 655             self.assertEqual(report.return_code, 123)
 
 656             report.failed(Path("e2"), "boom")
 
 657             self.assertEqual(len(out_lines), 2)
 
 658             self.assertEqual(len(err_lines), 2)
 
 659             self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
 
 661                 unstyle(str(report)),
 
 662                 "2 files reformatted, 2 files left unchanged, 2 files failed to"
 
 665             self.assertEqual(report.return_code, 123)
 
 666             report.path_ignored(Path("wat"), "no match")
 
 667             self.assertEqual(len(out_lines), 2)
 
 668             self.assertEqual(len(err_lines), 2)
 
 670                 unstyle(str(report)),
 
 671                 "2 files reformatted, 2 files left unchanged, 2 files failed to"
 
 674             self.assertEqual(report.return_code, 123)
 
 675             report.done(Path("f4"), black.Changed.NO)
 
 676             self.assertEqual(len(out_lines), 2)
 
 677             self.assertEqual(len(err_lines), 2)
 
 679                 unstyle(str(report)),
 
 680                 "2 files reformatted, 3 files left unchanged, 2 files failed to"
 
 683             self.assertEqual(report.return_code, 123)
 
 686                 unstyle(str(report)),
 
 687                 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
 
 688                 " would fail to reformat.",
 
 693                 unstyle(str(report)),
 
 694                 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
 
 695                 " would fail to reformat.",
 
 698     def test_lib2to3_parse(self) -> None:
 
 699         with self.assertRaises(black.InvalidInput):
 
 700             black.lib2to3_parse("invalid syntax")
 
 703         black.lib2to3_parse(straddling)
 
 704         black.lib2to3_parse(straddling, {TargetVersion.PY36})
 
 707         with self.assertRaises(black.InvalidInput):
 
 708             black.lib2to3_parse(py2_only, {TargetVersion.PY36})
 
 710         py3_only = "exec(x, end=y)"
 
 711         black.lib2to3_parse(py3_only)
 
 712         black.lib2to3_parse(py3_only, {TargetVersion.PY36})
 
 714     def test_get_features_used_decorator(self) -> None:
 
 715         # Test the feature detection of new decorator syntax
 
 716         # since this makes some test cases of test_get_features_used()
 
 717         # fails if it fails, this is tested first so that a useful case
 
 719         simples, relaxed = read_data("miscellaneous", "decorators")
 
 720         # skip explanation comments at the top of the file
 
 721         for simple_test in simples.split("##")[1:]:
 
 722             node = black.lib2to3_parse(simple_test)
 
 723             decorator = str(node.children[0].children[0]).strip()
 
 725                 Feature.RELAXED_DECORATORS,
 
 726                 black.get_features_used(node),
 
 728                     f"decorator '{decorator}' follows python<=3.8 syntax"
 
 729                     "but is detected as 3.9+"
 
 730                     # f"The full node is\n{node!r}"
 
 733         # skip the '# output' comment at the top of the output part
 
 734         for relaxed_test in relaxed.split("##")[1:]:
 
 735             node = black.lib2to3_parse(relaxed_test)
 
 736             decorator = str(node.children[0].children[0]).strip()
 
 738                 Feature.RELAXED_DECORATORS,
 
 739                 black.get_features_used(node),
 
 741                     f"decorator '{decorator}' uses python3.9+ syntax"
 
 742                     "but is detected as python<=3.8"
 
 743                     # f"The full node is\n{node!r}"
 
 747     def test_get_features_used(self) -> None:
 
 748         node = black.lib2to3_parse("def f(*, arg): ...\n")
 
 749         self.assertEqual(black.get_features_used(node), set())
 
 750         node = black.lib2to3_parse("def f(*, arg,): ...\n")
 
 751         self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
 
 752         node = black.lib2to3_parse("f(*arg,)\n")
 
 754             black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
 
 756         node = black.lib2to3_parse("def f(*, arg): f'string'\n")
 
 757         self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
 
 758         node = black.lib2to3_parse("123_456\n")
 
 759         self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
 
 760         node = black.lib2to3_parse("123456\n")
 
 761         self.assertEqual(black.get_features_used(node), set())
 
 762         source, expected = read_data("simple_cases", "function")
 
 763         node = black.lib2to3_parse(source)
 
 764         expected_features = {
 
 765             Feature.TRAILING_COMMA_IN_CALL,
 
 766             Feature.TRAILING_COMMA_IN_DEF,
 
 769         self.assertEqual(black.get_features_used(node), expected_features)
 
 770         node = black.lib2to3_parse(expected)
 
 771         self.assertEqual(black.get_features_used(node), expected_features)
 
 772         source, expected = read_data("simple_cases", "expression")
 
 773         node = black.lib2to3_parse(source)
 
 774         self.assertEqual(black.get_features_used(node), set())
 
 775         node = black.lib2to3_parse(expected)
 
 776         self.assertEqual(black.get_features_used(node), set())
 
 777         node = black.lib2to3_parse("lambda a, /, b: ...")
 
 778         self.assertEqual(black.get_features_used(node), {Feature.POS_ONLY_ARGUMENTS})
 
 779         node = black.lib2to3_parse("def fn(a, /, b): ...")
 
 780         self.assertEqual(black.get_features_used(node), {Feature.POS_ONLY_ARGUMENTS})
 
 781         node = black.lib2to3_parse("def fn(): yield a, b")
 
 782         self.assertEqual(black.get_features_used(node), set())
 
 783         node = black.lib2to3_parse("def fn(): return a, b")
 
 784         self.assertEqual(black.get_features_used(node), set())
 
 785         node = black.lib2to3_parse("def fn(): yield *b, c")
 
 786         self.assertEqual(black.get_features_used(node), {Feature.UNPACKING_ON_FLOW})
 
 787         node = black.lib2to3_parse("def fn(): return a, *b, c")
 
 788         self.assertEqual(black.get_features_used(node), {Feature.UNPACKING_ON_FLOW})
 
 789         node = black.lib2to3_parse("x = a, *b, c")
 
 790         self.assertEqual(black.get_features_used(node), set())
 
 791         node = black.lib2to3_parse("x: Any = regular")
 
 792         self.assertEqual(black.get_features_used(node), set())
 
 793         node = black.lib2to3_parse("x: Any = (regular, regular)")
 
 794         self.assertEqual(black.get_features_used(node), set())
 
 795         node = black.lib2to3_parse("x: Any = Complex(Type(1))[something]")
 
 796         self.assertEqual(black.get_features_used(node), set())
 
 797         node = black.lib2to3_parse("x: Tuple[int, ...] = a, b, c")
 
 799             black.get_features_used(node), {Feature.ANN_ASSIGN_EXTENDED_RHS}
 
 801         node = black.lib2to3_parse("try: pass\nexcept Something: pass")
 
 802         self.assertEqual(black.get_features_used(node), set())
 
 803         node = black.lib2to3_parse("try: pass\nexcept (*Something,): pass")
 
 804         self.assertEqual(black.get_features_used(node), set())
 
 805         node = black.lib2to3_parse("try: pass\nexcept *Group: pass")
 
 806         self.assertEqual(black.get_features_used(node), {Feature.EXCEPT_STAR})
 
 807         node = black.lib2to3_parse("a[*b]")
 
 808         self.assertEqual(black.get_features_used(node), {Feature.VARIADIC_GENERICS})
 
 809         node = black.lib2to3_parse("a[x, *y(), z] = t")
 
 810         self.assertEqual(black.get_features_used(node), {Feature.VARIADIC_GENERICS})
 
 811         node = black.lib2to3_parse("def fn(*args: *T): pass")
 
 812         self.assertEqual(black.get_features_used(node), {Feature.VARIADIC_GENERICS})
 
 814     def test_get_features_used_for_future_flags(self) -> None:
 
 815         for src, features in [
 
 816             ("from __future__ import annotations", {Feature.FUTURE_ANNOTATIONS}),
 
 818                 "from __future__ import (other, annotations)",
 
 819                 {Feature.FUTURE_ANNOTATIONS},
 
 821             ("a = 1 + 2\nfrom something import annotations", set()),
 
 822             ("from __future__ import x, y", set()),
 
 824             with self.subTest(src=src, features=features):
 
 825                 node = black.lib2to3_parse(src)
 
 826                 future_imports = black.get_future_imports(node)
 
 828                     black.get_features_used(node, future_imports=future_imports),
 
 832     def test_get_future_imports(self) -> None:
 
 833         node = black.lib2to3_parse("\n")
 
 834         self.assertEqual(set(), black.get_future_imports(node))
 
 835         node = black.lib2to3_parse("from __future__ import black\n")
 
 836         self.assertEqual({"black"}, black.get_future_imports(node))
 
 837         node = black.lib2to3_parse("from __future__ import multiple, imports\n")
 
 838         self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
 
 839         node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
 
 840         self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
 
 841         node = black.lib2to3_parse(
 
 842             "from __future__ import multiple\nfrom __future__ import imports\n"
 
 844         self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
 
 845         node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
 
 846         self.assertEqual({"black"}, black.get_future_imports(node))
 
 847         node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
 
 848         self.assertEqual({"black"}, black.get_future_imports(node))
 
 849         node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
 
 850         self.assertEqual(set(), black.get_future_imports(node))
 
 851         node = black.lib2to3_parse("from some.module import black\n")
 
 852         self.assertEqual(set(), black.get_future_imports(node))
 
 853         node = black.lib2to3_parse(
 
 854             "from __future__ import unicode_literals as _unicode_literals"
 
 856         self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
 
 857         node = black.lib2to3_parse(
 
 858             "from __future__ import unicode_literals as _lol, print"
 
 860         self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
 
 862     @pytest.mark.incompatible_with_mypyc
 
 863     def test_debug_visitor(self) -> None:
 
 864         source, _ = read_data("miscellaneous", "debug_visitor")
 
 865         expected, _ = read_data("miscellaneous", "debug_visitor.out")
 
 869         def out(msg: str, **kwargs: Any) -> None:
 
 870             out_lines.append(msg)
 
 872         def err(msg: str, **kwargs: Any) -> None:
 
 873             err_lines.append(msg)
 
 875         with patch("black.debug.out", out):
 
 876             DebugVisitor.show(source)
 
 877         actual = "\n".join(out_lines) + "\n"
 
 879         if expected != actual:
 
 880             log_name = black.dump_to_file(*out_lines)
 
 884             f"AST print out is different. Actual version dumped to {log_name}",
 
 887     def test_format_file_contents(self) -> None:
 
 890         with self.assertRaises(black.NothingChanged):
 
 891             black.format_file_contents(empty, mode=mode, fast=False)
 
 893         with self.assertRaises(black.NothingChanged):
 
 894             black.format_file_contents(just_nl, mode=mode, fast=False)
 
 895         same = "j = [1, 2, 3]\n"
 
 896         with self.assertRaises(black.NothingChanged):
 
 897             black.format_file_contents(same, mode=mode, fast=False)
 
 898         different = "j = [1,2,3]"
 
 900         actual = black.format_file_contents(different, mode=mode, fast=False)
 
 901         self.assertEqual(expected, actual)
 
 902         invalid = "return if you can"
 
 903         with self.assertRaises(black.InvalidInput) as e:
 
 904             black.format_file_contents(invalid, mode=mode, fast=False)
 
 905         self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
 
 907     def test_endmarker(self) -> None:
 
 908         n = black.lib2to3_parse("\n")
 
 909         self.assertEqual(n.type, black.syms.file_input)
 
 910         self.assertEqual(len(n.children), 1)
 
 911         self.assertEqual(n.children[0].type, black.token.ENDMARKER)
 
 913     @pytest.mark.incompatible_with_mypyc
 
 914     @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
 
 915     def test_assertFormatEqual(self) -> None:
 
 919         def out(msg: str, **kwargs: Any) -> None:
 
 920             out_lines.append(msg)
 
 922         def err(msg: str, **kwargs: Any) -> None:
 
 923             err_lines.append(msg)
 
 925         with patch("black.output._out", out), patch("black.output._err", err):
 
 926             with self.assertRaises(AssertionError):
 
 927                 self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")
 
 929         out_str = "".join(out_lines)
 
 930         self.assertIn("Expected tree:", out_str)
 
 931         self.assertIn("Actual tree:", out_str)
 
 932         self.assertEqual("".join(err_lines), "")
 
 935     @patch("concurrent.futures.ProcessPoolExecutor", MagicMock(side_effect=OSError))
 
 936     def test_works_in_mono_process_only_environment(self) -> None:
 
 937         with cache_dir() as workspace:
 
 939                 (workspace / "one.py").resolve(),
 
 940                 (workspace / "two.py").resolve(),
 
 942                 f.write_text('print("hello")\n')
 
 943             self.invokeBlack([str(workspace)])
 
 946     def test_check_diff_use_together(self) -> None:
 
 948             # Files which will be reformatted.
 
 949             src1 = get_case_path("miscellaneous", "string_quotes")
 
 950             self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
 
 951             # Files which will not be reformatted.
 
 952             src2 = get_case_path("simple_cases", "composition")
 
 953             self.invokeBlack([str(src2), "--diff", "--check"])
 
 954             # Multi file command.
 
 955             self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
 
 957     def test_no_src_fails(self) -> None:
 
 959             self.invokeBlack([], exit_code=1)
 
 961     def test_src_and_code_fails(self) -> None:
 
 963             self.invokeBlack([".", "-c", "0"], exit_code=1)
 
 965     def test_broken_symlink(self) -> None:
 
 966         with cache_dir() as workspace:
 
 967             symlink = workspace / "broken_link.py"
 
 969                 symlink.symlink_to("nonexistent.py")
 
 970             except (OSError, NotImplementedError) as e:
 
 971                 self.skipTest(f"Can't create symlinks: {e}")
 
 972             self.invokeBlack([str(workspace.resolve())])
 
 974     def test_single_file_force_pyi(self) -> None:
 
 975         pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
 
 976         contents, expected = read_data("miscellaneous", "force_pyi")
 
 977         with cache_dir() as workspace:
 
 978             path = (workspace / "file.py").resolve()
 
 979             with open(path, "w") as fh:
 
 981             self.invokeBlack([str(path), "--pyi"])
 
 982             with open(path, "r") as fh:
 
 984             # verify cache with --pyi is separate
 
 985             pyi_cache = black.read_cache(pyi_mode)
 
 986             self.assertIn(str(path), pyi_cache)
 
 987             normal_cache = black.read_cache(DEFAULT_MODE)
 
 988             self.assertNotIn(str(path), normal_cache)
 
 989         self.assertFormatEqual(expected, actual)
 
 990         black.assert_equivalent(contents, actual)
 
 991         black.assert_stable(contents, actual, pyi_mode)
 
 994     def test_multi_file_force_pyi(self) -> None:
 
 995         reg_mode = DEFAULT_MODE
 
 996         pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
 
 997         contents, expected = read_data("miscellaneous", "force_pyi")
 
 998         with cache_dir() as workspace:
 
1000                 (workspace / "file1.py").resolve(),
 
1001                 (workspace / "file2.py").resolve(),
 
1004                 with open(path, "w") as fh:
 
1006             self.invokeBlack([str(p) for p in paths] + ["--pyi"])
 
1008                 with open(path, "r") as fh:
 
1010                 self.assertEqual(actual, expected)
 
1011             # verify cache with --pyi is separate
 
1012             pyi_cache = black.read_cache(pyi_mode)
 
1013             normal_cache = black.read_cache(reg_mode)
 
1015                 self.assertIn(str(path), pyi_cache)
 
1016                 self.assertNotIn(str(path), normal_cache)
 
1018     def test_pipe_force_pyi(self) -> None:
 
1019         source, expected = read_data("miscellaneous", "force_pyi")
 
1020         result = CliRunner().invoke(
 
1021             black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
 
1023         self.assertEqual(result.exit_code, 0)
 
1024         actual = result.output
 
1025         self.assertFormatEqual(actual, expected)
 
1027     def test_single_file_force_py36(self) -> None:
 
1028         reg_mode = DEFAULT_MODE
 
1029         py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
 
1030         source, expected = read_data("miscellaneous", "force_py36")
 
1031         with cache_dir() as workspace:
 
1032             path = (workspace / "file.py").resolve()
 
1033             with open(path, "w") as fh:
 
1035             self.invokeBlack([str(path), *PY36_ARGS])
 
1036             with open(path, "r") as fh:
 
1038             # verify cache with --target-version is separate
 
1039             py36_cache = black.read_cache(py36_mode)
 
1040             self.assertIn(str(path), py36_cache)
 
1041             normal_cache = black.read_cache(reg_mode)
 
1042             self.assertNotIn(str(path), normal_cache)
 
1043         self.assertEqual(actual, expected)
 
1046     def test_multi_file_force_py36(self) -> None:
 
1047         reg_mode = DEFAULT_MODE
 
1048         py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
 
1049         source, expected = read_data("miscellaneous", "force_py36")
 
1050         with cache_dir() as workspace:
 
1052                 (workspace / "file1.py").resolve(),
 
1053                 (workspace / "file2.py").resolve(),
 
1056                 with open(path, "w") as fh:
 
1058             self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
 
1060                 with open(path, "r") as fh:
 
1062                 self.assertEqual(actual, expected)
 
1063             # verify cache with --target-version is separate
 
1064             pyi_cache = black.read_cache(py36_mode)
 
1065             normal_cache = black.read_cache(reg_mode)
 
1067                 self.assertIn(str(path), pyi_cache)
 
1068                 self.assertNotIn(str(path), normal_cache)
 
1070     def test_pipe_force_py36(self) -> None:
 
1071         source, expected = read_data("miscellaneous", "force_py36")
 
1072         result = CliRunner().invoke(
 
1074             ["-", "-q", "--target-version=py36"],
 
1075             input=BytesIO(source.encode("utf8")),
 
1077         self.assertEqual(result.exit_code, 0)
 
1078         actual = result.output
 
1079         self.assertFormatEqual(actual, expected)
 
1081     @pytest.mark.incompatible_with_mypyc
 
1082     def test_reformat_one_with_stdin(self) -> None:
 
1084             "black.format_stdin_to_stdout",
 
1085             return_value=lambda *args, **kwargs: black.Changed.YES,
 
1087             report = MagicMock()
 
1092                 write_back=black.WriteBack.YES,
 
1096             fsts.assert_called_once()
 
1097             report.done.assert_called_with(path, black.Changed.YES)
 
1099     @pytest.mark.incompatible_with_mypyc
 
1100     def test_reformat_one_with_stdin_filename(self) -> None:
 
1102             "black.format_stdin_to_stdout",
 
1103             return_value=lambda *args, **kwargs: black.Changed.YES,
 
1105             report = MagicMock()
 
1107             path = Path(f"__BLACK_STDIN_FILENAME__{p}")
 
1112                 write_back=black.WriteBack.YES,
 
1116             fsts.assert_called_once_with(
 
1117                 fast=True, write_back=black.WriteBack.YES, mode=DEFAULT_MODE
 
1119             # __BLACK_STDIN_FILENAME__ should have been stripped
 
1120             report.done.assert_called_with(expected, black.Changed.YES)
 
1122     @pytest.mark.incompatible_with_mypyc
 
1123     def test_reformat_one_with_stdin_filename_pyi(self) -> None:
 
1125             "black.format_stdin_to_stdout",
 
1126             return_value=lambda *args, **kwargs: black.Changed.YES,
 
1128             report = MagicMock()
 
1130             path = Path(f"__BLACK_STDIN_FILENAME__{p}")
 
1135                 write_back=black.WriteBack.YES,
 
1139             fsts.assert_called_once_with(
 
1141                 write_back=black.WriteBack.YES,
 
1142                 mode=replace(DEFAULT_MODE, is_pyi=True),
 
1144             # __BLACK_STDIN_FILENAME__ should have been stripped
 
1145             report.done.assert_called_with(expected, black.Changed.YES)
 
1147     @pytest.mark.incompatible_with_mypyc
 
1148     def test_reformat_one_with_stdin_filename_ipynb(self) -> None:
 
1150             "black.format_stdin_to_stdout",
 
1151             return_value=lambda *args, **kwargs: black.Changed.YES,
 
1153             report = MagicMock()
 
1155             path = Path(f"__BLACK_STDIN_FILENAME__{p}")
 
1160                 write_back=black.WriteBack.YES,
 
1164             fsts.assert_called_once_with(
 
1166                 write_back=black.WriteBack.YES,
 
1167                 mode=replace(DEFAULT_MODE, is_ipynb=True),
 
1169             # __BLACK_STDIN_FILENAME__ should have been stripped
 
1170             report.done.assert_called_with(expected, black.Changed.YES)
 
1172     @pytest.mark.incompatible_with_mypyc
 
1173     def test_reformat_one_with_stdin_and_existing_path(self) -> None:
 
1175             "black.format_stdin_to_stdout",
 
1176             return_value=lambda *args, **kwargs: black.Changed.YES,
 
1178             report = MagicMock()
 
1179             # Even with an existing file, since we are forcing stdin, black
 
1180             # should output to stdout and not modify the file inplace
 
1181             p = THIS_DIR / "data" / "simple_cases" / "collections.py"
 
1182             # Make sure is_file actually returns True
 
1183             self.assertTrue(p.is_file())
 
1184             path = Path(f"__BLACK_STDIN_FILENAME__{p}")
 
1189                 write_back=black.WriteBack.YES,
 
1193             fsts.assert_called_once()
 
1194             # __BLACK_STDIN_FILENAME__ should have been stripped
 
1195             report.done.assert_called_with(expected, black.Changed.YES)
 
1197     def test_reformat_one_with_stdin_empty(self) -> None:
 
1198         output = io.StringIO()
 
1199         with patch("io.TextIOWrapper", lambda *args, **kwargs: output):
 
1201                 black.format_stdin_to_stdout(
 
1204                     write_back=black.WriteBack.YES,
 
1207             except io.UnsupportedOperation:
 
1208                 pass  # StringIO does not support detach
 
1209             assert output.getvalue() == ""
 
1211     def test_invalid_cli_regex(self) -> None:
 
1212         for option in ["--include", "--exclude", "--extend-exclude", "--force-exclude"]:
 
1213             self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
 
1215     def test_required_version_matches_version(self) -> None:
 
1217             ["--required-version", black.__version__, "-c", "0"],
 
1222     def test_required_version_matches_partial_version(self) -> None:
 
1224             ["--required-version", black.__version__.split(".")[0], "-c", "0"],
 
1229     def test_required_version_does_not_match_on_minor_version(self) -> None:
 
1231             ["--required-version", black.__version__.split(".")[0] + ".999", "-c", "0"],
 
1236     def test_required_version_does_not_match_version(self) -> None:
 
1237         result = BlackRunner().invoke(
 
1239             ["--required-version", "20.99b", "-c", "0"],
 
1241         self.assertEqual(result.exit_code, 1)
 
1242         self.assertIn("required version", result.stderr)
 
1244     def test_preserves_line_endings(self) -> None:
 
1245         with TemporaryDirectory() as workspace:
 
1246             test_file = Path(workspace) / "test.py"
 
1247             for nl in ["\n", "\r\n"]:
 
1248                 contents = nl.join(["def f(  ):", "    pass"])
 
1249                 test_file.write_bytes(contents.encode())
 
1250                 ff(test_file, write_back=black.WriteBack.YES)
 
1251                 updated_contents: bytes = test_file.read_bytes()
 
1252                 self.assertIn(nl.encode(), updated_contents)
 
1254                     self.assertNotIn(b"\r\n", updated_contents)
 
1256     def test_preserves_line_endings_via_stdin(self) -> None:
 
1257         for nl in ["\n", "\r\n"]:
 
1258             contents = nl.join(["def f(  ):", "    pass"])
 
1259             runner = BlackRunner()
 
1260             result = runner.invoke(
 
1261                 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
 
1263             self.assertEqual(result.exit_code, 0)
 
1264             output = result.stdout_bytes
 
1265             self.assertIn(nl.encode("utf8"), output)
 
1267                 self.assertNotIn(b"\r\n", output)
 
1269     def test_assert_equivalent_different_asts(self) -> None:
 
1270         with self.assertRaises(AssertionError):
 
1271             black.assert_equivalent("{}", "None")
 
1273     def test_shhh_click(self) -> None:
 
1275             from click import _unicodefun  # type: ignore
 
1277             self.skipTest("Incompatible Click version")
 
1279         if not hasattr(_unicodefun, "_verify_python_env"):
 
1280             self.skipTest("Incompatible Click version")
 
1282         # First, let's see if Click is crashing with a preferred ASCII charset.
 
1283         with patch("locale.getpreferredencoding") as gpe:
 
1284             gpe.return_value = "ASCII"
 
1285             with self.assertRaises(RuntimeError):
 
1286                 _unicodefun._verify_python_env()
 
1287         # Now, let's silence Click...
 
1289         # ...and confirm it's silent.
 
1290         with patch("locale.getpreferredencoding") as gpe:
 
1291             gpe.return_value = "ASCII"
 
1293                 _unicodefun._verify_python_env()
 
1294             except RuntimeError as re:
 
1295                 self.fail(f"`patch_click()` failed, exception still raised: {re}")
 
1297     def test_root_logger_not_used_directly(self) -> None:
 
1298         def fail(*args: Any, **kwargs: Any) -> None:
 
1299             self.fail("Record created with root logger")
 
1301         with patch.multiple(
 
1310             ff(THIS_DIR / "util.py")
 
1312     def test_invalid_config_return_code(self) -> None:
 
1313         tmp_file = Path(black.dump_to_file())
 
1315             tmp_config = Path(black.dump_to_file())
 
1317             args = ["--config", str(tmp_config), str(tmp_file)]
 
1318             self.invokeBlack(args, exit_code=2, ignore_config=False)
 
1322     def test_parse_pyproject_toml(self) -> None:
 
1323         test_toml_file = THIS_DIR / "test.toml"
 
1324         config = black.parse_pyproject_toml(str(test_toml_file))
 
1325         self.assertEqual(config["verbose"], 1)
 
1326         self.assertEqual(config["check"], "no")
 
1327         self.assertEqual(config["diff"], "y")
 
1328         self.assertEqual(config["color"], True)
 
1329         self.assertEqual(config["line_length"], 79)
 
1330         self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
 
1331         self.assertEqual(config["python_cell_magics"], ["custom1", "custom2"])
 
1332         self.assertEqual(config["exclude"], r"\.pyi?$")
 
1333         self.assertEqual(config["include"], r"\.py?$")
 
1335     def test_read_pyproject_toml(self) -> None:
 
1336         test_toml_file = THIS_DIR / "test.toml"
 
1337         fake_ctx = FakeContext()
 
1338         black.read_pyproject_toml(fake_ctx, FakeParameter(), str(test_toml_file))
 
1339         config = fake_ctx.default_map
 
1340         self.assertEqual(config["verbose"], "1")
 
1341         self.assertEqual(config["check"], "no")
 
1342         self.assertEqual(config["diff"], "y")
 
1343         self.assertEqual(config["color"], "True")
 
1344         self.assertEqual(config["line_length"], "79")
 
1345         self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
 
1346         self.assertEqual(config["exclude"], r"\.pyi?$")
 
1347         self.assertEqual(config["include"], r"\.py?$")
 
1349     @pytest.mark.incompatible_with_mypyc
 
1350     def test_find_project_root(self) -> None:
 
1351         with TemporaryDirectory() as workspace:
 
1352             root = Path(workspace)
 
1353             test_dir = root / "test"
 
1356             src_dir = root / "src"
 
1359             root_pyproject = root / "pyproject.toml"
 
1360             root_pyproject.touch()
 
1361             src_pyproject = src_dir / "pyproject.toml"
 
1362             src_pyproject.touch()
 
1363             src_python = src_dir / "foo.py"
 
1367                 black.find_project_root((src_dir, test_dir)),
 
1368                 (root.resolve(), "pyproject.toml"),
 
1371                 black.find_project_root((src_dir,)),
 
1372                 (src_dir.resolve(), "pyproject.toml"),
 
1375                 black.find_project_root((src_python,)),
 
1376                 (src_dir.resolve(), "pyproject.toml"),
 
1380         "black.files.find_user_pyproject_toml",
 
1382     def test_find_pyproject_toml(self, find_user_pyproject_toml: MagicMock) -> None:
 
1383         find_user_pyproject_toml.side_effect = RuntimeError()
 
1385         with redirect_stderr(io.StringIO()) as stderr:
 
1386             result = black.files.find_pyproject_toml(
 
1387                 path_search_start=(str(Path.cwd().root),)
 
1390         assert result is None
 
1391         err = stderr.getvalue()
 
1392         assert "Ignoring user configuration" in err
 
1395         "black.files.find_user_pyproject_toml",
 
1396         black.files.find_user_pyproject_toml.__wrapped__,
 
1398     def test_find_user_pyproject_toml_linux(self) -> None:
 
1399         if system() == "Windows":
 
1402         # Test if XDG_CONFIG_HOME is checked
 
1403         with TemporaryDirectory() as workspace:
 
1404             tmp_user_config = Path(workspace) / "black"
 
1405             with patch.dict("os.environ", {"XDG_CONFIG_HOME": workspace}):
 
1407                     black.files.find_user_pyproject_toml(), tmp_user_config.resolve()
 
1410         # Test fallback for XDG_CONFIG_HOME
 
1411         with patch.dict("os.environ"):
 
1412             os.environ.pop("XDG_CONFIG_HOME", None)
 
1413             fallback_user_config = Path("~/.config").expanduser() / "black"
 
1415                 black.files.find_user_pyproject_toml(), fallback_user_config.resolve()
 
1418     def test_find_user_pyproject_toml_windows(self) -> None:
 
1419         if system() != "Windows":
 
1422         user_config_path = Path.home() / ".black"
 
1424             black.files.find_user_pyproject_toml(), user_config_path.resolve()
 
1427     def test_bpo_33660_workaround(self) -> None:
 
1428         if system() == "Windows":
 
1431         # https://bugs.python.org/issue33660
 
1433         with change_directory(root):
 
1434             path = Path("workspace") / "project"
 
1435             report = black.Report(verbose=True)
 
1436             normalized_path = black.normalize_path_maybe_ignore(path, root, report)
 
1437             self.assertEqual(normalized_path, "workspace/project")
 
1439     def test_normalize_path_ignore_windows_junctions_outside_of_root(self) -> None:
 
1440         if system() != "Windows":
 
1443         with TemporaryDirectory() as workspace:
 
1444             root = Path(workspace)
 
1445             junction_dir = root / "junction"
 
1446             junction_target_outside_of_root = root / ".."
 
1447             os.system(f"mklink /J {junction_dir} {junction_target_outside_of_root}")
 
1449             report = black.Report(verbose=True)
 
1450             normalized_path = black.normalize_path_maybe_ignore(
 
1451                 junction_dir, root, report
 
1453             # Manually delete for Python < 3.8
 
1454             os.system(f"rmdir {junction_dir}")
 
1456             self.assertEqual(normalized_path, None)
 
1458     def test_newline_comment_interaction(self) -> None:
 
1459         source = "class A:\\\r\n# type: ignore\n pass\n"
 
1460         output = black.format_str(source, mode=DEFAULT_MODE)
 
1461         black.assert_stable(source, output, mode=DEFAULT_MODE)
 
1463     def test_bpo_2142_workaround(self) -> None:
 
1465         # https://bugs.python.org/issue2142
 
1467         source, _ = read_data("miscellaneous", "missing_final_newline")
 
1468         # read_data adds a trailing newline
 
1469         source = source.rstrip()
 
1470         expected, _ = read_data("miscellaneous", "missing_final_newline.diff")
 
1471         tmp_file = Path(black.dump_to_file(source, ensure_final_newline=False))
 
1472         diff_header = re.compile(
 
1473             rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
 
1474             r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
 
1477             result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
 
1478             self.assertEqual(result.exit_code, 0)
 
1481         actual = result.output
 
1482         actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
 
1483         self.assertEqual(actual, expected)
 
1486     def compare_results(
 
1487         result: click.testing.Result, expected_value: str, expected_exit_code: int
 
1489         """Helper method to test the value and exit code of a click Result."""
 
1491             result.output == expected_value
 
1492         ), "The output did not match the expected value."
 
1493         assert result.exit_code == expected_exit_code, "The exit code is incorrect."
 
1495     def test_code_option(self) -> None:
 
1496         """Test the code option with no changes."""
 
1497         code = 'print("Hello world")\n'
 
1498         args = ["--code", code]
 
1499         result = CliRunner().invoke(black.main, args)
 
1501         self.compare_results(result, code, 0)
 
1503     def test_code_option_changed(self) -> None:
 
1504         """Test the code option when changes are required."""
 
1505         code = "print('hello world')"
 
1506         formatted = black.format_str(code, mode=DEFAULT_MODE)
 
1508         args = ["--code", code]
 
1509         result = CliRunner().invoke(black.main, args)
 
1511         self.compare_results(result, formatted, 0)
 
1513     def test_code_option_check(self) -> None:
 
1514         """Test the code option when check is passed."""
 
1515         args = ["--check", "--code", 'print("Hello world")\n']
 
1516         result = CliRunner().invoke(black.main, args)
 
1517         self.compare_results(result, "", 0)
 
1519     def test_code_option_check_changed(self) -> None:
 
1520         """Test the code option when changes are required, and check is passed."""
 
1521         args = ["--check", "--code", "print('hello world')"]
 
1522         result = CliRunner().invoke(black.main, args)
 
1523         self.compare_results(result, "", 1)
 
1525     def test_code_option_diff(self) -> None:
 
1526         """Test the code option when diff is passed."""
 
1527         code = "print('hello world')"
 
1528         formatted = black.format_str(code, mode=DEFAULT_MODE)
 
1529         result_diff = diff(code, formatted, "STDIN", "STDOUT")
 
1531         args = ["--diff", "--code", code]
 
1532         result = CliRunner().invoke(black.main, args)
 
1534         # Remove time from diff
 
1535         output = DIFF_TIME.sub("", result.output)
 
1537         assert output == result_diff, "The output did not match the expected value."
 
1538         assert result.exit_code == 0, "The exit code is incorrect."
 
1540     def test_code_option_color_diff(self) -> None:
 
1541         """Test the code option when color and diff are passed."""
 
1542         code = "print('hello world')"
 
1543         formatted = black.format_str(code, mode=DEFAULT_MODE)
 
1545         result_diff = diff(code, formatted, "STDIN", "STDOUT")
 
1546         result_diff = color_diff(result_diff)
 
1548         args = ["--diff", "--color", "--code", code]
 
1549         result = CliRunner().invoke(black.main, args)
 
1551         # Remove time from diff
 
1552         output = DIFF_TIME.sub("", result.output)
 
1554         assert output == result_diff, "The output did not match the expected value."
 
1555         assert result.exit_code == 0, "The exit code is incorrect."
 
1557     @pytest.mark.incompatible_with_mypyc
 
1558     def test_code_option_safe(self) -> None:
 
1559         """Test that the code option throws an error when the sanity checks fail."""
 
1560         # Patch black.assert_equivalent to ensure the sanity checks fail
 
1561         with patch.object(black, "assert_equivalent", side_effect=AssertionError):
 
1562             code = 'print("Hello world")'
 
1563             error_msg = f"{code}\nerror: cannot format <string>: \n"
 
1565             args = ["--safe", "--code", code]
 
1566             result = CliRunner().invoke(black.main, args)
 
1568             self.compare_results(result, error_msg, 123)
 
1570     def test_code_option_fast(self) -> None:
 
1571         """Test that the code option ignores errors when the sanity checks fail."""
 
1572         # Patch black.assert_equivalent to ensure the sanity checks fail
 
1573         with patch.object(black, "assert_equivalent", side_effect=AssertionError):
 
1574             code = 'print("Hello world")'
 
1575             formatted = black.format_str(code, mode=DEFAULT_MODE)
 
1577             args = ["--fast", "--code", code]
 
1578             result = CliRunner().invoke(black.main, args)
 
1580             self.compare_results(result, formatted, 0)
 
1582     @pytest.mark.incompatible_with_mypyc
 
1583     def test_code_option_config(self) -> None:
 
1585         Test that the code option finds the pyproject.toml in the current directory.
 
1587         with patch.object(black, "parse_pyproject_toml", return_value={}) as parse:
 
1588             args = ["--code", "print"]
 
1589             # This is the only directory known to contain a pyproject.toml
 
1590             with change_directory(PROJECT_ROOT):
 
1591                 CliRunner().invoke(black.main, args)
 
1592                 pyproject_path = Path(Path.cwd(), "pyproject.toml").resolve()
 
1595                 len(parse.mock_calls) >= 1
 
1596             ), "Expected config parse to be called with the current directory."
 
1598             _, call_args, _ = parse.mock_calls[0]
 
1600                 call_args[0].lower() == str(pyproject_path).lower()
 
1601             ), "Incorrect config loaded."
 
1603     @pytest.mark.incompatible_with_mypyc
 
1604     def test_code_option_parent_config(self) -> None:
 
1606         Test that the code option finds the pyproject.toml in the parent directory.
 
1608         with patch.object(black, "parse_pyproject_toml", return_value={}) as parse:
 
1609             with change_directory(THIS_DIR):
 
1610                 args = ["--code", "print"]
 
1611                 CliRunner().invoke(black.main, args)
 
1613                 pyproject_path = Path(Path().cwd().parent, "pyproject.toml").resolve()
 
1615                     len(parse.mock_calls) >= 1
 
1616                 ), "Expected config parse to be called with the current directory."
 
1618                 _, call_args, _ = parse.mock_calls[0]
 
1620                     call_args[0].lower() == str(pyproject_path).lower()
 
1621                 ), "Incorrect config loaded."
 
1623     def test_for_handled_unexpected_eof_error(self) -> None:
 
1625         Test that an unexpected EOF SyntaxError is nicely presented.
 
1627         with pytest.raises(black.parsing.InvalidInput) as exc_info:
 
1628             black.lib2to3_parse("print(", {})
 
1630         exc_info.match("Cannot parse: 2:0: EOF in multi-line statement")
 
1632     def test_equivalency_ast_parse_failure_includes_error(self) -> None:
 
1633         with pytest.raises(AssertionError) as err:
 
1634             black.assert_equivalent("a«»a  = 1", "a«»a  = 1")
 
1637         # Unfortunately the SyntaxError message has changed in newer versions so we
 
1638         # can't match it directly.
 
1639         err.match("invalid character")
 
1640         err.match(r"\(<unknown>, line 1\)")
 
1644     def test_get_cache_dir(
 
1647         monkeypatch: pytest.MonkeyPatch,
 
1649         # Create multiple cache directories
 
1650         workspace1 = tmp_path / "ws1"
 
1652         workspace2 = tmp_path / "ws2"
 
1655         # Force user_cache_dir to use the temporary directory for easier assertions
 
1656         patch_user_cache_dir = patch(
 
1657             target="black.cache.user_cache_dir",
 
1659             return_value=str(workspace1),
 
1662         # If BLACK_CACHE_DIR is not set, use user_cache_dir
 
1663         monkeypatch.delenv("BLACK_CACHE_DIR", raising=False)
 
1664         with patch_user_cache_dir:
 
1665             assert get_cache_dir() == workspace1
 
1667         # If it is set, use the path provided in the env var.
 
1668         monkeypatch.setenv("BLACK_CACHE_DIR", str(workspace2))
 
1669         assert get_cache_dir() == workspace2
 
1671     def test_cache_broken_file(self) -> None:
 
1673         with cache_dir() as workspace:
 
1674             cache_file = get_cache_file(mode)
 
1675             cache_file.write_text("this is not a pickle")
 
1676             assert black.read_cache(mode) == {}
 
1677             src = (workspace / "test.py").resolve()
 
1678             src.write_text("print('hello')")
 
1679             invokeBlack([str(src)])
 
1680             cache = black.read_cache(mode)
 
1681             assert str(src) in cache
 
1683     def test_cache_single_file_already_cached(self) -> None:
 
1685         with cache_dir() as workspace:
 
1686             src = (workspace / "test.py").resolve()
 
1687             src.write_text("print('hello')")
 
1688             black.write_cache({}, [src], mode)
 
1689             invokeBlack([str(src)])
 
1690             assert src.read_text() == "print('hello')"
 
1693     def test_cache_multiple_files(self) -> None:
 
1695         with cache_dir() as workspace, patch(
 
1696             "concurrent.futures.ProcessPoolExecutor", new=ThreadPoolExecutor
 
1698             one = (workspace / "one.py").resolve()
 
1699             with one.open("w") as fobj:
 
1700                 fobj.write("print('hello')")
 
1701             two = (workspace / "two.py").resolve()
 
1702             with two.open("w") as fobj:
 
1703                 fobj.write("print('hello')")
 
1704             black.write_cache({}, [one], mode)
 
1705             invokeBlack([str(workspace)])
 
1706             with one.open("r") as fobj:
 
1707                 assert fobj.read() == "print('hello')"
 
1708             with two.open("r") as fobj:
 
1709                 assert fobj.read() == 'print("hello")\n'
 
1710             cache = black.read_cache(mode)
 
1711             assert str(one) in cache
 
1712             assert str(two) in cache
 
1714     @pytest.mark.parametrize("color", [False, True], ids=["no-color", "with-color"])
 
1715     def test_no_cache_when_writeback_diff(self, color: bool) -> None:
 
1717         with cache_dir() as workspace:
 
1718             src = (workspace / "test.py").resolve()
 
1719             with src.open("w") as fobj:
 
1720                 fobj.write("print('hello')")
 
1721             with patch("black.read_cache") as read_cache, patch(
 
1724                 cmd = [str(src), "--diff"]
 
1726                     cmd.append("--color")
 
1728                 cache_file = get_cache_file(mode)
 
1729                 assert cache_file.exists() is False
 
1730                 write_cache.assert_not_called()
 
1731                 read_cache.assert_not_called()
 
1733     @pytest.mark.parametrize("color", [False, True], ids=["no-color", "with-color"])
 
1735     def test_output_locking_when_writeback_diff(self, color: bool) -> None:
 
1736         with cache_dir() as workspace:
 
1737             for tag in range(0, 4):
 
1738                 src = (workspace / f"test{tag}.py").resolve()
 
1739                 with src.open("w") as fobj:
 
1740                     fobj.write("print('hello')")
 
1741             with patch("black.Manager", wraps=multiprocessing.Manager) as mgr:
 
1742                 cmd = ["--diff", str(workspace)]
 
1744                     cmd.append("--color")
 
1745                 invokeBlack(cmd, exit_code=0)
 
1746                 # this isn't quite doing what we want, but if it _isn't_
 
1747                 # called then we cannot be using the lock it provides
 
1750     def test_no_cache_when_stdin(self) -> None:
 
1753             result = CliRunner().invoke(
 
1754                 black.main, ["-"], input=BytesIO(b"print('hello')")
 
1756             assert not result.exit_code
 
1757             cache_file = get_cache_file(mode)
 
1758             assert not cache_file.exists()
 
1760     def test_read_cache_no_cachefile(self) -> None:
 
1763             assert black.read_cache(mode) == {}
 
1765     def test_write_cache_read_cache(self) -> None:
 
1767         with cache_dir() as workspace:
 
1768             src = (workspace / "test.py").resolve()
 
1770             black.write_cache({}, [src], mode)
 
1771             cache = black.read_cache(mode)
 
1772             assert str(src) in cache
 
1773             assert cache[str(src)] == black.get_cache_info(src)
 
1775     def test_filter_cached(self) -> None:
 
1776         with TemporaryDirectory() as workspace:
 
1777             path = Path(workspace)
 
1778             uncached = (path / "uncached").resolve()
 
1779             cached = (path / "cached").resolve()
 
1780             cached_but_changed = (path / "changed").resolve()
 
1783             cached_but_changed.touch()
 
1785                 str(cached): black.get_cache_info(cached),
 
1786                 str(cached_but_changed): (0.0, 0),
 
1788             todo, done = black.filter_cached(
 
1789                 cache, {uncached, cached, cached_but_changed}
 
1791             assert todo == {uncached, cached_but_changed}
 
1792             assert done == {cached}
 
1794     def test_write_cache_creates_directory_if_needed(self) -> None:
 
1796         with cache_dir(exists=False) as workspace:
 
1797             assert not workspace.exists()
 
1798             black.write_cache({}, [], mode)
 
1799             assert workspace.exists()
 
1802     def test_failed_formatting_does_not_get_cached(self) -> None:
 
1804         with cache_dir() as workspace, patch(
 
1805             "concurrent.futures.ProcessPoolExecutor", new=ThreadPoolExecutor
 
1807             failing = (workspace / "failing.py").resolve()
 
1808             with failing.open("w") as fobj:
 
1809                 fobj.write("not actually python")
 
1810             clean = (workspace / "clean.py").resolve()
 
1811             with clean.open("w") as fobj:
 
1812                 fobj.write('print("hello")\n')
 
1813             invokeBlack([str(workspace)], exit_code=123)
 
1814             cache = black.read_cache(mode)
 
1815             assert str(failing) not in cache
 
1816             assert str(clean) in cache
 
1818     def test_write_cache_write_fail(self) -> None:
 
1820         with cache_dir(), patch.object(Path, "open") as mock:
 
1821             mock.side_effect = OSError
 
1822             black.write_cache({}, [], mode)
 
1824     def test_read_cache_line_lengths(self) -> None:
 
1826         short_mode = replace(DEFAULT_MODE, line_length=1)
 
1827         with cache_dir() as workspace:
 
1828             path = (workspace / "file.py").resolve()
 
1830             black.write_cache({}, [path], mode)
 
1831             one = black.read_cache(mode)
 
1832             assert str(path) in one
 
1833             two = black.read_cache(short_mode)
 
1834             assert str(path) not in two
 
1837 def assert_collected_sources(
 
1838     src: Sequence[Union[str, Path]],
 
1839     expected: Sequence[Union[str, Path]],
 
1841     ctx: Optional[FakeContext] = None,
 
1842     exclude: Optional[str] = None,
 
1843     include: Optional[str] = None,
 
1844     extend_exclude: Optional[str] = None,
 
1845     force_exclude: Optional[str] = None,
 
1846     stdin_filename: Optional[str] = None,
 
1848     gs_src = tuple(str(Path(s)) for s in src)
 
1849     gs_expected = [Path(s) for s in expected]
 
1850     gs_exclude = None if exclude is None else compile_pattern(exclude)
 
1851     gs_include = DEFAULT_INCLUDE if include is None else compile_pattern(include)
 
1852     gs_extend_exclude = (
 
1853         None if extend_exclude is None else compile_pattern(extend_exclude)
 
1855     gs_force_exclude = None if force_exclude is None else compile_pattern(force_exclude)
 
1856     collected = black.get_sources(
 
1857         ctx=ctx or FakeContext(),
 
1863         extend_exclude=gs_extend_exclude,
 
1864         force_exclude=gs_force_exclude,
 
1865         report=black.Report(),
 
1866         stdin_filename=stdin_filename,
 
1868     assert sorted(collected) == sorted(gs_expected)
 
1871 class TestFileCollection:
 
1872     def test_include_exclude(self) -> None:
 
1873         path = THIS_DIR / "data" / "include_exclude_tests"
 
1876             Path(path / "b/dont_exclude/a.py"),
 
1877             Path(path / "b/dont_exclude/a.pyi"),
 
1879         assert_collected_sources(
 
1883             exclude=r"/exclude/|/\.definitely_exclude/",
 
1886     def test_gitignore_used_as_default(self) -> None:
 
1887         base = Path(DATA_DIR / "include_exclude_tests")
 
1889             base / "b/.definitely_exclude/a.py",
 
1890             base / "b/.definitely_exclude/a.pyi",
 
1894         ctx.obj["root"] = base
 
1895         assert_collected_sources(src, expected, ctx=ctx, extend_exclude=r"/exclude/")
 
1897     @patch("black.find_project_root", lambda *args: (THIS_DIR.resolve(), None))
 
1898     def test_exclude_for_issue_1572(self) -> None:
 
1899         # Exclude shouldn't touch files that were explicitly given to Black through the
 
1900         # CLI. Exclude is supposed to only apply to the recursive discovery of files.
 
1901         # https://github.com/psf/black/issues/1572
 
1902         path = DATA_DIR / "include_exclude_tests"
 
1903         src = [path / "b/exclude/a.py"]
 
1904         expected = [path / "b/exclude/a.py"]
 
1905         assert_collected_sources(src, expected, include="", exclude=r"/exclude/|a\.py")
 
1907     def test_gitignore_exclude(self) -> None:
 
1908         path = THIS_DIR / "data" / "include_exclude_tests"
 
1909         include = re.compile(r"\.pyi?$")
 
1910         exclude = re.compile(r"")
 
1911         report = black.Report()
 
1912         gitignore = PathSpec.from_lines(
 
1913             "gitwildmatch", ["exclude/", ".definitely_exclude"]
 
1915         sources: List[Path] = []
 
1917             Path(path / "b/dont_exclude/a.py"),
 
1918             Path(path / "b/dont_exclude/a.pyi"),
 
1920         this_abs = THIS_DIR.resolve()
 
1922             black.gen_python_files(
 
1935         assert sorted(expected) == sorted(sources)
 
1937     def test_nested_gitignore(self) -> None:
 
1938         path = Path(THIS_DIR / "data" / "nested_gitignore_tests")
 
1939         include = re.compile(r"\.pyi?$")
 
1940         exclude = re.compile(r"")
 
1941         root_gitignore = black.files.get_gitignore(path)
 
1942         report = black.Report()
 
1943         expected: List[Path] = [
 
1944             Path(path / "x.py"),
 
1945             Path(path / "root/b.py"),
 
1946             Path(path / "root/c.py"),
 
1947             Path(path / "root/child/c.py"),
 
1949         this_abs = THIS_DIR.resolve()
 
1951             black.gen_python_files(
 
1964         assert sorted(expected) == sorted(sources)
 
1966     def test_invalid_gitignore(self) -> None:
 
1967         path = THIS_DIR / "data" / "invalid_gitignore_tests"
 
1968         empty_config = path / "pyproject.toml"
 
1969         result = BlackRunner().invoke(
 
1970             black.main, ["--verbose", "--config", str(empty_config), str(path)]
 
1972         assert result.exit_code == 1
 
1973         assert result.stderr_bytes is not None
 
1975         gitignore = path / ".gitignore"
 
1976         assert f"Could not parse {gitignore}" in result.stderr_bytes.decode()
 
1978     def test_invalid_nested_gitignore(self) -> None:
 
1979         path = THIS_DIR / "data" / "invalid_nested_gitignore_tests"
 
1980         empty_config = path / "pyproject.toml"
 
1981         result = BlackRunner().invoke(
 
1982             black.main, ["--verbose", "--config", str(empty_config), str(path)]
 
1984         assert result.exit_code == 1
 
1985         assert result.stderr_bytes is not None
 
1987         gitignore = path / "a" / ".gitignore"
 
1988         assert f"Could not parse {gitignore}" in result.stderr_bytes.decode()
 
1990     def test_empty_include(self) -> None:
 
1991         path = DATA_DIR / "include_exclude_tests"
 
1994             Path(path / "b/exclude/a.pie"),
 
1995             Path(path / "b/exclude/a.py"),
 
1996             Path(path / "b/exclude/a.pyi"),
 
1997             Path(path / "b/dont_exclude/a.pie"),
 
1998             Path(path / "b/dont_exclude/a.py"),
 
1999             Path(path / "b/dont_exclude/a.pyi"),
 
2000             Path(path / "b/.definitely_exclude/a.pie"),
 
2001             Path(path / "b/.definitely_exclude/a.py"),
 
2002             Path(path / "b/.definitely_exclude/a.pyi"),
 
2003             Path(path / ".gitignore"),
 
2004             Path(path / "pyproject.toml"),
 
2006         # Setting exclude explicitly to an empty string to block .gitignore usage.
 
2007         assert_collected_sources(src, expected, include="", exclude="")
 
2009     def test_extend_exclude(self) -> None:
 
2010         path = DATA_DIR / "include_exclude_tests"
 
2013             Path(path / "b/exclude/a.py"),
 
2014             Path(path / "b/dont_exclude/a.py"),
 
2016         assert_collected_sources(
 
2017             src, expected, exclude=r"\.pyi$", extend_exclude=r"\.definitely_exclude"
 
2020     @pytest.mark.incompatible_with_mypyc
 
2021     def test_symlink_out_of_root_directory(self) -> None:
 
2023         root = THIS_DIR.resolve()
 
2025         include = re.compile(black.DEFAULT_INCLUDES)
 
2026         exclude = re.compile(black.DEFAULT_EXCLUDES)
 
2027         report = black.Report()
 
2028         gitignore = PathSpec.from_lines("gitwildmatch", [])
 
2029         # `child` should behave like a symlink which resolved path is clearly
 
2030         # outside of the `root` directory.
 
2031         path.iterdir.return_value = [child]
 
2032         child.resolve.return_value = Path("/a/b/c")
 
2033         child.as_posix.return_value = "/a/b/c"
 
2036                 black.gen_python_files(
 
2049         except ValueError as ve:
 
2050             pytest.fail(f"`get_python_files_in_dir()` failed: {ve}")
 
2051         path.iterdir.assert_called_once()
 
2052         child.resolve.assert_called_once()
 
2054     @patch("black.find_project_root", lambda *args: (THIS_DIR.resolve(), None))
 
2055     def test_get_sources_with_stdin(self) -> None:
 
2058         assert_collected_sources(src, expected, include="", exclude=r"/exclude/|a\.py")
 
2060     @patch("black.find_project_root", lambda *args: (THIS_DIR.resolve(), None))
 
2061     def test_get_sources_with_stdin_filename(self) -> None:
 
2063         stdin_filename = str(THIS_DIR / "data/collections.py")
 
2064         expected = [f"__BLACK_STDIN_FILENAME__{stdin_filename}"]
 
2065         assert_collected_sources(
 
2068             exclude=r"/exclude/a\.py",
 
2069             stdin_filename=stdin_filename,
 
2072     @patch("black.find_project_root", lambda *args: (THIS_DIR.resolve(), None))
 
2073     def test_get_sources_with_stdin_filename_and_exclude(self) -> None:
 
2074         # Exclude shouldn't exclude stdin_filename since it is mimicking the
 
2075         # file being passed directly. This is the same as
 
2076         # test_exclude_for_issue_1572
 
2077         path = DATA_DIR / "include_exclude_tests"
 
2079         stdin_filename = str(path / "b/exclude/a.py")
 
2080         expected = [f"__BLACK_STDIN_FILENAME__{stdin_filename}"]
 
2081         assert_collected_sources(
 
2084             exclude=r"/exclude/|a\.py",
 
2085             stdin_filename=stdin_filename,
 
2088     @patch("black.find_project_root", lambda *args: (THIS_DIR.resolve(), None))
 
2089     def test_get_sources_with_stdin_filename_and_extend_exclude(self) -> None:
 
2090         # Extend exclude shouldn't exclude stdin_filename since it is mimicking the
 
2091         # file being passed directly. This is the same as
 
2092         # test_exclude_for_issue_1572
 
2094         path = THIS_DIR / "data" / "include_exclude_tests"
 
2095         stdin_filename = str(path / "b/exclude/a.py")
 
2096         expected = [f"__BLACK_STDIN_FILENAME__{stdin_filename}"]
 
2097         assert_collected_sources(
 
2100             extend_exclude=r"/exclude/|a\.py",
 
2101             stdin_filename=stdin_filename,
 
2104     @patch("black.find_project_root", lambda *args: (THIS_DIR.resolve(), None))
 
2105     def test_get_sources_with_stdin_filename_and_force_exclude(self) -> None:
 
2106         # Force exclude should exclude the file when passing it through
 
2108         path = THIS_DIR / "data" / "include_exclude_tests"
 
2109         stdin_filename = str(path / "b/exclude/a.py")
 
2110         assert_collected_sources(
 
2113             force_exclude=r"/exclude/|a\.py",
 
2114             stdin_filename=stdin_filename,
 
2119     with open(black.__file__, "r", encoding="utf-8") as _bf:
 
2120         black_source_lines = _bf.readlines()
 
2121 except UnicodeDecodeError:
 
2122     if not black.COMPILED:
 
2127     frame: types.FrameType, event: str, arg: Any
 
2128 ) -> Callable[[types.FrameType, str, Any], Any]:
 
2129     """Show function calls `from black/__init__.py` as they happen.
 
2131     Register this with `sys.settrace()` in a test you're debugging.
 
2136     stack = len(inspect.stack()) - 19
 
2138     filename = frame.f_code.co_filename
 
2139     lineno = frame.f_lineno
 
2140     func_sig_lineno = lineno - 1
 
2141     funcname = black_source_lines[func_sig_lineno].strip()
 
2142     while funcname.startswith("@"):
 
2143         func_sig_lineno += 1
 
2144         funcname = black_source_lines[func_sig_lineno].strip()
 
2145     if "black/__init__.py" in filename:
 
2146         print(f"{' ' * stack}{lineno}:{funcname}")