import types
import unittest
from concurrent.futures import ThreadPoolExecutor
-from contextlib import contextmanager
+from contextlib import contextmanager, redirect_stderr
from dataclasses import replace
from io import BytesIO
from pathlib import Path
import black.files
from black import Feature, TargetVersion
from black import re_compile_maybe_verbose as compile_pattern
-from black.cache import get_cache_file
+from black.cache import get_cache_dir, get_cache_file
from black.debug import DebugVisitor
from black.output import color_diff, diff
from black.report import Report
)
THIS_FILE = Path(__file__)
+EMPTY_CONFIG = THIS_DIR / "data" / "empty_pyproject.toml"
PY36_ARGS = [f"--target-version={version.name.lower()}" for version in PY36_VERSIONS]
DEFAULT_EXCLUDE = black.re_compile_maybe_verbose(black.const.DEFAULT_EXCLUDES)
DEFAULT_INCLUDE = black.re_compile_maybe_verbose(black.const.DEFAULT_INCLUDES)
source, expected = read_data("src/black/__init__", data=False)
result = BlackRunner().invoke(
black.main,
- ["-", "--fast", f"--line-length={black.DEFAULT_LINE_LENGTH}"],
+ [
+ "-",
+ "--fast",
+ f"--line-length={black.DEFAULT_LINE_LENGTH}",
+ f"--config={EMPTY_CONFIG}",
+ ],
input=BytesIO(source.encode("utf8")),
)
self.assertEqual(result.exit_code, 0)
)
source, _ = read_data("expression.py")
expected, _ = read_data("expression.diff")
- config = THIS_DIR / "data" / "empty_pyproject.toml"
args = [
"-",
"--fast",
f"--line-length={black.DEFAULT_LINE_LENGTH}",
"--diff",
- f"--config={config}",
+ f"--config={EMPTY_CONFIG}",
]
result = BlackRunner().invoke(
black.main, args, input=BytesIO(source.encode("utf8"))
def test_piping_diff_with_color(self) -> None:
source, _ = read_data("expression.py")
- config = THIS_DIR / "data" / "empty_pyproject.toml"
args = [
"-",
"--fast",
f"--line-length={black.DEFAULT_LINE_LENGTH}",
"--diff",
"--color",
- f"--config={config}",
+ f"--config={EMPTY_CONFIG}",
]
result = BlackRunner().invoke(
black.main, args, input=BytesIO(source.encode("utf8"))
black.assert_equivalent(source, actual)
black.assert_stable(source, actual, black.FileMode())
- @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_trailing_comma_optional_parens_stability1_pass2(self) -> None:
- source, _expected = read_data("trailing_comma_optional_parens1")
- actual = fs(fs(source)) # this is what `format_file_contents` does with --safe
- black.assert_stable(source, actual, DEFAULT_MODE)
-
- @patch("black.dump_to_file", dump_to_stderr)
- def test_trailing_comma_optional_parens_stability2_pass2(self) -> None:
- source, _expected = read_data("trailing_comma_optional_parens2")
- actual = fs(fs(source)) # this is what `format_file_contents` does with --safe
- black.assert_stable(source, actual, DEFAULT_MODE)
-
- @patch("black.dump_to_file", dump_to_stderr)
- def test_trailing_comma_optional_parens_stability3_pass2(self) -> None:
- source, _expected = read_data("trailing_comma_optional_parens3")
- actual = fs(fs(source)) # this is what `format_file_contents` does with --safe
- black.assert_stable(source, actual, DEFAULT_MODE)
-
def test_pep_572_version_detection(self) -> None:
source, _ = read_data("pep_572")
root = black.lib2to3_parse(source)
def test_expression_diff(self) -> None:
source, _ = read_data("expression.py")
- config = THIS_DIR / "data" / "empty_pyproject.toml"
expected, _ = read_data("expression.diff")
tmp_file = Path(black.dump_to_file(source))
diff_header = re.compile(
)
try:
result = BlackRunner().invoke(
- black.main, ["--diff", str(tmp_file), f"--config={config}"]
+ black.main, ["--diff", str(tmp_file), f"--config={EMPTY_CONFIG}"]
)
self.assertEqual(result.exit_code, 0)
finally:
def test_expression_diff_with_color(self) -> None:
source, _ = read_data("expression.py")
- config = THIS_DIR / "data" / "empty_pyproject.toml"
expected, _ = read_data("expression.diff")
tmp_file = Path(black.dump_to_file(source))
try:
result = BlackRunner().invoke(
- black.main, ["--diff", "--color", str(tmp_file), f"--config={config}"]
+ black.main,
+ ["--diff", "--color", str(tmp_file), f"--config={EMPTY_CONFIG}"],
)
finally:
os.unlink(tmp_file)
r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
)
try:
- result = BlackRunner().invoke(black.main, ["-C", "--diff", str(tmp_file)])
+ result = BlackRunner().invoke(
+ black.main, ["-C", "--diff", str(tmp_file), f"--config={EMPTY_CONFIG}"]
+ )
self.assertEqual(result.exit_code, 0)
finally:
os.unlink(tmp_file)
self.assertEqual(
black.get_features_used(node), {Feature.ANN_ASSIGN_EXTENDED_RHS}
)
+ node = black.lib2to3_parse("try: pass\nexcept Something: pass")
+ self.assertEqual(black.get_features_used(node), set())
+ node = black.lib2to3_parse("try: pass\nexcept (*Something,): pass")
+ self.assertEqual(black.get_features_used(node), set())
+ node = black.lib2to3_parse("try: pass\nexcept *Group: pass")
+ self.assertEqual(black.get_features_used(node), {Feature.EXCEPT_STAR})
def test_get_features_used_for_future_flags(self) -> None:
for src, features in [
# Multi file command.
self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
- def test_no_files(self) -> None:
+ def test_no_src_fails(self) -> None:
+ with cache_dir():
+ self.invokeBlack([], exit_code=1)
+
+ def test_src_and_code_fails(self) -> None:
with cache_dir():
- # Without an argument, black exits with error code 0.
- self.invokeBlack([])
+ self.invokeBlack([".", "-c", "0"], exit_code=1)
def test_broken_symlink(self) -> None:
with cache_dir() as workspace:
def test_required_version_matches_version(self) -> None:
self.invokeBlack(
- ["--required-version", black.__version__], exit_code=0, ignore_config=True
+ ["--required-version", black.__version__, "-c", "0"],
+ exit_code=0,
+ ignore_config=True,
)
- def test_required_version_does_not_match_version(self) -> None:
+ def test_required_version_matches_partial_version(self) -> None:
self.invokeBlack(
- ["--required-version", "20.99b"], exit_code=1, ignore_config=True
+ ["--required-version", black.__version__.split(".")[0], "-c", "0"],
+ exit_code=0,
+ ignore_config=True,
)
+ def test_required_version_does_not_match_on_minor_version(self) -> None:
+ self.invokeBlack(
+ ["--required-version", black.__version__.split(".")[0] + ".999", "-c", "0"],
+ exit_code=1,
+ ignore_config=True,
+ )
+
+ def test_required_version_does_not_match_version(self) -> None:
+ result = BlackRunner().invoke(
+ black.main,
+ ["--required-version", "20.99b", "-c", "0"],
+ )
+ self.assertEqual(result.exit_code, 1)
+ self.assertIn("required version", result.stderr)
+
def test_preserves_line_endings(self) -> None:
with TemporaryDirectory() as workspace:
test_file = Path(workspace) / "test.py"
def test_shhh_click(self) -> None:
try:
- from click import _unicodefun
- except ModuleNotFoundError:
+ from click import _unicodefun # type: ignore
+ except ImportError:
self.skipTest("Incompatible Click version")
- if not hasattr(_unicodefun, "_verify_python3_env"):
+
+ if not hasattr(_unicodefun, "_verify_python_env"):
self.skipTest("Incompatible Click version")
+
# First, let's see if Click is crashing with a preferred ASCII charset.
with patch("locale.getpreferredencoding") as gpe:
gpe.return_value = "ASCII"
with self.assertRaises(RuntimeError):
- _unicodefun._verify_python3_env() # type: ignore
+ _unicodefun._verify_python_env()
# Now, let's silence Click...
black.patch_click()
# ...and confirm it's silent.
with patch("locale.getpreferredencoding") as gpe:
gpe.return_value = "ASCII"
try:
- _unicodefun._verify_python3_env() # type: ignore
+ _unicodefun._verify_python_env()
except RuntimeError as re:
self.fail(f"`patch_click()` failed, exception still raised: {re}")
(src_dir.resolve(), "pyproject.toml"),
)
+ @patch(
+ "black.files.find_user_pyproject_toml",
+ )
+ def test_find_pyproject_toml(self, find_user_pyproject_toml: MagicMock) -> None:
+ find_user_pyproject_toml.side_effect = RuntimeError()
+
+ with redirect_stderr(io.StringIO()) as stderr:
+ result = black.files.find_pyproject_toml(
+ path_search_start=(str(Path.cwd().root),)
+ )
+
+ assert result is None
+ err = stderr.getvalue()
+ assert "Ignoring user configuration" in err
+
@patch(
"black.files.find_user_pyproject_toml",
black.files.find_user_pyproject_toml.__wrapped__,
normalized_path = black.normalize_path_maybe_ignore(path, root, report)
self.assertEqual(normalized_path, "workspace/project")
+ def test_normalize_path_ignore_windows_junctions_outside_of_root(self) -> None:
+ if system() != "Windows":
+ return
+
+ with TemporaryDirectory() as workspace:
+ root = Path(workspace)
+ junction_dir = root / "junction"
+ junction_target_outside_of_root = root / ".."
+ os.system(f"mklink /J {junction_dir} {junction_target_outside_of_root}")
+
+ report = black.Report(verbose=True)
+ normalized_path = black.normalize_path_maybe_ignore(
+ junction_dir, root, report
+ )
+ # Manually delete for Python < 3.8
+ os.system(f"rmdir {junction_dir}")
+
+ self.assertEqual(normalized_path, None)
+
def test_newline_comment_interaction(self) -> None:
source = "class A:\\\r\n# type: ignore\n pass\n"
output = black.format_str(source, mode=DEFAULT_MODE)
class TestCaching:
+ def test_get_cache_dir(
+ self,
+ tmp_path: Path,
+ monkeypatch: pytest.MonkeyPatch,
+ ) -> None:
+ # Create multiple cache directories
+ workspace1 = tmp_path / "ws1"
+ workspace1.mkdir()
+ workspace2 = tmp_path / "ws2"
+ workspace2.mkdir()
+
+ # Force user_cache_dir to use the temporary directory for easier assertions
+ patch_user_cache_dir = patch(
+ target="black.cache.user_cache_dir",
+ autospec=True,
+ return_value=str(workspace1),
+ )
+
+ # If BLACK_CACHE_DIR is not set, use user_cache_dir
+ monkeypatch.delenv("BLACK_CACHE_DIR", raising=False)
+ with patch_user_cache_dir:
+ assert get_cache_dir() == workspace1
+
+ # If it is set, use the path provided in the env var.
+ monkeypatch.setenv("BLACK_CACHE_DIR", str(workspace2))
+ assert get_cache_dir() == workspace2
+
def test_cache_broken_file(self) -> None:
mode = DEFAULT_MODE
with cache_dir() as workspace:
path.iterdir.return_value = [child]
child.resolve.return_value = Path("/a/b/c")
child.as_posix.return_value = "/a/b/c"
- child.is_symlink.return_value = True
try:
list(
black.gen_python_files(
pytest.fail(f"`get_python_files_in_dir()` failed: {ve}")
path.iterdir.assert_called_once()
child.resolve.assert_called_once()
- child.is_symlink.assert_called_once()
- # `child` should behave like a strange file which resolved path is clearly
- # outside of the `root` directory.
- child.is_symlink.return_value = False
- with pytest.raises(ValueError):
- list(
- black.gen_python_files(
- path.iterdir(),
- root,
- include,
- exclude,
- None,
- None,
- report,
- gitignore,
- verbose=False,
- quiet=False,
- )
- )
- path.iterdir.assert_called()
- assert path.iterdir.call_count == 2
- child.resolve.assert_called()
- assert child.resolve.call_count == 2
- child.is_symlink.assert_called()
- assert child.is_symlink.call_count == 2
@patch("black.find_project_root", lambda *args: (THIS_DIR.resolve(), None))
def test_get_sources_with_stdin(self) -> None: