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.
   5 from concurrent.futures import ThreadPoolExecutor
 
   6 from contextlib import contextmanager
 
   7 from dataclasses import replace
 
  10 from io import BytesIO
 
  12 from pathlib import Path
 
  13 from platform import system
 
  16 from tempfile import TemporaryDirectory
 
  28 from unittest.mock import patch, MagicMock
 
  31 from click import unstyle
 
  32 from click.testing import CliRunner
 
  35 from black import Feature, TargetVersion
 
  36 from black.cache import get_cache_file
 
  37 from black.debug import DebugVisitor
 
  38 from black.output import diff, color_diff
 
  39 from black.report import Report
 
  42 from pathspec import PathSpec
 
  44 # Import other test classes
 
  45 from tests.util import (
 
  57 THIS_FILE = Path(__file__)
 
  64 PY36_ARGS = [f"--target-version={version.name.lower()}" for version in PY36_VERSIONS]
 
  68 # Match the time output in a diff, but nothing else
 
  69 DIFF_TIME = re.compile(r"\t[\d-:+\. ]+")
 
  73 def cache_dir(exists: bool = True) -> Iterator[Path]:
 
  74     with TemporaryDirectory() as workspace:
 
  75         cache_dir = Path(workspace)
 
  77             cache_dir = cache_dir / "new"
 
  78         with patch("black.cache.CACHE_DIR", cache_dir):
 
  83 def event_loop() -> Iterator[None]:
 
  84     policy = asyncio.get_event_loop_policy()
 
  85     loop = policy.new_event_loop()
 
  86     asyncio.set_event_loop(loop)
 
  94 class FakeContext(click.Context):
 
  95     """A fake click Context for when calling functions that need it."""
 
  97     def __init__(self) -> None:
 
  98         self.default_map: Dict[str, Any] = {}
 
 101 class FakeParameter(click.Parameter):
 
 102     """A fake click Parameter for when calling functions that need it."""
 
 104     def __init__(self) -> None:
 
 108 class BlackRunner(CliRunner):
 
 109     """Make sure STDOUT and STDERR are kept separate when testing Black via its CLI."""
 
 111     def __init__(self) -> None:
 
 112         super().__init__(mix_stderr=False)
 
 115 class BlackTestCase(BlackBaseTestCase):
 
 117         self, args: List[str], exit_code: int = 0, ignore_config: bool = True
 
 119         runner = BlackRunner()
 
 121             args = ["--verbose", "--config", str(THIS_DIR / "empty.toml"), *args]
 
 122         result = runner.invoke(black.main, args)
 
 123         assert result.stdout_bytes is not None
 
 124         assert result.stderr_bytes is not None
 
 129                 f"Failed with args: {args}\n"
 
 130                 f"stdout: {result.stdout_bytes.decode()!r}\n"
 
 131                 f"stderr: {result.stderr_bytes.decode()!r}\n"
 
 132                 f"exception: {result.exception}"
 
 136     @patch("black.dump_to_file", dump_to_stderr)
 
 137     def test_empty(self) -> None:
 
 138         source = expected = ""
 
 140         self.assertFormatEqual(expected, actual)
 
 141         black.assert_equivalent(source, actual)
 
 142         black.assert_stable(source, actual, DEFAULT_MODE)
 
 144     def test_empty_ff(self) -> None:
 
 146         tmp_file = Path(black.dump_to_file())
 
 148             self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES))
 
 149             with open(tmp_file, encoding="utf8") as f:
 
 153         self.assertFormatEqual(expected, actual)
 
 155     def test_piping(self) -> None:
 
 156         source, expected = read_data("src/black/__init__", data=False)
 
 157         result = BlackRunner().invoke(
 
 159             ["-", "--fast", f"--line-length={black.DEFAULT_LINE_LENGTH}"],
 
 160             input=BytesIO(source.encode("utf8")),
 
 162         self.assertEqual(result.exit_code, 0)
 
 163         self.assertFormatEqual(expected, result.output)
 
 164         if source != result.output:
 
 165             black.assert_equivalent(source, result.output)
 
 166             black.assert_stable(source, result.output, DEFAULT_MODE)
 
 168     def test_piping_diff(self) -> None:
 
 169         diff_header = re.compile(
 
 170             r"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d\d\d\d "
 
 173         source, _ = read_data("expression.py")
 
 174         expected, _ = read_data("expression.diff")
 
 175         config = THIS_DIR / "data" / "empty_pyproject.toml"
 
 179             f"--line-length={black.DEFAULT_LINE_LENGTH}",
 
 181             f"--config={config}",
 
 183         result = BlackRunner().invoke(
 
 184             black.main, args, input=BytesIO(source.encode("utf8"))
 
 186         self.assertEqual(result.exit_code, 0)
 
 187         actual = diff_header.sub(DETERMINISTIC_HEADER, result.output)
 
 188         actual = actual.rstrip() + "\n"  # the diff output has a trailing space
 
 189         self.assertEqual(expected, actual)
 
 191     def test_piping_diff_with_color(self) -> None:
 
 192         source, _ = read_data("expression.py")
 
 193         config = THIS_DIR / "data" / "empty_pyproject.toml"
 
 197             f"--line-length={black.DEFAULT_LINE_LENGTH}",
 
 200             f"--config={config}",
 
 202         result = BlackRunner().invoke(
 
 203             black.main, args, input=BytesIO(source.encode("utf8"))
 
 205         actual = result.output
 
 206         # Again, the contents are checked in a different test, so only look for colors.
 
 207         self.assertIn("\033[1;37m", actual)
 
 208         self.assertIn("\033[36m", actual)
 
 209         self.assertIn("\033[32m", actual)
 
 210         self.assertIn("\033[31m", actual)
 
 211         self.assertIn("\033[0m", actual)
 
 213     @patch("black.dump_to_file", dump_to_stderr)
 
 214     def _test_wip(self) -> None:
 
 215         source, expected = read_data("wip")
 
 216         sys.settrace(tracefunc)
 
 219             experimental_string_processing=False,
 
 220             target_versions={black.TargetVersion.PY38},
 
 222         actual = fs(source, mode=mode)
 
 224         self.assertFormatEqual(expected, actual)
 
 225         black.assert_equivalent(source, actual)
 
 226         black.assert_stable(source, actual, black.FileMode())
 
 228     @unittest.expectedFailure
 
 229     @patch("black.dump_to_file", dump_to_stderr)
 
 230     def test_trailing_comma_optional_parens_stability1(self) -> None:
 
 231         source, _expected = read_data("trailing_comma_optional_parens1")
 
 233         black.assert_stable(source, actual, DEFAULT_MODE)
 
 235     @unittest.expectedFailure
 
 236     @patch("black.dump_to_file", dump_to_stderr)
 
 237     def test_trailing_comma_optional_parens_stability2(self) -> None:
 
 238         source, _expected = read_data("trailing_comma_optional_parens2")
 
 240         black.assert_stable(source, actual, DEFAULT_MODE)
 
 242     @unittest.expectedFailure
 
 243     @patch("black.dump_to_file", dump_to_stderr)
 
 244     def test_trailing_comma_optional_parens_stability3(self) -> None:
 
 245         source, _expected = read_data("trailing_comma_optional_parens3")
 
 247         black.assert_stable(source, actual, DEFAULT_MODE)
 
 249     @patch("black.dump_to_file", dump_to_stderr)
 
 250     def test_trailing_comma_optional_parens_stability1_pass2(self) -> None:
 
 251         source, _expected = read_data("trailing_comma_optional_parens1")
 
 252         actual = fs(fs(source))  # this is what `format_file_contents` does with --safe
 
 253         black.assert_stable(source, actual, DEFAULT_MODE)
 
 255     @patch("black.dump_to_file", dump_to_stderr)
 
 256     def test_trailing_comma_optional_parens_stability2_pass2(self) -> None:
 
 257         source, _expected = read_data("trailing_comma_optional_parens2")
 
 258         actual = fs(fs(source))  # this is what `format_file_contents` does with --safe
 
 259         black.assert_stable(source, actual, DEFAULT_MODE)
 
 261     @patch("black.dump_to_file", dump_to_stderr)
 
 262     def test_trailing_comma_optional_parens_stability3_pass2(self) -> None:
 
 263         source, _expected = read_data("trailing_comma_optional_parens3")
 
 264         actual = fs(fs(source))  # this is what `format_file_contents` does with --safe
 
 265         black.assert_stable(source, actual, DEFAULT_MODE)
 
 267     @patch("black.dump_to_file", dump_to_stderr)
 
 268     def test_pep_572(self) -> None:
 
 269         source, expected = read_data("pep_572")
 
 271         self.assertFormatEqual(expected, actual)
 
 272         black.assert_stable(source, actual, DEFAULT_MODE)
 
 273         if sys.version_info >= (3, 8):
 
 274             black.assert_equivalent(source, actual)
 
 276     @patch("black.dump_to_file", dump_to_stderr)
 
 277     def test_pep_572_remove_parens(self) -> None:
 
 278         source, expected = read_data("pep_572_remove_parens")
 
 280         self.assertFormatEqual(expected, actual)
 
 281         black.assert_stable(source, actual, DEFAULT_MODE)
 
 282         if sys.version_info >= (3, 8):
 
 283             black.assert_equivalent(source, actual)
 
 285     @patch("black.dump_to_file", dump_to_stderr)
 
 286     def test_pep_572_do_not_remove_parens(self) -> None:
 
 287         source, expected = read_data("pep_572_do_not_remove_parens")
 
 288         # the AST safety checks will fail, but that's expected, just make sure no
 
 289         # parentheses are touched
 
 290         actual = black.format_str(source, mode=DEFAULT_MODE)
 
 291         self.assertFormatEqual(expected, actual)
 
 293     def test_pep_572_version_detection(self) -> None:
 
 294         source, _ = read_data("pep_572")
 
 295         root = black.lib2to3_parse(source)
 
 296         features = black.get_features_used(root)
 
 297         self.assertIn(black.Feature.ASSIGNMENT_EXPRESSIONS, features)
 
 298         versions = black.detect_target_versions(root)
 
 299         self.assertIn(black.TargetVersion.PY38, versions)
 
 301     def test_expression_ff(self) -> None:
 
 302         source, expected = read_data("expression")
 
 303         tmp_file = Path(black.dump_to_file(source))
 
 305             self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
 
 306             with open(tmp_file, encoding="utf8") as f:
 
 310         self.assertFormatEqual(expected, actual)
 
 311         with patch("black.dump_to_file", dump_to_stderr):
 
 312             black.assert_equivalent(source, actual)
 
 313             black.assert_stable(source, actual, DEFAULT_MODE)
 
 315     def test_expression_diff(self) -> None:
 
 316         source, _ = read_data("expression.py")
 
 317         config = THIS_DIR / "data" / "empty_pyproject.toml"
 
 318         expected, _ = read_data("expression.diff")
 
 319         tmp_file = Path(black.dump_to_file(source))
 
 320         diff_header = re.compile(
 
 321             rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
 
 322             r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
 
 325             result = BlackRunner().invoke(
 
 326                 black.main, ["--diff", str(tmp_file), f"--config={config}"]
 
 328             self.assertEqual(result.exit_code, 0)
 
 331         actual = result.output
 
 332         actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
 
 333         if expected != actual:
 
 334             dump = black.dump_to_file(actual)
 
 336                 "Expected diff isn't equal to the actual. If you made changes to"
 
 337                 " expression.py and this is an anticipated difference, overwrite"
 
 338                 f" tests/data/expression.diff with {dump}"
 
 340             self.assertEqual(expected, actual, msg)
 
 342     def test_expression_diff_with_color(self) -> None:
 
 343         source, _ = read_data("expression.py")
 
 344         config = THIS_DIR / "data" / "empty_pyproject.toml"
 
 345         expected, _ = read_data("expression.diff")
 
 346         tmp_file = Path(black.dump_to_file(source))
 
 348             result = BlackRunner().invoke(
 
 349                 black.main, ["--diff", "--color", str(tmp_file), f"--config={config}"]
 
 353         actual = result.output
 
 354         # We check the contents of the diff in `test_expression_diff`. All
 
 355         # we need to check here is that color codes exist in the result.
 
 356         self.assertIn("\033[1;37m", actual)
 
 357         self.assertIn("\033[36m", actual)
 
 358         self.assertIn("\033[32m", actual)
 
 359         self.assertIn("\033[31m", actual)
 
 360         self.assertIn("\033[0m", actual)
 
 362     @patch("black.dump_to_file", dump_to_stderr)
 
 363     def test_pep_570(self) -> None:
 
 364         source, expected = read_data("pep_570")
 
 366         self.assertFormatEqual(expected, actual)
 
 367         black.assert_stable(source, actual, DEFAULT_MODE)
 
 368         if sys.version_info >= (3, 8):
 
 369             black.assert_equivalent(source, actual)
 
 371     def test_detect_pos_only_arguments(self) -> None:
 
 372         source, _ = read_data("pep_570")
 
 373         root = black.lib2to3_parse(source)
 
 374         features = black.get_features_used(root)
 
 375         self.assertIn(black.Feature.POS_ONLY_ARGUMENTS, features)
 
 376         versions = black.detect_target_versions(root)
 
 377         self.assertIn(black.TargetVersion.PY38, versions)
 
 379     @patch("black.dump_to_file", dump_to_stderr)
 
 380     def test_string_quotes(self) -> None:
 
 381         source, expected = read_data("string_quotes")
 
 382         mode = black.Mode(experimental_string_processing=True)
 
 383         actual = fs(source, mode=mode)
 
 384         self.assertFormatEqual(expected, actual)
 
 385         black.assert_equivalent(source, actual)
 
 386         black.assert_stable(source, actual, mode)
 
 387         mode = replace(mode, string_normalization=False)
 
 388         not_normalized = fs(source, mode=mode)
 
 389         self.assertFormatEqual(source.replace("\\\n", ""), not_normalized)
 
 390         black.assert_equivalent(source, not_normalized)
 
 391         black.assert_stable(source, not_normalized, mode=mode)
 
 393     @patch("black.dump_to_file", dump_to_stderr)
 
 394     def test_docstring_no_string_normalization(self) -> None:
 
 395         """Like test_docstring but with string normalization off."""
 
 396         source, expected = read_data("docstring_no_string_normalization")
 
 397         mode = replace(DEFAULT_MODE, string_normalization=False)
 
 398         actual = fs(source, mode=mode)
 
 399         self.assertFormatEqual(expected, actual)
 
 400         black.assert_equivalent(source, actual)
 
 401         black.assert_stable(source, actual, mode)
 
 403     def test_long_strings_flag_disabled(self) -> None:
 
 404         """Tests for turning off the string processing logic."""
 
 405         source, expected = read_data("long_strings_flag_disabled")
 
 406         mode = replace(DEFAULT_MODE, experimental_string_processing=False)
 
 407         actual = fs(source, mode=mode)
 
 408         self.assertFormatEqual(expected, actual)
 
 409         black.assert_stable(expected, actual, mode)
 
 411     @patch("black.dump_to_file", dump_to_stderr)
 
 412     def test_numeric_literals(self) -> None:
 
 413         source, expected = read_data("numeric_literals")
 
 414         mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
 
 415         actual = fs(source, mode=mode)
 
 416         self.assertFormatEqual(expected, actual)
 
 417         black.assert_equivalent(source, actual)
 
 418         black.assert_stable(source, actual, mode)
 
 420     @patch("black.dump_to_file", dump_to_stderr)
 
 421     def test_numeric_literals_ignoring_underscores(self) -> None:
 
 422         source, expected = read_data("numeric_literals_skip_underscores")
 
 423         mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
 
 424         actual = fs(source, mode=mode)
 
 425         self.assertFormatEqual(expected, actual)
 
 426         black.assert_equivalent(source, actual)
 
 427         black.assert_stable(source, actual, mode)
 
 429     def test_skip_magic_trailing_comma(self) -> None:
 
 430         source, _ = read_data("expression.py")
 
 431         expected, _ = read_data("expression_skip_magic_trailing_comma.diff")
 
 432         tmp_file = Path(black.dump_to_file(source))
 
 433         diff_header = re.compile(
 
 434             rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
 
 435             r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
 
 438             result = BlackRunner().invoke(black.main, ["-C", "--diff", str(tmp_file)])
 
 439             self.assertEqual(result.exit_code, 0)
 
 442         actual = result.output
 
 443         actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
 
 444         actual = actual.rstrip() + "\n"  # the diff output has a trailing space
 
 445         if expected != actual:
 
 446             dump = black.dump_to_file(actual)
 
 448                 "Expected diff isn't equal to the actual. If you made changes to"
 
 449                 " expression.py and this is an anticipated difference, overwrite"
 
 450                 f" tests/data/expression_skip_magic_trailing_comma.diff with {dump}"
 
 452             self.assertEqual(expected, actual, msg)
 
 455     @patch("black.dump_to_file", dump_to_stderr)
 
 456     def test_python2_print_function(self) -> None:
 
 457         source, expected = read_data("python2_print_function")
 
 458         mode = replace(DEFAULT_MODE, target_versions={TargetVersion.PY27})
 
 459         actual = fs(source, mode=mode)
 
 460         self.assertFormatEqual(expected, actual)
 
 461         black.assert_equivalent(source, actual)
 
 462         black.assert_stable(source, actual, mode)
 
 464     @patch("black.dump_to_file", dump_to_stderr)
 
 465     def test_stub(self) -> None:
 
 466         mode = replace(DEFAULT_MODE, is_pyi=True)
 
 467         source, expected = read_data("stub.pyi")
 
 468         actual = fs(source, mode=mode)
 
 469         self.assertFormatEqual(expected, actual)
 
 470         black.assert_stable(source, actual, mode)
 
 472     @patch("black.dump_to_file", dump_to_stderr)
 
 473     def test_async_as_identifier(self) -> None:
 
 474         source_path = (THIS_DIR / "data" / "async_as_identifier.py").resolve()
 
 475         source, expected = read_data("async_as_identifier")
 
 477         self.assertFormatEqual(expected, actual)
 
 478         major, minor = sys.version_info[:2]
 
 479         if major < 3 or (major <= 3 and minor < 7):
 
 480             black.assert_equivalent(source, actual)
 
 481         black.assert_stable(source, actual, DEFAULT_MODE)
 
 482         # ensure black can parse this when the target is 3.6
 
 483         self.invokeBlack([str(source_path), "--target-version", "py36"])
 
 484         # but not on 3.7, because async/await is no longer an identifier
 
 485         self.invokeBlack([str(source_path), "--target-version", "py37"], exit_code=123)
 
 487     @patch("black.dump_to_file", dump_to_stderr)
 
 488     def test_python37(self) -> None:
 
 489         source_path = (THIS_DIR / "data" / "python37.py").resolve()
 
 490         source, expected = read_data("python37")
 
 492         self.assertFormatEqual(expected, actual)
 
 493         major, minor = sys.version_info[:2]
 
 494         if major > 3 or (major == 3 and minor >= 7):
 
 495             black.assert_equivalent(source, actual)
 
 496         black.assert_stable(source, actual, DEFAULT_MODE)
 
 497         # ensure black can parse this when the target is 3.7
 
 498         self.invokeBlack([str(source_path), "--target-version", "py37"])
 
 499         # but not on 3.6, because we use async as a reserved keyword
 
 500         self.invokeBlack([str(source_path), "--target-version", "py36"], exit_code=123)
 
 502     @patch("black.dump_to_file", dump_to_stderr)
 
 503     def test_python38(self) -> None:
 
 504         source, expected = read_data("python38")
 
 506         self.assertFormatEqual(expected, actual)
 
 507         major, minor = sys.version_info[:2]
 
 508         if major > 3 or (major == 3 and minor >= 8):
 
 509             black.assert_equivalent(source, actual)
 
 510         black.assert_stable(source, actual, DEFAULT_MODE)
 
 512     @patch("black.dump_to_file", dump_to_stderr)
 
 513     def test_python39(self) -> None:
 
 514         source, expected = read_data("python39")
 
 516         self.assertFormatEqual(expected, actual)
 
 517         major, minor = sys.version_info[:2]
 
 518         if major > 3 or (major == 3 and minor >= 9):
 
 519             black.assert_equivalent(source, actual)
 
 520         black.assert_stable(source, actual, DEFAULT_MODE)
 
 522     def test_tab_comment_indentation(self) -> None:
 
 523         contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
 
 524         contents_spc = "if 1:\n    if 2:\n        pass\n    # comment\n    pass\n"
 
 525         self.assertFormatEqual(contents_spc, fs(contents_spc))
 
 526         self.assertFormatEqual(contents_spc, fs(contents_tab))
 
 528         contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
 
 529         contents_spc = "if 1:\n    if 2:\n        pass\n        # comment\n    pass\n"
 
 530         self.assertFormatEqual(contents_spc, fs(contents_spc))
 
 531         self.assertFormatEqual(contents_spc, fs(contents_tab))
 
 533         # mixed tabs and spaces (valid Python 2 code)
 
 534         contents_tab = "if 1:\n        if 2:\n\t\tpass\n\t# comment\n        pass\n"
 
 535         contents_spc = "if 1:\n    if 2:\n        pass\n    # comment\n    pass\n"
 
 536         self.assertFormatEqual(contents_spc, fs(contents_spc))
 
 537         self.assertFormatEqual(contents_spc, fs(contents_tab))
 
 539         contents_tab = "if 1:\n        if 2:\n\t\tpass\n\t\t# comment\n        pass\n"
 
 540         contents_spc = "if 1:\n    if 2:\n        pass\n        # comment\n    pass\n"
 
 541         self.assertFormatEqual(contents_spc, fs(contents_spc))
 
 542         self.assertFormatEqual(contents_spc, fs(contents_tab))
 
 544     def test_report_verbose(self) -> None:
 
 545         report = Report(verbose=True)
 
 549         def out(msg: str, **kwargs: Any) -> None:
 
 550             out_lines.append(msg)
 
 552         def err(msg: str, **kwargs: Any) -> None:
 
 553             err_lines.append(msg)
 
 555         with patch("black.output._out", out), patch("black.output._err", err):
 
 556             report.done(Path("f1"), black.Changed.NO)
 
 557             self.assertEqual(len(out_lines), 1)
 
 558             self.assertEqual(len(err_lines), 0)
 
 559             self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
 
 560             self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
 
 561             self.assertEqual(report.return_code, 0)
 
 562             report.done(Path("f2"), black.Changed.YES)
 
 563             self.assertEqual(len(out_lines), 2)
 
 564             self.assertEqual(len(err_lines), 0)
 
 565             self.assertEqual(out_lines[-1], "reformatted f2")
 
 567                 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
 
 569             report.done(Path("f3"), black.Changed.CACHED)
 
 570             self.assertEqual(len(out_lines), 3)
 
 571             self.assertEqual(len(err_lines), 0)
 
 573                 out_lines[-1], "f3 wasn't modified on disk since last run."
 
 576                 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
 
 578             self.assertEqual(report.return_code, 0)
 
 580             self.assertEqual(report.return_code, 1)
 
 582             report.failed(Path("e1"), "boom")
 
 583             self.assertEqual(len(out_lines), 3)
 
 584             self.assertEqual(len(err_lines), 1)
 
 585             self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
 
 587                 unstyle(str(report)),
 
 588                 "1 file reformatted, 2 files left unchanged, 1 file failed to"
 
 591             self.assertEqual(report.return_code, 123)
 
 592             report.done(Path("f3"), black.Changed.YES)
 
 593             self.assertEqual(len(out_lines), 4)
 
 594             self.assertEqual(len(err_lines), 1)
 
 595             self.assertEqual(out_lines[-1], "reformatted f3")
 
 597                 unstyle(str(report)),
 
 598                 "2 files reformatted, 2 files left unchanged, 1 file failed to"
 
 601             self.assertEqual(report.return_code, 123)
 
 602             report.failed(Path("e2"), "boom")
 
 603             self.assertEqual(len(out_lines), 4)
 
 604             self.assertEqual(len(err_lines), 2)
 
 605             self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
 
 607                 unstyle(str(report)),
 
 608                 "2 files reformatted, 2 files left unchanged, 2 files failed to"
 
 611             self.assertEqual(report.return_code, 123)
 
 612             report.path_ignored(Path("wat"), "no match")
 
 613             self.assertEqual(len(out_lines), 5)
 
 614             self.assertEqual(len(err_lines), 2)
 
 615             self.assertEqual(out_lines[-1], "wat ignored: no match")
 
 617                 unstyle(str(report)),
 
 618                 "2 files reformatted, 2 files left unchanged, 2 files failed to"
 
 621             self.assertEqual(report.return_code, 123)
 
 622             report.done(Path("f4"), black.Changed.NO)
 
 623             self.assertEqual(len(out_lines), 6)
 
 624             self.assertEqual(len(err_lines), 2)
 
 625             self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
 
 627                 unstyle(str(report)),
 
 628                 "2 files reformatted, 3 files left unchanged, 2 files failed to"
 
 631             self.assertEqual(report.return_code, 123)
 
 634                 unstyle(str(report)),
 
 635                 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
 
 636                 " would fail to reformat.",
 
 641                 unstyle(str(report)),
 
 642                 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
 
 643                 " would fail to reformat.",
 
 646     def test_report_quiet(self) -> None:
 
 647         report = Report(quiet=True)
 
 651         def out(msg: str, **kwargs: Any) -> None:
 
 652             out_lines.append(msg)
 
 654         def err(msg: str, **kwargs: Any) -> None:
 
 655             err_lines.append(msg)
 
 657         with patch("black.output._out", out), patch("black.output._err", err):
 
 658             report.done(Path("f1"), black.Changed.NO)
 
 659             self.assertEqual(len(out_lines), 0)
 
 660             self.assertEqual(len(err_lines), 0)
 
 661             self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
 
 662             self.assertEqual(report.return_code, 0)
 
 663             report.done(Path("f2"), black.Changed.YES)
 
 664             self.assertEqual(len(out_lines), 0)
 
 665             self.assertEqual(len(err_lines), 0)
 
 667                 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
 
 669             report.done(Path("f3"), black.Changed.CACHED)
 
 670             self.assertEqual(len(out_lines), 0)
 
 671             self.assertEqual(len(err_lines), 0)
 
 673                 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
 
 675             self.assertEqual(report.return_code, 0)
 
 677             self.assertEqual(report.return_code, 1)
 
 679             report.failed(Path("e1"), "boom")
 
 680             self.assertEqual(len(out_lines), 0)
 
 681             self.assertEqual(len(err_lines), 1)
 
 682             self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
 
 684                 unstyle(str(report)),
 
 685                 "1 file reformatted, 2 files left unchanged, 1 file failed to"
 
 688             self.assertEqual(report.return_code, 123)
 
 689             report.done(Path("f3"), black.Changed.YES)
 
 690             self.assertEqual(len(out_lines), 0)
 
 691             self.assertEqual(len(err_lines), 1)
 
 693                 unstyle(str(report)),
 
 694                 "2 files reformatted, 2 files left unchanged, 1 file failed to"
 
 697             self.assertEqual(report.return_code, 123)
 
 698             report.failed(Path("e2"), "boom")
 
 699             self.assertEqual(len(out_lines), 0)
 
 700             self.assertEqual(len(err_lines), 2)
 
 701             self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
 
 703                 unstyle(str(report)),
 
 704                 "2 files reformatted, 2 files left unchanged, 2 files failed to"
 
 707             self.assertEqual(report.return_code, 123)
 
 708             report.path_ignored(Path("wat"), "no match")
 
 709             self.assertEqual(len(out_lines), 0)
 
 710             self.assertEqual(len(err_lines), 2)
 
 712                 unstyle(str(report)),
 
 713                 "2 files reformatted, 2 files left unchanged, 2 files failed to"
 
 716             self.assertEqual(report.return_code, 123)
 
 717             report.done(Path("f4"), black.Changed.NO)
 
 718             self.assertEqual(len(out_lines), 0)
 
 719             self.assertEqual(len(err_lines), 2)
 
 721                 unstyle(str(report)),
 
 722                 "2 files reformatted, 3 files left unchanged, 2 files failed to"
 
 725             self.assertEqual(report.return_code, 123)
 
 728                 unstyle(str(report)),
 
 729                 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
 
 730                 " would fail to reformat.",
 
 735                 unstyle(str(report)),
 
 736                 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
 
 737                 " would fail to reformat.",
 
 740     def test_report_normal(self) -> None:
 
 741         report = black.Report()
 
 745         def out(msg: str, **kwargs: Any) -> None:
 
 746             out_lines.append(msg)
 
 748         def err(msg: str, **kwargs: Any) -> None:
 
 749             err_lines.append(msg)
 
 751         with patch("black.output._out", out), patch("black.output._err", err):
 
 752             report.done(Path("f1"), black.Changed.NO)
 
 753             self.assertEqual(len(out_lines), 0)
 
 754             self.assertEqual(len(err_lines), 0)
 
 755             self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
 
 756             self.assertEqual(report.return_code, 0)
 
 757             report.done(Path("f2"), black.Changed.YES)
 
 758             self.assertEqual(len(out_lines), 1)
 
 759             self.assertEqual(len(err_lines), 0)
 
 760             self.assertEqual(out_lines[-1], "reformatted f2")
 
 762                 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
 
 764             report.done(Path("f3"), black.Changed.CACHED)
 
 765             self.assertEqual(len(out_lines), 1)
 
 766             self.assertEqual(len(err_lines), 0)
 
 767             self.assertEqual(out_lines[-1], "reformatted f2")
 
 769                 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
 
 771             self.assertEqual(report.return_code, 0)
 
 773             self.assertEqual(report.return_code, 1)
 
 775             report.failed(Path("e1"), "boom")
 
 776             self.assertEqual(len(out_lines), 1)
 
 777             self.assertEqual(len(err_lines), 1)
 
 778             self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
 
 780                 unstyle(str(report)),
 
 781                 "1 file reformatted, 2 files left unchanged, 1 file failed to"
 
 784             self.assertEqual(report.return_code, 123)
 
 785             report.done(Path("f3"), black.Changed.YES)
 
 786             self.assertEqual(len(out_lines), 2)
 
 787             self.assertEqual(len(err_lines), 1)
 
 788             self.assertEqual(out_lines[-1], "reformatted f3")
 
 790                 unstyle(str(report)),
 
 791                 "2 files reformatted, 2 files left unchanged, 1 file failed to"
 
 794             self.assertEqual(report.return_code, 123)
 
 795             report.failed(Path("e2"), "boom")
 
 796             self.assertEqual(len(out_lines), 2)
 
 797             self.assertEqual(len(err_lines), 2)
 
 798             self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
 
 800                 unstyle(str(report)),
 
 801                 "2 files reformatted, 2 files left unchanged, 2 files failed to"
 
 804             self.assertEqual(report.return_code, 123)
 
 805             report.path_ignored(Path("wat"), "no match")
 
 806             self.assertEqual(len(out_lines), 2)
 
 807             self.assertEqual(len(err_lines), 2)
 
 809                 unstyle(str(report)),
 
 810                 "2 files reformatted, 2 files left unchanged, 2 files failed to"
 
 813             self.assertEqual(report.return_code, 123)
 
 814             report.done(Path("f4"), black.Changed.NO)
 
 815             self.assertEqual(len(out_lines), 2)
 
 816             self.assertEqual(len(err_lines), 2)
 
 818                 unstyle(str(report)),
 
 819                 "2 files reformatted, 3 files left unchanged, 2 files failed to"
 
 822             self.assertEqual(report.return_code, 123)
 
 825                 unstyle(str(report)),
 
 826                 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
 
 827                 " would fail to reformat.",
 
 832                 unstyle(str(report)),
 
 833                 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
 
 834                 " would fail to reformat.",
 
 837     def test_lib2to3_parse(self) -> None:
 
 838         with self.assertRaises(black.InvalidInput):
 
 839             black.lib2to3_parse("invalid syntax")
 
 842         black.lib2to3_parse(straddling)
 
 843         black.lib2to3_parse(straddling, {TargetVersion.PY27})
 
 844         black.lib2to3_parse(straddling, {TargetVersion.PY36})
 
 845         black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
 
 848         black.lib2to3_parse(py2_only)
 
 849         black.lib2to3_parse(py2_only, {TargetVersion.PY27})
 
 850         with self.assertRaises(black.InvalidInput):
 
 851             black.lib2to3_parse(py2_only, {TargetVersion.PY36})
 
 852         with self.assertRaises(black.InvalidInput):
 
 853             black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
 
 855         py3_only = "exec(x, end=y)"
 
 856         black.lib2to3_parse(py3_only)
 
 857         with self.assertRaises(black.InvalidInput):
 
 858             black.lib2to3_parse(py3_only, {TargetVersion.PY27})
 
 859         black.lib2to3_parse(py3_only, {TargetVersion.PY36})
 
 860         black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
 
 862     def test_get_features_used_decorator(self) -> None:
 
 863         # Test the feature detection of new decorator syntax
 
 864         # since this makes some test cases of test_get_features_used()
 
 865         # fails if it fails, this is tested first so that a useful case
 
 867         simples, relaxed = read_data("decorators")
 
 868         # skip explanation comments at the top of the file
 
 869         for simple_test in simples.split("##")[1:]:
 
 870             node = black.lib2to3_parse(simple_test)
 
 871             decorator = str(node.children[0].children[0]).strip()
 
 873                 Feature.RELAXED_DECORATORS,
 
 874                 black.get_features_used(node),
 
 876                     f"decorator '{decorator}' follows python<=3.8 syntax"
 
 877                     "but is detected as 3.9+"
 
 878                     # f"The full node is\n{node!r}"
 
 881         # skip the '# output' comment at the top of the output part
 
 882         for relaxed_test in relaxed.split("##")[1:]:
 
 883             node = black.lib2to3_parse(relaxed_test)
 
 884             decorator = str(node.children[0].children[0]).strip()
 
 886                 Feature.RELAXED_DECORATORS,
 
 887                 black.get_features_used(node),
 
 889                     f"decorator '{decorator}' uses python3.9+ syntax"
 
 890                     "but is detected as python<=3.8"
 
 891                     # f"The full node is\n{node!r}"
 
 895     def test_get_features_used(self) -> None:
 
 896         node = black.lib2to3_parse("def f(*, arg): ...\n")
 
 897         self.assertEqual(black.get_features_used(node), set())
 
 898         node = black.lib2to3_parse("def f(*, arg,): ...\n")
 
 899         self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
 
 900         node = black.lib2to3_parse("f(*arg,)\n")
 
 902             black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
 
 904         node = black.lib2to3_parse("def f(*, arg): f'string'\n")
 
 905         self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
 
 906         node = black.lib2to3_parse("123_456\n")
 
 907         self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
 
 908         node = black.lib2to3_parse("123456\n")
 
 909         self.assertEqual(black.get_features_used(node), set())
 
 910         source, expected = read_data("function")
 
 911         node = black.lib2to3_parse(source)
 
 912         expected_features = {
 
 913             Feature.TRAILING_COMMA_IN_CALL,
 
 914             Feature.TRAILING_COMMA_IN_DEF,
 
 917         self.assertEqual(black.get_features_used(node), expected_features)
 
 918         node = black.lib2to3_parse(expected)
 
 919         self.assertEqual(black.get_features_used(node), expected_features)
 
 920         source, expected = read_data("expression")
 
 921         node = black.lib2to3_parse(source)
 
 922         self.assertEqual(black.get_features_used(node), set())
 
 923         node = black.lib2to3_parse(expected)
 
 924         self.assertEqual(black.get_features_used(node), set())
 
 926     def test_get_future_imports(self) -> None:
 
 927         node = black.lib2to3_parse("\n")
 
 928         self.assertEqual(set(), black.get_future_imports(node))
 
 929         node = black.lib2to3_parse("from __future__ import black\n")
 
 930         self.assertEqual({"black"}, black.get_future_imports(node))
 
 931         node = black.lib2to3_parse("from __future__ import multiple, imports\n")
 
 932         self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
 
 933         node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
 
 934         self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
 
 935         node = black.lib2to3_parse(
 
 936             "from __future__ import multiple\nfrom __future__ import imports\n"
 
 938         self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
 
 939         node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
 
 940         self.assertEqual({"black"}, black.get_future_imports(node))
 
 941         node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
 
 942         self.assertEqual({"black"}, black.get_future_imports(node))
 
 943         node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
 
 944         self.assertEqual(set(), black.get_future_imports(node))
 
 945         node = black.lib2to3_parse("from some.module import black\n")
 
 946         self.assertEqual(set(), black.get_future_imports(node))
 
 947         node = black.lib2to3_parse(
 
 948             "from __future__ import unicode_literals as _unicode_literals"
 
 950         self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
 
 951         node = black.lib2to3_parse(
 
 952             "from __future__ import unicode_literals as _lol, print"
 
 954         self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
 
 956     def test_debug_visitor(self) -> None:
 
 957         source, _ = read_data("debug_visitor.py")
 
 958         expected, _ = read_data("debug_visitor.out")
 
 962         def out(msg: str, **kwargs: Any) -> None:
 
 963             out_lines.append(msg)
 
 965         def err(msg: str, **kwargs: Any) -> None:
 
 966             err_lines.append(msg)
 
 968         with patch("black.debug.out", out):
 
 969             DebugVisitor.show(source)
 
 970         actual = "\n".join(out_lines) + "\n"
 
 972         if expected != actual:
 
 973             log_name = black.dump_to_file(*out_lines)
 
 977             f"AST print out is different. Actual version dumped to {log_name}",
 
 980     def test_format_file_contents(self) -> None:
 
 983         with self.assertRaises(black.NothingChanged):
 
 984             black.format_file_contents(empty, mode=mode, fast=False)
 
 986         with self.assertRaises(black.NothingChanged):
 
 987             black.format_file_contents(just_nl, mode=mode, fast=False)
 
 988         same = "j = [1, 2, 3]\n"
 
 989         with self.assertRaises(black.NothingChanged):
 
 990             black.format_file_contents(same, mode=mode, fast=False)
 
 991         different = "j = [1,2,3]"
 
 993         actual = black.format_file_contents(different, mode=mode, fast=False)
 
 994         self.assertEqual(expected, actual)
 
 995         invalid = "return if you can"
 
 996         with self.assertRaises(black.InvalidInput) as e:
 
 997             black.format_file_contents(invalid, mode=mode, fast=False)
 
 998         self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
 
1000     def test_endmarker(self) -> None:
 
1001         n = black.lib2to3_parse("\n")
 
1002         self.assertEqual(n.type, black.syms.file_input)
 
1003         self.assertEqual(len(n.children), 1)
 
1004         self.assertEqual(n.children[0].type, black.token.ENDMARKER)
 
1006     @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
 
1007     def test_assertFormatEqual(self) -> None:
 
1011         def out(msg: str, **kwargs: Any) -> None:
 
1012             out_lines.append(msg)
 
1014         def err(msg: str, **kwargs: Any) -> None:
 
1015             err_lines.append(msg)
 
1017         with patch("black.output._out", out), patch("black.output._err", err):
 
1018             with self.assertRaises(AssertionError):
 
1019                 self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")
 
1021         out_str = "".join(out_lines)
 
1022         self.assertTrue("Expected tree:" in out_str)
 
1023         self.assertTrue("Actual tree:" in out_str)
 
1024         self.assertEqual("".join(err_lines), "")
 
1026     def test_cache_broken_file(self) -> None:
 
1028         with cache_dir() as workspace:
 
1029             cache_file = get_cache_file(mode)
 
1030             with cache_file.open("w") as fobj:
 
1031                 fobj.write("this is not a pickle")
 
1032             self.assertEqual(black.read_cache(mode), {})
 
1033             src = (workspace / "test.py").resolve()
 
1034             with src.open("w") as fobj:
 
1035                 fobj.write("print('hello')")
 
1036             self.invokeBlack([str(src)])
 
1037             cache = black.read_cache(mode)
 
1038             self.assertIn(str(src), cache)
 
1040     def test_cache_single_file_already_cached(self) -> None:
 
1042         with cache_dir() as workspace:
 
1043             src = (workspace / "test.py").resolve()
 
1044             with src.open("w") as fobj:
 
1045                 fobj.write("print('hello')")
 
1046             black.write_cache({}, [src], mode)
 
1047             self.invokeBlack([str(src)])
 
1048             with src.open("r") as fobj:
 
1049                 self.assertEqual(fobj.read(), "print('hello')")
 
1052     def test_cache_multiple_files(self) -> None:
 
1054         with cache_dir() as workspace, patch(
 
1055             "black.ProcessPoolExecutor", new=ThreadPoolExecutor
 
1057             one = (workspace / "one.py").resolve()
 
1058             with one.open("w") as fobj:
 
1059                 fobj.write("print('hello')")
 
1060             two = (workspace / "two.py").resolve()
 
1061             with two.open("w") as fobj:
 
1062                 fobj.write("print('hello')")
 
1063             black.write_cache({}, [one], mode)
 
1064             self.invokeBlack([str(workspace)])
 
1065             with one.open("r") as fobj:
 
1066                 self.assertEqual(fobj.read(), "print('hello')")
 
1067             with two.open("r") as fobj:
 
1068                 self.assertEqual(fobj.read(), 'print("hello")\n')
 
1069             cache = black.read_cache(mode)
 
1070             self.assertIn(str(one), cache)
 
1071             self.assertIn(str(two), cache)
 
1073     def test_no_cache_when_writeback_diff(self) -> None:
 
1075         with cache_dir() as workspace:
 
1076             src = (workspace / "test.py").resolve()
 
1077             with src.open("w") as fobj:
 
1078                 fobj.write("print('hello')")
 
1079             with patch("black.read_cache") as read_cache, patch(
 
1082                 self.invokeBlack([str(src), "--diff"])
 
1083                 cache_file = get_cache_file(mode)
 
1084                 self.assertFalse(cache_file.exists())
 
1085                 write_cache.assert_not_called()
 
1086                 read_cache.assert_not_called()
 
1088     def test_no_cache_when_writeback_color_diff(self) -> None:
 
1090         with cache_dir() as workspace:
 
1091             src = (workspace / "test.py").resolve()
 
1092             with src.open("w") as fobj:
 
1093                 fobj.write("print('hello')")
 
1094             with patch("black.read_cache") as read_cache, patch(
 
1097                 self.invokeBlack([str(src), "--diff", "--color"])
 
1098                 cache_file = get_cache_file(mode)
 
1099                 self.assertFalse(cache_file.exists())
 
1100                 write_cache.assert_not_called()
 
1101                 read_cache.assert_not_called()
 
1104     def test_output_locking_when_writeback_diff(self) -> None:
 
1105         with cache_dir() as workspace:
 
1106             for tag in range(0, 4):
 
1107                 src = (workspace / f"test{tag}.py").resolve()
 
1108                 with src.open("w") as fobj:
 
1109                     fobj.write("print('hello')")
 
1110             with patch("black.Manager", wraps=multiprocessing.Manager) as mgr:
 
1111                 self.invokeBlack(["--diff", str(workspace)], exit_code=0)
 
1112                 # this isn't quite doing what we want, but if it _isn't_
 
1113                 # called then we cannot be using the lock it provides
 
1117     def test_output_locking_when_writeback_color_diff(self) -> None:
 
1118         with cache_dir() as workspace:
 
1119             for tag in range(0, 4):
 
1120                 src = (workspace / f"test{tag}.py").resolve()
 
1121                 with src.open("w") as fobj:
 
1122                     fobj.write("print('hello')")
 
1123             with patch("black.Manager", wraps=multiprocessing.Manager) as mgr:
 
1124                 self.invokeBlack(["--diff", "--color", str(workspace)], exit_code=0)
 
1125                 # this isn't quite doing what we want, but if it _isn't_
 
1126                 # called then we cannot be using the lock it provides
 
1129     def test_no_cache_when_stdin(self) -> None:
 
1132             result = CliRunner().invoke(
 
1133                 black.main, ["-"], input=BytesIO(b"print('hello')")
 
1135             self.assertEqual(result.exit_code, 0)
 
1136             cache_file = get_cache_file(mode)
 
1137             self.assertFalse(cache_file.exists())
 
1139     def test_read_cache_no_cachefile(self) -> None:
 
1142             self.assertEqual(black.read_cache(mode), {})
 
1144     def test_write_cache_read_cache(self) -> None:
 
1146         with cache_dir() as workspace:
 
1147             src = (workspace / "test.py").resolve()
 
1149             black.write_cache({}, [src], mode)
 
1150             cache = black.read_cache(mode)
 
1151             self.assertIn(str(src), cache)
 
1152             self.assertEqual(cache[str(src)], black.get_cache_info(src))
 
1154     def test_filter_cached(self) -> None:
 
1155         with TemporaryDirectory() as workspace:
 
1156             path = Path(workspace)
 
1157             uncached = (path / "uncached").resolve()
 
1158             cached = (path / "cached").resolve()
 
1159             cached_but_changed = (path / "changed").resolve()
 
1162             cached_but_changed.touch()
 
1164                 str(cached): black.get_cache_info(cached),
 
1165                 str(cached_but_changed): (0.0, 0),
 
1167             todo, done = black.filter_cached(
 
1168                 cache, {uncached, cached, cached_but_changed}
 
1170             self.assertEqual(todo, {uncached, cached_but_changed})
 
1171             self.assertEqual(done, {cached})
 
1173     def test_write_cache_creates_directory_if_needed(self) -> None:
 
1175         with cache_dir(exists=False) as workspace:
 
1176             self.assertFalse(workspace.exists())
 
1177             black.write_cache({}, [], mode)
 
1178             self.assertTrue(workspace.exists())
 
1181     def test_failed_formatting_does_not_get_cached(self) -> None:
 
1183         with cache_dir() as workspace, patch(
 
1184             "black.ProcessPoolExecutor", new=ThreadPoolExecutor
 
1186             failing = (workspace / "failing.py").resolve()
 
1187             with failing.open("w") as fobj:
 
1188                 fobj.write("not actually python")
 
1189             clean = (workspace / "clean.py").resolve()
 
1190             with clean.open("w") as fobj:
 
1191                 fobj.write('print("hello")\n')
 
1192             self.invokeBlack([str(workspace)], exit_code=123)
 
1193             cache = black.read_cache(mode)
 
1194             self.assertNotIn(str(failing), cache)
 
1195             self.assertIn(str(clean), cache)
 
1197     def test_write_cache_write_fail(self) -> None:
 
1199         with cache_dir(), patch.object(Path, "open") as mock:
 
1200             mock.side_effect = OSError
 
1201             black.write_cache({}, [], mode)
 
1204     @patch("black.ProcessPoolExecutor", MagicMock(side_effect=OSError))
 
1205     def test_works_in_mono_process_only_environment(self) -> None:
 
1206         with cache_dir() as workspace:
 
1208                 (workspace / "one.py").resolve(),
 
1209                 (workspace / "two.py").resolve(),
 
1211                 f.write_text('print("hello")\n')
 
1212             self.invokeBlack([str(workspace)])
 
1215     def test_check_diff_use_together(self) -> None:
 
1217             # Files which will be reformatted.
 
1218             src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
 
1219             self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
 
1220             # Files which will not be reformatted.
 
1221             src2 = (THIS_DIR / "data" / "composition.py").resolve()
 
1222             self.invokeBlack([str(src2), "--diff", "--check"])
 
1223             # Multi file command.
 
1224             self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
 
1226     def test_no_files(self) -> None:
 
1228             # Without an argument, black exits with error code 0.
 
1229             self.invokeBlack([])
 
1231     def test_broken_symlink(self) -> None:
 
1232         with cache_dir() as workspace:
 
1233             symlink = workspace / "broken_link.py"
 
1235                 symlink.symlink_to("nonexistent.py")
 
1236             except OSError as e:
 
1237                 self.skipTest(f"Can't create symlinks: {e}")
 
1238             self.invokeBlack([str(workspace.resolve())])
 
1240     def test_read_cache_line_lengths(self) -> None:
 
1242         short_mode = replace(DEFAULT_MODE, line_length=1)
 
1243         with cache_dir() as workspace:
 
1244             path = (workspace / "file.py").resolve()
 
1246             black.write_cache({}, [path], mode)
 
1247             one = black.read_cache(mode)
 
1248             self.assertIn(str(path), one)
 
1249             two = black.read_cache(short_mode)
 
1250             self.assertNotIn(str(path), two)
 
1252     def test_single_file_force_pyi(self) -> None:
 
1253         pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
 
1254         contents, expected = read_data("force_pyi")
 
1255         with cache_dir() as workspace:
 
1256             path = (workspace / "file.py").resolve()
 
1257             with open(path, "w") as fh:
 
1259             self.invokeBlack([str(path), "--pyi"])
 
1260             with open(path, "r") as fh:
 
1262             # verify cache with --pyi is separate
 
1263             pyi_cache = black.read_cache(pyi_mode)
 
1264             self.assertIn(str(path), pyi_cache)
 
1265             normal_cache = black.read_cache(DEFAULT_MODE)
 
1266             self.assertNotIn(str(path), normal_cache)
 
1267         self.assertFormatEqual(expected, actual)
 
1268         black.assert_equivalent(contents, actual)
 
1269         black.assert_stable(contents, actual, pyi_mode)
 
1272     def test_multi_file_force_pyi(self) -> None:
 
1273         reg_mode = DEFAULT_MODE
 
1274         pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
 
1275         contents, expected = read_data("force_pyi")
 
1276         with cache_dir() as workspace:
 
1278                 (workspace / "file1.py").resolve(),
 
1279                 (workspace / "file2.py").resolve(),
 
1282                 with open(path, "w") as fh:
 
1284             self.invokeBlack([str(p) for p in paths] + ["--pyi"])
 
1286                 with open(path, "r") as fh:
 
1288                 self.assertEqual(actual, expected)
 
1289             # verify cache with --pyi is separate
 
1290             pyi_cache = black.read_cache(pyi_mode)
 
1291             normal_cache = black.read_cache(reg_mode)
 
1293                 self.assertIn(str(path), pyi_cache)
 
1294                 self.assertNotIn(str(path), normal_cache)
 
1296     def test_pipe_force_pyi(self) -> None:
 
1297         source, expected = read_data("force_pyi")
 
1298         result = CliRunner().invoke(
 
1299             black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
 
1301         self.assertEqual(result.exit_code, 0)
 
1302         actual = result.output
 
1303         self.assertFormatEqual(actual, expected)
 
1305     def test_single_file_force_py36(self) -> None:
 
1306         reg_mode = DEFAULT_MODE
 
1307         py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
 
1308         source, expected = read_data("force_py36")
 
1309         with cache_dir() as workspace:
 
1310             path = (workspace / "file.py").resolve()
 
1311             with open(path, "w") as fh:
 
1313             self.invokeBlack([str(path), *PY36_ARGS])
 
1314             with open(path, "r") as fh:
 
1316             # verify cache with --target-version is separate
 
1317             py36_cache = black.read_cache(py36_mode)
 
1318             self.assertIn(str(path), py36_cache)
 
1319             normal_cache = black.read_cache(reg_mode)
 
1320             self.assertNotIn(str(path), normal_cache)
 
1321         self.assertEqual(actual, expected)
 
1324     def test_multi_file_force_py36(self) -> None:
 
1325         reg_mode = DEFAULT_MODE
 
1326         py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
 
1327         source, expected = read_data("force_py36")
 
1328         with cache_dir() as workspace:
 
1330                 (workspace / "file1.py").resolve(),
 
1331                 (workspace / "file2.py").resolve(),
 
1334                 with open(path, "w") as fh:
 
1336             self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
 
1338                 with open(path, "r") as fh:
 
1340                 self.assertEqual(actual, expected)
 
1341             # verify cache with --target-version is separate
 
1342             pyi_cache = black.read_cache(py36_mode)
 
1343             normal_cache = black.read_cache(reg_mode)
 
1345                 self.assertIn(str(path), pyi_cache)
 
1346                 self.assertNotIn(str(path), normal_cache)
 
1348     def test_pipe_force_py36(self) -> None:
 
1349         source, expected = read_data("force_py36")
 
1350         result = CliRunner().invoke(
 
1352             ["-", "-q", "--target-version=py36"],
 
1353             input=BytesIO(source.encode("utf8")),
 
1355         self.assertEqual(result.exit_code, 0)
 
1356         actual = result.output
 
1357         self.assertFormatEqual(actual, expected)
 
1359     def test_include_exclude(self) -> None:
 
1360         path = THIS_DIR / "data" / "include_exclude_tests"
 
1361         include = re.compile(r"\.pyi?$")
 
1362         exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
 
1363         report = black.Report()
 
1364         gitignore = PathSpec.from_lines("gitwildmatch", [])
 
1365         sources: List[Path] = []
 
1367             Path(path / "b/dont_exclude/a.py"),
 
1368             Path(path / "b/dont_exclude/a.pyi"),
 
1370         this_abs = THIS_DIR.resolve()
 
1372             black.gen_python_files(
 
1383         self.assertEqual(sorted(expected), sorted(sources))
 
1385     def test_gitignore_used_as_default(self) -> None:
 
1386         path = Path(THIS_DIR / "data" / "include_exclude_tests")
 
1387         include = re.compile(r"\.pyi?$")
 
1388         extend_exclude = re.compile(r"/exclude/")
 
1389         src = str(path / "b/")
 
1390         report = black.Report()
 
1391         expected: List[Path] = [
 
1392             path / "b/.definitely_exclude/a.py",
 
1393             path / "b/.definitely_exclude/a.pyi",
 
1403                 extend_exclude=extend_exclude,
 
1406                 stdin_filename=None,
 
1409         self.assertEqual(sorted(expected), sorted(sources))
 
1411     @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
 
1412     def test_exclude_for_issue_1572(self) -> None:
 
1413         # Exclude shouldn't touch files that were explicitly given to Black through the
 
1414         # CLI. Exclude is supposed to only apply to the recursive discovery of files.
 
1415         # https://github.com/psf/black/issues/1572
 
1416         path = THIS_DIR / "data" / "include_exclude_tests"
 
1418         exclude = r"/exclude/|a\.py"
 
1419         src = str(path / "b/exclude/a.py")
 
1420         report = black.Report()
 
1421         expected = [Path(path / "b/exclude/a.py")]
 
1428                 include=re.compile(include),
 
1429                 exclude=re.compile(exclude),
 
1430                 extend_exclude=None,
 
1433                 stdin_filename=None,
 
1436         self.assertEqual(sorted(expected), sorted(sources))
 
1438     @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
 
1439     def test_get_sources_with_stdin(self) -> None:
 
1441         exclude = r"/exclude/|a\.py"
 
1443         report = black.Report()
 
1444         expected = [Path("-")]
 
1451                 include=re.compile(include),
 
1452                 exclude=re.compile(exclude),
 
1453                 extend_exclude=None,
 
1456                 stdin_filename=None,
 
1459         self.assertEqual(sorted(expected), sorted(sources))
 
1461     @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
 
1462     def test_get_sources_with_stdin_filename(self) -> None:
 
1464         exclude = r"/exclude/|a\.py"
 
1466         report = black.Report()
 
1467         stdin_filename = str(THIS_DIR / "data/collections.py")
 
1468         expected = [Path(f"__BLACK_STDIN_FILENAME__{stdin_filename}")]
 
1475                 include=re.compile(include),
 
1476                 exclude=re.compile(exclude),
 
1477                 extend_exclude=None,
 
1480                 stdin_filename=stdin_filename,
 
1483         self.assertEqual(sorted(expected), sorted(sources))
 
1485     @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
 
1486     def test_get_sources_with_stdin_filename_and_exclude(self) -> None:
 
1487         # Exclude shouldn't exclude stdin_filename since it is mimicking the
 
1488         # file being passed directly. This is the same as
 
1489         # test_exclude_for_issue_1572
 
1490         path = THIS_DIR / "data" / "include_exclude_tests"
 
1492         exclude = r"/exclude/|a\.py"
 
1494         report = black.Report()
 
1495         stdin_filename = str(path / "b/exclude/a.py")
 
1496         expected = [Path(f"__BLACK_STDIN_FILENAME__{stdin_filename}")]
 
1503                 include=re.compile(include),
 
1504                 exclude=re.compile(exclude),
 
1505                 extend_exclude=None,
 
1508                 stdin_filename=stdin_filename,
 
1511         self.assertEqual(sorted(expected), sorted(sources))
 
1513     @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
 
1514     def test_get_sources_with_stdin_filename_and_extend_exclude(self) -> None:
 
1515         # Extend exclude shouldn't exclude stdin_filename since it is mimicking the
 
1516         # file being passed directly. This is the same as
 
1517         # test_exclude_for_issue_1572
 
1518         path = THIS_DIR / "data" / "include_exclude_tests"
 
1520         extend_exclude = r"/exclude/|a\.py"
 
1522         report = black.Report()
 
1523         stdin_filename = str(path / "b/exclude/a.py")
 
1524         expected = [Path(f"__BLACK_STDIN_FILENAME__{stdin_filename}")]
 
1531                 include=re.compile(include),
 
1532                 exclude=re.compile(""),
 
1533                 extend_exclude=re.compile(extend_exclude),
 
1536                 stdin_filename=stdin_filename,
 
1539         self.assertEqual(sorted(expected), sorted(sources))
 
1541     @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
 
1542     def test_get_sources_with_stdin_filename_and_force_exclude(self) -> None:
 
1543         # Force exclude should exclude the file when passing it through
 
1545         path = THIS_DIR / "data" / "include_exclude_tests"
 
1547         force_exclude = r"/exclude/|a\.py"
 
1549         report = black.Report()
 
1550         stdin_filename = str(path / "b/exclude/a.py")
 
1557                 include=re.compile(include),
 
1558                 exclude=re.compile(""),
 
1559                 extend_exclude=None,
 
1560                 force_exclude=re.compile(force_exclude),
 
1562                 stdin_filename=stdin_filename,
 
1565         self.assertEqual([], sorted(sources))
 
1567     def test_reformat_one_with_stdin(self) -> None:
 
1569             "black.format_stdin_to_stdout",
 
1570             return_value=lambda *args, **kwargs: black.Changed.YES,
 
1572             report = MagicMock()
 
1577                 write_back=black.WriteBack.YES,
 
1581             fsts.assert_called_once()
 
1582             report.done.assert_called_with(path, black.Changed.YES)
 
1584     def test_reformat_one_with_stdin_filename(self) -> None:
 
1586             "black.format_stdin_to_stdout",
 
1587             return_value=lambda *args, **kwargs: black.Changed.YES,
 
1589             report = MagicMock()
 
1591             path = Path(f"__BLACK_STDIN_FILENAME__{p}")
 
1596                 write_back=black.WriteBack.YES,
 
1600             fsts.assert_called_once_with(
 
1601                 fast=True, write_back=black.WriteBack.YES, mode=DEFAULT_MODE
 
1603             # __BLACK_STDIN_FILENAME__ should have been stripped
 
1604             report.done.assert_called_with(expected, black.Changed.YES)
 
1606     def test_reformat_one_with_stdin_filename_pyi(self) -> None:
 
1608             "black.format_stdin_to_stdout",
 
1609             return_value=lambda *args, **kwargs: black.Changed.YES,
 
1611             report = MagicMock()
 
1613             path = Path(f"__BLACK_STDIN_FILENAME__{p}")
 
1618                 write_back=black.WriteBack.YES,
 
1622             fsts.assert_called_once_with(
 
1624                 write_back=black.WriteBack.YES,
 
1625                 mode=replace(DEFAULT_MODE, is_pyi=True),
 
1627             # __BLACK_STDIN_FILENAME__ should have been stripped
 
1628             report.done.assert_called_with(expected, black.Changed.YES)
 
1630     def test_reformat_one_with_stdin_and_existing_path(self) -> None:
 
1632             "black.format_stdin_to_stdout",
 
1633             return_value=lambda *args, **kwargs: black.Changed.YES,
 
1635             report = MagicMock()
 
1636             # Even with an existing file, since we are forcing stdin, black
 
1637             # should output to stdout and not modify the file inplace
 
1638             p = Path(str(THIS_DIR / "data/collections.py"))
 
1639             # Make sure is_file actually returns True
 
1640             self.assertTrue(p.is_file())
 
1641             path = Path(f"__BLACK_STDIN_FILENAME__{p}")
 
1646                 write_back=black.WriteBack.YES,
 
1650             fsts.assert_called_once()
 
1651             # __BLACK_STDIN_FILENAME__ should have been stripped
 
1652             report.done.assert_called_with(expected, black.Changed.YES)
 
1654     def test_reformat_one_with_stdin_empty(self) -> None:
 
1655         output = io.StringIO()
 
1656         with patch("io.TextIOWrapper", lambda *args, **kwargs: output):
 
1658                 black.format_stdin_to_stdout(
 
1661                     write_back=black.WriteBack.YES,
 
1664             except io.UnsupportedOperation:
 
1665                 pass  # StringIO does not support detach
 
1666             assert output.getvalue() == ""
 
1668     def test_gitignore_exclude(self) -> None:
 
1669         path = THIS_DIR / "data" / "include_exclude_tests"
 
1670         include = re.compile(r"\.pyi?$")
 
1671         exclude = re.compile(r"")
 
1672         report = black.Report()
 
1673         gitignore = PathSpec.from_lines(
 
1674             "gitwildmatch", ["exclude/", ".definitely_exclude"]
 
1676         sources: List[Path] = []
 
1678             Path(path / "b/dont_exclude/a.py"),
 
1679             Path(path / "b/dont_exclude/a.pyi"),
 
1681         this_abs = THIS_DIR.resolve()
 
1683             black.gen_python_files(
 
1694         self.assertEqual(sorted(expected), sorted(sources))
 
1696     def test_nested_gitignore(self) -> None:
 
1697         path = Path(THIS_DIR / "data" / "nested_gitignore_tests")
 
1698         include = re.compile(r"\.pyi?$")
 
1699         exclude = re.compile(r"")
 
1700         root_gitignore = black.files.get_gitignore(path)
 
1701         report = black.Report()
 
1702         expected: List[Path] = [
 
1703             Path(path / "x.py"),
 
1704             Path(path / "root/b.py"),
 
1705             Path(path / "root/c.py"),
 
1706             Path(path / "root/child/c.py"),
 
1708         this_abs = THIS_DIR.resolve()
 
1710             black.gen_python_files(
 
1721         self.assertEqual(sorted(expected), sorted(sources))
 
1723     def test_empty_include(self) -> None:
 
1724         path = THIS_DIR / "data" / "include_exclude_tests"
 
1725         report = black.Report()
 
1726         gitignore = PathSpec.from_lines("gitwildmatch", [])
 
1727         empty = re.compile(r"")
 
1728         sources: List[Path] = []
 
1730             Path(path / "b/exclude/a.pie"),
 
1731             Path(path / "b/exclude/a.py"),
 
1732             Path(path / "b/exclude/a.pyi"),
 
1733             Path(path / "b/dont_exclude/a.pie"),
 
1734             Path(path / "b/dont_exclude/a.py"),
 
1735             Path(path / "b/dont_exclude/a.pyi"),
 
1736             Path(path / "b/.definitely_exclude/a.pie"),
 
1737             Path(path / "b/.definitely_exclude/a.py"),
 
1738             Path(path / "b/.definitely_exclude/a.pyi"),
 
1739             Path(path / ".gitignore"),
 
1740             Path(path / "pyproject.toml"),
 
1742         this_abs = THIS_DIR.resolve()
 
1744             black.gen_python_files(
 
1748                 re.compile(black.DEFAULT_EXCLUDES),
 
1755         self.assertEqual(sorted(expected), sorted(sources))
 
1757     def test_extend_exclude(self) -> None:
 
1758         path = THIS_DIR / "data" / "include_exclude_tests"
 
1759         report = black.Report()
 
1760         gitignore = PathSpec.from_lines("gitwildmatch", [])
 
1761         sources: List[Path] = []
 
1763             Path(path / "b/exclude/a.py"),
 
1764             Path(path / "b/dont_exclude/a.py"),
 
1766         this_abs = THIS_DIR.resolve()
 
1768             black.gen_python_files(
 
1771                 re.compile(black.DEFAULT_INCLUDES),
 
1772                 re.compile(r"\.pyi$"),
 
1773                 re.compile(r"\.definitely_exclude"),
 
1779         self.assertEqual(sorted(expected), sorted(sources))
 
1781     def test_invalid_cli_regex(self) -> None:
 
1782         for option in ["--include", "--exclude", "--extend-exclude", "--force-exclude"]:
 
1783             self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
 
1785     def test_required_version_matches_version(self) -> None:
 
1787             ["--required-version", black.__version__], exit_code=0, ignore_config=True
 
1790     def test_required_version_does_not_match_version(self) -> None:
 
1792             ["--required-version", "20.99b"], exit_code=1, ignore_config=True
 
1795     def test_preserves_line_endings(self) -> None:
 
1796         with TemporaryDirectory() as workspace:
 
1797             test_file = Path(workspace) / "test.py"
 
1798             for nl in ["\n", "\r\n"]:
 
1799                 contents = nl.join(["def f(  ):", "    pass"])
 
1800                 test_file.write_bytes(contents.encode())
 
1801                 ff(test_file, write_back=black.WriteBack.YES)
 
1802                 updated_contents: bytes = test_file.read_bytes()
 
1803                 self.assertIn(nl.encode(), updated_contents)
 
1805                     self.assertNotIn(b"\r\n", updated_contents)
 
1807     def test_preserves_line_endings_via_stdin(self) -> None:
 
1808         for nl in ["\n", "\r\n"]:
 
1809             contents = nl.join(["def f(  ):", "    pass"])
 
1810             runner = BlackRunner()
 
1811             result = runner.invoke(
 
1812                 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
 
1814             self.assertEqual(result.exit_code, 0)
 
1815             output = result.stdout_bytes
 
1816             self.assertIn(nl.encode("utf8"), output)
 
1818                 self.assertNotIn(b"\r\n", output)
 
1820     def test_assert_equivalent_different_asts(self) -> None:
 
1821         with self.assertRaises(AssertionError):
 
1822             black.assert_equivalent("{}", "None")
 
1824     def test_symlink_out_of_root_directory(self) -> None:
 
1826         root = THIS_DIR.resolve()
 
1828         include = re.compile(black.DEFAULT_INCLUDES)
 
1829         exclude = re.compile(black.DEFAULT_EXCLUDES)
 
1830         report = black.Report()
 
1831         gitignore = PathSpec.from_lines("gitwildmatch", [])
 
1832         # `child` should behave like a symlink which resolved path is clearly
 
1833         # outside of the `root` directory.
 
1834         path.iterdir.return_value = [child]
 
1835         child.resolve.return_value = Path("/a/b/c")
 
1836         child.as_posix.return_value = "/a/b/c"
 
1837         child.is_symlink.return_value = True
 
1840                 black.gen_python_files(
 
1851         except ValueError as ve:
 
1852             self.fail(f"`get_python_files_in_dir()` failed: {ve}")
 
1853         path.iterdir.assert_called_once()
 
1854         child.resolve.assert_called_once()
 
1855         child.is_symlink.assert_called_once()
 
1856         # `child` should behave like a strange file which resolved path is clearly
 
1857         # outside of the `root` directory.
 
1858         child.is_symlink.return_value = False
 
1859         with self.assertRaises(ValueError):
 
1861                 black.gen_python_files(
 
1872         path.iterdir.assert_called()
 
1873         self.assertEqual(path.iterdir.call_count, 2)
 
1874         child.resolve.assert_called()
 
1875         self.assertEqual(child.resolve.call_count, 2)
 
1876         child.is_symlink.assert_called()
 
1877         self.assertEqual(child.is_symlink.call_count, 2)
 
1879     def test_shhh_click(self) -> None:
 
1881             from click import _unicodefun
 
1882         except ModuleNotFoundError:
 
1883             self.skipTest("Incompatible Click version")
 
1884         if not hasattr(_unicodefun, "_verify_python3_env"):
 
1885             self.skipTest("Incompatible Click version")
 
1886         # First, let's see if Click is crashing with a preferred ASCII charset.
 
1887         with patch("locale.getpreferredencoding") as gpe:
 
1888             gpe.return_value = "ASCII"
 
1889             with self.assertRaises(RuntimeError):
 
1890                 _unicodefun._verify_python3_env()  # type: ignore
 
1891         # Now, let's silence Click...
 
1893         # ...and confirm it's silent.
 
1894         with patch("locale.getpreferredencoding") as gpe:
 
1895             gpe.return_value = "ASCII"
 
1897                 _unicodefun._verify_python3_env()  # type: ignore
 
1898             except RuntimeError as re:
 
1899                 self.fail(f"`patch_click()` failed, exception still raised: {re}")
 
1901     def test_root_logger_not_used_directly(self) -> None:
 
1902         def fail(*args: Any, **kwargs: Any) -> None:
 
1903             self.fail("Record created with root logger")
 
1905         with patch.multiple(
 
1914             ff(THIS_DIR / "util.py")
 
1916     def test_invalid_config_return_code(self) -> None:
 
1917         tmp_file = Path(black.dump_to_file())
 
1919             tmp_config = Path(black.dump_to_file())
 
1921             args = ["--config", str(tmp_config), str(tmp_file)]
 
1922             self.invokeBlack(args, exit_code=2, ignore_config=False)
 
1926     def test_parse_pyproject_toml(self) -> None:
 
1927         test_toml_file = THIS_DIR / "test.toml"
 
1928         config = black.parse_pyproject_toml(str(test_toml_file))
 
1929         self.assertEqual(config["verbose"], 1)
 
1930         self.assertEqual(config["check"], "no")
 
1931         self.assertEqual(config["diff"], "y")
 
1932         self.assertEqual(config["color"], True)
 
1933         self.assertEqual(config["line_length"], 79)
 
1934         self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
 
1935         self.assertEqual(config["exclude"], r"\.pyi?$")
 
1936         self.assertEqual(config["include"], r"\.py?$")
 
1938     def test_read_pyproject_toml(self) -> None:
 
1939         test_toml_file = THIS_DIR / "test.toml"
 
1940         fake_ctx = FakeContext()
 
1941         black.read_pyproject_toml(fake_ctx, FakeParameter(), str(test_toml_file))
 
1942         config = fake_ctx.default_map
 
1943         self.assertEqual(config["verbose"], "1")
 
1944         self.assertEqual(config["check"], "no")
 
1945         self.assertEqual(config["diff"], "y")
 
1946         self.assertEqual(config["color"], "True")
 
1947         self.assertEqual(config["line_length"], "79")
 
1948         self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
 
1949         self.assertEqual(config["exclude"], r"\.pyi?$")
 
1950         self.assertEqual(config["include"], r"\.py?$")
 
1952     def test_find_project_root(self) -> None:
 
1953         with TemporaryDirectory() as workspace:
 
1954             root = Path(workspace)
 
1955             test_dir = root / "test"
 
1958             src_dir = root / "src"
 
1961             root_pyproject = root / "pyproject.toml"
 
1962             root_pyproject.touch()
 
1963             src_pyproject = src_dir / "pyproject.toml"
 
1964             src_pyproject.touch()
 
1965             src_python = src_dir / "foo.py"
 
1969                 black.find_project_root((src_dir, test_dir)), root.resolve()
 
1971             self.assertEqual(black.find_project_root((src_dir,)), src_dir.resolve())
 
1972             self.assertEqual(black.find_project_root((src_python,)), src_dir.resolve())
 
1975         "black.files.find_user_pyproject_toml",
 
1976         black.files.find_user_pyproject_toml.__wrapped__,
 
1978     def test_find_user_pyproject_toml_linux(self) -> None:
 
1979         if system() == "Windows":
 
1982         # Test if XDG_CONFIG_HOME is checked
 
1983         with TemporaryDirectory() as workspace:
 
1984             tmp_user_config = Path(workspace) / "black"
 
1985             with patch.dict("os.environ", {"XDG_CONFIG_HOME": workspace}):
 
1987                     black.files.find_user_pyproject_toml(), tmp_user_config.resolve()
 
1990         # Test fallback for XDG_CONFIG_HOME
 
1991         with patch.dict("os.environ"):
 
1992             os.environ.pop("XDG_CONFIG_HOME", None)
 
1993             fallback_user_config = Path("~/.config").expanduser() / "black"
 
1995                 black.files.find_user_pyproject_toml(), fallback_user_config.resolve()
 
1998     def test_find_user_pyproject_toml_windows(self) -> None:
 
1999         if system() != "Windows":
 
2002         user_config_path = Path.home() / ".black"
 
2004             black.files.find_user_pyproject_toml(), user_config_path.resolve()
 
2007     def test_bpo_33660_workaround(self) -> None:
 
2008         if system() == "Windows":
 
2011         # https://bugs.python.org/issue33660
 
2013         old_cwd = Path.cwd()
 
2017             path = Path("workspace") / "project"
 
2018             report = black.Report(verbose=True)
 
2019             normalized_path = black.normalize_path_maybe_ignore(path, root, report)
 
2020             self.assertEqual(normalized_path, "workspace/project")
 
2022             os.chdir(str(old_cwd))
 
2024     def test_newline_comment_interaction(self) -> None:
 
2025         source = "class A:\\\r\n# type: ignore\n pass\n"
 
2026         output = black.format_str(source, mode=DEFAULT_MODE)
 
2027         black.assert_stable(source, output, mode=DEFAULT_MODE)
 
2029     def test_bpo_2142_workaround(self) -> None:
 
2031         # https://bugs.python.org/issue2142
 
2033         source, _ = read_data("missing_final_newline.py")
 
2034         # read_data adds a trailing newline
 
2035         source = source.rstrip()
 
2036         expected, _ = read_data("missing_final_newline.diff")
 
2037         tmp_file = Path(black.dump_to_file(source, ensure_final_newline=False))
 
2038         diff_header = re.compile(
 
2039             rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
 
2040             r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
 
2043             result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
 
2044             self.assertEqual(result.exit_code, 0)
 
2047         actual = result.output
 
2048         actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
 
2049         self.assertEqual(actual, expected)
 
2051     @pytest.mark.python2
 
2052     def test_docstring_reformat_for_py27(self) -> None:
 
2054         Check that stripping trailing whitespace from Python 2 docstrings
 
2055         doesn't trigger a "not equivalent to source" error
 
2058             b'def foo():\r\n    """Testing\r\n    Testing """\r\n    print "Foo"\r\n'
 
2060         expected = 'def foo():\n    """Testing\n    Testing"""\n    print "Foo"\n'
 
2062         result = CliRunner().invoke(
 
2064             ["-", "-q", "--target-version=py27"],
 
2065             input=BytesIO(source),
 
2068         self.assertEqual(result.exit_code, 0)
 
2069         actual = result.output
 
2070         self.assertFormatEqual(actual, expected)
 
2073     def compare_results(
 
2074         result: click.testing.Result, expected_value: str, expected_exit_code: int
 
2076         """Helper method to test the value and exit code of a click Result."""
 
2078             result.output == expected_value
 
2079         ), "The output did not match the expected value."
 
2080         assert result.exit_code == expected_exit_code, "The exit code is incorrect."
 
2082     def test_code_option(self) -> None:
 
2083         """Test the code option with no changes."""
 
2084         code = 'print("Hello world")\n'
 
2085         args = ["--code", code]
 
2086         result = CliRunner().invoke(black.main, args)
 
2088         self.compare_results(result, code, 0)
 
2090     def test_code_option_changed(self) -> None:
 
2091         """Test the code option when changes are required."""
 
2092         code = "print('hello world')"
 
2093         formatted = black.format_str(code, mode=DEFAULT_MODE)
 
2095         args = ["--code", code]
 
2096         result = CliRunner().invoke(black.main, args)
 
2098         self.compare_results(result, formatted, 0)
 
2100     def test_code_option_check(self) -> None:
 
2101         """Test the code option when check is passed."""
 
2102         args = ["--check", "--code", 'print("Hello world")\n']
 
2103         result = CliRunner().invoke(black.main, args)
 
2104         self.compare_results(result, "", 0)
 
2106     def test_code_option_check_changed(self) -> None:
 
2107         """Test the code option when changes are required, and check is passed."""
 
2108         args = ["--check", "--code", "print('hello world')"]
 
2109         result = CliRunner().invoke(black.main, args)
 
2110         self.compare_results(result, "", 1)
 
2112     def test_code_option_diff(self) -> None:
 
2113         """Test the code option when diff is passed."""
 
2114         code = "print('hello world')"
 
2115         formatted = black.format_str(code, mode=DEFAULT_MODE)
 
2116         result_diff = diff(code, formatted, "STDIN", "STDOUT")
 
2118         args = ["--diff", "--code", code]
 
2119         result = CliRunner().invoke(black.main, args)
 
2121         # Remove time from diff
 
2122         output = DIFF_TIME.sub("", result.output)
 
2124         assert output == result_diff, "The output did not match the expected value."
 
2125         assert result.exit_code == 0, "The exit code is incorrect."
 
2127     def test_code_option_color_diff(self) -> None:
 
2128         """Test the code option when color and diff are passed."""
 
2129         code = "print('hello world')"
 
2130         formatted = black.format_str(code, mode=DEFAULT_MODE)
 
2132         result_diff = diff(code, formatted, "STDIN", "STDOUT")
 
2133         result_diff = color_diff(result_diff)
 
2135         args = ["--diff", "--color", "--code", code]
 
2136         result = CliRunner().invoke(black.main, args)
 
2138         # Remove time from diff
 
2139         output = DIFF_TIME.sub("", result.output)
 
2141         assert output == result_diff, "The output did not match the expected value."
 
2142         assert result.exit_code == 0, "The exit code is incorrect."
 
2144     def test_code_option_safe(self) -> None:
 
2145         """Test that the code option throws an error when the sanity checks fail."""
 
2146         # Patch black.assert_equivalent to ensure the sanity checks fail
 
2147         with patch.object(black, "assert_equivalent", side_effect=AssertionError):
 
2148             code = 'print("Hello world")'
 
2149             error_msg = f"{code}\nerror: cannot format <string>: \n"
 
2151             args = ["--safe", "--code", code]
 
2152             result = CliRunner().invoke(black.main, args)
 
2154             self.compare_results(result, error_msg, 123)
 
2156     def test_code_option_fast(self) -> None:
 
2157         """Test that the code option ignores errors when the sanity checks fail."""
 
2158         # Patch black.assert_equivalent to ensure the sanity checks fail
 
2159         with patch.object(black, "assert_equivalent", side_effect=AssertionError):
 
2160             code = 'print("Hello world")'
 
2161             formatted = black.format_str(code, mode=DEFAULT_MODE)
 
2163             args = ["--fast", "--code", code]
 
2164             result = CliRunner().invoke(black.main, args)
 
2166             self.compare_results(result, formatted, 0)
 
2168     def test_code_option_config(self) -> None:
 
2170         Test that the code option finds the pyproject.toml in the current directory.
 
2172         with patch.object(black, "parse_pyproject_toml", return_value={}) as parse:
 
2173             # Make sure we are in the project root with the pyproject file
 
2174             if not Path("tests").exists():
 
2177             args = ["--code", "print"]
 
2178             CliRunner().invoke(black.main, args)
 
2180             pyproject_path = Path(Path().cwd(), "pyproject.toml").resolve()
 
2182                 len(parse.mock_calls) >= 1
 
2183             ), "Expected config parse to be called with the current directory."
 
2185             _, call_args, _ = parse.mock_calls[0]
 
2187                 call_args[0].lower() == str(pyproject_path).lower()
 
2188             ), "Incorrect config loaded."
 
2190     def test_code_option_parent_config(self) -> None:
 
2192         Test that the code option finds the pyproject.toml in the parent directory.
 
2194         with patch.object(black, "parse_pyproject_toml", return_value={}) as parse:
 
2195             # Make sure we are in the tests directory
 
2196             if Path("tests").exists():
 
2199             args = ["--code", "print"]
 
2200             CliRunner().invoke(black.main, args)
 
2202             pyproject_path = Path(Path().cwd().parent, "pyproject.toml").resolve()
 
2204                 len(parse.mock_calls) >= 1
 
2205             ), "Expected config parse to be called with the current directory."
 
2207             _, call_args, _ = parse.mock_calls[0]
 
2209                 call_args[0].lower() == str(pyproject_path).lower()
 
2210             ), "Incorrect config loaded."
 
2213 with open(black.__file__, "r", encoding="utf-8") as _bf:
 
2214     black_source_lines = _bf.readlines()
 
2217 def tracefunc(frame: types.FrameType, event: str, arg: Any) -> Callable:
 
2218     """Show function calls `from black/__init__.py` as they happen.
 
2220     Register this with `sys.settrace()` in a test you're debugging.
 
2225     stack = len(inspect.stack()) - 19
 
2227     filename = frame.f_code.co_filename
 
2228     lineno = frame.f_lineno
 
2229     func_sig_lineno = lineno - 1
 
2230     funcname = black_source_lines[func_sig_lineno].strip()
 
2231     while funcname.startswith("@"):
 
2232         func_sig_lineno += 1
 
2233         funcname = black_source_lines[func_sig_lineno].strip()
 
2234     if "black/__init__.py" in filename:
 
2235         print(f"{' ' * stack}{lineno}:{funcname}")
 
2239 if __name__ == "__main__":
 
2240     unittest.main(module="test_black")