Iterator,
TypeVar,
)
+import pytest
import unittest
from unittest.mock import patch, MagicMock
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)
+
@patch("black.dump_to_file", dump_to_stderr)
def test_pep_572(self) -> None:
source, expected = read_data("pep_572")
if sys.version_info >= (3, 8):
black.assert_equivalent(source, actual)
+ @patch("black.dump_to_file", dump_to_stderr)
+ def test_pep_572_remove_parens(self) -> None:
+ source, expected = read_data("pep_572_remove_parens")
+ actual = fs(source)
+ self.assertFormatEqual(expected, actual)
+ black.assert_stable(source, actual, DEFAULT_MODE)
+ if sys.version_info >= (3, 8):
+ black.assert_equivalent(source, actual)
+
+ @patch("black.dump_to_file", dump_to_stderr)
+ def test_pep_572_do_not_remove_parens(self) -> None:
+ source, expected = read_data("pep_572_do_not_remove_parens")
+ # the AST safety checks will fail, but that's expected, just make sure no
+ # parentheses are touched
+ actual = black.format_str(source, mode=DEFAULT_MODE)
+ self.assertFormatEqual(expected, actual)
+
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(
r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
)
try:
- result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
+ result = BlackRunner().invoke(
+ black.main, ["--diff", str(tmp_file), f"--config={config}"]
+ )
self.assertEqual(result.exit_code, 0)
finally:
os.unlink(tmp_file)
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)]
+ black.main, ["--diff", "--color", str(tmp_file), f"--config={config}"]
)
finally:
os.unlink(tmp_file)
@patch("black.dump_to_file", dump_to_stderr)
def test_string_quotes(self) -> None:
source, expected = read_data("string_quotes")
- actual = fs(source)
+ mode = black.Mode(experimental_string_processing=True)
+ actual = fs(source, mode=mode)
self.assertFormatEqual(expected, actual)
black.assert_equivalent(source, actual)
- black.assert_stable(source, actual, DEFAULT_MODE)
- mode = replace(DEFAULT_MODE, string_normalization=False)
+ black.assert_stable(source, actual, mode)
+ mode = replace(mode, string_normalization=False)
not_normalized = fs(source, mode=mode)
self.assertFormatEqual(source.replace("\\\n", ""), not_normalized)
black.assert_equivalent(source, not_normalized)
)
self.assertEqual(expected, actual, msg)
+ @pytest.mark.no_python2
+ def test_python2_should_fail_without_optional_install(self) -> None:
+ if sys.version_info < (3, 8):
+ self.skipTest(
+ "Python 3.6 and 3.7 will install typed-ast to work and as such will be"
+ " able to parse Python 2 syntax without explicitly specifying the"
+ " python2 extra"
+ )
+
+ source = "x = 1234l"
+ tmp_file = Path(black.dump_to_file(source))
+ try:
+ runner = BlackRunner()
+ result = runner.invoke(black.main, [str(tmp_file)])
+ self.assertEqual(result.exit_code, 123)
+ finally:
+ os.unlink(tmp_file)
+ actual = (
+ runner.stderr_bytes.decode()
+ .replace("\n", "")
+ .replace("\\n", "")
+ .replace("\\r", "")
+ .replace("\r", "")
+ )
+ msg = (
+ "The requested source code has invalid Python 3 syntax."
+ "If you are trying to format Python 2 files please reinstall Black"
+ " with the 'python2' extra: `python3 -m pip install black[python2]`."
+ )
+ self.assertIn(msg, actual)
+
+ @pytest.mark.python2
@patch("black.dump_to_file", dump_to_stderr)
def test_python2_print_function(self) -> None:
source, expected = read_data("python2_print_function")
this_abs = THIS_DIR.resolve()
sources.extend(
black.gen_python_files(
- path.iterdir(), this_abs, include, exclude, None, report, gitignore
+ path.iterdir(),
+ this_abs,
+ include,
+ exclude,
+ None,
+ None,
+ report,
+ gitignore,
+ )
+ )
+ self.assertEqual(sorted(expected), sorted(sources))
+
+ def test_gitingore_used_as_default(self) -> None:
+ path = Path(THIS_DIR / "data" / "include_exclude_tests")
+ include = re.compile(r"\.pyi?$")
+ extend_exclude = re.compile(r"/exclude/")
+ src = str(path / "b/")
+ report = black.Report()
+ expected: List[Path] = [
+ path / "b/.definitely_exclude/a.py",
+ path / "b/.definitely_exclude/a.pyi",
+ ]
+ sources = list(
+ black.get_sources(
+ ctx=FakeContext(),
+ src=(src,),
+ quiet=True,
+ verbose=False,
+ include=include,
+ exclude=None,
+ extend_exclude=extend_exclude,
+ force_exclude=None,
+ report=report,
+ stdin_filename=None,
)
)
self.assertEqual(sorted(expected), sorted(sources))
src=(src,),
quiet=True,
verbose=False,
- include=include,
- exclude=exclude,
+ include=re.compile(include),
+ exclude=re.compile(exclude),
+ extend_exclude=None,
force_exclude=None,
report=report,
stdin_filename=None,
src=(src,),
quiet=True,
verbose=False,
- include=include,
- exclude=exclude,
+ include=re.compile(include),
+ exclude=re.compile(exclude),
+ extend_exclude=None,
force_exclude=None,
report=report,
stdin_filename=None,
src=(src,),
quiet=True,
verbose=False,
- include=include,
- exclude=exclude,
+ include=re.compile(include),
+ exclude=re.compile(exclude),
+ extend_exclude=None,
force_exclude=None,
report=report,
stdin_filename=stdin_filename,
src=(src,),
quiet=True,
verbose=False,
- include=include,
- exclude=exclude,
+ include=re.compile(include),
+ exclude=re.compile(exclude),
+ extend_exclude=None,
+ force_exclude=None,
+ report=report,
+ stdin_filename=stdin_filename,
+ )
+ )
+ self.assertEqual(sorted(expected), sorted(sources))
+
+ @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
+ def test_get_sources_with_stdin_filename_and_extend_exclude(self) -> None:
+ # Extend exclude shouldn't exclude stdin_filename since it is mimicking the
+ # file being passed directly. This is the same as
+ # test_exclude_for_issue_1572
+ path = THIS_DIR / "data" / "include_exclude_tests"
+ include = ""
+ extend_exclude = r"/exclude/|a\.py"
+ src = "-"
+ report = black.Report()
+ stdin_filename = str(path / "b/exclude/a.py")
+ expected = [Path(f"__BLACK_STDIN_FILENAME__{stdin_filename}")]
+ sources = list(
+ black.get_sources(
+ ctx=FakeContext(),
+ src=(src,),
+ quiet=True,
+ verbose=False,
+ include=re.compile(include),
+ exclude=re.compile(""),
+ extend_exclude=re.compile(extend_exclude),
force_exclude=None,
report=report,
stdin_filename=stdin_filename,
src=(src,),
quiet=True,
verbose=False,
- include=include,
- exclude="",
- force_exclude=force_exclude,
+ include=re.compile(include),
+ exclude=re.compile(""),
+ extend_exclude=None,
+ force_exclude=re.compile(force_exclude),
report=report,
stdin_filename=stdin_filename,
)
mode=DEFAULT_MODE,
report=report,
)
- fsts.assert_called_once()
- # __BLACK_STDIN_FILENAME__ should have been striped
+ fsts.assert_called_once_with(
+ fast=True, write_back=black.WriteBack.YES, mode=DEFAULT_MODE
+ )
+ # __BLACK_STDIN_FILENAME__ should have been stripped
+ report.done.assert_called_with(expected, black.Changed.YES)
+
+ def test_reformat_one_with_stdin_filename_pyi(self) -> None:
+ with patch(
+ "black.format_stdin_to_stdout",
+ return_value=lambda *args, **kwargs: black.Changed.YES,
+ ) as fsts:
+ report = MagicMock()
+ p = "foo.pyi"
+ path = Path(f"__BLACK_STDIN_FILENAME__{p}")
+ expected = Path(p)
+ black.reformat_one(
+ path,
+ fast=True,
+ write_back=black.WriteBack.YES,
+ mode=DEFAULT_MODE,
+ report=report,
+ )
+ fsts.assert_called_once_with(
+ fast=True,
+ write_back=black.WriteBack.YES,
+ mode=replace(DEFAULT_MODE, is_pyi=True),
+ )
+ # __BLACK_STDIN_FILENAME__ should have been stripped
report.done.assert_called_with(expected, black.Changed.YES)
def test_reformat_one_with_stdin_and_existing_path(self) -> None:
report=report,
)
fsts.assert_called_once()
- # __BLACK_STDIN_FILENAME__ should have been striped
+ # __BLACK_STDIN_FILENAME__ should have been stripped
report.done.assert_called_with(expected, black.Changed.YES)
def test_gitignore_exclude(self) -> None:
this_abs = THIS_DIR.resolve()
sources.extend(
black.gen_python_files(
- path.iterdir(), this_abs, include, exclude, None, report, gitignore
+ path.iterdir(),
+ this_abs,
+ include,
+ exclude,
+ None,
+ None,
+ report,
+ gitignore,
)
)
self.assertEqual(sorted(expected), sorted(sources))
Path(path / "b/.definitely_exclude/a.pie"),
Path(path / "b/.definitely_exclude/a.py"),
Path(path / "b/.definitely_exclude/a.pyi"),
+ Path(path / ".gitignore"),
+ Path(path / "pyproject.toml"),
]
this_abs = THIS_DIR.resolve()
sources.extend(
empty,
re.compile(black.DEFAULT_EXCLUDES),
None,
+ None,
report,
gitignore,
)
)
self.assertEqual(sorted(expected), sorted(sources))
- def test_empty_exclude(self) -> None:
+ def test_extend_exclude(self) -> None:
path = THIS_DIR / "data" / "include_exclude_tests"
report = black.Report()
gitignore = PathSpec.from_lines("gitwildmatch", [])
- empty = re.compile(r"")
sources: List[Path] = []
expected = [
- Path(path / "b/dont_exclude/a.py"),
- Path(path / "b/dont_exclude/a.pyi"),
Path(path / "b/exclude/a.py"),
- Path(path / "b/exclude/a.pyi"),
- Path(path / "b/.definitely_exclude/a.py"),
- Path(path / "b/.definitely_exclude/a.pyi"),
+ Path(path / "b/dont_exclude/a.py"),
]
this_abs = THIS_DIR.resolve()
sources.extend(
path.iterdir(),
this_abs,
re.compile(black.DEFAULT_INCLUDES),
- empty,
+ re.compile(r"\.pyi$"),
+ re.compile(r"\.definitely_exclude"),
None,
report,
gitignore,
)
self.assertEqual(sorted(expected), sorted(sources))
- def test_invalid_include_exclude(self) -> None:
- for option in ["--include", "--exclude"]:
+ def test_invalid_cli_regex(self) -> None:
+ for option in ["--include", "--exclude", "--extend-exclude", "--force-exclude"]:
self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
def test_preserves_line_endings(self) -> None:
try:
list(
black.gen_python_files(
- path.iterdir(), root, include, exclude, None, report, gitignore
+ path.iterdir(),
+ root,
+ include,
+ exclude,
+ None,
+ None,
+ report,
+ gitignore,
)
)
except ValueError as ve:
with self.assertRaises(ValueError):
list(
black.gen_python_files(
- path.iterdir(), root, include, exclude, None, report, gitignore
+ path.iterdir(),
+ root,
+ include,
+ exclude,
+ None,
+ None,
+ report,
+ gitignore,
)
)
path.iterdir.assert_called()
self.assertEqual(black.find_project_root((src_dir,)), src_dir.resolve())
self.assertEqual(black.find_project_root((src_python,)), src_dir.resolve())
+ @patch("black.find_user_pyproject_toml", black.find_user_pyproject_toml.__wrapped__)
+ def test_find_user_pyproject_toml_linux(self) -> None:
+ if system() == "Windows":
+ return
+
+ # Test if XDG_CONFIG_HOME is checked
+ with TemporaryDirectory() as workspace:
+ tmp_user_config = Path(workspace) / "black"
+ with patch.dict("os.environ", {"XDG_CONFIG_HOME": workspace}):
+ self.assertEqual(
+ black.find_user_pyproject_toml(), tmp_user_config.resolve()
+ )
+
+ # Test fallback for XDG_CONFIG_HOME
+ with patch.dict("os.environ"):
+ os.environ.pop("XDG_CONFIG_HOME", None)
+ fallback_user_config = Path("~/.config").expanduser() / "black"
+ self.assertEqual(
+ black.find_user_pyproject_toml(), fallback_user_config.resolve()
+ )
+
+ def test_find_user_pyproject_toml_windows(self) -> None:
+ if system() != "Windows":
+ return
+
+ user_config_path = Path.home() / ".black"
+ self.assertEqual(black.find_user_pyproject_toml(), user_config_path.resolve())
+
def test_bpo_33660_workaround(self) -> None:
if system() == "Windows":
return
actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
self.assertEqual(actual, expected)
+ @pytest.mark.python2
+ def test_docstring_reformat_for_py27(self) -> None:
+ """
+ Check that stripping trailing whitespace from Python 2 docstrings
+ doesn't trigger a "not equivalent to source" error
+ """
+ source = (
+ b'def foo():\r\n """Testing\r\n Testing """\r\n print "Foo"\r\n'
+ )
+ expected = 'def foo():\n """Testing\n Testing"""\n print "Foo"\n'
+
+ result = CliRunner().invoke(
+ black.main,
+ ["-", "-q", "--target-version=py27"],
+ input=BytesIO(source),
+ )
+
+ self.assertEqual(result.exit_code, 0)
+ actual = result.output
+ self.assertFormatEqual(actual, expected)
+
with open(black.__file__, "r", encoding="utf-8") as _bf:
black_source_lines = _bf.readlines()