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.
  13 from concurrent.futures import ThreadPoolExecutor
 
  14 from contextlib import contextmanager, redirect_stderr
 
  15 from dataclasses import replace
 
  16 from io import BytesIO
 
  17 from pathlib import Path
 
  18 from platform import system
 
  19 from tempfile import TemporaryDirectory
 
  31 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     def test_detect_debug_f_strings(self) -> None:
 
 314         root = black.lib2to3_parse("""f"{x=}" """)
 
 315         features = black.get_features_used(root)
 
 316         self.assertIn(black.Feature.DEBUG_F_STRINGS, features)
 
 317         versions = black.detect_target_versions(root)
 
 318         self.assertIn(black.TargetVersion.PY38, versions)
 
 320         root = black.lib2to3_parse(
 
 321             """f"{x}"\nf'{"="}'\nf'{(x:=5)}'\nf'{f(a="3=")}'\nf'{x:=10}'\n"""
 
 323         features = black.get_features_used(root)
 
 324         self.assertNotIn(black.Feature.DEBUG_F_STRINGS, features)
 
 326         # We don't yet support feature version detection in nested f-strings
 
 327         root = black.lib2to3_parse(
 
 328             """f"heard a rumour that { f'{1+1=}' } ... seems like it could be true" """
 
 330         features = black.get_features_used(root)
 
 331         self.assertNotIn(black.Feature.DEBUG_F_STRINGS, features)
 
 333     @patch("black.dump_to_file", dump_to_stderr)
 
 334     def test_string_quotes(self) -> None:
 
 335         source, expected = read_data("miscellaneous", "string_quotes")
 
 336         mode = black.Mode(preview=True)
 
 337         assert_format(source, expected, mode)
 
 338         mode = replace(mode, string_normalization=False)
 
 339         not_normalized = fs(source, mode=mode)
 
 340         self.assertFormatEqual(source.replace("\\\n", ""), not_normalized)
 
 341         black.assert_equivalent(source, not_normalized)
 
 342         black.assert_stable(source, not_normalized, mode=mode)
 
 344     def test_skip_source_first_line(self) -> None:
 
 345         source, _ = read_data("miscellaneous", "invalid_header")
 
 346         tmp_file = Path(black.dump_to_file(source))
 
 347         # Full source should fail (invalid syntax at header)
 
 348         self.invokeBlack([str(tmp_file), "--diff", "--check"], exit_code=123)
 
 349         # So, skipping the first line should work
 
 350         result = BlackRunner().invoke(
 
 351             black.main, [str(tmp_file), "-x", f"--config={EMPTY_CONFIG}"]
 
 353         self.assertEqual(result.exit_code, 0)
 
 354         with open(tmp_file, encoding="utf8") as f:
 
 356         self.assertFormatEqual(source, actual)
 
 358     def test_skip_source_first_line_when_mixing_newlines(self) -> None:
 
 359         code_mixing_newlines = b"Header will be skipped\r\ni = [1,2,3]\nj = [1,2,3]\n"
 
 360         expected = b"Header will be skipped\r\ni = [1, 2, 3]\nj = [1, 2, 3]\n"
 
 361         with TemporaryDirectory() as workspace:
 
 362             test_file = Path(workspace) / "skip_header.py"
 
 363             test_file.write_bytes(code_mixing_newlines)
 
 364             mode = replace(DEFAULT_MODE, skip_source_first_line=True)
 
 365             ff(test_file, mode=mode, write_back=black.WriteBack.YES)
 
 366             self.assertEqual(test_file.read_bytes(), expected)
 
 368     def test_skip_magic_trailing_comma(self) -> None:
 
 369         source, _ = read_data("simple_cases", "expression")
 
 370         expected, _ = read_data(
 
 371             "miscellaneous", "expression_skip_magic_trailing_comma.diff"
 
 373         tmp_file = Path(black.dump_to_file(source))
 
 374         diff_header = re.compile(
 
 375             rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
 
 376             r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
 
 379             result = BlackRunner().invoke(
 
 380                 black.main, ["-C", "--diff", str(tmp_file), f"--config={EMPTY_CONFIG}"]
 
 382             self.assertEqual(result.exit_code, 0)
 
 385         actual = result.output
 
 386         actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
 
 387         actual = actual.rstrip() + "\n"  # the diff output has a trailing space
 
 388         if expected != actual:
 
 389             dump = black.dump_to_file(actual)
 
 391                 "Expected diff isn't equal to the actual. If you made changes to"
 
 392                 " expression.py and this is an anticipated difference, overwrite"
 
 393                 f" tests/data/expression_skip_magic_trailing_comma.diff with {dump}"
 
 395             self.assertEqual(expected, actual, msg)
 
 397     @patch("black.dump_to_file", dump_to_stderr)
 
 398     def test_async_as_identifier(self) -> None:
 
 399         source_path = get_case_path("miscellaneous", "async_as_identifier")
 
 400         source, expected = read_data_from_file(source_path)
 
 402         self.assertFormatEqual(expected, actual)
 
 403         major, minor = sys.version_info[:2]
 
 404         if major < 3 or (major <= 3 and minor < 7):
 
 405             black.assert_equivalent(source, actual)
 
 406         black.assert_stable(source, actual, DEFAULT_MODE)
 
 407         # ensure black can parse this when the target is 3.6
 
 408         self.invokeBlack([str(source_path), "--target-version", "py36"])
 
 409         # but not on 3.7, because async/await is no longer an identifier
 
 410         self.invokeBlack([str(source_path), "--target-version", "py37"], exit_code=123)
 
 412     @patch("black.dump_to_file", dump_to_stderr)
 
 413     def test_python37(self) -> None:
 
 414         source_path = get_case_path("py_37", "python37")
 
 415         source, expected = read_data_from_file(source_path)
 
 417         self.assertFormatEqual(expected, actual)
 
 418         major, minor = sys.version_info[:2]
 
 419         if major > 3 or (major == 3 and minor >= 7):
 
 420             black.assert_equivalent(source, actual)
 
 421         black.assert_stable(source, actual, DEFAULT_MODE)
 
 422         # ensure black can parse this when the target is 3.7
 
 423         self.invokeBlack([str(source_path), "--target-version", "py37"])
 
 424         # but not on 3.6, because we use async as a reserved keyword
 
 425         self.invokeBlack([str(source_path), "--target-version", "py36"], exit_code=123)
 
 427     def test_tab_comment_indentation(self) -> None:
 
 428         contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
 
 429         contents_spc = "if 1:\n    if 2:\n        pass\n    # comment\n    pass\n"
 
 430         self.assertFormatEqual(contents_spc, fs(contents_spc))
 
 431         self.assertFormatEqual(contents_spc, fs(contents_tab))
 
 433         contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
 
 434         contents_spc = "if 1:\n    if 2:\n        pass\n        # comment\n    pass\n"
 
 435         self.assertFormatEqual(contents_spc, fs(contents_spc))
 
 436         self.assertFormatEqual(contents_spc, fs(contents_tab))
 
 438         # mixed tabs and spaces (valid Python 2 code)
 
 439         contents_tab = "if 1:\n        if 2:\n\t\tpass\n\t# comment\n        pass\n"
 
 440         contents_spc = "if 1:\n    if 2:\n        pass\n    # comment\n    pass\n"
 
 441         self.assertFormatEqual(contents_spc, fs(contents_spc))
 
 442         self.assertFormatEqual(contents_spc, fs(contents_tab))
 
 444         contents_tab = "if 1:\n        if 2:\n\t\tpass\n\t\t# comment\n        pass\n"
 
 445         contents_spc = "if 1:\n    if 2:\n        pass\n        # comment\n    pass\n"
 
 446         self.assertFormatEqual(contents_spc, fs(contents_spc))
 
 447         self.assertFormatEqual(contents_spc, fs(contents_tab))
 
 449     def test_report_verbose(self) -> None:
 
 450         report = Report(verbose=True)
 
 454         def out(msg: str, **kwargs: Any) -> None:
 
 455             out_lines.append(msg)
 
 457         def err(msg: str, **kwargs: Any) -> None:
 
 458             err_lines.append(msg)
 
 460         with patch("black.output._out", out), patch("black.output._err", err):
 
 461             report.done(Path("f1"), black.Changed.NO)
 
 462             self.assertEqual(len(out_lines), 1)
 
 463             self.assertEqual(len(err_lines), 0)
 
 464             self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
 
 465             self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
 
 466             self.assertEqual(report.return_code, 0)
 
 467             report.done(Path("f2"), black.Changed.YES)
 
 468             self.assertEqual(len(out_lines), 2)
 
 469             self.assertEqual(len(err_lines), 0)
 
 470             self.assertEqual(out_lines[-1], "reformatted f2")
 
 472                 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
 
 474             report.done(Path("f3"), black.Changed.CACHED)
 
 475             self.assertEqual(len(out_lines), 3)
 
 476             self.assertEqual(len(err_lines), 0)
 
 478                 out_lines[-1], "f3 wasn't modified on disk since last run."
 
 481                 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
 
 483             self.assertEqual(report.return_code, 0)
 
 485             self.assertEqual(report.return_code, 1)
 
 487             report.failed(Path("e1"), "boom")
 
 488             self.assertEqual(len(out_lines), 3)
 
 489             self.assertEqual(len(err_lines), 1)
 
 490             self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
 
 492                 unstyle(str(report)),
 
 494                     "1 file reformatted, 2 files left unchanged, 1 file failed to"
 
 498             self.assertEqual(report.return_code, 123)
 
 499             report.done(Path("f3"), black.Changed.YES)
 
 500             self.assertEqual(len(out_lines), 4)
 
 501             self.assertEqual(len(err_lines), 1)
 
 502             self.assertEqual(out_lines[-1], "reformatted f3")
 
 504                 unstyle(str(report)),
 
 506                     "2 files reformatted, 2 files left unchanged, 1 file failed to"
 
 510             self.assertEqual(report.return_code, 123)
 
 511             report.failed(Path("e2"), "boom")
 
 512             self.assertEqual(len(out_lines), 4)
 
 513             self.assertEqual(len(err_lines), 2)
 
 514             self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
 
 516                 unstyle(str(report)),
 
 518                     "2 files reformatted, 2 files left unchanged, 2 files failed to"
 
 522             self.assertEqual(report.return_code, 123)
 
 523             report.path_ignored(Path("wat"), "no match")
 
 524             self.assertEqual(len(out_lines), 5)
 
 525             self.assertEqual(len(err_lines), 2)
 
 526             self.assertEqual(out_lines[-1], "wat ignored: no match")
 
 528                 unstyle(str(report)),
 
 530                     "2 files reformatted, 2 files left unchanged, 2 files failed to"
 
 534             self.assertEqual(report.return_code, 123)
 
 535             report.done(Path("f4"), black.Changed.NO)
 
 536             self.assertEqual(len(out_lines), 6)
 
 537             self.assertEqual(len(err_lines), 2)
 
 538             self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
 
 540                 unstyle(str(report)),
 
 542                     "2 files reformatted, 3 files left unchanged, 2 files failed to"
 
 546             self.assertEqual(report.return_code, 123)
 
 549                 unstyle(str(report)),
 
 551                     "2 files would be reformatted, 3 files would be left unchanged, 2"
 
 552                     " files would fail to reformat."
 
 558                 unstyle(str(report)),
 
 560                     "2 files would be reformatted, 3 files would be left unchanged, 2"
 
 561                     " files would fail to reformat."
 
 565     def test_report_quiet(self) -> None:
 
 566         report = Report(quiet=True)
 
 570         def out(msg: str, **kwargs: Any) -> None:
 
 571             out_lines.append(msg)
 
 573         def err(msg: str, **kwargs: Any) -> None:
 
 574             err_lines.append(msg)
 
 576         with patch("black.output._out", out), patch("black.output._err", err):
 
 577             report.done(Path("f1"), black.Changed.NO)
 
 578             self.assertEqual(len(out_lines), 0)
 
 579             self.assertEqual(len(err_lines), 0)
 
 580             self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
 
 581             self.assertEqual(report.return_code, 0)
 
 582             report.done(Path("f2"), black.Changed.YES)
 
 583             self.assertEqual(len(out_lines), 0)
 
 584             self.assertEqual(len(err_lines), 0)
 
 586                 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
 
 588             report.done(Path("f3"), black.Changed.CACHED)
 
 589             self.assertEqual(len(out_lines), 0)
 
 590             self.assertEqual(len(err_lines), 0)
 
 592                 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
 
 594             self.assertEqual(report.return_code, 0)
 
 596             self.assertEqual(report.return_code, 1)
 
 598             report.failed(Path("e1"), "boom")
 
 599             self.assertEqual(len(out_lines), 0)
 
 600             self.assertEqual(len(err_lines), 1)
 
 601             self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
 
 603                 unstyle(str(report)),
 
 605                     "1 file reformatted, 2 files left unchanged, 1 file failed to"
 
 609             self.assertEqual(report.return_code, 123)
 
 610             report.done(Path("f3"), black.Changed.YES)
 
 611             self.assertEqual(len(out_lines), 0)
 
 612             self.assertEqual(len(err_lines), 1)
 
 614                 unstyle(str(report)),
 
 616                     "2 files reformatted, 2 files left unchanged, 1 file failed to"
 
 620             self.assertEqual(report.return_code, 123)
 
 621             report.failed(Path("e2"), "boom")
 
 622             self.assertEqual(len(out_lines), 0)
 
 623             self.assertEqual(len(err_lines), 2)
 
 624             self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
 
 626                 unstyle(str(report)),
 
 628                     "2 files reformatted, 2 files left unchanged, 2 files failed to"
 
 632             self.assertEqual(report.return_code, 123)
 
 633             report.path_ignored(Path("wat"), "no match")
 
 634             self.assertEqual(len(out_lines), 0)
 
 635             self.assertEqual(len(err_lines), 2)
 
 637                 unstyle(str(report)),
 
 639                     "2 files reformatted, 2 files left unchanged, 2 files failed to"
 
 643             self.assertEqual(report.return_code, 123)
 
 644             report.done(Path("f4"), black.Changed.NO)
 
 645             self.assertEqual(len(out_lines), 0)
 
 646             self.assertEqual(len(err_lines), 2)
 
 648                 unstyle(str(report)),
 
 650                     "2 files reformatted, 3 files left unchanged, 2 files failed to"
 
 654             self.assertEqual(report.return_code, 123)
 
 657                 unstyle(str(report)),
 
 659                     "2 files would be reformatted, 3 files would be left unchanged, 2"
 
 660                     " files would fail to reformat."
 
 666                 unstyle(str(report)),
 
 668                     "2 files would be reformatted, 3 files would be left unchanged, 2"
 
 669                     " files would fail to reformat."
 
 673     def test_report_normal(self) -> None:
 
 674         report = black.Report()
 
 678         def out(msg: str, **kwargs: Any) -> None:
 
 679             out_lines.append(msg)
 
 681         def err(msg: str, **kwargs: Any) -> None:
 
 682             err_lines.append(msg)
 
 684         with patch("black.output._out", out), patch("black.output._err", err):
 
 685             report.done(Path("f1"), black.Changed.NO)
 
 686             self.assertEqual(len(out_lines), 0)
 
 687             self.assertEqual(len(err_lines), 0)
 
 688             self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
 
 689             self.assertEqual(report.return_code, 0)
 
 690             report.done(Path("f2"), black.Changed.YES)
 
 691             self.assertEqual(len(out_lines), 1)
 
 692             self.assertEqual(len(err_lines), 0)
 
 693             self.assertEqual(out_lines[-1], "reformatted f2")
 
 695                 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
 
 697             report.done(Path("f3"), black.Changed.CACHED)
 
 698             self.assertEqual(len(out_lines), 1)
 
 699             self.assertEqual(len(err_lines), 0)
 
 700             self.assertEqual(out_lines[-1], "reformatted f2")
 
 702                 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
 
 704             self.assertEqual(report.return_code, 0)
 
 706             self.assertEqual(report.return_code, 1)
 
 708             report.failed(Path("e1"), "boom")
 
 709             self.assertEqual(len(out_lines), 1)
 
 710             self.assertEqual(len(err_lines), 1)
 
 711             self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
 
 713                 unstyle(str(report)),
 
 715                     "1 file reformatted, 2 files left unchanged, 1 file failed to"
 
 719             self.assertEqual(report.return_code, 123)
 
 720             report.done(Path("f3"), black.Changed.YES)
 
 721             self.assertEqual(len(out_lines), 2)
 
 722             self.assertEqual(len(err_lines), 1)
 
 723             self.assertEqual(out_lines[-1], "reformatted f3")
 
 725                 unstyle(str(report)),
 
 727                     "2 files reformatted, 2 files left unchanged, 1 file failed to"
 
 731             self.assertEqual(report.return_code, 123)
 
 732             report.failed(Path("e2"), "boom")
 
 733             self.assertEqual(len(out_lines), 2)
 
 734             self.assertEqual(len(err_lines), 2)
 
 735             self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
 
 737                 unstyle(str(report)),
 
 739                     "2 files reformatted, 2 files left unchanged, 2 files failed to"
 
 743             self.assertEqual(report.return_code, 123)
 
 744             report.path_ignored(Path("wat"), "no match")
 
 745             self.assertEqual(len(out_lines), 2)
 
 746             self.assertEqual(len(err_lines), 2)
 
 748                 unstyle(str(report)),
 
 750                     "2 files reformatted, 2 files left unchanged, 2 files failed to"
 
 754             self.assertEqual(report.return_code, 123)
 
 755             report.done(Path("f4"), black.Changed.NO)
 
 756             self.assertEqual(len(out_lines), 2)
 
 757             self.assertEqual(len(err_lines), 2)
 
 759                 unstyle(str(report)),
 
 761                     "2 files reformatted, 3 files left unchanged, 2 files failed to"
 
 765             self.assertEqual(report.return_code, 123)
 
 768                 unstyle(str(report)),
 
 770                     "2 files would be reformatted, 3 files would be left unchanged, 2"
 
 771                     " files would fail to reformat."
 
 777                 unstyle(str(report)),
 
 779                     "2 files would be reformatted, 3 files would be left unchanged, 2"
 
 780                     " files would fail to reformat."
 
 784     def test_lib2to3_parse(self) -> None:
 
 785         with self.assertRaises(black.InvalidInput):
 
 786             black.lib2to3_parse("invalid syntax")
 
 789         black.lib2to3_parse(straddling)
 
 790         black.lib2to3_parse(straddling, {TargetVersion.PY36})
 
 793         with self.assertRaises(black.InvalidInput):
 
 794             black.lib2to3_parse(py2_only, {TargetVersion.PY36})
 
 796         py3_only = "exec(x, end=y)"
 
 797         black.lib2to3_parse(py3_only)
 
 798         black.lib2to3_parse(py3_only, {TargetVersion.PY36})
 
 800     def test_get_features_used_decorator(self) -> None:
 
 801         # Test the feature detection of new decorator syntax
 
 802         # since this makes some test cases of test_get_features_used()
 
 803         # fails if it fails, this is tested first so that a useful case
 
 805         simples, relaxed = read_data("miscellaneous", "decorators")
 
 806         # skip explanation comments at the top of the file
 
 807         for simple_test in simples.split("##")[1:]:
 
 808             node = black.lib2to3_parse(simple_test)
 
 809             decorator = str(node.children[0].children[0]).strip()
 
 811                 Feature.RELAXED_DECORATORS,
 
 812                 black.get_features_used(node),
 
 814                     f"decorator '{decorator}' follows python<=3.8 syntax"
 
 815                     "but is detected as 3.9+"
 
 816                     # f"The full node is\n{node!r}"
 
 819         # skip the '# output' comment at the top of the output part
 
 820         for relaxed_test in relaxed.split("##")[1:]:
 
 821             node = black.lib2to3_parse(relaxed_test)
 
 822             decorator = str(node.children[0].children[0]).strip()
 
 824                 Feature.RELAXED_DECORATORS,
 
 825                 black.get_features_used(node),
 
 827                     f"decorator '{decorator}' uses python3.9+ syntax"
 
 828                     "but is detected as python<=3.8"
 
 829                     # f"The full node is\n{node!r}"
 
 833     def test_get_features_used(self) -> None:
 
 834         node = black.lib2to3_parse("def f(*, arg): ...\n")
 
 835         self.assertEqual(black.get_features_used(node), set())
 
 836         node = black.lib2to3_parse("def f(*, arg,): ...\n")
 
 837         self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
 
 838         node = black.lib2to3_parse("f(*arg,)\n")
 
 840             black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
 
 842         node = black.lib2to3_parse("def f(*, arg): f'string'\n")
 
 843         self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
 
 844         node = black.lib2to3_parse("123_456\n")
 
 845         self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
 
 846         node = black.lib2to3_parse("123456\n")
 
 847         self.assertEqual(black.get_features_used(node), set())
 
 848         source, expected = read_data("simple_cases", "function")
 
 849         node = black.lib2to3_parse(source)
 
 850         expected_features = {
 
 851             Feature.TRAILING_COMMA_IN_CALL,
 
 852             Feature.TRAILING_COMMA_IN_DEF,
 
 855         self.assertEqual(black.get_features_used(node), expected_features)
 
 856         node = black.lib2to3_parse(expected)
 
 857         self.assertEqual(black.get_features_used(node), expected_features)
 
 858         source, expected = read_data("simple_cases", "expression")
 
 859         node = black.lib2to3_parse(source)
 
 860         self.assertEqual(black.get_features_used(node), set())
 
 861         node = black.lib2to3_parse(expected)
 
 862         self.assertEqual(black.get_features_used(node), set())
 
 863         node = black.lib2to3_parse("lambda a, /, b: ...")
 
 864         self.assertEqual(black.get_features_used(node), {Feature.POS_ONLY_ARGUMENTS})
 
 865         node = black.lib2to3_parse("def fn(a, /, b): ...")
 
 866         self.assertEqual(black.get_features_used(node), {Feature.POS_ONLY_ARGUMENTS})
 
 867         node = black.lib2to3_parse("def fn(): yield a, b")
 
 868         self.assertEqual(black.get_features_used(node), set())
 
 869         node = black.lib2to3_parse("def fn(): return a, b")
 
 870         self.assertEqual(black.get_features_used(node), set())
 
 871         node = black.lib2to3_parse("def fn(): yield *b, c")
 
 872         self.assertEqual(black.get_features_used(node), {Feature.UNPACKING_ON_FLOW})
 
 873         node = black.lib2to3_parse("def fn(): return a, *b, c")
 
 874         self.assertEqual(black.get_features_used(node), {Feature.UNPACKING_ON_FLOW})
 
 875         node = black.lib2to3_parse("x = a, *b, c")
 
 876         self.assertEqual(black.get_features_used(node), set())
 
 877         node = black.lib2to3_parse("x: Any = regular")
 
 878         self.assertEqual(black.get_features_used(node), set())
 
 879         node = black.lib2to3_parse("x: Any = (regular, regular)")
 
 880         self.assertEqual(black.get_features_used(node), set())
 
 881         node = black.lib2to3_parse("x: Any = Complex(Type(1))[something]")
 
 882         self.assertEqual(black.get_features_used(node), set())
 
 883         node = black.lib2to3_parse("x: Tuple[int, ...] = a, b, c")
 
 885             black.get_features_used(node), {Feature.ANN_ASSIGN_EXTENDED_RHS}
 
 887         node = black.lib2to3_parse("try: pass\nexcept Something: pass")
 
 888         self.assertEqual(black.get_features_used(node), set())
 
 889         node = black.lib2to3_parse("try: pass\nexcept (*Something,): pass")
 
 890         self.assertEqual(black.get_features_used(node), set())
 
 891         node = black.lib2to3_parse("try: pass\nexcept *Group: pass")
 
 892         self.assertEqual(black.get_features_used(node), {Feature.EXCEPT_STAR})
 
 893         node = black.lib2to3_parse("a[*b]")
 
 894         self.assertEqual(black.get_features_used(node), {Feature.VARIADIC_GENERICS})
 
 895         node = black.lib2to3_parse("a[x, *y(), z] = t")
 
 896         self.assertEqual(black.get_features_used(node), {Feature.VARIADIC_GENERICS})
 
 897         node = black.lib2to3_parse("def fn(*args: *T): pass")
 
 898         self.assertEqual(black.get_features_used(node), {Feature.VARIADIC_GENERICS})
 
 900     def test_get_features_used_for_future_flags(self) -> None:
 
 901         for src, features in [
 
 902             ("from __future__ import annotations", {Feature.FUTURE_ANNOTATIONS}),
 
 904                 "from __future__ import (other, annotations)",
 
 905                 {Feature.FUTURE_ANNOTATIONS},
 
 907             ("a = 1 + 2\nfrom something import annotations", set()),
 
 908             ("from __future__ import x, y", set()),
 
 910             with self.subTest(src=src, features=features):
 
 911                 node = black.lib2to3_parse(src)
 
 912                 future_imports = black.get_future_imports(node)
 
 914                     black.get_features_used(node, future_imports=future_imports),
 
 918     def test_get_future_imports(self) -> None:
 
 919         node = black.lib2to3_parse("\n")
 
 920         self.assertEqual(set(), black.get_future_imports(node))
 
 921         node = black.lib2to3_parse("from __future__ import black\n")
 
 922         self.assertEqual({"black"}, black.get_future_imports(node))
 
 923         node = black.lib2to3_parse("from __future__ import multiple, imports\n")
 
 924         self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
 
 925         node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
 
 926         self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
 
 927         node = black.lib2to3_parse(
 
 928             "from __future__ import multiple\nfrom __future__ import imports\n"
 
 930         self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
 
 931         node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
 
 932         self.assertEqual({"black"}, black.get_future_imports(node))
 
 933         node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
 
 934         self.assertEqual({"black"}, black.get_future_imports(node))
 
 935         node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
 
 936         self.assertEqual(set(), black.get_future_imports(node))
 
 937         node = black.lib2to3_parse("from some.module import black\n")
 
 938         self.assertEqual(set(), black.get_future_imports(node))
 
 939         node = black.lib2to3_parse(
 
 940             "from __future__ import unicode_literals as _unicode_literals"
 
 942         self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
 
 943         node = black.lib2to3_parse(
 
 944             "from __future__ import unicode_literals as _lol, print"
 
 946         self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
 
 948     @pytest.mark.incompatible_with_mypyc
 
 949     def test_debug_visitor(self) -> None:
 
 950         source, _ = read_data("miscellaneous", "debug_visitor")
 
 951         expected, _ = read_data("miscellaneous", "debug_visitor.out")
 
 955         def out(msg: str, **kwargs: Any) -> None:
 
 956             out_lines.append(msg)
 
 958         def err(msg: str, **kwargs: Any) -> None:
 
 959             err_lines.append(msg)
 
 961         with patch("black.debug.out", out):
 
 962             DebugVisitor.show(source)
 
 963         actual = "\n".join(out_lines) + "\n"
 
 965         if expected != actual:
 
 966             log_name = black.dump_to_file(*out_lines)
 
 970             f"AST print out is different. Actual version dumped to {log_name}",
 
 973     def test_format_file_contents(self) -> None:
 
 976         with self.assertRaises(black.NothingChanged):
 
 977             black.format_file_contents(empty, mode=mode, fast=False)
 
 979         with self.assertRaises(black.NothingChanged):
 
 980             black.format_file_contents(just_nl, mode=mode, fast=False)
 
 981         same = "j = [1, 2, 3]\n"
 
 982         with self.assertRaises(black.NothingChanged):
 
 983             black.format_file_contents(same, mode=mode, fast=False)
 
 984         different = "j = [1,2,3]"
 
 986         actual = black.format_file_contents(different, mode=mode, fast=False)
 
 987         self.assertEqual(expected, actual)
 
 988         invalid = "return if you can"
 
 989         with self.assertRaises(black.InvalidInput) as e:
 
 990             black.format_file_contents(invalid, mode=mode, fast=False)
 
 991         self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
 
 993     def test_endmarker(self) -> None:
 
 994         n = black.lib2to3_parse("\n")
 
 995         self.assertEqual(n.type, black.syms.file_input)
 
 996         self.assertEqual(len(n.children), 1)
 
 997         self.assertEqual(n.children[0].type, black.token.ENDMARKER)
 
 999     @pytest.mark.incompatible_with_mypyc
 
1000     @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
 
1001     def test_assertFormatEqual(self) -> None:
 
1005         def out(msg: str, **kwargs: Any) -> None:
 
1006             out_lines.append(msg)
 
1008         def err(msg: str, **kwargs: Any) -> None:
 
1009             err_lines.append(msg)
 
1011         with patch("black.output._out", out), patch("black.output._err", err):
 
1012             with self.assertRaises(AssertionError):
 
1013                 self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")
 
1015         out_str = "".join(out_lines)
 
1016         self.assertIn("Expected tree:", out_str)
 
1017         self.assertIn("Actual tree:", out_str)
 
1018         self.assertEqual("".join(err_lines), "")
 
1021     @patch("concurrent.futures.ProcessPoolExecutor", MagicMock(side_effect=OSError))
 
1022     def test_works_in_mono_process_only_environment(self) -> None:
 
1023         with cache_dir() as workspace:
 
1025                 (workspace / "one.py").resolve(),
 
1026                 (workspace / "two.py").resolve(),
 
1028                 f.write_text('print("hello")\n')
 
1029             self.invokeBlack([str(workspace)])
 
1032     def test_check_diff_use_together(self) -> None:
 
1034             # Files which will be reformatted.
 
1035             src1 = get_case_path("miscellaneous", "string_quotes")
 
1036             self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
 
1037             # Files which will not be reformatted.
 
1038             src2 = get_case_path("simple_cases", "composition")
 
1039             self.invokeBlack([str(src2), "--diff", "--check"])
 
1040             # Multi file command.
 
1041             self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
 
1043     def test_no_src_fails(self) -> None:
 
1045             self.invokeBlack([], exit_code=1)
 
1047     def test_src_and_code_fails(self) -> None:
 
1049             self.invokeBlack([".", "-c", "0"], exit_code=1)
 
1051     def test_broken_symlink(self) -> None:
 
1052         with cache_dir() as workspace:
 
1053             symlink = workspace / "broken_link.py"
 
1055                 symlink.symlink_to("nonexistent.py")
 
1056             except (OSError, NotImplementedError) as e:
 
1057                 self.skipTest(f"Can't create symlinks: {e}")
 
1058             self.invokeBlack([str(workspace.resolve())])
 
1060     def test_single_file_force_pyi(self) -> None:
 
1061         pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
 
1062         contents, expected = read_data("miscellaneous", "force_pyi")
 
1063         with cache_dir() as workspace:
 
1064             path = (workspace / "file.py").resolve()
 
1065             with open(path, "w") as fh:
 
1067             self.invokeBlack([str(path), "--pyi"])
 
1068             with open(path, "r") as fh:
 
1070             # verify cache with --pyi is separate
 
1071             pyi_cache = black.read_cache(pyi_mode)
 
1072             self.assertIn(str(path), pyi_cache)
 
1073             normal_cache = black.read_cache(DEFAULT_MODE)
 
1074             self.assertNotIn(str(path), normal_cache)
 
1075         self.assertFormatEqual(expected, actual)
 
1076         black.assert_equivalent(contents, actual)
 
1077         black.assert_stable(contents, actual, pyi_mode)
 
1080     def test_multi_file_force_pyi(self) -> None:
 
1081         reg_mode = DEFAULT_MODE
 
1082         pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
 
1083         contents, expected = read_data("miscellaneous", "force_pyi")
 
1084         with cache_dir() as workspace:
 
1086                 (workspace / "file1.py").resolve(),
 
1087                 (workspace / "file2.py").resolve(),
 
1090                 with open(path, "w") as fh:
 
1092             self.invokeBlack([str(p) for p in paths] + ["--pyi"])
 
1094                 with open(path, "r") as fh:
 
1096                 self.assertEqual(actual, expected)
 
1097             # verify cache with --pyi is separate
 
1098             pyi_cache = black.read_cache(pyi_mode)
 
1099             normal_cache = black.read_cache(reg_mode)
 
1101                 self.assertIn(str(path), pyi_cache)
 
1102                 self.assertNotIn(str(path), normal_cache)
 
1104     def test_pipe_force_pyi(self) -> None:
 
1105         source, expected = read_data("miscellaneous", "force_pyi")
 
1106         result = CliRunner().invoke(
 
1107             black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
 
1109         self.assertEqual(result.exit_code, 0)
 
1110         actual = result.output
 
1111         self.assertFormatEqual(actual, expected)
 
1113     def test_single_file_force_py36(self) -> None:
 
1114         reg_mode = DEFAULT_MODE
 
1115         py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
 
1116         source, expected = read_data("miscellaneous", "force_py36")
 
1117         with cache_dir() as workspace:
 
1118             path = (workspace / "file.py").resolve()
 
1119             with open(path, "w") as fh:
 
1121             self.invokeBlack([str(path), *PY36_ARGS])
 
1122             with open(path, "r") as fh:
 
1124             # verify cache with --target-version is separate
 
1125             py36_cache = black.read_cache(py36_mode)
 
1126             self.assertIn(str(path), py36_cache)
 
1127             normal_cache = black.read_cache(reg_mode)
 
1128             self.assertNotIn(str(path), normal_cache)
 
1129         self.assertEqual(actual, expected)
 
1132     def test_multi_file_force_py36(self) -> None:
 
1133         reg_mode = DEFAULT_MODE
 
1134         py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
 
1135         source, expected = read_data("miscellaneous", "force_py36")
 
1136         with cache_dir() as workspace:
 
1138                 (workspace / "file1.py").resolve(),
 
1139                 (workspace / "file2.py").resolve(),
 
1142                 with open(path, "w") as fh:
 
1144             self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
 
1146                 with open(path, "r") as fh:
 
1148                 self.assertEqual(actual, expected)
 
1149             # verify cache with --target-version is separate
 
1150             pyi_cache = black.read_cache(py36_mode)
 
1151             normal_cache = black.read_cache(reg_mode)
 
1153                 self.assertIn(str(path), pyi_cache)
 
1154                 self.assertNotIn(str(path), normal_cache)
 
1156     def test_pipe_force_py36(self) -> None:
 
1157         source, expected = read_data("miscellaneous", "force_py36")
 
1158         result = CliRunner().invoke(
 
1160             ["-", "-q", "--target-version=py36"],
 
1161             input=BytesIO(source.encode("utf8")),
 
1163         self.assertEqual(result.exit_code, 0)
 
1164         actual = result.output
 
1165         self.assertFormatEqual(actual, expected)
 
1167     @pytest.mark.incompatible_with_mypyc
 
1168     def test_reformat_one_with_stdin(self) -> None:
 
1170             "black.format_stdin_to_stdout",
 
1171             return_value=lambda *args, **kwargs: black.Changed.YES,
 
1173             report = MagicMock()
 
1178                 write_back=black.WriteBack.YES,
 
1182             fsts.assert_called_once()
 
1183             report.done.assert_called_with(path, black.Changed.YES)
 
1185     @pytest.mark.incompatible_with_mypyc
 
1186     def test_reformat_one_with_stdin_filename(self) -> None:
 
1188             "black.format_stdin_to_stdout",
 
1189             return_value=lambda *args, **kwargs: black.Changed.YES,
 
1191             report = MagicMock()
 
1193             path = Path(f"__BLACK_STDIN_FILENAME__{p}")
 
1198                 write_back=black.WriteBack.YES,
 
1202             fsts.assert_called_once_with(
 
1203                 fast=True, write_back=black.WriteBack.YES, mode=DEFAULT_MODE
 
1205             # __BLACK_STDIN_FILENAME__ should have been stripped
 
1206             report.done.assert_called_with(expected, black.Changed.YES)
 
1208     @pytest.mark.incompatible_with_mypyc
 
1209     def test_reformat_one_with_stdin_filename_pyi(self) -> None:
 
1211             "black.format_stdin_to_stdout",
 
1212             return_value=lambda *args, **kwargs: black.Changed.YES,
 
1214             report = MagicMock()
 
1216             path = Path(f"__BLACK_STDIN_FILENAME__{p}")
 
1221                 write_back=black.WriteBack.YES,
 
1225             fsts.assert_called_once_with(
 
1227                 write_back=black.WriteBack.YES,
 
1228                 mode=replace(DEFAULT_MODE, is_pyi=True),
 
1230             # __BLACK_STDIN_FILENAME__ should have been stripped
 
1231             report.done.assert_called_with(expected, black.Changed.YES)
 
1233     @pytest.mark.incompatible_with_mypyc
 
1234     def test_reformat_one_with_stdin_filename_ipynb(self) -> None:
 
1236             "black.format_stdin_to_stdout",
 
1237             return_value=lambda *args, **kwargs: black.Changed.YES,
 
1239             report = MagicMock()
 
1241             path = Path(f"__BLACK_STDIN_FILENAME__{p}")
 
1246                 write_back=black.WriteBack.YES,
 
1250             fsts.assert_called_once_with(
 
1252                 write_back=black.WriteBack.YES,
 
1253                 mode=replace(DEFAULT_MODE, is_ipynb=True),
 
1255             # __BLACK_STDIN_FILENAME__ should have been stripped
 
1256             report.done.assert_called_with(expected, black.Changed.YES)
 
1258     @pytest.mark.incompatible_with_mypyc
 
1259     def test_reformat_one_with_stdin_and_existing_path(self) -> None:
 
1261             "black.format_stdin_to_stdout",
 
1262             return_value=lambda *args, **kwargs: black.Changed.YES,
 
1264             report = MagicMock()
 
1265             # Even with an existing file, since we are forcing stdin, black
 
1266             # should output to stdout and not modify the file inplace
 
1267             p = THIS_DIR / "data" / "simple_cases" / "collections.py"
 
1268             # Make sure is_file actually returns True
 
1269             self.assertTrue(p.is_file())
 
1270             path = Path(f"__BLACK_STDIN_FILENAME__{p}")
 
1275                 write_back=black.WriteBack.YES,
 
1279             fsts.assert_called_once()
 
1280             # __BLACK_STDIN_FILENAME__ should have been stripped
 
1281             report.done.assert_called_with(expected, black.Changed.YES)
 
1283     def test_reformat_one_with_stdin_empty(self) -> None:
 
1284         output = io.StringIO()
 
1285         with patch("io.TextIOWrapper", lambda *args, **kwargs: output):
 
1287                 black.format_stdin_to_stdout(
 
1290                     write_back=black.WriteBack.YES,
 
1293             except io.UnsupportedOperation:
 
1294                 pass  # StringIO does not support detach
 
1295             assert output.getvalue() == ""
 
1297     def test_invalid_cli_regex(self) -> None:
 
1298         for option in ["--include", "--exclude", "--extend-exclude", "--force-exclude"]:
 
1299             self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
 
1301     def test_required_version_matches_version(self) -> None:
 
1303             ["--required-version", black.__version__, "-c", "0"],
 
1308     def test_required_version_matches_partial_version(self) -> None:
 
1310             ["--required-version", black.__version__.split(".")[0], "-c", "0"],
 
1315     def test_required_version_does_not_match_on_minor_version(self) -> None:
 
1317             ["--required-version", black.__version__.split(".")[0] + ".999", "-c", "0"],
 
1322     def test_required_version_does_not_match_version(self) -> None:
 
1323         result = BlackRunner().invoke(
 
1325             ["--required-version", "20.99b", "-c", "0"],
 
1327         self.assertEqual(result.exit_code, 1)
 
1328         self.assertIn("required version", result.stderr)
 
1330     def test_preserves_line_endings(self) -> None:
 
1331         with TemporaryDirectory() as workspace:
 
1332             test_file = Path(workspace) / "test.py"
 
1333             for nl in ["\n", "\r\n"]:
 
1334                 contents = nl.join(["def f(  ):", "    pass"])
 
1335                 test_file.write_bytes(contents.encode())
 
1336                 ff(test_file, write_back=black.WriteBack.YES)
 
1337                 updated_contents: bytes = test_file.read_bytes()
 
1338                 self.assertIn(nl.encode(), updated_contents)
 
1340                     self.assertNotIn(b"\r\n", updated_contents)
 
1342     def test_preserves_line_endings_via_stdin(self) -> None:
 
1343         for nl in ["\n", "\r\n"]:
 
1344             contents = nl.join(["def f(  ):", "    pass"])
 
1345             runner = BlackRunner()
 
1346             result = runner.invoke(
 
1347                 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
 
1349             self.assertEqual(result.exit_code, 0)
 
1350             output = result.stdout_bytes
 
1351             self.assertIn(nl.encode("utf8"), output)
 
1353                 self.assertNotIn(b"\r\n", output)
 
1355     def test_normalize_line_endings(self) -> None:
 
1356         with TemporaryDirectory() as workspace:
 
1357             test_file = Path(workspace) / "test.py"
 
1358             for data, expected in (
 
1359                 (b"c\r\nc\n ", b"c\r\nc\r\n"),
 
1360                 (b"l\nl\r\n ", b"l\nl\n"),
 
1362                 test_file.write_bytes(data)
 
1363                 ff(test_file, write_back=black.WriteBack.YES)
 
1364                 self.assertEqual(test_file.read_bytes(), expected)
 
1366     def test_assert_equivalent_different_asts(self) -> None:
 
1367         with self.assertRaises(AssertionError):
 
1368             black.assert_equivalent("{}", "None")
 
1370     def test_shhh_click(self) -> None:
 
1372             from click import _unicodefun  # type: ignore
 
1374             self.skipTest("Incompatible Click version")
 
1376         if not hasattr(_unicodefun, "_verify_python_env"):
 
1377             self.skipTest("Incompatible Click version")
 
1379         # First, let's see if Click is crashing with a preferred ASCII charset.
 
1380         with patch("locale.getpreferredencoding") as gpe:
 
1381             gpe.return_value = "ASCII"
 
1382             with self.assertRaises(RuntimeError):
 
1383                 _unicodefun._verify_python_env()
 
1384         # Now, let's silence Click...
 
1386         # ...and confirm it's silent.
 
1387         with patch("locale.getpreferredencoding") as gpe:
 
1388             gpe.return_value = "ASCII"
 
1390                 _unicodefun._verify_python_env()
 
1391             except RuntimeError as re:
 
1392                 self.fail(f"`patch_click()` failed, exception still raised: {re}")
 
1394     def test_root_logger_not_used_directly(self) -> None:
 
1395         def fail(*args: Any, **kwargs: Any) -> None:
 
1396             self.fail("Record created with root logger")
 
1398         with patch.multiple(
 
1407             ff(THIS_DIR / "util.py")
 
1409     def test_invalid_config_return_code(self) -> None:
 
1410         tmp_file = Path(black.dump_to_file())
 
1412             tmp_config = Path(black.dump_to_file())
 
1414             args = ["--config", str(tmp_config), str(tmp_file)]
 
1415             self.invokeBlack(args, exit_code=2, ignore_config=False)
 
1419     def test_parse_pyproject_toml(self) -> None:
 
1420         test_toml_file = THIS_DIR / "test.toml"
 
1421         config = black.parse_pyproject_toml(str(test_toml_file))
 
1422         self.assertEqual(config["verbose"], 1)
 
1423         self.assertEqual(config["check"], "no")
 
1424         self.assertEqual(config["diff"], "y")
 
1425         self.assertEqual(config["color"], True)
 
1426         self.assertEqual(config["line_length"], 79)
 
1427         self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
 
1428         self.assertEqual(config["python_cell_magics"], ["custom1", "custom2"])
 
1429         self.assertEqual(config["exclude"], r"\.pyi?$")
 
1430         self.assertEqual(config["include"], r"\.py?$")
 
1432     def test_read_pyproject_toml(self) -> None:
 
1433         test_toml_file = THIS_DIR / "test.toml"
 
1434         fake_ctx = FakeContext()
 
1435         black.read_pyproject_toml(fake_ctx, FakeParameter(), str(test_toml_file))
 
1436         config = fake_ctx.default_map
 
1437         self.assertEqual(config["verbose"], "1")
 
1438         self.assertEqual(config["check"], "no")
 
1439         self.assertEqual(config["diff"], "y")
 
1440         self.assertEqual(config["color"], "True")
 
1441         self.assertEqual(config["line_length"], "79")
 
1442         self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
 
1443         self.assertEqual(config["exclude"], r"\.pyi?$")
 
1444         self.assertEqual(config["include"], r"\.py?$")
 
1446     @pytest.mark.incompatible_with_mypyc
 
1447     def test_find_project_root(self) -> None:
 
1448         with TemporaryDirectory() as workspace:
 
1449             root = Path(workspace)
 
1450             test_dir = root / "test"
 
1453             src_dir = root / "src"
 
1456             root_pyproject = root / "pyproject.toml"
 
1457             root_pyproject.touch()
 
1458             src_pyproject = src_dir / "pyproject.toml"
 
1459             src_pyproject.touch()
 
1460             src_python = src_dir / "foo.py"
 
1464                 black.find_project_root((src_dir, test_dir)),
 
1465                 (root.resolve(), "pyproject.toml"),
 
1468                 black.find_project_root((src_dir,)),
 
1469                 (src_dir.resolve(), "pyproject.toml"),
 
1472                 black.find_project_root((src_python,)),
 
1473                 (src_dir.resolve(), "pyproject.toml"),
 
1476             with change_directory(test_dir):
 
1478                     black.find_project_root(("-",), stdin_filename="../src/a.py"),
 
1479                     (src_dir.resolve(), "pyproject.toml"),
 
1483         "black.files.find_user_pyproject_toml",
 
1485     def test_find_pyproject_toml(self, find_user_pyproject_toml: MagicMock) -> None:
 
1486         find_user_pyproject_toml.side_effect = RuntimeError()
 
1488         with redirect_stderr(io.StringIO()) as stderr:
 
1489             result = black.files.find_pyproject_toml(
 
1490                 path_search_start=(str(Path.cwd().root),)
 
1493         assert result is None
 
1494         err = stderr.getvalue()
 
1495         assert "Ignoring user configuration" in err
 
1498         "black.files.find_user_pyproject_toml",
 
1499         black.files.find_user_pyproject_toml.__wrapped__,
 
1501     def test_find_user_pyproject_toml_linux(self) -> None:
 
1502         if system() == "Windows":
 
1505         # Test if XDG_CONFIG_HOME is checked
 
1506         with TemporaryDirectory() as workspace:
 
1507             tmp_user_config = Path(workspace) / "black"
 
1508             with patch.dict("os.environ", {"XDG_CONFIG_HOME": workspace}):
 
1510                     black.files.find_user_pyproject_toml(), tmp_user_config.resolve()
 
1513         # Test fallback for XDG_CONFIG_HOME
 
1514         with patch.dict("os.environ"):
 
1515             os.environ.pop("XDG_CONFIG_HOME", None)
 
1516             fallback_user_config = Path("~/.config").expanduser() / "black"
 
1518                 black.files.find_user_pyproject_toml(), fallback_user_config.resolve()
 
1521     def test_find_user_pyproject_toml_windows(self) -> None:
 
1522         if system() != "Windows":
 
1525         user_config_path = Path.home() / ".black"
 
1527             black.files.find_user_pyproject_toml(), user_config_path.resolve()
 
1530     def test_bpo_33660_workaround(self) -> None:
 
1531         if system() == "Windows":
 
1534         # https://bugs.python.org/issue33660
 
1536         with change_directory(root):
 
1537             path = Path("workspace") / "project"
 
1538             report = black.Report(verbose=True)
 
1539             normalized_path = black.normalize_path_maybe_ignore(path, root, report)
 
1540             self.assertEqual(normalized_path, "workspace/project")
 
1542     def test_normalize_path_ignore_windows_junctions_outside_of_root(self) -> None:
 
1543         if system() != "Windows":
 
1546         with TemporaryDirectory() as workspace:
 
1547             root = Path(workspace)
 
1548             junction_dir = root / "junction"
 
1549             junction_target_outside_of_root = root / ".."
 
1550             os.system(f"mklink /J {junction_dir} {junction_target_outside_of_root}")
 
1552             report = black.Report(verbose=True)
 
1553             normalized_path = black.normalize_path_maybe_ignore(
 
1554                 junction_dir, root, report
 
1556             # Manually delete for Python < 3.8
 
1557             os.system(f"rmdir {junction_dir}")
 
1559             self.assertEqual(normalized_path, None)
 
1561     def test_newline_comment_interaction(self) -> None:
 
1562         source = "class A:\\\r\n# type: ignore\n pass\n"
 
1563         output = black.format_str(source, mode=DEFAULT_MODE)
 
1564         black.assert_stable(source, output, mode=DEFAULT_MODE)
 
1566     def test_bpo_2142_workaround(self) -> None:
 
1567         # https://bugs.python.org/issue2142
 
1569         source, _ = read_data("miscellaneous", "missing_final_newline")
 
1570         # read_data adds a trailing newline
 
1571         source = source.rstrip()
 
1572         expected, _ = read_data("miscellaneous", "missing_final_newline.diff")
 
1573         tmp_file = Path(black.dump_to_file(source, ensure_final_newline=False))
 
1574         diff_header = re.compile(
 
1575             rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
 
1576             r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
 
1579             result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
 
1580             self.assertEqual(result.exit_code, 0)
 
1583         actual = result.output
 
1584         actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
 
1585         self.assertEqual(actual, expected)
 
1588     def compare_results(
 
1589         result: click.testing.Result, expected_value: str, expected_exit_code: int
 
1591         """Helper method to test the value and exit code of a click Result."""
 
1593             result.output == expected_value
 
1594         ), "The output did not match the expected value."
 
1595         assert result.exit_code == expected_exit_code, "The exit code is incorrect."
 
1597     def test_code_option(self) -> None:
 
1598         """Test the code option with no changes."""
 
1599         code = 'print("Hello world")\n'
 
1600         args = ["--code", code]
 
1601         result = CliRunner().invoke(black.main, args)
 
1603         self.compare_results(result, code, 0)
 
1605     def test_code_option_changed(self) -> None:
 
1606         """Test the code option when changes are required."""
 
1607         code = "print('hello world')"
 
1608         formatted = black.format_str(code, mode=DEFAULT_MODE)
 
1610         args = ["--code", code]
 
1611         result = CliRunner().invoke(black.main, args)
 
1613         self.compare_results(result, formatted, 0)
 
1615     def test_code_option_check(self) -> None:
 
1616         """Test the code option when check is passed."""
 
1617         args = ["--check", "--code", 'print("Hello world")\n']
 
1618         result = CliRunner().invoke(black.main, args)
 
1619         self.compare_results(result, "", 0)
 
1621     def test_code_option_check_changed(self) -> None:
 
1622         """Test the code option when changes are required, and check is passed."""
 
1623         args = ["--check", "--code", "print('hello world')"]
 
1624         result = CliRunner().invoke(black.main, args)
 
1625         self.compare_results(result, "", 1)
 
1627     def test_code_option_diff(self) -> None:
 
1628         """Test the code option when diff is passed."""
 
1629         code = "print('hello world')"
 
1630         formatted = black.format_str(code, mode=DEFAULT_MODE)
 
1631         result_diff = diff(code, formatted, "STDIN", "STDOUT")
 
1633         args = ["--diff", "--code", code]
 
1634         result = CliRunner().invoke(black.main, args)
 
1636         # Remove time from diff
 
1637         output = DIFF_TIME.sub("", result.output)
 
1639         assert output == result_diff, "The output did not match the expected value."
 
1640         assert result.exit_code == 0, "The exit code is incorrect."
 
1642     def test_code_option_color_diff(self) -> None:
 
1643         """Test the code option when color and diff are passed."""
 
1644         code = "print('hello world')"
 
1645         formatted = black.format_str(code, mode=DEFAULT_MODE)
 
1647         result_diff = diff(code, formatted, "STDIN", "STDOUT")
 
1648         result_diff = color_diff(result_diff)
 
1650         args = ["--diff", "--color", "--code", code]
 
1651         result = CliRunner().invoke(black.main, args)
 
1653         # Remove time from diff
 
1654         output = DIFF_TIME.sub("", result.output)
 
1656         assert output == result_diff, "The output did not match the expected value."
 
1657         assert result.exit_code == 0, "The exit code is incorrect."
 
1659     @pytest.mark.incompatible_with_mypyc
 
1660     def test_code_option_safe(self) -> None:
 
1661         """Test that the code option throws an error when the sanity checks fail."""
 
1662         # Patch black.assert_equivalent to ensure the sanity checks fail
 
1663         with patch.object(black, "assert_equivalent", side_effect=AssertionError):
 
1664             code = 'print("Hello world")'
 
1665             error_msg = f"{code}\nerror: cannot format <string>: \n"
 
1667             args = ["--safe", "--code", code]
 
1668             result = CliRunner().invoke(black.main, args)
 
1670             self.compare_results(result, error_msg, 123)
 
1672     def test_code_option_fast(self) -> None:
 
1673         """Test that the code option ignores errors when the sanity checks fail."""
 
1674         # Patch black.assert_equivalent to ensure the sanity checks fail
 
1675         with patch.object(black, "assert_equivalent", side_effect=AssertionError):
 
1676             code = 'print("Hello world")'
 
1677             formatted = black.format_str(code, mode=DEFAULT_MODE)
 
1679             args = ["--fast", "--code", code]
 
1680             result = CliRunner().invoke(black.main, args)
 
1682             self.compare_results(result, formatted, 0)
 
1684     @pytest.mark.incompatible_with_mypyc
 
1685     def test_code_option_config(self) -> None:
 
1687         Test that the code option finds the pyproject.toml in the current directory.
 
1689         with patch.object(black, "parse_pyproject_toml", return_value={}) as parse:
 
1690             args = ["--code", "print"]
 
1691             # This is the only directory known to contain a pyproject.toml
 
1692             with change_directory(PROJECT_ROOT):
 
1693                 CliRunner().invoke(black.main, args)
 
1694                 pyproject_path = Path(Path.cwd(), "pyproject.toml").resolve()
 
1697                 len(parse.mock_calls) >= 1
 
1698             ), "Expected config parse to be called with the current directory."
 
1700             _, call_args, _ = parse.mock_calls[0]
 
1702                 call_args[0].lower() == str(pyproject_path).lower()
 
1703             ), "Incorrect config loaded."
 
1705     @pytest.mark.incompatible_with_mypyc
 
1706     def test_code_option_parent_config(self) -> None:
 
1708         Test that the code option finds the pyproject.toml in the parent directory.
 
1710         with patch.object(black, "parse_pyproject_toml", return_value={}) as parse:
 
1711             with change_directory(THIS_DIR):
 
1712                 args = ["--code", "print"]
 
1713                 CliRunner().invoke(black.main, args)
 
1715                 pyproject_path = Path(Path().cwd().parent, "pyproject.toml").resolve()
 
1717                     len(parse.mock_calls) >= 1
 
1718                 ), "Expected config parse to be called with the current directory."
 
1720                 _, call_args, _ = parse.mock_calls[0]
 
1722                     call_args[0].lower() == str(pyproject_path).lower()
 
1723                 ), "Incorrect config loaded."
 
1725     def test_for_handled_unexpected_eof_error(self) -> None:
 
1727         Test that an unexpected EOF SyntaxError is nicely presented.
 
1729         with pytest.raises(black.parsing.InvalidInput) as exc_info:
 
1730             black.lib2to3_parse("print(", {})
 
1732         exc_info.match("Cannot parse: 2:0: EOF in multi-line statement")
 
1734     def test_equivalency_ast_parse_failure_includes_error(self) -> None:
 
1735         with pytest.raises(AssertionError) as err:
 
1736             black.assert_equivalent("a«»a  = 1", "a«»a  = 1")
 
1739         # Unfortunately the SyntaxError message has changed in newer versions so we
 
1740         # can't match it directly.
 
1741         err.match("invalid character")
 
1742         err.match(r"\(<unknown>, line 1\)")
 
1746     def test_get_cache_dir(
 
1749         monkeypatch: pytest.MonkeyPatch,
 
1751         # Create multiple cache directories
 
1752         workspace1 = tmp_path / "ws1"
 
1754         workspace2 = tmp_path / "ws2"
 
1757         # Force user_cache_dir to use the temporary directory for easier assertions
 
1758         patch_user_cache_dir = patch(
 
1759             target="black.cache.user_cache_dir",
 
1761             return_value=str(workspace1),
 
1764         # If BLACK_CACHE_DIR is not set, use user_cache_dir
 
1765         monkeypatch.delenv("BLACK_CACHE_DIR", raising=False)
 
1766         with patch_user_cache_dir:
 
1767             assert get_cache_dir() == workspace1
 
1769         # If it is set, use the path provided in the env var.
 
1770         monkeypatch.setenv("BLACK_CACHE_DIR", str(workspace2))
 
1771         assert get_cache_dir() == workspace2
 
1773     def test_cache_broken_file(self) -> None:
 
1775         with cache_dir() as workspace:
 
1776             cache_file = get_cache_file(mode)
 
1777             cache_file.write_text("this is not a pickle")
 
1778             assert black.read_cache(mode) == {}
 
1779             src = (workspace / "test.py").resolve()
 
1780             src.write_text("print('hello')")
 
1781             invokeBlack([str(src)])
 
1782             cache = black.read_cache(mode)
 
1783             assert str(src) in cache
 
1785     def test_cache_single_file_already_cached(self) -> None:
 
1787         with cache_dir() as workspace:
 
1788             src = (workspace / "test.py").resolve()
 
1789             src.write_text("print('hello')")
 
1790             black.write_cache({}, [src], mode)
 
1791             invokeBlack([str(src)])
 
1792             assert src.read_text() == "print('hello')"
 
1795     def test_cache_multiple_files(self) -> None:
 
1797         with cache_dir() as workspace, patch(
 
1798             "concurrent.futures.ProcessPoolExecutor", new=ThreadPoolExecutor
 
1800             one = (workspace / "one.py").resolve()
 
1801             with one.open("w") as fobj:
 
1802                 fobj.write("print('hello')")
 
1803             two = (workspace / "two.py").resolve()
 
1804             with two.open("w") as fobj:
 
1805                 fobj.write("print('hello')")
 
1806             black.write_cache({}, [one], mode)
 
1807             invokeBlack([str(workspace)])
 
1808             with one.open("r") as fobj:
 
1809                 assert fobj.read() == "print('hello')"
 
1810             with two.open("r") as fobj:
 
1811                 assert fobj.read() == 'print("hello")\n'
 
1812             cache = black.read_cache(mode)
 
1813             assert str(one) in cache
 
1814             assert str(two) in cache
 
1816     @pytest.mark.parametrize("color", [False, True], ids=["no-color", "with-color"])
 
1817     def test_no_cache_when_writeback_diff(self, color: bool) -> None:
 
1819         with cache_dir() as workspace:
 
1820             src = (workspace / "test.py").resolve()
 
1821             with src.open("w") as fobj:
 
1822                 fobj.write("print('hello')")
 
1823             with patch("black.read_cache") as read_cache, patch(
 
1826                 cmd = [str(src), "--diff"]
 
1828                     cmd.append("--color")
 
1830                 cache_file = get_cache_file(mode)
 
1831                 assert cache_file.exists() is False
 
1832                 write_cache.assert_not_called()
 
1833                 read_cache.assert_not_called()
 
1835     @pytest.mark.parametrize("color", [False, True], ids=["no-color", "with-color"])
 
1837     def test_output_locking_when_writeback_diff(self, color: bool) -> None:
 
1838         with cache_dir() as workspace:
 
1839             for tag in range(0, 4):
 
1840                 src = (workspace / f"test{tag}.py").resolve()
 
1841                 with src.open("w") as fobj:
 
1842                     fobj.write("print('hello')")
 
1844                 "black.concurrency.Manager", wraps=multiprocessing.Manager
 
1846                 cmd = ["--diff", str(workspace)]
 
1848                     cmd.append("--color")
 
1849                 invokeBlack(cmd, exit_code=0)
 
1850                 # this isn't quite doing what we want, but if it _isn't_
 
1851                 # called then we cannot be using the lock it provides
 
1854     def test_no_cache_when_stdin(self) -> None:
 
1857             result = CliRunner().invoke(
 
1858                 black.main, ["-"], input=BytesIO(b"print('hello')")
 
1860             assert not result.exit_code
 
1861             cache_file = get_cache_file(mode)
 
1862             assert not cache_file.exists()
 
1864     def test_read_cache_no_cachefile(self) -> None:
 
1867             assert black.read_cache(mode) == {}
 
1869     def test_write_cache_read_cache(self) -> None:
 
1871         with cache_dir() as workspace:
 
1872             src = (workspace / "test.py").resolve()
 
1874             black.write_cache({}, [src], mode)
 
1875             cache = black.read_cache(mode)
 
1876             assert str(src) in cache
 
1877             assert cache[str(src)] == black.get_cache_info(src)
 
1879     def test_filter_cached(self) -> None:
 
1880         with TemporaryDirectory() as workspace:
 
1881             path = Path(workspace)
 
1882             uncached = (path / "uncached").resolve()
 
1883             cached = (path / "cached").resolve()
 
1884             cached_but_changed = (path / "changed").resolve()
 
1887             cached_but_changed.touch()
 
1889                 str(cached): black.get_cache_info(cached),
 
1890                 str(cached_but_changed): (0.0, 0),
 
1892             todo, done = black.cache.filter_cached(
 
1893                 cache, {uncached, cached, cached_but_changed}
 
1895             assert todo == {uncached, cached_but_changed}
 
1896             assert done == {cached}
 
1898     def test_write_cache_creates_directory_if_needed(self) -> None:
 
1900         with cache_dir(exists=False) as workspace:
 
1901             assert not workspace.exists()
 
1902             black.write_cache({}, [], mode)
 
1903             assert workspace.exists()
 
1906     def test_failed_formatting_does_not_get_cached(self) -> None:
 
1908         with cache_dir() as workspace, patch(
 
1909             "concurrent.futures.ProcessPoolExecutor", new=ThreadPoolExecutor
 
1911             failing = (workspace / "failing.py").resolve()
 
1912             with failing.open("w") as fobj:
 
1913                 fobj.write("not actually python")
 
1914             clean = (workspace / "clean.py").resolve()
 
1915             with clean.open("w") as fobj:
 
1916                 fobj.write('print("hello")\n')
 
1917             invokeBlack([str(workspace)], exit_code=123)
 
1918             cache = black.read_cache(mode)
 
1919             assert str(failing) not in cache
 
1920             assert str(clean) in cache
 
1922     def test_write_cache_write_fail(self) -> None:
 
1924         with cache_dir(), patch.object(Path, "open") as mock:
 
1925             mock.side_effect = OSError
 
1926             black.write_cache({}, [], mode)
 
1928     def test_read_cache_line_lengths(self) -> None:
 
1930         short_mode = replace(DEFAULT_MODE, line_length=1)
 
1931         with cache_dir() as workspace:
 
1932             path = (workspace / "file.py").resolve()
 
1934             black.write_cache({}, [path], mode)
 
1935             one = black.read_cache(mode)
 
1936             assert str(path) in one
 
1937             two = black.read_cache(short_mode)
 
1938             assert str(path) not in two
 
1941 def assert_collected_sources(
 
1942     src: Sequence[Union[str, Path]],
 
1943     expected: Sequence[Union[str, Path]],
 
1945     ctx: Optional[FakeContext] = None,
 
1946     exclude: Optional[str] = None,
 
1947     include: Optional[str] = None,
 
1948     extend_exclude: Optional[str] = None,
 
1949     force_exclude: Optional[str] = None,
 
1950     stdin_filename: Optional[str] = None,
 
1952     gs_src = tuple(str(Path(s)) for s in src)
 
1953     gs_expected = [Path(s) for s in expected]
 
1954     gs_exclude = None if exclude is None else compile_pattern(exclude)
 
1955     gs_include = DEFAULT_INCLUDE if include is None else compile_pattern(include)
 
1956     gs_extend_exclude = (
 
1957         None if extend_exclude is None else compile_pattern(extend_exclude)
 
1959     gs_force_exclude = None if force_exclude is None else compile_pattern(force_exclude)
 
1960     collected = black.get_sources(
 
1961         ctx=ctx or FakeContext(),
 
1967         extend_exclude=gs_extend_exclude,
 
1968         force_exclude=gs_force_exclude,
 
1969         report=black.Report(),
 
1970         stdin_filename=stdin_filename,
 
1972     assert sorted(collected) == sorted(gs_expected)
 
1975 class TestFileCollection:
 
1976     def test_include_exclude(self) -> None:
 
1977         path = THIS_DIR / "data" / "include_exclude_tests"
 
1980             Path(path / "b/dont_exclude/a.py"),
 
1981             Path(path / "b/dont_exclude/a.pyi"),
 
1983         assert_collected_sources(
 
1987             exclude=r"/exclude/|/\.definitely_exclude/",
 
1990     def test_gitignore_used_as_default(self) -> None:
 
1991         base = Path(DATA_DIR / "include_exclude_tests")
 
1993             base / "b/.definitely_exclude/a.py",
 
1994             base / "b/.definitely_exclude/a.pyi",
 
1998         ctx.obj["root"] = base
 
1999         assert_collected_sources(src, expected, ctx=ctx, extend_exclude=r"/exclude/")
 
2001     @patch("black.find_project_root", lambda *args: (THIS_DIR.resolve(), None))
 
2002     def test_exclude_for_issue_1572(self) -> None:
 
2003         # Exclude shouldn't touch files that were explicitly given to Black through the
 
2004         # CLI. Exclude is supposed to only apply to the recursive discovery of files.
 
2005         # https://github.com/psf/black/issues/1572
 
2006         path = DATA_DIR / "include_exclude_tests"
 
2007         src = [path / "b/exclude/a.py"]
 
2008         expected = [path / "b/exclude/a.py"]
 
2009         assert_collected_sources(src, expected, include="", exclude=r"/exclude/|a\.py")
 
2011     def test_gitignore_exclude(self) -> None:
 
2012         path = THIS_DIR / "data" / "include_exclude_tests"
 
2013         include = re.compile(r"\.pyi?$")
 
2014         exclude = re.compile(r"")
 
2015         report = black.Report()
 
2016         gitignore = PathSpec.from_lines(
 
2017             "gitwildmatch", ["exclude/", ".definitely_exclude"]
 
2019         sources: List[Path] = []
 
2021             Path(path / "b/dont_exclude/a.py"),
 
2022             Path(path / "b/dont_exclude/a.pyi"),
 
2024         this_abs = THIS_DIR.resolve()
 
2026             black.gen_python_files(
 
2039         assert sorted(expected) == sorted(sources)
 
2041     def test_nested_gitignore(self) -> None:
 
2042         path = Path(THIS_DIR / "data" / "nested_gitignore_tests")
 
2043         include = re.compile(r"\.pyi?$")
 
2044         exclude = re.compile(r"")
 
2045         root_gitignore = black.files.get_gitignore(path)
 
2046         report = black.Report()
 
2047         expected: List[Path] = [
 
2048             Path(path / "x.py"),
 
2049             Path(path / "root/b.py"),
 
2050             Path(path / "root/c.py"),
 
2051             Path(path / "root/child/c.py"),
 
2053         this_abs = THIS_DIR.resolve()
 
2055             black.gen_python_files(
 
2068         assert sorted(expected) == sorted(sources)
 
2070     def test_nested_gitignore_directly_in_source_directory(self) -> None:
 
2071         # https://github.com/psf/black/issues/2598
 
2072         path = Path(DATA_DIR / "nested_gitignore_tests")
 
2073         src = Path(path / "root" / "child")
 
2074         expected = [src / "a.py", src / "c.py"]
 
2075         assert_collected_sources([src], expected)
 
2077     def test_invalid_gitignore(self) -> None:
 
2078         path = THIS_DIR / "data" / "invalid_gitignore_tests"
 
2079         empty_config = path / "pyproject.toml"
 
2080         result = BlackRunner().invoke(
 
2081             black.main, ["--verbose", "--config", str(empty_config), str(path)]
 
2083         assert result.exit_code == 1
 
2084         assert result.stderr_bytes is not None
 
2086         gitignore = path / ".gitignore"
 
2087         assert f"Could not parse {gitignore}" in result.stderr_bytes.decode()
 
2089     def test_invalid_nested_gitignore(self) -> None:
 
2090         path = THIS_DIR / "data" / "invalid_nested_gitignore_tests"
 
2091         empty_config = path / "pyproject.toml"
 
2092         result = BlackRunner().invoke(
 
2093             black.main, ["--verbose", "--config", str(empty_config), str(path)]
 
2095         assert result.exit_code == 1
 
2096         assert result.stderr_bytes is not None
 
2098         gitignore = path / "a" / ".gitignore"
 
2099         assert f"Could not parse {gitignore}" in result.stderr_bytes.decode()
 
2101     def test_empty_include(self) -> None:
 
2102         path = DATA_DIR / "include_exclude_tests"
 
2105             Path(path / "b/exclude/a.pie"),
 
2106             Path(path / "b/exclude/a.py"),
 
2107             Path(path / "b/exclude/a.pyi"),
 
2108             Path(path / "b/dont_exclude/a.pie"),
 
2109             Path(path / "b/dont_exclude/a.py"),
 
2110             Path(path / "b/dont_exclude/a.pyi"),
 
2111             Path(path / "b/.definitely_exclude/a.pie"),
 
2112             Path(path / "b/.definitely_exclude/a.py"),
 
2113             Path(path / "b/.definitely_exclude/a.pyi"),
 
2114             Path(path / ".gitignore"),
 
2115             Path(path / "pyproject.toml"),
 
2117         # Setting exclude explicitly to an empty string to block .gitignore usage.
 
2118         assert_collected_sources(src, expected, include="", exclude="")
 
2120     def test_extend_exclude(self) -> None:
 
2121         path = DATA_DIR / "include_exclude_tests"
 
2124             Path(path / "b/exclude/a.py"),
 
2125             Path(path / "b/dont_exclude/a.py"),
 
2127         assert_collected_sources(
 
2128             src, expected, exclude=r"\.pyi$", extend_exclude=r"\.definitely_exclude"
 
2131     @pytest.mark.incompatible_with_mypyc
 
2132     def test_symlink_out_of_root_directory(self) -> None:
 
2134         root = THIS_DIR.resolve()
 
2136         include = re.compile(black.DEFAULT_INCLUDES)
 
2137         exclude = re.compile(black.DEFAULT_EXCLUDES)
 
2138         report = black.Report()
 
2139         gitignore = PathSpec.from_lines("gitwildmatch", [])
 
2140         # `child` should behave like a symlink which resolved path is clearly
 
2141         # outside of the `root` directory.
 
2142         path.iterdir.return_value = [child]
 
2143         child.resolve.return_value = Path("/a/b/c")
 
2144         child.as_posix.return_value = "/a/b/c"
 
2147                 black.gen_python_files(
 
2160         except ValueError as ve:
 
2161             pytest.fail(f"`get_python_files_in_dir()` failed: {ve}")
 
2162         path.iterdir.assert_called_once()
 
2163         child.resolve.assert_called_once()
 
2165     @patch("black.find_project_root", lambda *args: (THIS_DIR.resolve(), None))
 
2166     def test_get_sources_with_stdin(self) -> None:
 
2169         assert_collected_sources(src, expected, include="", exclude=r"/exclude/|a\.py")
 
2171     @patch("black.find_project_root", lambda *args: (THIS_DIR.resolve(), None))
 
2172     def test_get_sources_with_stdin_filename(self) -> None:
 
2174         stdin_filename = str(THIS_DIR / "data/collections.py")
 
2175         expected = [f"__BLACK_STDIN_FILENAME__{stdin_filename}"]
 
2176         assert_collected_sources(
 
2179             exclude=r"/exclude/a\.py",
 
2180             stdin_filename=stdin_filename,
 
2183     @patch("black.find_project_root", lambda *args: (THIS_DIR.resolve(), None))
 
2184     def test_get_sources_with_stdin_filename_and_exclude(self) -> None:
 
2185         # Exclude shouldn't exclude stdin_filename since it is mimicking the
 
2186         # file being passed directly. This is the same as
 
2187         # test_exclude_for_issue_1572
 
2188         path = DATA_DIR / "include_exclude_tests"
 
2190         stdin_filename = str(path / "b/exclude/a.py")
 
2191         expected = [f"__BLACK_STDIN_FILENAME__{stdin_filename}"]
 
2192         assert_collected_sources(
 
2195             exclude=r"/exclude/|a\.py",
 
2196             stdin_filename=stdin_filename,
 
2199     @patch("black.find_project_root", lambda *args: (THIS_DIR.resolve(), None))
 
2200     def test_get_sources_with_stdin_filename_and_extend_exclude(self) -> None:
 
2201         # Extend exclude shouldn't exclude stdin_filename since it is mimicking the
 
2202         # file being passed directly. This is the same as
 
2203         # test_exclude_for_issue_1572
 
2205         path = THIS_DIR / "data" / "include_exclude_tests"
 
2206         stdin_filename = str(path / "b/exclude/a.py")
 
2207         expected = [f"__BLACK_STDIN_FILENAME__{stdin_filename}"]
 
2208         assert_collected_sources(
 
2211             extend_exclude=r"/exclude/|a\.py",
 
2212             stdin_filename=stdin_filename,
 
2215     @patch("black.find_project_root", lambda *args: (THIS_DIR.resolve(), None))
 
2216     def test_get_sources_with_stdin_filename_and_force_exclude(self) -> None:
 
2217         # Force exclude should exclude the file when passing it through
 
2219         path = THIS_DIR / "data" / "include_exclude_tests"
 
2220         stdin_filename = str(path / "b/exclude/a.py")
 
2221         assert_collected_sources(
 
2224             force_exclude=r"/exclude/|a\.py",
 
2225             stdin_filename=stdin_filename,
 
2230     with open(black.__file__, "r", encoding="utf-8") as _bf:
 
2231         black_source_lines = _bf.readlines()
 
2232 except UnicodeDecodeError:
 
2233     if not black.COMPILED:
 
2238     frame: types.FrameType, event: str, arg: Any
 
2239 ) -> Callable[[types.FrameType, str, Any], Any]:
 
2240     """Show function calls `from black/__init__.py` as they happen.
 
2242     Register this with `sys.settrace()` in a test you're debugging.
 
2247     stack = len(inspect.stack()) - 19
 
2249     filename = frame.f_code.co_filename
 
2250     lineno = frame.f_lineno
 
2251     func_sig_lineno = lineno - 1
 
2252     funcname = black_source_lines[func_sig_lineno].strip()
 
2253     while funcname.startswith("@"):
 
2254         func_sig_lineno += 1
 
2255         funcname = black_source_lines[func_sig_lineno].strip()
 
2256     if "black/__init__.py" in filename:
 
2257         print(f"{' ' * stack}{lineno}:{funcname}")