X-Git-Url: https://git.madduck.net/etc/vim.git/blobdiff_plain/8d6d92aa5b5248b5ff70ebf7977f8af5cbcb10b9..1ebe9b70c5624da964364841e30a2e3ffe109c4e:/tests/test_black.py diff --git a/tests/test_black.py b/tests/test_black.py index 11bba1f..f5d4e11 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -3,17 +3,31 @@ import asyncio import logging from concurrent.futures import ThreadPoolExecutor from contextlib import contextmanager +from dataclasses import replace from functools import partial +import inspect from io import BytesIO, TextIOWrapper import os from pathlib import Path import regex as re import sys from tempfile import TemporaryDirectory -from typing import Any, BinaryIO, Generator, List, Tuple, Iterator, TypeVar +import types +from typing import ( + Any, + BinaryIO, + Callable, + Dict, + Generator, + List, + Tuple, + Iterator, + TypeVar, +) import unittest from unittest.mock import patch, MagicMock +import click from click import unstyle from click.testing import CliRunner @@ -31,10 +45,16 @@ else: from pathspec import PathSpec -ff = partial(black.format_file_in_place, mode=black.FileMode(), fast=True) -fs = partial(black.format_str, mode=black.FileMode()) +# Import other test classes +from .test_primer import PrimerCLITests # noqa: F401 + + +DEFAULT_MODE = black.FileMode(experimental_string_processing=True) +ff = partial(black.format_file_in_place, mode=DEFAULT_MODE, fast=True) +fs = partial(black.format_str, mode=DEFAULT_MODE) THIS_FILE = Path(__file__) THIS_DIR = THIS_FILE.parent +PROJECT_ROOT = THIS_DIR.parent DETERMINISTIC_HEADER = "[Deterministic header]" EMPTY_LINE = "# EMPTY LINE WITH WHITESPACE" + " (this comment will be removed)" PY36_ARGS = [ @@ -54,7 +74,7 @@ def read_data(name: str, data: bool = True) -> Tuple[str, str]: name += ".py" _input: List[str] = [] _output: List[str] = [] - base_dir = THIS_DIR / "data" if data else THIS_DIR + base_dir = THIS_DIR / "data" if data else PROJECT_ROOT with open(base_dir / name, "r", encoding="utf8") as test: lines = test.readlines() result = _input @@ -82,7 +102,7 @@ def cache_dir(exists: bool = True) -> Iterator[Path]: @contextmanager -def event_loop(close: bool) -> Iterator[None]: +def event_loop() -> Iterator[None]: policy = asyncio.get_event_loop_policy() loop = policy.new_event_loop() asyncio.set_event_loop(loop) @@ -90,8 +110,7 @@ def event_loop(close: bool) -> Iterator[None]: yield finally: - if close: - loop.close() + loop.close() @contextmanager @@ -105,6 +124,20 @@ def skip_if_exception(e: str) -> Iterator[None]: raise +class FakeContext(click.Context): + """A fake click Context for when calling functions that need it.""" + + def __init__(self) -> None: + self.default_map: Dict[str, Any] = {} + + +class FakeParameter(click.Parameter): + """A fake click Parameter for when calling functions that need it.""" + + def __init__(self) -> None: + pass + + class BlackRunner(CliRunner): """Modify CliRunner so that stderr is not merged with stdout. @@ -132,6 +165,7 @@ class BlackRunner(CliRunner): class BlackTestCase(unittest.TestCase): maxDiff = None + _diffThreshold = 2 ** 20 def assertFormatEqual(self, expected: str, actual: str) -> None: if actual != expected and not os.environ.get("SKIP_AST_PRINT"): @@ -150,25 +184,34 @@ class BlackTestCase(unittest.TestCase): list(bdv.visit(exp_node)) except Exception as ve: black.err(str(ve)) - self.assertEqual(expected, actual) + self.assertMultiLineEqual(expected, actual) def invokeBlack( self, args: List[str], exit_code: int = 0, ignore_config: bool = True ) -> None: runner = BlackRunner() if ignore_config: - args = ["--config", str(THIS_DIR / "empty.toml"), *args] + args = ["--verbose", "--config", str(THIS_DIR / "empty.toml"), *args] result = runner.invoke(black.main, args) - self.assertEqual(result.exit_code, exit_code, msg=runner.stderr_bytes.decode()) + self.assertEqual( + result.exit_code, + exit_code, + msg=( + f"Failed with args: {args}\n" + f"stdout: {runner.stdout_bytes.decode()!r}\n" + f"stderr: {runner.stderr_bytes.decode()!r}\n" + f"exception: {result.exception}" + ), + ) @patch("black.dump_to_file", dump_to_stderr) - def checkSourceFile(self, name: str) -> None: + def checkSourceFile(self, name: str, mode: black.FileMode = DEFAULT_MODE) -> None: path = THIS_DIR.parent / name source, expected = read_data(str(path), data=False) - actual = fs(source) + actual = fs(source, mode=mode) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, mode) self.assertFalse(ff(path)) @patch("black.dump_to_file", dump_to_stderr) @@ -177,7 +220,7 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) def test_empty_ff(self) -> None: expected = "" @@ -194,43 +237,43 @@ class BlackTestCase(unittest.TestCase): self.checkSourceFile("tests/test_black.py") def test_black(self) -> None: - self.checkSourceFile("black.py") + self.checkSourceFile("src/black/__init__.py") def test_pygram(self) -> None: - self.checkSourceFile("blib2to3/pygram.py") + self.checkSourceFile("src/blib2to3/pygram.py") def test_pytree(self) -> None: - self.checkSourceFile("blib2to3/pytree.py") + self.checkSourceFile("src/blib2to3/pytree.py") def test_conv(self) -> None: - self.checkSourceFile("blib2to3/pgen2/conv.py") + self.checkSourceFile("src/blib2to3/pgen2/conv.py") def test_driver(self) -> None: - self.checkSourceFile("blib2to3/pgen2/driver.py") + self.checkSourceFile("src/blib2to3/pgen2/driver.py") def test_grammar(self) -> None: - self.checkSourceFile("blib2to3/pgen2/grammar.py") + self.checkSourceFile("src/blib2to3/pgen2/grammar.py") def test_literals(self) -> None: - self.checkSourceFile("blib2to3/pgen2/literals.py") + self.checkSourceFile("src/blib2to3/pgen2/literals.py") def test_parse(self) -> None: - self.checkSourceFile("blib2to3/pgen2/parse.py") + self.checkSourceFile("src/blib2to3/pgen2/parse.py") def test_pgen(self) -> None: - self.checkSourceFile("blib2to3/pgen2/pgen.py") + self.checkSourceFile("src/blib2to3/pgen2/pgen.py") def test_tokenize(self) -> None: - self.checkSourceFile("blib2to3/pgen2/tokenize.py") + self.checkSourceFile("src/blib2to3/pgen2/tokenize.py") def test_token(self) -> None: - self.checkSourceFile("blib2to3/pgen2/token.py") + self.checkSourceFile("src/blib2to3/pgen2/token.py") def test_setup(self) -> None: self.checkSourceFile("setup.py") def test_piping(self) -> None: - source, expected = read_data("../black", data=False) + source, expected = read_data("src/black/__init__", data=False) result = BlackRunner().invoke( black.main, ["-", "--fast", f"--line-length={black.DEFAULT_LINE_LENGTH}"], @@ -239,12 +282,12 @@ class BlackTestCase(unittest.TestCase): self.assertEqual(result.exit_code, 0) self.assertFormatEqual(expected, result.output) black.assert_equivalent(source, result.output) - black.assert_stable(source, result.output, black.FileMode()) + black.assert_stable(source, result.output, DEFAULT_MODE) def test_piping_diff(self) -> None: diff_header = re.compile( - rf"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d\d\d\d " - rf"\+\d\d\d\d" + r"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d\d\d\d " + r"\+\d\d\d\d" ) source, _ = read_data("expression.py") expected, _ = read_data("expression.diff") @@ -292,7 +335,7 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) @patch("black.dump_to_file", dump_to_stderr) def test_function2(self) -> None: @@ -300,6 +343,21 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) + black.assert_stable(source, actual, DEFAULT_MODE) + + @patch("black.dump_to_file", dump_to_stderr) + def _test_wip(self) -> None: + source, expected = read_data("wip") + sys.settrace(tracefunc) + mode = replace( + DEFAULT_MODE, + experimental_string_processing=False, + target_versions={black.TargetVersion.PY38}, + ) + actual = fs(source, mode=mode) + sys.settrace(None) + self.assertFormatEqual(expected, actual) + black.assert_equivalent(source, actual) black.assert_stable(source, actual, black.FileMode()) @patch("black.dump_to_file", dump_to_stderr) @@ -308,7 +366,28 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) + + @unittest.expectedFailure + @patch("black.dump_to_file", dump_to_stderr) + def test_trailing_comma_optional_parens_stability1(self) -> None: + source, _expected = read_data("trailing_comma_optional_parens1") + actual = fs(source) + black.assert_stable(source, actual, DEFAULT_MODE) + + @unittest.expectedFailure + @patch("black.dump_to_file", dump_to_stderr) + def test_trailing_comma_optional_parens_stability2(self) -> None: + source, _expected = read_data("trailing_comma_optional_parens2") + actual = fs(source) + black.assert_stable(source, actual, DEFAULT_MODE) + + @unittest.expectedFailure + @patch("black.dump_to_file", dump_to_stderr) + def test_trailing_comma_optional_parens_stability3(self) -> None: + source, _expected = read_data("trailing_comma_optional_parens3") + actual = fs(source) + black.assert_stable(source, actual, DEFAULT_MODE) @patch("black.dump_to_file", dump_to_stderr) def test_expression(self) -> None: @@ -316,14 +395,14 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) @patch("black.dump_to_file", dump_to_stderr) def test_pep_572(self) -> None: source, expected = read_data("pep_572") actual = fs(source) self.assertFormatEqual(expected, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) if sys.version_info >= (3, 8): black.assert_equivalent(source, actual) @@ -347,7 +426,7 @@ class BlackTestCase(unittest.TestCase): self.assertFormatEqual(expected, actual) with patch("black.dump_to_file", dump_to_stderr): black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) def test_expression_diff(self) -> None: source, _ = read_data("expression.py") @@ -399,14 +478,14 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) @patch("black.dump_to_file", dump_to_stderr) def test_pep_570(self) -> None: source, expected = read_data("pep_570") actual = fs(source) self.assertFormatEqual(expected, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) if sys.version_info >= (3, 8): black.assert_equivalent(source, actual) @@ -424,8 +503,8 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) - mode = black.FileMode(string_normalization=False) + black.assert_stable(source, actual, DEFAULT_MODE) + mode = replace(DEFAULT_MODE, string_normalization=False) not_normalized = fs(source, mode=mode) self.assertFormatEqual(source.replace("\\\n", ""), not_normalized) black.assert_equivalent(source, not_normalized) @@ -437,7 +516,12 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) + mode = replace(DEFAULT_MODE, string_normalization=False) + not_normalized = fs(source, mode=mode) + self.assertFormatEqual(expected, not_normalized) + black.assert_equivalent(source, not_normalized) + black.assert_stable(source, not_normalized, mode=mode) def test_long_strings(self) -> None: """Tests for splitting long strings.""" @@ -445,7 +529,15 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) + + def test_long_strings_flag_disabled(self) -> None: + """Tests for turning off the string processing logic.""" + source, expected = read_data("long_strings_flag_disabled") + mode = replace(DEFAULT_MODE, experimental_string_processing=False) + actual = fs(source, mode=mode) + self.assertFormatEqual(expected, actual) + black.assert_stable(expected, actual, mode) @patch("black.dump_to_file", dump_to_stderr) def test_long_strings__edge_case(self) -> None: @@ -454,7 +546,7 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) @patch("black.dump_to_file", dump_to_stderr) def test_long_strings__regression(self) -> None: @@ -463,7 +555,7 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) @patch("black.dump_to_file", dump_to_stderr) def test_slices(self) -> None: @@ -471,7 +563,15 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) + + @patch("black.dump_to_file", dump_to_stderr) + def test_percent_precedence(self) -> None: + source, expected = read_data("percent_precedence") + actual = fs(source) + self.assertFormatEqual(expected, actual) + black.assert_equivalent(source, actual) + black.assert_stable(source, actual, DEFAULT_MODE) @patch("black.dump_to_file", dump_to_stderr) def test_comments(self) -> None: @@ -479,7 +579,7 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) @patch("black.dump_to_file", dump_to_stderr) def test_comments2(self) -> None: @@ -487,7 +587,7 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) @patch("black.dump_to_file", dump_to_stderr) def test_comments3(self) -> None: @@ -495,7 +595,7 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) @patch("black.dump_to_file", dump_to_stderr) def test_comments4(self) -> None: @@ -503,7 +603,7 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) @patch("black.dump_to_file", dump_to_stderr) def test_comments5(self) -> None: @@ -511,7 +611,7 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) @patch("black.dump_to_file", dump_to_stderr) def test_comments6(self) -> None: @@ -519,15 +619,16 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) @patch("black.dump_to_file", dump_to_stderr) def test_comments7(self) -> None: source, expected = read_data("comments7") - actual = fs(source) + mode = replace(DEFAULT_MODE, target_versions={black.TargetVersion.PY38}) + actual = fs(source, mode=mode) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) @patch("black.dump_to_file", dump_to_stderr) def test_comment_after_escaped_newline(self) -> None: @@ -535,7 +636,7 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) @patch("black.dump_to_file", dump_to_stderr) def test_cantfit(self) -> None: @@ -543,7 +644,7 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) @patch("black.dump_to_file", dump_to_stderr) def test_import_spacing(self) -> None: @@ -551,7 +652,7 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) @patch("black.dump_to_file", dump_to_stderr) def test_composition(self) -> None: @@ -559,7 +660,16 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) + + @patch("black.dump_to_file", dump_to_stderr) + def test_composition_no_trailing_comma(self) -> None: + source, expected = read_data("composition_no_trailing_comma") + mode = replace(DEFAULT_MODE, target_versions={black.TargetVersion.PY38}) + actual = fs(source, mode=mode) + self.assertFormatEqual(expected, actual) + black.assert_equivalent(source, actual) + black.assert_stable(source, actual, DEFAULT_MODE) @patch("black.dump_to_file", dump_to_stderr) def test_empty_lines(self) -> None: @@ -567,7 +677,7 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) @patch("black.dump_to_file", dump_to_stderr) def test_remove_parens(self) -> None: @@ -575,7 +685,7 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) @patch("black.dump_to_file", dump_to_stderr) def test_string_prefixes(self) -> None: @@ -583,12 +693,12 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) @patch("black.dump_to_file", dump_to_stderr) def test_numeric_literals(self) -> None: source, expected = read_data("numeric_literals") - mode = black.FileMode(target_versions=black.PY36_VERSIONS) + mode = replace(DEFAULT_MODE, target_versions=black.PY36_VERSIONS) actual = fs(source, mode=mode) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) @@ -597,7 +707,7 @@ class BlackTestCase(unittest.TestCase): @patch("black.dump_to_file", dump_to_stderr) def test_numeric_literals_ignoring_underscores(self) -> None: source, expected = read_data("numeric_literals_skip_underscores") - mode = black.FileMode(target_versions=black.PY36_VERSIONS) + mode = replace(DEFAULT_MODE, target_versions=black.PY36_VERSIONS) actual = fs(source, mode=mode) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) @@ -608,7 +718,7 @@ class BlackTestCase(unittest.TestCase): source, expected = read_data("numeric_literals_py2") actual = fs(source) self.assertFormatEqual(expected, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) @patch("black.dump_to_file", dump_to_stderr) def test_python2(self) -> None: @@ -616,12 +726,12 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) @patch("black.dump_to_file", dump_to_stderr) def test_python2_print_function(self) -> None: source, expected = read_data("python2_print_function") - mode = black.FileMode(target_versions={TargetVersion.PY27}) + mode = replace(DEFAULT_MODE, target_versions={TargetVersion.PY27}) actual = fs(source, mode=mode) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) @@ -633,11 +743,11 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) @patch("black.dump_to_file", dump_to_stderr) def test_stub(self) -> None: - mode = black.FileMode(is_pyi=True) + mode = replace(DEFAULT_MODE, is_pyi=True) source, expected = read_data("stub.pyi") actual = fs(source, mode=mode) self.assertFormatEqual(expected, actual) @@ -652,7 +762,7 @@ class BlackTestCase(unittest.TestCase): major, minor = sys.version_info[:2] if major < 3 or (major <= 3 and minor < 7): black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) # ensure black can parse this when the target is 3.6 self.invokeBlack([str(source_path), "--target-version", "py36"]) # but not on 3.7, because async/await is no longer an identifier @@ -667,7 +777,7 @@ class BlackTestCase(unittest.TestCase): major, minor = sys.version_info[:2] if major > 3 or (major == 3 and minor >= 7): black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) # ensure black can parse this when the target is 3.7 self.invokeBlack([str(source_path), "--target-version", "py37"]) # but not on 3.6, because we use async as a reserved keyword @@ -681,7 +791,7 @@ class BlackTestCase(unittest.TestCase): major, minor = sys.version_info[:2] if major > 3 or (major == 3 and minor >= 8): black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) @patch("black.dump_to_file", dump_to_stderr) def test_fmtonoff(self) -> None: @@ -689,7 +799,7 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) @patch("black.dump_to_file", dump_to_stderr) def test_fmtonoff2(self) -> None: @@ -697,7 +807,7 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) @patch("black.dump_to_file", dump_to_stderr) def test_fmtonoff3(self) -> None: @@ -705,7 +815,7 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) @patch("black.dump_to_file", dump_to_stderr) def test_fmtonoff4(self) -> None: @@ -713,7 +823,7 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) @patch("black.dump_to_file", dump_to_stderr) def test_remove_empty_parentheses_after_class(self) -> None: @@ -721,7 +831,7 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) @patch("black.dump_to_file", dump_to_stderr) def test_new_line_between_class_and_code(self) -> None: @@ -729,7 +839,7 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) @patch("black.dump_to_file", dump_to_stderr) def test_bracket_match(self) -> None: @@ -737,7 +847,7 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) @patch("black.dump_to_file", dump_to_stderr) def test_tuple_assign(self) -> None: @@ -745,7 +855,7 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) @patch("black.dump_to_file", dump_to_stderr) def test_beginning_backslash(self) -> None: @@ -753,7 +863,7 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) def test_tab_comment_indentation(self) -> None: contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n" @@ -1182,7 +1292,7 @@ class BlackTestCase(unittest.TestCase): def test_format_file_contents(self) -> None: empty = "" - mode = black.FileMode() + mode = DEFAULT_MODE with self.assertRaises(black.NothingChanged): black.format_file_contents(empty, mode=mode, fast=False) just_nl = "\n" @@ -1227,7 +1337,7 @@ class BlackTestCase(unittest.TestCase): self.assertEqual("".join(err_lines), "") def test_cache_broken_file(self) -> None: - mode = black.FileMode() + mode = DEFAULT_MODE with cache_dir() as workspace: cache_file = black.get_cache_file(mode) with cache_file.open("w") as fobj: @@ -1241,7 +1351,7 @@ class BlackTestCase(unittest.TestCase): self.assertIn(src, cache) def test_cache_single_file_already_cached(self) -> None: - mode = black.FileMode() + mode = DEFAULT_MODE with cache_dir() as workspace: src = (workspace / "test.py").resolve() with src.open("w") as fobj: @@ -1251,9 +1361,9 @@ class BlackTestCase(unittest.TestCase): with src.open("r") as fobj: self.assertEqual(fobj.read(), "print('hello')") - @event_loop(close=False) + @event_loop() def test_cache_multiple_files(self) -> None: - mode = black.FileMode() + mode = DEFAULT_MODE with cache_dir() as workspace, patch( "black.ProcessPoolExecutor", new=ThreadPoolExecutor ): @@ -1274,7 +1384,7 @@ class BlackTestCase(unittest.TestCase): self.assertIn(two, cache) def test_no_cache_when_writeback_diff(self) -> None: - mode = black.FileMode() + mode = DEFAULT_MODE with cache_dir() as workspace: src = (workspace / "test.py").resolve() with src.open("w") as fobj: @@ -1284,7 +1394,7 @@ class BlackTestCase(unittest.TestCase): self.assertFalse(cache_file.exists()) def test_no_cache_when_stdin(self) -> None: - mode = black.FileMode() + mode = DEFAULT_MODE with cache_dir(): result = CliRunner().invoke( black.main, ["-"], input=BytesIO(b"print('hello')") @@ -1294,12 +1404,12 @@ class BlackTestCase(unittest.TestCase): self.assertFalse(cache_file.exists()) def test_read_cache_no_cachefile(self) -> None: - mode = black.FileMode() + mode = DEFAULT_MODE with cache_dir(): self.assertEqual(black.read_cache(mode), {}) def test_write_cache_read_cache(self) -> None: - mode = black.FileMode() + mode = DEFAULT_MODE with cache_dir() as workspace: src = (workspace / "test.py").resolve() src.touch() @@ -1325,15 +1435,15 @@ class BlackTestCase(unittest.TestCase): self.assertEqual(done, {cached}) def test_write_cache_creates_directory_if_needed(self) -> None: - mode = black.FileMode() + mode = DEFAULT_MODE with cache_dir(exists=False) as workspace: self.assertFalse(workspace.exists()) black.write_cache({}, [], mode) self.assertTrue(workspace.exists()) - @event_loop(close=False) + @event_loop() def test_failed_formatting_does_not_get_cached(self) -> None: - mode = black.FileMode() + mode = DEFAULT_MODE with cache_dir() as workspace, patch( "black.ProcessPoolExecutor", new=ThreadPoolExecutor ): @@ -1349,12 +1459,23 @@ class BlackTestCase(unittest.TestCase): self.assertIn(clean, cache) def test_write_cache_write_fail(self) -> None: - mode = black.FileMode() + mode = DEFAULT_MODE with cache_dir(), patch.object(Path, "open") as mock: mock.side_effect = OSError black.write_cache({}, [], mode) - @event_loop(close=False) + @event_loop() + @patch("black.ProcessPoolExecutor", MagicMock(side_effect=OSError)) + def test_works_in_mono_process_only_environment(self) -> None: + with cache_dir() as workspace: + for f in [ + (workspace / "one.py").resolve(), + (workspace / "two.py").resolve(), + ]: + f.write_text('print("hello")\n') + self.invokeBlack([str(workspace)]) + + @event_loop() def test_check_diff_use_together(self) -> None: with cache_dir(): # Files which will be reformatted. @@ -1381,8 +1502,8 @@ class BlackTestCase(unittest.TestCase): self.invokeBlack([str(workspace.resolve())]) def test_read_cache_line_lengths(self) -> None: - mode = black.FileMode() - short_mode = black.FileMode(line_length=1) + mode = DEFAULT_MODE + short_mode = replace(DEFAULT_MODE, line_length=1) with cache_dir() as workspace: path = (workspace / "file.py").resolve() path.touch() @@ -1397,11 +1518,11 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) def test_single_file_force_pyi(self) -> None: - reg_mode = black.FileMode() - pyi_mode = black.FileMode(is_pyi=True) + reg_mode = DEFAULT_MODE + pyi_mode = replace(DEFAULT_MODE, is_pyi=True) contents, expected = read_data("force_pyi") with cache_dir() as workspace: path = (workspace / "file.py").resolve() @@ -1417,10 +1538,10 @@ class BlackTestCase(unittest.TestCase): self.assertNotIn(path, normal_cache) self.assertEqual(actual, expected) - @event_loop(close=False) + @event_loop() def test_multi_file_force_pyi(self) -> None: - reg_mode = black.FileMode() - pyi_mode = black.FileMode(is_pyi=True) + reg_mode = DEFAULT_MODE + pyi_mode = replace(DEFAULT_MODE, is_pyi=True) contents, expected = read_data("force_pyi") with cache_dir() as workspace: paths = [ @@ -1452,8 +1573,8 @@ class BlackTestCase(unittest.TestCase): self.assertFormatEqual(actual, expected) def test_single_file_force_py36(self) -> None: - reg_mode = black.FileMode() - py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS) + reg_mode = DEFAULT_MODE + py36_mode = replace(DEFAULT_MODE, target_versions=black.PY36_VERSIONS) source, expected = read_data("force_py36") with cache_dir() as workspace: path = (workspace / "file.py").resolve() @@ -1469,10 +1590,10 @@ class BlackTestCase(unittest.TestCase): self.assertNotIn(path, normal_cache) self.assertEqual(actual, expected) - @event_loop(close=False) + @event_loop() def test_multi_file_force_py36(self) -> None: - reg_mode = black.FileMode() - py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS) + reg_mode = DEFAULT_MODE + py36_mode = replace(DEFAULT_MODE, target_versions=black.PY36_VERSIONS) source, expected = read_data("force_py36") with cache_dir() as workspace: paths = [ @@ -1499,7 +1620,7 @@ class BlackTestCase(unittest.TestCase): actual = fs(source) self.assertFormatEqual(expected, actual) black.assert_equivalent(source, actual) - black.assert_stable(source, actual, black.FileMode()) + black.assert_stable(source, actual, DEFAULT_MODE) def test_pipe_force_py36(self) -> None: source, expected = read_data("force_py36") @@ -1525,8 +1646,33 @@ class BlackTestCase(unittest.TestCase): ] this_abs = THIS_DIR.resolve() sources.extend( - black.gen_python_files_in_dir( - path, this_abs, include, exclude, report, gitignore + black.gen_python_files( + path.iterdir(), this_abs, include, exclude, None, report, gitignore + ) + ) + self.assertEqual(sorted(expected), sorted(sources)) + + @patch("black.find_project_root", lambda *args: THIS_DIR.resolve()) + def test_exclude_for_issue_1572(self) -> None: + # Exclude shouldn't touch files that were explicitly given to Black through the + # CLI. Exclude is supposed to only apply to the recursive discovery of files. + # https://github.com/psf/black/issues/1572 + path = THIS_DIR / "data" / "include_exclude_tests" + include = "" + exclude = r"/exclude/|a\.py" + src = str(path / "b/exclude/a.py") + report = black.Report() + expected = [Path(path / "b/exclude/a.py")] + sources = list( + black.get_sources( + ctx=FakeContext(), + src=(src,), + quiet=True, + verbose=False, + include=include, + exclude=exclude, + force_exclude=None, + report=report, ) ) self.assertEqual(sorted(expected), sorted(sources)) @@ -1546,8 +1692,8 @@ class BlackTestCase(unittest.TestCase): ] this_abs = THIS_DIR.resolve() sources.extend( - black.gen_python_files_in_dir( - path, this_abs, include, exclude, report, gitignore + black.gen_python_files( + path.iterdir(), this_abs, include, exclude, None, report, gitignore ) ) self.assertEqual(sorted(expected), sorted(sources)) @@ -1571,11 +1717,12 @@ class BlackTestCase(unittest.TestCase): ] this_abs = THIS_DIR.resolve() sources.extend( - black.gen_python_files_in_dir( - path, + black.gen_python_files( + path.iterdir(), this_abs, empty, re.compile(black.DEFAULT_EXCLUDES), + None, report, gitignore, ) @@ -1598,11 +1745,12 @@ class BlackTestCase(unittest.TestCase): ] this_abs = THIS_DIR.resolve() sources.extend( - black.gen_python_files_in_dir( - path, + black.gen_python_files( + path.iterdir(), this_abs, re.compile(black.DEFAULT_INCLUDES), empty, + None, report, gitignore, ) @@ -1644,7 +1792,7 @@ class BlackTestCase(unittest.TestCase): def test_symlink_out_of_root_directory(self) -> None: path = MagicMock() - root = THIS_DIR + root = THIS_DIR.resolve() child = MagicMock() include = re.compile(black.DEFAULT_INCLUDES) exclude = re.compile(black.DEFAULT_EXCLUDES) @@ -1658,8 +1806,8 @@ class BlackTestCase(unittest.TestCase): child.is_symlink.return_value = True try: list( - black.gen_python_files_in_dir( - path, root, include, exclude, report, gitignore + black.gen_python_files( + path.iterdir(), root, include, exclude, None, report, gitignore ) ) except ValueError as ve: @@ -1672,8 +1820,8 @@ class BlackTestCase(unittest.TestCase): child.is_symlink.return_value = False with self.assertRaises(ValueError): list( - black.gen_python_files_in_dir( - path, root, include, exclude, report, gitignore + black.gen_python_files( + path.iterdir(), root, include, exclude, None, report, gitignore ) ) path.iterdir.assert_called() @@ -1738,6 +1886,54 @@ class BlackTestCase(unittest.TestCase): finally: tmp_file.unlink() + def test_parse_pyproject_toml(self) -> None: + test_toml_file = THIS_DIR / "test.toml" + config = black.parse_pyproject_toml(str(test_toml_file)) + self.assertEqual(config["verbose"], 1) + self.assertEqual(config["check"], "no") + self.assertEqual(config["diff"], "y") + self.assertEqual(config["color"], True) + self.assertEqual(config["line_length"], 79) + self.assertEqual(config["target_version"], ["py36", "py37", "py38"]) + self.assertEqual(config["exclude"], r"\.pyi?$") + self.assertEqual(config["include"], r"\.py?$") + + def test_read_pyproject_toml(self) -> None: + test_toml_file = THIS_DIR / "test.toml" + fake_ctx = FakeContext() + black.read_pyproject_toml(fake_ctx, FakeParameter(), str(test_toml_file)) + config = fake_ctx.default_map + self.assertEqual(config["verbose"], "1") + self.assertEqual(config["check"], "no") + self.assertEqual(config["diff"], "y") + self.assertEqual(config["color"], "True") + self.assertEqual(config["line_length"], "79") + self.assertEqual(config["target_version"], ["py36", "py37", "py38"]) + self.assertEqual(config["exclude"], r"\.pyi?$") + self.assertEqual(config["include"], r"\.py?$") + + def test_find_project_root(self) -> None: + with TemporaryDirectory() as workspace: + root = Path(workspace) + test_dir = root / "test" + test_dir.mkdir() + + src_dir = root / "src" + src_dir.mkdir() + + root_pyproject = root / "pyproject.toml" + root_pyproject.touch() + src_pyproject = src_dir / "pyproject.toml" + src_pyproject.touch() + src_python = src_dir / "foo.py" + src_python.touch() + + self.assertEqual( + black.find_project_root((src_dir, test_dir)), root.resolve() + ) + self.assertEqual(black.find_project_root((src_dir,)), src_dir.resolve()) + self.assertEqual(black.find_project_root((src_python,)), src_dir.resolve()) + class BlackDTestCase(AioHTTPTestCase): async def get_application(self) -> web.Application: @@ -1828,7 +2024,7 @@ class BlackDTestCase(AioHTTPTestCase): @unittest_run_loop async def test_blackd_diff(self) -> None: diff_header = re.compile( - rf"(In|Out)\t\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d" + r"(In|Out)\t\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d" ) source, _ = read_data("blackd_diff.py") @@ -1907,5 +2103,31 @@ class BlackDTestCase(AioHTTPTestCase): self.assertIsNotNone(response.headers.get(blackd.BLACK_VERSION_HEADER)) +with open(black.__file__, "r", encoding="utf-8") as _bf: + black_source_lines = _bf.readlines() + + +def tracefunc(frame: types.FrameType, event: str, arg: Any) -> Callable: + """Show function calls `from black/__init__.py` as they happen. + + Register this with `sys.settrace()` in a test you're debugging. + """ + if event != "call": + return tracefunc + + stack = len(inspect.stack()) - 19 + stack *= 2 + filename = frame.f_code.co_filename + lineno = frame.f_lineno + func_sig_lineno = lineno - 1 + funcname = black_source_lines[func_sig_lineno].strip() + while funcname.startswith("@"): + func_sig_lineno += 1 + funcname = black_source_lines[func_sig_lineno].strip() + if "black/__init__.py" in filename: + print(f"{' ' * stack}{lineno}:{funcname}") + return tracefunc + + if __name__ == "__main__": unittest.main(module="test_black")