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.
   4 from concurrent.futures import ThreadPoolExecutor
 
   5 from contextlib import contextmanager
 
   6 from functools import partial, wraps
 
   7 from io import BytesIO, TextIOWrapper
 
   9 from pathlib import Path
 
  12 from tempfile import TemporaryDirectory
 
  25 from unittest.mock import patch, MagicMock
 
  27 from click import unstyle
 
  28 from click.testing import CliRunner
 
  31 from black import Feature, TargetVersion
 
  35     from aiohttp.test_utils import TestClient, TestServer
 
  37     has_blackd_deps = False
 
  39     has_blackd_deps = True
 
  41 ff = partial(black.format_file_in_place, mode=black.FileMode(), fast=True)
 
  42 fs = partial(black.format_str, mode=black.FileMode())
 
  43 THIS_FILE = Path(__file__)
 
  44 THIS_DIR = THIS_FILE.parent
 
  45 EMPTY_LINE = "# EMPTY LINE WITH WHITESPACE" + " (this comment will be removed)"
 
  47     f"--target-version={version.name.lower()}" for version in black.PY36_VERSIONS
 
  53 def dump_to_stderr(*output: str) -> str:
 
  54     return "\n" + "\n".join(output) + "\n"
 
  57 def read_data(name: str, data: bool = True) -> Tuple[str, str]:
 
  58     """read_data('test_name') -> 'input', 'output'"""
 
  59     if not name.endswith((".py", ".pyi", ".out", ".diff")):
 
  61     _input: List[str] = []
 
  62     _output: List[str] = []
 
  63     base_dir = THIS_DIR / "data" if data else THIS_DIR
 
  64     with open(base_dir / name, "r", encoding="utf8") as test:
 
  65         lines = test.readlines()
 
  68         line = line.replace(EMPTY_LINE, "")
 
  69         if line.rstrip() == "# output":
 
  74     if _input and not _output:
 
  75         # If there's no output marker, treat the entire file as already pre-formatted.
 
  77     return "".join(_input).strip() + "\n", "".join(_output).strip() + "\n"
 
  81 def cache_dir(exists: bool = True) -> Iterator[Path]:
 
  82     with TemporaryDirectory() as workspace:
 
  83         cache_dir = Path(workspace)
 
  85             cache_dir = cache_dir / "new"
 
  86         with patch("black.CACHE_DIR", cache_dir):
 
  91 def event_loop(close: bool) -> Iterator[None]:
 
  92     policy = asyncio.get_event_loop_policy()
 
  93     old_loop = policy.get_event_loop()
 
  94     loop = policy.new_event_loop()
 
  95     asyncio.set_event_loop(loop)
 
 100         policy.set_event_loop(old_loop)
 
 105 def async_test(f: Callable[..., Coroutine[Any, None, R]]) -> Callable[..., None]:
 
 106     @event_loop(close=True)
 
 108     def wrapper(*args: Any, **kwargs: Any) -> None:
 
 109         asyncio.get_event_loop().run_until_complete(f(*args, **kwargs))
 
 114 class BlackRunner(CliRunner):
 
 115     """Modify CliRunner so that stderr is not merged with stdout.
 
 117     This is a hack that can be removed once we depend on Click 7.x"""
 
 119     def __init__(self) -> None:
 
 120         self.stderrbuf = BytesIO()
 
 121         self.stdoutbuf = BytesIO()
 
 122         self.stdout_bytes = b""
 
 123         self.stderr_bytes = b""
 
 127     def isolation(self, *args: Any, **kwargs: Any) -> Generator[BinaryIO, None, None]:
 
 128         with super().isolation(*args, **kwargs) as output:
 
 130                 hold_stderr = sys.stderr
 
 131                 sys.stderr = TextIOWrapper(self.stderrbuf, encoding=self.charset)
 
 134                 self.stdout_bytes = sys.stdout.buffer.getvalue()  # type: ignore
 
 135                 self.stderr_bytes = sys.stderr.buffer.getvalue()  # type: ignore
 
 136                 sys.stderr = hold_stderr
 
 139 class BlackTestCase(unittest.TestCase):
 
 142     def assertFormatEqual(self, expected: str, actual: str) -> None:
 
 143         if actual != expected and not os.environ.get("SKIP_AST_PRINT"):
 
 144             bdv: black.DebugVisitor[Any]
 
 145             black.out("Expected tree:", fg="green")
 
 147                 exp_node = black.lib2to3_parse(expected)
 
 148                 bdv = black.DebugVisitor()
 
 149                 list(bdv.visit(exp_node))
 
 150             except Exception as ve:
 
 152             black.out("Actual tree:", fg="red")
 
 154                 exp_node = black.lib2to3_parse(actual)
 
 155                 bdv = black.DebugVisitor()
 
 156                 list(bdv.visit(exp_node))
 
 157             except Exception as ve:
 
 159         self.assertEqual(expected, actual)
 
 162         self, args: List[str], exit_code: int = 0, ignore_config: bool = True
 
 164         runner = BlackRunner()
 
 166             args = ["--config", str(THIS_DIR / "empty.toml"), *args]
 
 167         result = runner.invoke(black.main, args)
 
 168         self.assertEqual(result.exit_code, exit_code, msg=runner.stderr_bytes.decode())
 
 170     @patch("black.dump_to_file", dump_to_stderr)
 
 171     def test_empty(self) -> None:
 
 172         source = expected = ""
 
 174         self.assertFormatEqual(expected, actual)
 
 175         black.assert_equivalent(source, actual)
 
 176         black.assert_stable(source, actual, black.FileMode())
 
 178     def test_empty_ff(self) -> None:
 
 180         tmp_file = Path(black.dump_to_file())
 
 182             self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES))
 
 183             with open(tmp_file, encoding="utf8") as f:
 
 187         self.assertFormatEqual(expected, actual)
 
 189     @patch("black.dump_to_file", dump_to_stderr)
 
 190     def test_self(self) -> None:
 
 191         source, expected = read_data("test_black", data=False)
 
 193         self.assertFormatEqual(expected, actual)
 
 194         black.assert_equivalent(source, actual)
 
 195         black.assert_stable(source, actual, black.FileMode())
 
 196         self.assertFalse(ff(THIS_FILE))
 
 198     @patch("black.dump_to_file", dump_to_stderr)
 
 199     def test_black(self) -> None:
 
 200         source, expected = read_data("../black", data=False)
 
 202         self.assertFormatEqual(expected, actual)
 
 203         black.assert_equivalent(source, actual)
 
 204         black.assert_stable(source, actual, black.FileMode())
 
 205         self.assertFalse(ff(THIS_DIR / ".." / "black.py"))
 
 207     def test_piping(self) -> None:
 
 208         source, expected = read_data("../black", data=False)
 
 209         result = BlackRunner().invoke(
 
 211             ["-", "--fast", f"--line-length={black.DEFAULT_LINE_LENGTH}"],
 
 212             input=BytesIO(source.encode("utf8")),
 
 214         self.assertEqual(result.exit_code, 0)
 
 215         self.assertFormatEqual(expected, result.output)
 
 216         black.assert_equivalent(source, result.output)
 
 217         black.assert_stable(source, result.output, black.FileMode())
 
 219     def test_piping_diff(self) -> None:
 
 220         diff_header = re.compile(
 
 221             rf"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d "
 
 222             rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
 
 224         source, _ = read_data("expression.py")
 
 225         expected, _ = read_data("expression.diff")
 
 226         config = THIS_DIR / "data" / "empty_pyproject.toml"
 
 230             f"--line-length={black.DEFAULT_LINE_LENGTH}",
 
 232             f"--config={config}",
 
 234         result = BlackRunner().invoke(
 
 235             black.main, args, input=BytesIO(source.encode("utf8"))
 
 237         self.assertEqual(result.exit_code, 0)
 
 238         actual = diff_header.sub("[Deterministic header]", result.output)
 
 239         actual = actual.rstrip() + "\n"  # the diff output has a trailing space
 
 240         self.assertEqual(expected, actual)
 
 242     @patch("black.dump_to_file", dump_to_stderr)
 
 243     def test_setup(self) -> None:
 
 244         source, expected = read_data("../setup", data=False)
 
 246         self.assertFormatEqual(expected, actual)
 
 247         black.assert_equivalent(source, actual)
 
 248         black.assert_stable(source, actual, black.FileMode())
 
 249         self.assertFalse(ff(THIS_DIR / ".." / "setup.py"))
 
 251     @patch("black.dump_to_file", dump_to_stderr)
 
 252     def test_function(self) -> None:
 
 253         source, expected = read_data("function")
 
 255         self.assertFormatEqual(expected, actual)
 
 256         black.assert_equivalent(source, actual)
 
 257         black.assert_stable(source, actual, black.FileMode())
 
 259     @patch("black.dump_to_file", dump_to_stderr)
 
 260     def test_function2(self) -> None:
 
 261         source, expected = read_data("function2")
 
 263         self.assertFormatEqual(expected, actual)
 
 264         black.assert_equivalent(source, actual)
 
 265         black.assert_stable(source, actual, black.FileMode())
 
 267     @patch("black.dump_to_file", dump_to_stderr)
 
 268     def test_expression(self) -> None:
 
 269         source, expected = read_data("expression")
 
 271         self.assertFormatEqual(expected, actual)
 
 272         black.assert_equivalent(source, actual)
 
 273         black.assert_stable(source, actual, black.FileMode())
 
 275     def test_expression_ff(self) -> None:
 
 276         source, expected = read_data("expression")
 
 277         tmp_file = Path(black.dump_to_file(source))
 
 279             self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
 
 280             with open(tmp_file, encoding="utf8") as f:
 
 284         self.assertFormatEqual(expected, actual)
 
 285         with patch("black.dump_to_file", dump_to_stderr):
 
 286             black.assert_equivalent(source, actual)
 
 287             black.assert_stable(source, actual, black.FileMode())
 
 289     def test_expression_diff(self) -> None:
 
 290         source, _ = read_data("expression.py")
 
 291         expected, _ = read_data("expression.diff")
 
 292         tmp_file = Path(black.dump_to_file(source))
 
 293         diff_header = re.compile(
 
 294             rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
 
 295             rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
 
 298             result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
 
 299             self.assertEqual(result.exit_code, 0)
 
 302         actual = result.output
 
 303         actual = diff_header.sub("[Deterministic header]", actual)
 
 304         actual = actual.rstrip() + "\n"  # the diff output has a trailing space
 
 305         if expected != actual:
 
 306             dump = black.dump_to_file(actual)
 
 308                 f"Expected diff isn't equal to the actual. If you made changes "
 
 309                 f"to expression.py and this is an anticipated difference, "
 
 310                 f"overwrite tests/data/expression.diff with {dump}"
 
 312             self.assertEqual(expected, actual, msg)
 
 314     @patch("black.dump_to_file", dump_to_stderr)
 
 315     def test_fstring(self) -> None:
 
 316         source, expected = read_data("fstring")
 
 318         self.assertFormatEqual(expected, actual)
 
 319         black.assert_equivalent(source, actual)
 
 320         black.assert_stable(source, actual, black.FileMode())
 
 322     @patch("black.dump_to_file", dump_to_stderr)
 
 323     def test_string_quotes(self) -> None:
 
 324         source, expected = read_data("string_quotes")
 
 326         self.assertFormatEqual(expected, actual)
 
 327         black.assert_equivalent(source, actual)
 
 328         black.assert_stable(source, actual, black.FileMode())
 
 329         mode = black.FileMode(string_normalization=False)
 
 330         not_normalized = fs(source, mode=mode)
 
 331         self.assertFormatEqual(source, not_normalized)
 
 332         black.assert_equivalent(source, not_normalized)
 
 333         black.assert_stable(source, not_normalized, mode=mode)
 
 335     @patch("black.dump_to_file", dump_to_stderr)
 
 336     def test_slices(self) -> None:
 
 337         source, expected = read_data("slices")
 
 339         self.assertFormatEqual(expected, actual)
 
 340         black.assert_equivalent(source, actual)
 
 341         black.assert_stable(source, actual, black.FileMode())
 
 343     @patch("black.dump_to_file", dump_to_stderr)
 
 344     def test_comments(self) -> None:
 
 345         source, expected = read_data("comments")
 
 347         self.assertFormatEqual(expected, actual)
 
 348         black.assert_equivalent(source, actual)
 
 349         black.assert_stable(source, actual, black.FileMode())
 
 351     @patch("black.dump_to_file", dump_to_stderr)
 
 352     def test_comments2(self) -> None:
 
 353         source, expected = read_data("comments2")
 
 355         self.assertFormatEqual(expected, actual)
 
 356         black.assert_equivalent(source, actual)
 
 357         black.assert_stable(source, actual, black.FileMode())
 
 359     @patch("black.dump_to_file", dump_to_stderr)
 
 360     def test_comments3(self) -> None:
 
 361         source, expected = read_data("comments3")
 
 363         self.assertFormatEqual(expected, actual)
 
 364         black.assert_equivalent(source, actual)
 
 365         black.assert_stable(source, actual, black.FileMode())
 
 367     @patch("black.dump_to_file", dump_to_stderr)
 
 368     def test_comments4(self) -> None:
 
 369         source, expected = read_data("comments4")
 
 371         self.assertFormatEqual(expected, actual)
 
 372         black.assert_equivalent(source, actual)
 
 373         black.assert_stable(source, actual, black.FileMode())
 
 375     @patch("black.dump_to_file", dump_to_stderr)
 
 376     def test_comments5(self) -> None:
 
 377         source, expected = read_data("comments5")
 
 379         self.assertFormatEqual(expected, actual)
 
 380         black.assert_equivalent(source, actual)
 
 381         black.assert_stable(source, actual, black.FileMode())
 
 383     @patch("black.dump_to_file", dump_to_stderr)
 
 384     def test_comments6(self) -> None:
 
 385         source, expected = read_data("comments6")
 
 387         self.assertFormatEqual(expected, actual)
 
 388         black.assert_equivalent(source, actual)
 
 389         black.assert_stable(source, actual, black.FileMode())
 
 391     @patch("black.dump_to_file", dump_to_stderr)
 
 392     def test_comments7(self) -> None:
 
 393         source, expected = read_data("comments7")
 
 395         self.assertFormatEqual(expected, actual)
 
 396         black.assert_equivalent(source, actual)
 
 397         black.assert_stable(source, actual, black.FileMode())
 
 399     @patch("black.dump_to_file", dump_to_stderr)
 
 400     def test_comment_after_escaped_newline(self) -> None:
 
 401         source, expected = read_data("comment_after_escaped_newline")
 
 403         self.assertFormatEqual(expected, actual)
 
 404         black.assert_equivalent(source, actual)
 
 405         black.assert_stable(source, actual, black.FileMode())
 
 407     @patch("black.dump_to_file", dump_to_stderr)
 
 408     def test_cantfit(self) -> None:
 
 409         source, expected = read_data("cantfit")
 
 411         self.assertFormatEqual(expected, actual)
 
 412         black.assert_equivalent(source, actual)
 
 413         black.assert_stable(source, actual, black.FileMode())
 
 415     @patch("black.dump_to_file", dump_to_stderr)
 
 416     def test_import_spacing(self) -> None:
 
 417         source, expected = read_data("import_spacing")
 
 419         self.assertFormatEqual(expected, actual)
 
 420         black.assert_equivalent(source, actual)
 
 421         black.assert_stable(source, actual, black.FileMode())
 
 423     @patch("black.dump_to_file", dump_to_stderr)
 
 424     def test_composition(self) -> None:
 
 425         source, expected = read_data("composition")
 
 427         self.assertFormatEqual(expected, actual)
 
 428         black.assert_equivalent(source, actual)
 
 429         black.assert_stable(source, actual, black.FileMode())
 
 431     @patch("black.dump_to_file", dump_to_stderr)
 
 432     def test_empty_lines(self) -> None:
 
 433         source, expected = read_data("empty_lines")
 
 435         self.assertFormatEqual(expected, actual)
 
 436         black.assert_equivalent(source, actual)
 
 437         black.assert_stable(source, actual, black.FileMode())
 
 439     @patch("black.dump_to_file", dump_to_stderr)
 
 440     def test_string_prefixes(self) -> None:
 
 441         source, expected = read_data("string_prefixes")
 
 443         self.assertFormatEqual(expected, actual)
 
 444         black.assert_equivalent(source, actual)
 
 445         black.assert_stable(source, actual, black.FileMode())
 
 447     @patch("black.dump_to_file", dump_to_stderr)
 
 448     def test_numeric_literals(self) -> None:
 
 449         source, expected = read_data("numeric_literals")
 
 450         mode = black.FileMode(target_versions=black.PY36_VERSIONS)
 
 451         actual = fs(source, mode=mode)
 
 452         self.assertFormatEqual(expected, actual)
 
 453         black.assert_equivalent(source, actual)
 
 454         black.assert_stable(source, actual, mode)
 
 456     @patch("black.dump_to_file", dump_to_stderr)
 
 457     def test_numeric_literals_ignoring_underscores(self) -> None:
 
 458         source, expected = read_data("numeric_literals_skip_underscores")
 
 459         mode = black.FileMode(target_versions=black.PY36_VERSIONS)
 
 460         actual = fs(source, mode=mode)
 
 461         self.assertFormatEqual(expected, actual)
 
 462         black.assert_equivalent(source, actual)
 
 463         black.assert_stable(source, actual, mode)
 
 465     @patch("black.dump_to_file", dump_to_stderr)
 
 466     def test_numeric_literals_py2(self) -> None:
 
 467         source, expected = read_data("numeric_literals_py2")
 
 469         self.assertFormatEqual(expected, actual)
 
 470         black.assert_stable(source, actual, black.FileMode())
 
 472     @patch("black.dump_to_file", dump_to_stderr)
 
 473     def test_python2(self) -> None:
 
 474         source, expected = read_data("python2")
 
 476         self.assertFormatEqual(expected, actual)
 
 477         black.assert_equivalent(source, actual)
 
 478         black.assert_stable(source, actual, black.FileMode())
 
 480     @patch("black.dump_to_file", dump_to_stderr)
 
 481     def test_python2_print_function(self) -> None:
 
 482         source, expected = read_data("python2_print_function")
 
 483         mode = black.FileMode(target_versions={TargetVersion.PY27})
 
 484         actual = fs(source, mode=mode)
 
 485         self.assertFormatEqual(expected, actual)
 
 486         black.assert_equivalent(source, actual)
 
 487         black.assert_stable(source, actual, mode)
 
 489     @patch("black.dump_to_file", dump_to_stderr)
 
 490     def test_python2_unicode_literals(self) -> None:
 
 491         source, expected = read_data("python2_unicode_literals")
 
 493         self.assertFormatEqual(expected, actual)
 
 494         black.assert_equivalent(source, actual)
 
 495         black.assert_stable(source, actual, black.FileMode())
 
 497     @patch("black.dump_to_file", dump_to_stderr)
 
 498     def test_stub(self) -> None:
 
 499         mode = black.FileMode(is_pyi=True)
 
 500         source, expected = read_data("stub.pyi")
 
 501         actual = fs(source, mode=mode)
 
 502         self.assertFormatEqual(expected, actual)
 
 503         black.assert_stable(source, actual, mode)
 
 505     @patch("black.dump_to_file", dump_to_stderr)
 
 506     def test_async_as_identifier(self) -> None:
 
 507         source_path = (THIS_DIR / "data" / "async_as_identifier.py").resolve()
 
 508         source, expected = read_data("async_as_identifier")
 
 510         self.assertFormatEqual(expected, actual)
 
 511         major, minor = sys.version_info[:2]
 
 512         if major < 3 or (major <= 3 and minor < 7):
 
 513             black.assert_equivalent(source, actual)
 
 514         black.assert_stable(source, actual, black.FileMode())
 
 515         # ensure black can parse this when the target is 3.6
 
 516         self.invokeBlack([str(source_path), "--target-version", "py36"])
 
 517         # but not on 3.7, because async/await is no longer an identifier
 
 518         self.invokeBlack([str(source_path), "--target-version", "py37"], exit_code=123)
 
 520     @patch("black.dump_to_file", dump_to_stderr)
 
 521     def test_python37(self) -> None:
 
 522         source_path = (THIS_DIR / "data" / "python37.py").resolve()
 
 523         source, expected = read_data("python37")
 
 525         self.assertFormatEqual(expected, actual)
 
 526         major, minor = sys.version_info[:2]
 
 527         if major > 3 or (major == 3 and minor >= 7):
 
 528             black.assert_equivalent(source, actual)
 
 529         black.assert_stable(source, actual, black.FileMode())
 
 530         # ensure black can parse this when the target is 3.7
 
 531         self.invokeBlack([str(source_path), "--target-version", "py37"])
 
 532         # but not on 3.6, because we use async as a reserved keyword
 
 533         self.invokeBlack([str(source_path), "--target-version", "py36"], exit_code=123)
 
 535     @patch("black.dump_to_file", dump_to_stderr)
 
 536     def test_fmtonoff(self) -> None:
 
 537         source, expected = read_data("fmtonoff")
 
 539         self.assertFormatEqual(expected, actual)
 
 540         black.assert_equivalent(source, actual)
 
 541         black.assert_stable(source, actual, black.FileMode())
 
 543     @patch("black.dump_to_file", dump_to_stderr)
 
 544     def test_fmtonoff2(self) -> None:
 
 545         source, expected = read_data("fmtonoff2")
 
 547         self.assertFormatEqual(expected, actual)
 
 548         black.assert_equivalent(source, actual)
 
 549         black.assert_stable(source, actual, black.FileMode())
 
 551     @patch("black.dump_to_file", dump_to_stderr)
 
 552     def test_remove_empty_parentheses_after_class(self) -> None:
 
 553         source, expected = read_data("class_blank_parentheses")
 
 555         self.assertFormatEqual(expected, actual)
 
 556         black.assert_equivalent(source, actual)
 
 557         black.assert_stable(source, actual, black.FileMode())
 
 559     @patch("black.dump_to_file", dump_to_stderr)
 
 560     def test_new_line_between_class_and_code(self) -> None:
 
 561         source, expected = read_data("class_methods_new_line")
 
 563         self.assertFormatEqual(expected, actual)
 
 564         black.assert_equivalent(source, actual)
 
 565         black.assert_stable(source, actual, black.FileMode())
 
 567     @patch("black.dump_to_file", dump_to_stderr)
 
 568     def test_bracket_match(self) -> None:
 
 569         source, expected = read_data("bracketmatch")
 
 571         self.assertFormatEqual(expected, actual)
 
 572         black.assert_equivalent(source, actual)
 
 573         black.assert_stable(source, actual, black.FileMode())
 
 575     @patch("black.dump_to_file", dump_to_stderr)
 
 576     def test_tuple_assign(self) -> None:
 
 577         source, expected = read_data("tupleassign")
 
 579         self.assertFormatEqual(expected, actual)
 
 580         black.assert_equivalent(source, actual)
 
 581         black.assert_stable(source, actual, black.FileMode())
 
 583     def test_tab_comment_indentation(self) -> None:
 
 584         contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
 
 585         contents_spc = "if 1:\n    if 2:\n        pass\n    # comment\n    pass\n"
 
 586         self.assertFormatEqual(contents_spc, fs(contents_spc))
 
 587         self.assertFormatEqual(contents_spc, fs(contents_tab))
 
 589         contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
 
 590         contents_spc = "if 1:\n    if 2:\n        pass\n        # comment\n    pass\n"
 
 591         self.assertFormatEqual(contents_spc, fs(contents_spc))
 
 592         self.assertFormatEqual(contents_spc, fs(contents_tab))
 
 594         # mixed tabs and spaces (valid Python 2 code)
 
 595         contents_tab = "if 1:\n        if 2:\n\t\tpass\n\t# comment\n        pass\n"
 
 596         contents_spc = "if 1:\n    if 2:\n        pass\n    # comment\n    pass\n"
 
 597         self.assertFormatEqual(contents_spc, fs(contents_spc))
 
 598         self.assertFormatEqual(contents_spc, fs(contents_tab))
 
 600         contents_tab = "if 1:\n        if 2:\n\t\tpass\n\t\t# comment\n        pass\n"
 
 601         contents_spc = "if 1:\n    if 2:\n        pass\n        # comment\n    pass\n"
 
 602         self.assertFormatEqual(contents_spc, fs(contents_spc))
 
 603         self.assertFormatEqual(contents_spc, fs(contents_tab))
 
 605     def test_report_verbose(self) -> None:
 
 606         report = black.Report(verbose=True)
 
 610         def out(msg: str, **kwargs: Any) -> None:
 
 611             out_lines.append(msg)
 
 613         def err(msg: str, **kwargs: Any) -> None:
 
 614             err_lines.append(msg)
 
 616         with patch("black.out", out), patch("black.err", err):
 
 617             report.done(Path("f1"), black.Changed.NO)
 
 618             self.assertEqual(len(out_lines), 1)
 
 619             self.assertEqual(len(err_lines), 0)
 
 620             self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
 
 621             self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
 
 622             self.assertEqual(report.return_code, 0)
 
 623             report.done(Path("f2"), black.Changed.YES)
 
 624             self.assertEqual(len(out_lines), 2)
 
 625             self.assertEqual(len(err_lines), 0)
 
 626             self.assertEqual(out_lines[-1], "reformatted f2")
 
 628                 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
 
 630             report.done(Path("f3"), black.Changed.CACHED)
 
 631             self.assertEqual(len(out_lines), 3)
 
 632             self.assertEqual(len(err_lines), 0)
 
 634                 out_lines[-1], "f3 wasn't modified on disk since last run."
 
 637                 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
 
 639             self.assertEqual(report.return_code, 0)
 
 641             self.assertEqual(report.return_code, 1)
 
 643             report.failed(Path("e1"), "boom")
 
 644             self.assertEqual(len(out_lines), 3)
 
 645             self.assertEqual(len(err_lines), 1)
 
 646             self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
 
 648                 unstyle(str(report)),
 
 649                 "1 file reformatted, 2 files left unchanged, "
 
 650                 "1 file failed to reformat.",
 
 652             self.assertEqual(report.return_code, 123)
 
 653             report.done(Path("f3"), black.Changed.YES)
 
 654             self.assertEqual(len(out_lines), 4)
 
 655             self.assertEqual(len(err_lines), 1)
 
 656             self.assertEqual(out_lines[-1], "reformatted f3")
 
 658                 unstyle(str(report)),
 
 659                 "2 files reformatted, 2 files left unchanged, "
 
 660                 "1 file failed to reformat.",
 
 662             self.assertEqual(report.return_code, 123)
 
 663             report.failed(Path("e2"), "boom")
 
 664             self.assertEqual(len(out_lines), 4)
 
 665             self.assertEqual(len(err_lines), 2)
 
 666             self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
 
 668                 unstyle(str(report)),
 
 669                 "2 files reformatted, 2 files left unchanged, "
 
 670                 "2 files failed to reformat.",
 
 672             self.assertEqual(report.return_code, 123)
 
 673             report.path_ignored(Path("wat"), "no match")
 
 674             self.assertEqual(len(out_lines), 5)
 
 675             self.assertEqual(len(err_lines), 2)
 
 676             self.assertEqual(out_lines[-1], "wat ignored: no match")
 
 678                 unstyle(str(report)),
 
 679                 "2 files reformatted, 2 files left unchanged, "
 
 680                 "2 files failed to reformat.",
 
 682             self.assertEqual(report.return_code, 123)
 
 683             report.done(Path("f4"), black.Changed.NO)
 
 684             self.assertEqual(len(out_lines), 6)
 
 685             self.assertEqual(len(err_lines), 2)
 
 686             self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
 
 688                 unstyle(str(report)),
 
 689                 "2 files reformatted, 3 files left unchanged, "
 
 690                 "2 files failed to reformat.",
 
 692             self.assertEqual(report.return_code, 123)
 
 695                 unstyle(str(report)),
 
 696                 "2 files would be reformatted, 3 files would be left unchanged, "
 
 697                 "2 files would fail to reformat.",
 
 700     def test_report_quiet(self) -> None:
 
 701         report = black.Report(quiet=True)
 
 705         def out(msg: str, **kwargs: Any) -> None:
 
 706             out_lines.append(msg)
 
 708         def err(msg: str, **kwargs: Any) -> None:
 
 709             err_lines.append(msg)
 
 711         with patch("black.out", out), patch("black.err", err):
 
 712             report.done(Path("f1"), black.Changed.NO)
 
 713             self.assertEqual(len(out_lines), 0)
 
 714             self.assertEqual(len(err_lines), 0)
 
 715             self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
 
 716             self.assertEqual(report.return_code, 0)
 
 717             report.done(Path("f2"), black.Changed.YES)
 
 718             self.assertEqual(len(out_lines), 0)
 
 719             self.assertEqual(len(err_lines), 0)
 
 721                 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
 
 723             report.done(Path("f3"), black.Changed.CACHED)
 
 724             self.assertEqual(len(out_lines), 0)
 
 725             self.assertEqual(len(err_lines), 0)
 
 727                 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
 
 729             self.assertEqual(report.return_code, 0)
 
 731             self.assertEqual(report.return_code, 1)
 
 733             report.failed(Path("e1"), "boom")
 
 734             self.assertEqual(len(out_lines), 0)
 
 735             self.assertEqual(len(err_lines), 1)
 
 736             self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
 
 738                 unstyle(str(report)),
 
 739                 "1 file reformatted, 2 files left unchanged, "
 
 740                 "1 file failed to reformat.",
 
 742             self.assertEqual(report.return_code, 123)
 
 743             report.done(Path("f3"), black.Changed.YES)
 
 744             self.assertEqual(len(out_lines), 0)
 
 745             self.assertEqual(len(err_lines), 1)
 
 747                 unstyle(str(report)),
 
 748                 "2 files reformatted, 2 files left unchanged, "
 
 749                 "1 file failed to reformat.",
 
 751             self.assertEqual(report.return_code, 123)
 
 752             report.failed(Path("e2"), "boom")
 
 753             self.assertEqual(len(out_lines), 0)
 
 754             self.assertEqual(len(err_lines), 2)
 
 755             self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
 
 757                 unstyle(str(report)),
 
 758                 "2 files reformatted, 2 files left unchanged, "
 
 759                 "2 files failed to reformat.",
 
 761             self.assertEqual(report.return_code, 123)
 
 762             report.path_ignored(Path("wat"), "no match")
 
 763             self.assertEqual(len(out_lines), 0)
 
 764             self.assertEqual(len(err_lines), 2)
 
 766                 unstyle(str(report)),
 
 767                 "2 files reformatted, 2 files left unchanged, "
 
 768                 "2 files failed to reformat.",
 
 770             self.assertEqual(report.return_code, 123)
 
 771             report.done(Path("f4"), black.Changed.NO)
 
 772             self.assertEqual(len(out_lines), 0)
 
 773             self.assertEqual(len(err_lines), 2)
 
 775                 unstyle(str(report)),
 
 776                 "2 files reformatted, 3 files left unchanged, "
 
 777                 "2 files failed to reformat.",
 
 779             self.assertEqual(report.return_code, 123)
 
 782                 unstyle(str(report)),
 
 783                 "2 files would be reformatted, 3 files would be left unchanged, "
 
 784                 "2 files would fail to reformat.",
 
 787     def test_report_normal(self) -> None:
 
 788         report = black.Report()
 
 792         def out(msg: str, **kwargs: Any) -> None:
 
 793             out_lines.append(msg)
 
 795         def err(msg: str, **kwargs: Any) -> None:
 
 796             err_lines.append(msg)
 
 798         with patch("black.out", out), patch("black.err", err):
 
 799             report.done(Path("f1"), black.Changed.NO)
 
 800             self.assertEqual(len(out_lines), 0)
 
 801             self.assertEqual(len(err_lines), 0)
 
 802             self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
 
 803             self.assertEqual(report.return_code, 0)
 
 804             report.done(Path("f2"), black.Changed.YES)
 
 805             self.assertEqual(len(out_lines), 1)
 
 806             self.assertEqual(len(err_lines), 0)
 
 807             self.assertEqual(out_lines[-1], "reformatted f2")
 
 809                 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
 
 811             report.done(Path("f3"), black.Changed.CACHED)
 
 812             self.assertEqual(len(out_lines), 1)
 
 813             self.assertEqual(len(err_lines), 0)
 
 814             self.assertEqual(out_lines[-1], "reformatted f2")
 
 816                 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
 
 818             self.assertEqual(report.return_code, 0)
 
 820             self.assertEqual(report.return_code, 1)
 
 822             report.failed(Path("e1"), "boom")
 
 823             self.assertEqual(len(out_lines), 1)
 
 824             self.assertEqual(len(err_lines), 1)
 
 825             self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
 
 827                 unstyle(str(report)),
 
 828                 "1 file reformatted, 2 files left unchanged, "
 
 829                 "1 file failed to reformat.",
 
 831             self.assertEqual(report.return_code, 123)
 
 832             report.done(Path("f3"), black.Changed.YES)
 
 833             self.assertEqual(len(out_lines), 2)
 
 834             self.assertEqual(len(err_lines), 1)
 
 835             self.assertEqual(out_lines[-1], "reformatted f3")
 
 837                 unstyle(str(report)),
 
 838                 "2 files reformatted, 2 files left unchanged, "
 
 839                 "1 file failed to reformat.",
 
 841             self.assertEqual(report.return_code, 123)
 
 842             report.failed(Path("e2"), "boom")
 
 843             self.assertEqual(len(out_lines), 2)
 
 844             self.assertEqual(len(err_lines), 2)
 
 845             self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
 
 847                 unstyle(str(report)),
 
 848                 "2 files reformatted, 2 files left unchanged, "
 
 849                 "2 files failed to reformat.",
 
 851             self.assertEqual(report.return_code, 123)
 
 852             report.path_ignored(Path("wat"), "no match")
 
 853             self.assertEqual(len(out_lines), 2)
 
 854             self.assertEqual(len(err_lines), 2)
 
 856                 unstyle(str(report)),
 
 857                 "2 files reformatted, 2 files left unchanged, "
 
 858                 "2 files failed to reformat.",
 
 860             self.assertEqual(report.return_code, 123)
 
 861             report.done(Path("f4"), black.Changed.NO)
 
 862             self.assertEqual(len(out_lines), 2)
 
 863             self.assertEqual(len(err_lines), 2)
 
 865                 unstyle(str(report)),
 
 866                 "2 files reformatted, 3 files left unchanged, "
 
 867                 "2 files failed to reformat.",
 
 869             self.assertEqual(report.return_code, 123)
 
 872                 unstyle(str(report)),
 
 873                 "2 files would be reformatted, 3 files would be left unchanged, "
 
 874                 "2 files would fail to reformat.",
 
 877     def test_lib2to3_parse(self) -> None:
 
 878         with self.assertRaises(black.InvalidInput):
 
 879             black.lib2to3_parse("invalid syntax")
 
 882         black.lib2to3_parse(straddling)
 
 883         black.lib2to3_parse(straddling, {TargetVersion.PY27})
 
 884         black.lib2to3_parse(straddling, {TargetVersion.PY36})
 
 885         black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
 
 888         black.lib2to3_parse(py2_only)
 
 889         black.lib2to3_parse(py2_only, {TargetVersion.PY27})
 
 890         with self.assertRaises(black.InvalidInput):
 
 891             black.lib2to3_parse(py2_only, {TargetVersion.PY36})
 
 892         with self.assertRaises(black.InvalidInput):
 
 893             black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
 
 895         py3_only = "exec(x, end=y)"
 
 896         black.lib2to3_parse(py3_only)
 
 897         with self.assertRaises(black.InvalidInput):
 
 898             black.lib2to3_parse(py3_only, {TargetVersion.PY27})
 
 899         black.lib2to3_parse(py3_only, {TargetVersion.PY36})
 
 900         black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
 
 902     def test_get_features_used(self) -> None:
 
 903         node = black.lib2to3_parse("def f(*, arg): ...\n")
 
 904         self.assertEqual(black.get_features_used(node), set())
 
 905         node = black.lib2to3_parse("def f(*, arg,): ...\n")
 
 906         self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
 
 907         node = black.lib2to3_parse("f(*arg,)\n")
 
 909             black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
 
 911         node = black.lib2to3_parse("def f(*, arg): f'string'\n")
 
 912         self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
 
 913         node = black.lib2to3_parse("123_456\n")
 
 914         self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
 
 915         node = black.lib2to3_parse("123456\n")
 
 916         self.assertEqual(black.get_features_used(node), set())
 
 917         source, expected = read_data("function")
 
 918         node = black.lib2to3_parse(source)
 
 919         expected_features = {
 
 920             Feature.TRAILING_COMMA_IN_CALL,
 
 921             Feature.TRAILING_COMMA_IN_DEF,
 
 924         self.assertEqual(black.get_features_used(node), expected_features)
 
 925         node = black.lib2to3_parse(expected)
 
 926         self.assertEqual(black.get_features_used(node), expected_features)
 
 927         source, expected = read_data("expression")
 
 928         node = black.lib2to3_parse(source)
 
 929         self.assertEqual(black.get_features_used(node), set())
 
 930         node = black.lib2to3_parse(expected)
 
 931         self.assertEqual(black.get_features_used(node), set())
 
 933     def test_get_future_imports(self) -> None:
 
 934         node = black.lib2to3_parse("\n")
 
 935         self.assertEqual(set(), black.get_future_imports(node))
 
 936         node = black.lib2to3_parse("from __future__ import black\n")
 
 937         self.assertEqual({"black"}, black.get_future_imports(node))
 
 938         node = black.lib2to3_parse("from __future__ import multiple, imports\n")
 
 939         self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
 
 940         node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
 
 941         self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
 
 942         node = black.lib2to3_parse(
 
 943             "from __future__ import multiple\nfrom __future__ import imports\n"
 
 945         self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
 
 946         node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
 
 947         self.assertEqual({"black"}, black.get_future_imports(node))
 
 948         node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
 
 949         self.assertEqual({"black"}, black.get_future_imports(node))
 
 950         node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
 
 951         self.assertEqual(set(), black.get_future_imports(node))
 
 952         node = black.lib2to3_parse("from some.module import black\n")
 
 953         self.assertEqual(set(), black.get_future_imports(node))
 
 954         node = black.lib2to3_parse(
 
 955             "from __future__ import unicode_literals as _unicode_literals"
 
 957         self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
 
 958         node = black.lib2to3_parse(
 
 959             "from __future__ import unicode_literals as _lol, print"
 
 961         self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
 
 963     def test_debug_visitor(self) -> None:
 
 964         source, _ = read_data("debug_visitor.py")
 
 965         expected, _ = read_data("debug_visitor.out")
 
 969         def out(msg: str, **kwargs: Any) -> None:
 
 970             out_lines.append(msg)
 
 972         def err(msg: str, **kwargs: Any) -> None:
 
 973             err_lines.append(msg)
 
 975         with patch("black.out", out), patch("black.err", err):
 
 976             black.DebugVisitor.show(source)
 
 977         actual = "\n".join(out_lines) + "\n"
 
 979         if expected != actual:
 
 980             log_name = black.dump_to_file(*out_lines)
 
 984             f"AST print out is different. Actual version dumped to {log_name}",
 
 987     def test_format_file_contents(self) -> None:
 
 989         mode = black.FileMode()
 
 990         with self.assertRaises(black.NothingChanged):
 
 991             black.format_file_contents(empty, mode=mode, fast=False)
 
 993         with self.assertRaises(black.NothingChanged):
 
 994             black.format_file_contents(just_nl, mode=mode, fast=False)
 
 995         same = "l = [1, 2, 3]\n"
 
 996         with self.assertRaises(black.NothingChanged):
 
 997             black.format_file_contents(same, mode=mode, fast=False)
 
 998         different = "l = [1,2,3]"
 
1000         actual = black.format_file_contents(different, mode=mode, fast=False)
 
1001         self.assertEqual(expected, actual)
 
1002         invalid = "return if you can"
 
1003         with self.assertRaises(black.InvalidInput) as e:
 
1004             black.format_file_contents(invalid, mode=mode, fast=False)
 
1005         self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
 
1007     def test_endmarker(self) -> None:
 
1008         n = black.lib2to3_parse("\n")
 
1009         self.assertEqual(n.type, black.syms.file_input)
 
1010         self.assertEqual(len(n.children), 1)
 
1011         self.assertEqual(n.children[0].type, black.token.ENDMARKER)
 
1013     @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
 
1014     def test_assertFormatEqual(self) -> None:
 
1018         def out(msg: str, **kwargs: Any) -> None:
 
1019             out_lines.append(msg)
 
1021         def err(msg: str, **kwargs: Any) -> None:
 
1022             err_lines.append(msg)
 
1024         with patch("black.out", out), patch("black.err", err):
 
1025             with self.assertRaises(AssertionError):
 
1026                 self.assertFormatEqual("l = [1, 2, 3]", "l = [1, 2, 3,]")
 
1028         out_str = "".join(out_lines)
 
1029         self.assertTrue("Expected tree:" in out_str)
 
1030         self.assertTrue("Actual tree:" in out_str)
 
1031         self.assertEqual("".join(err_lines), "")
 
1033     def test_cache_broken_file(self) -> None:
 
1034         mode = black.FileMode()
 
1035         with cache_dir() as workspace:
 
1036             cache_file = black.get_cache_file(mode)
 
1037             with cache_file.open("w") as fobj:
 
1038                 fobj.write("this is not a pickle")
 
1039             self.assertEqual(black.read_cache(mode), {})
 
1040             src = (workspace / "test.py").resolve()
 
1041             with src.open("w") as fobj:
 
1042                 fobj.write("print('hello')")
 
1043             self.invokeBlack([str(src)])
 
1044             cache = black.read_cache(mode)
 
1045             self.assertIn(src, cache)
 
1047     def test_cache_single_file_already_cached(self) -> None:
 
1048         mode = black.FileMode()
 
1049         with cache_dir() as workspace:
 
1050             src = (workspace / "test.py").resolve()
 
1051             with src.open("w") as fobj:
 
1052                 fobj.write("print('hello')")
 
1053             black.write_cache({}, [src], mode)
 
1054             self.invokeBlack([str(src)])
 
1055             with src.open("r") as fobj:
 
1056                 self.assertEqual(fobj.read(), "print('hello')")
 
1058     @event_loop(close=False)
 
1059     def test_cache_multiple_files(self) -> None:
 
1060         mode = black.FileMode()
 
1061         with cache_dir() as workspace, patch(
 
1062             "black.ProcessPoolExecutor", new=ThreadPoolExecutor
 
1064             one = (workspace / "one.py").resolve()
 
1065             with one.open("w") as fobj:
 
1066                 fobj.write("print('hello')")
 
1067             two = (workspace / "two.py").resolve()
 
1068             with two.open("w") as fobj:
 
1069                 fobj.write("print('hello')")
 
1070             black.write_cache({}, [one], mode)
 
1071             self.invokeBlack([str(workspace)])
 
1072             with one.open("r") as fobj:
 
1073                 self.assertEqual(fobj.read(), "print('hello')")
 
1074             with two.open("r") as fobj:
 
1075                 self.assertEqual(fobj.read(), 'print("hello")\n')
 
1076             cache = black.read_cache(mode)
 
1077             self.assertIn(one, cache)
 
1078             self.assertIn(two, cache)
 
1080     def test_no_cache_when_writeback_diff(self) -> None:
 
1081         mode = black.FileMode()
 
1082         with cache_dir() as workspace:
 
1083             src = (workspace / "test.py").resolve()
 
1084             with src.open("w") as fobj:
 
1085                 fobj.write("print('hello')")
 
1086             self.invokeBlack([str(src), "--diff"])
 
1087             cache_file = black.get_cache_file(mode)
 
1088             self.assertFalse(cache_file.exists())
 
1090     def test_no_cache_when_stdin(self) -> None:
 
1091         mode = black.FileMode()
 
1093             result = CliRunner().invoke(
 
1094                 black.main, ["-"], input=BytesIO(b"print('hello')")
 
1096             self.assertEqual(result.exit_code, 0)
 
1097             cache_file = black.get_cache_file(mode)
 
1098             self.assertFalse(cache_file.exists())
 
1100     def test_read_cache_no_cachefile(self) -> None:
 
1101         mode = black.FileMode()
 
1103             self.assertEqual(black.read_cache(mode), {})
 
1105     def test_write_cache_read_cache(self) -> None:
 
1106         mode = black.FileMode()
 
1107         with cache_dir() as workspace:
 
1108             src = (workspace / "test.py").resolve()
 
1110             black.write_cache({}, [src], mode)
 
1111             cache = black.read_cache(mode)
 
1112             self.assertIn(src, cache)
 
1113             self.assertEqual(cache[src], black.get_cache_info(src))
 
1115     def test_filter_cached(self) -> None:
 
1116         with TemporaryDirectory() as workspace:
 
1117             path = Path(workspace)
 
1118             uncached = (path / "uncached").resolve()
 
1119             cached = (path / "cached").resolve()
 
1120             cached_but_changed = (path / "changed").resolve()
 
1123             cached_but_changed.touch()
 
1124             cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
 
1125             todo, done = black.filter_cached(
 
1126                 cache, {uncached, cached, cached_but_changed}
 
1128             self.assertEqual(todo, {uncached, cached_but_changed})
 
1129             self.assertEqual(done, {cached})
 
1131     def test_write_cache_creates_directory_if_needed(self) -> None:
 
1132         mode = black.FileMode()
 
1133         with cache_dir(exists=False) as workspace:
 
1134             self.assertFalse(workspace.exists())
 
1135             black.write_cache({}, [], mode)
 
1136             self.assertTrue(workspace.exists())
 
1138     @event_loop(close=False)
 
1139     def test_failed_formatting_does_not_get_cached(self) -> None:
 
1140         mode = black.FileMode()
 
1141         with cache_dir() as workspace, patch(
 
1142             "black.ProcessPoolExecutor", new=ThreadPoolExecutor
 
1144             failing = (workspace / "failing.py").resolve()
 
1145             with failing.open("w") as fobj:
 
1146                 fobj.write("not actually python")
 
1147             clean = (workspace / "clean.py").resolve()
 
1148             with clean.open("w") as fobj:
 
1149                 fobj.write('print("hello")\n')
 
1150             self.invokeBlack([str(workspace)], exit_code=123)
 
1151             cache = black.read_cache(mode)
 
1152             self.assertNotIn(failing, cache)
 
1153             self.assertIn(clean, cache)
 
1155     def test_write_cache_write_fail(self) -> None:
 
1156         mode = black.FileMode()
 
1157         with cache_dir(), patch.object(Path, "open") as mock:
 
1158             mock.side_effect = OSError
 
1159             black.write_cache({}, [], mode)
 
1161     @event_loop(close=False)
 
1162     def test_check_diff_use_together(self) -> None:
 
1164             # Files which will be reformatted.
 
1165             src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
 
1166             self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
 
1167             # Files which will not be reformatted.
 
1168             src2 = (THIS_DIR / "data" / "composition.py").resolve()
 
1169             self.invokeBlack([str(src2), "--diff", "--check"])
 
1170             # Multi file command.
 
1171             self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
 
1173     def test_no_files(self) -> None:
 
1175             # Without an argument, black exits with error code 0.
 
1176             self.invokeBlack([])
 
1178     def test_broken_symlink(self) -> None:
 
1179         with cache_dir() as workspace:
 
1180             symlink = workspace / "broken_link.py"
 
1182                 symlink.symlink_to("nonexistent.py")
 
1183             except OSError as e:
 
1184                 self.skipTest(f"Can't create symlinks: {e}")
 
1185             self.invokeBlack([str(workspace.resolve())])
 
1187     def test_read_cache_line_lengths(self) -> None:
 
1188         mode = black.FileMode()
 
1189         short_mode = black.FileMode(line_length=1)
 
1190         with cache_dir() as workspace:
 
1191             path = (workspace / "file.py").resolve()
 
1193             black.write_cache({}, [path], mode)
 
1194             one = black.read_cache(mode)
 
1195             self.assertIn(path, one)
 
1196             two = black.read_cache(short_mode)
 
1197             self.assertNotIn(path, two)
 
1199     def test_single_file_force_pyi(self) -> None:
 
1200         reg_mode = black.FileMode()
 
1201         pyi_mode = black.FileMode(is_pyi=True)
 
1202         contents, expected = read_data("force_pyi")
 
1203         with cache_dir() as workspace:
 
1204             path = (workspace / "file.py").resolve()
 
1205             with open(path, "w") as fh:
 
1207             self.invokeBlack([str(path), "--pyi"])
 
1208             with open(path, "r") as fh:
 
1210             # verify cache with --pyi is separate
 
1211             pyi_cache = black.read_cache(pyi_mode)
 
1212             self.assertIn(path, pyi_cache)
 
1213             normal_cache = black.read_cache(reg_mode)
 
1214             self.assertNotIn(path, normal_cache)
 
1215         self.assertEqual(actual, expected)
 
1217     @event_loop(close=False)
 
1218     def test_multi_file_force_pyi(self) -> None:
 
1219         reg_mode = black.FileMode()
 
1220         pyi_mode = black.FileMode(is_pyi=True)
 
1221         contents, expected = read_data("force_pyi")
 
1222         with cache_dir() as workspace:
 
1224                 (workspace / "file1.py").resolve(),
 
1225                 (workspace / "file2.py").resolve(),
 
1228                 with open(path, "w") as fh:
 
1230             self.invokeBlack([str(p) for p in paths] + ["--pyi"])
 
1232                 with open(path, "r") as fh:
 
1234                 self.assertEqual(actual, expected)
 
1235             # verify cache with --pyi is separate
 
1236             pyi_cache = black.read_cache(pyi_mode)
 
1237             normal_cache = black.read_cache(reg_mode)
 
1239                 self.assertIn(path, pyi_cache)
 
1240                 self.assertNotIn(path, normal_cache)
 
1242     def test_pipe_force_pyi(self) -> None:
 
1243         source, expected = read_data("force_pyi")
 
1244         result = CliRunner().invoke(
 
1245             black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
 
1247         self.assertEqual(result.exit_code, 0)
 
1248         actual = result.output
 
1249         self.assertFormatEqual(actual, expected)
 
1251     def test_single_file_force_py36(self) -> None:
 
1252         reg_mode = black.FileMode()
 
1253         py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
 
1254         source, expected = read_data("force_py36")
 
1255         with cache_dir() as workspace:
 
1256             path = (workspace / "file.py").resolve()
 
1257             with open(path, "w") as fh:
 
1259             self.invokeBlack([str(path), *PY36_ARGS])
 
1260             with open(path, "r") as fh:
 
1262             # verify cache with --target-version is separate
 
1263             py36_cache = black.read_cache(py36_mode)
 
1264             self.assertIn(path, py36_cache)
 
1265             normal_cache = black.read_cache(reg_mode)
 
1266             self.assertNotIn(path, normal_cache)
 
1267         self.assertEqual(actual, expected)
 
1269     @event_loop(close=False)
 
1270     def test_multi_file_force_py36(self) -> None:
 
1271         reg_mode = black.FileMode()
 
1272         py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
 
1273         source, expected = read_data("force_py36")
 
1274         with cache_dir() as workspace:
 
1276                 (workspace / "file1.py").resolve(),
 
1277                 (workspace / "file2.py").resolve(),
 
1280                 with open(path, "w") as fh:
 
1282             self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
 
1284                 with open(path, "r") as fh:
 
1286                 self.assertEqual(actual, expected)
 
1287             # verify cache with --target-version is separate
 
1288             pyi_cache = black.read_cache(py36_mode)
 
1289             normal_cache = black.read_cache(reg_mode)
 
1291                 self.assertIn(path, pyi_cache)
 
1292                 self.assertNotIn(path, normal_cache)
 
1294     def test_pipe_force_py36(self) -> None:
 
1295         source, expected = read_data("force_py36")
 
1296         result = CliRunner().invoke(
 
1298             ["-", "-q", "--target-version=py36"],
 
1299             input=BytesIO(source.encode("utf8")),
 
1301         self.assertEqual(result.exit_code, 0)
 
1302         actual = result.output
 
1303         self.assertFormatEqual(actual, expected)
 
1305     def test_include_exclude(self) -> None:
 
1306         path = THIS_DIR / "data" / "include_exclude_tests"
 
1307         include = re.compile(r"\.pyi?$")
 
1308         exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
 
1309         report = black.Report()
 
1310         sources: List[Path] = []
 
1312             Path(path / "b/dont_exclude/a.py"),
 
1313             Path(path / "b/dont_exclude/a.pyi"),
 
1315         this_abs = THIS_DIR.resolve()
 
1317             black.gen_python_files_in_dir(path, this_abs, include, exclude, report)
 
1319         self.assertEqual(sorted(expected), sorted(sources))
 
1321     def test_empty_include(self) -> None:
 
1322         path = THIS_DIR / "data" / "include_exclude_tests"
 
1323         report = black.Report()
 
1324         empty = re.compile(r"")
 
1325         sources: List[Path] = []
 
1327             Path(path / "b/exclude/a.pie"),
 
1328             Path(path / "b/exclude/a.py"),
 
1329             Path(path / "b/exclude/a.pyi"),
 
1330             Path(path / "b/dont_exclude/a.pie"),
 
1331             Path(path / "b/dont_exclude/a.py"),
 
1332             Path(path / "b/dont_exclude/a.pyi"),
 
1333             Path(path / "b/.definitely_exclude/a.pie"),
 
1334             Path(path / "b/.definitely_exclude/a.py"),
 
1335             Path(path / "b/.definitely_exclude/a.pyi"),
 
1337         this_abs = THIS_DIR.resolve()
 
1339             black.gen_python_files_in_dir(
 
1340                 path, this_abs, empty, re.compile(black.DEFAULT_EXCLUDES), report
 
1343         self.assertEqual(sorted(expected), sorted(sources))
 
1345     def test_empty_exclude(self) -> None:
 
1346         path = THIS_DIR / "data" / "include_exclude_tests"
 
1347         report = black.Report()
 
1348         empty = re.compile(r"")
 
1349         sources: List[Path] = []
 
1351             Path(path / "b/dont_exclude/a.py"),
 
1352             Path(path / "b/dont_exclude/a.pyi"),
 
1353             Path(path / "b/exclude/a.py"),
 
1354             Path(path / "b/exclude/a.pyi"),
 
1355             Path(path / "b/.definitely_exclude/a.py"),
 
1356             Path(path / "b/.definitely_exclude/a.pyi"),
 
1358         this_abs = THIS_DIR.resolve()
 
1360             black.gen_python_files_in_dir(
 
1361                 path, this_abs, re.compile(black.DEFAULT_INCLUDES), empty, report
 
1364         self.assertEqual(sorted(expected), sorted(sources))
 
1366     def test_invalid_include_exclude(self) -> None:
 
1367         for option in ["--include", "--exclude"]:
 
1368             self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
 
1370     def test_preserves_line_endings(self) -> None:
 
1371         with TemporaryDirectory() as workspace:
 
1372             test_file = Path(workspace) / "test.py"
 
1373             for nl in ["\n", "\r\n"]:
 
1374                 contents = nl.join(["def f(  ):", "    pass"])
 
1375                 test_file.write_bytes(contents.encode())
 
1376                 ff(test_file, write_back=black.WriteBack.YES)
 
1377                 updated_contents: bytes = test_file.read_bytes()
 
1378                 self.assertIn(nl.encode(), updated_contents)
 
1380                     self.assertNotIn(b"\r\n", updated_contents)
 
1382     def test_preserves_line_endings_via_stdin(self) -> None:
 
1383         for nl in ["\n", "\r\n"]:
 
1384             contents = nl.join(["def f(  ):", "    pass"])
 
1385             runner = BlackRunner()
 
1386             result = runner.invoke(
 
1387                 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
 
1389             self.assertEqual(result.exit_code, 0)
 
1390             output = runner.stdout_bytes
 
1391             self.assertIn(nl.encode("utf8"), output)
 
1393                 self.assertNotIn(b"\r\n", output)
 
1395     def test_assert_equivalent_different_asts(self) -> None:
 
1396         with self.assertRaises(AssertionError):
 
1397             black.assert_equivalent("{}", "None")
 
1399     def test_symlink_out_of_root_directory(self) -> None:
 
1403         include = re.compile(black.DEFAULT_INCLUDES)
 
1404         exclude = re.compile(black.DEFAULT_EXCLUDES)
 
1405         report = black.Report()
 
1406         # `child` should behave like a symlink which resolved path is clearly
 
1407         # outside of the `root` directory.
 
1408         path.iterdir.return_value = [child]
 
1409         child.resolve.return_value = Path("/a/b/c")
 
1410         child.is_symlink.return_value = True
 
1412             list(black.gen_python_files_in_dir(path, root, include, exclude, report))
 
1413         except ValueError as ve:
 
1414             self.fail(f"`get_python_files_in_dir()` failed: {ve}")
 
1415         path.iterdir.assert_called_once()
 
1416         child.resolve.assert_called_once()
 
1417         child.is_symlink.assert_called_once()
 
1418         # `child` should behave like a strange file which resolved path is clearly
 
1419         # outside of the `root` directory.
 
1420         child.is_symlink.return_value = False
 
1421         with self.assertRaises(ValueError):
 
1422             list(black.gen_python_files_in_dir(path, root, include, exclude, report))
 
1423         path.iterdir.assert_called()
 
1424         self.assertEqual(path.iterdir.call_count, 2)
 
1425         child.resolve.assert_called()
 
1426         self.assertEqual(child.resolve.call_count, 2)
 
1427         child.is_symlink.assert_called()
 
1428         self.assertEqual(child.is_symlink.call_count, 2)
 
1430     def test_shhh_click(self) -> None:
 
1432             from click import _unicodefun  # type: ignore
 
1433         except ModuleNotFoundError:
 
1434             self.skipTest("Incompatible Click version")
 
1435         if not hasattr(_unicodefun, "_verify_python3_env"):
 
1436             self.skipTest("Incompatible Click version")
 
1437         # First, let's see if Click is crashing with a preferred ASCII charset.
 
1438         with patch("locale.getpreferredencoding") as gpe:
 
1439             gpe.return_value = "ASCII"
 
1440             with self.assertRaises(RuntimeError):
 
1441                 _unicodefun._verify_python3_env()
 
1442         # Now, let's silence Click...
 
1444         # ...and confirm it's silent.
 
1445         with patch("locale.getpreferredencoding") as gpe:
 
1446             gpe.return_value = "ASCII"
 
1448                 _unicodefun._verify_python3_env()
 
1449             except RuntimeError as re:
 
1450                 self.fail(f"`patch_click()` failed, exception still raised: {re}")
 
1452     def test_root_logger_not_used_directly(self) -> None:
 
1453         def fail(*args: Any, **kwargs: Any) -> None:
 
1454             self.fail("Record created with root logger")
 
1456         with patch.multiple(
 
1467     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
 
1469     async def test_blackd_request_needs_formatting(self) -> None:
 
1470         app = blackd.make_app()
 
1471         async with TestClient(TestServer(app)) as client:
 
1472             response = await client.post("/", data=b"print('hello world')")
 
1473             self.assertEqual(response.status, 200)
 
1474             self.assertEqual(response.charset, "utf8")
 
1475             self.assertEqual(await response.read(), b'print("hello world")\n')
 
1477     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
 
1479     async def test_blackd_request_no_change(self) -> None:
 
1480         app = blackd.make_app()
 
1481         async with TestClient(TestServer(app)) as client:
 
1482             response = await client.post("/", data=b'print("hello world")\n')
 
1483             self.assertEqual(response.status, 204)
 
1484             self.assertEqual(await response.read(), b"")
 
1486     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
 
1488     async def test_blackd_request_syntax_error(self) -> None:
 
1489         app = blackd.make_app()
 
1490         async with TestClient(TestServer(app)) as client:
 
1491             response = await client.post("/", data=b"what even ( is")
 
1492             self.assertEqual(response.status, 400)
 
1493             content = await response.text()
 
1495                 content.startswith("Cannot parse"),
 
1496                 msg=f"Expected error to start with 'Cannot parse', got {repr(content)}",
 
1499     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
 
1501     async def test_blackd_unsupported_version(self) -> None:
 
1502         app = blackd.make_app()
 
1503         async with TestClient(TestServer(app)) as client:
 
1504             response = await client.post(
 
1505                 "/", data=b"what", headers={blackd.VERSION_HEADER: "2"}
 
1507             self.assertEqual(response.status, 501)
 
1509     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
 
1511     async def test_blackd_supported_version(self) -> None:
 
1512         app = blackd.make_app()
 
1513         async with TestClient(TestServer(app)) as client:
 
1514             response = await client.post(
 
1515                 "/", data=b"what", headers={blackd.VERSION_HEADER: "1"}
 
1517             self.assertEqual(response.status, 200)
 
1519     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
 
1521     async def test_blackd_invalid_python_variant(self) -> None:
 
1522         app = blackd.make_app()
 
1523         async with TestClient(TestServer(app)) as client:
 
1525             async def check(header_value: str, expected_status: int = 400) -> None:
 
1526                 response = await client.post(
 
1529                     headers={blackd.PYTHON_VARIANT_HEADER: header_value},
 
1531                 self.assertEqual(response.status, expected_status)
 
1534             await check("ruby3.5")
 
1535             await check("pyi3.6")
 
1536             await check("py1.5")
 
1538             await check("py2.8")
 
1540             await check("pypy3.0")
 
1541             await check("jython3.4")
 
1543     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
 
1545     async def test_blackd_pyi(self) -> None:
 
1546         app = blackd.make_app()
 
1547         async with TestClient(TestServer(app)) as client:
 
1548             source, expected = read_data("stub.pyi")
 
1549             response = await client.post(
 
1550                 "/", data=source, headers={blackd.PYTHON_VARIANT_HEADER: "pyi"}
 
1552             self.assertEqual(response.status, 200)
 
1553             self.assertEqual(await response.text(), expected)
 
1555     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
 
1557     async def test_blackd_python_variant(self) -> None:
 
1558         app = blackd.make_app()
 
1561             "    and_has_a_bunch_of,\n"
 
1562             "    very_long_arguments_too,\n"
 
1563             "    and_lots_of_them_as_well_lol,\n"
 
1564             "    **and_very_long_keyword_arguments\n"
 
1568         async with TestClient(TestServer(app)) as client:
 
1570             async def check(header_value: str, expected_status: int) -> None:
 
1571                 response = await client.post(
 
1572                     "/", data=code, headers={blackd.PYTHON_VARIANT_HEADER: header_value}
 
1574                 self.assertEqual(response.status, expected_status)
 
1576             await check("3.6", 200)
 
1577             await check("py3.6", 200)
 
1578             await check("3.6,3.7", 200)
 
1579             await check("3.6,py3.7", 200)
 
1581             await check("2", 204)
 
1582             await check("2.7", 204)
 
1583             await check("py2.7", 204)
 
1584             await check("3.4", 204)
 
1585             await check("py3.4", 204)
 
1587     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
 
1589     async def test_blackd_line_length(self) -> None:
 
1590         app = blackd.make_app()
 
1591         async with TestClient(TestServer(app)) as client:
 
1592             response = await client.post(
 
1593                 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "7"}
 
1595             self.assertEqual(response.status, 200)
 
1597     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
 
1599     async def test_blackd_invalid_line_length(self) -> None:
 
1600         app = blackd.make_app()
 
1601         async with TestClient(TestServer(app)) as client:
 
1602             response = await client.post(
 
1604                 data=b'print("hello")\n',
 
1605                 headers={blackd.LINE_LENGTH_HEADER: "NaN"},
 
1607             self.assertEqual(response.status, 400)
 
1609     @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
 
1610     def test_blackd_main(self) -> None:
 
1611         with patch("blackd.web.run_app"):
 
1612             result = CliRunner().invoke(blackd.main, [])
 
1613             if result.exception is not None:
 
1614                 raise result.exception
 
1615             self.assertEqual(result.exit_code, 0)
 
1618 if __name__ == "__main__":
 
1619     unittest.main(module="test_black")