X-Git-Url: https://git.madduck.net/etc/vim.git/blobdiff_plain/b8c1020b526b488a1a216d9a4d58fc8616b61a99..14c76e89716b5b53c97ece80bb935ea956b7dd89:/tests/test_black.py?ds=inline diff --git a/tests/test_black.py b/tests/test_black.py index a2efef8..7d855ca 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -24,6 +24,7 @@ from typing import ( Iterator, TypeVar, ) +import pytest import unittest from unittest.mock import patch, MagicMock @@ -254,6 +255,24 @@ class BlackTestCase(BlackBaseTestCase): 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") @@ -263,6 +282,23 @@ class BlackTestCase(BlackBaseTestCase): 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) @@ -287,6 +323,7 @@ class BlackTestCase(BlackBaseTestCase): 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( @@ -294,13 +331,14 @@ class BlackTestCase(BlackBaseTestCase): 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) actual = result.output actual = diff_header.sub(DETERMINISTIC_HEADER, actual) - actual = actual.rstrip() + "\n" # the diff output has a trailing space if expected != actual: dump = black.dump_to_file(actual) msg = ( @@ -312,11 +350,12 @@ class BlackTestCase(BlackBaseTestCase): 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) @@ -349,11 +388,12 @@ class BlackTestCase(BlackBaseTestCase): @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) @@ -420,6 +460,34 @@ class BlackTestCase(BlackBaseTestCase): ) self.assertEqual(expected, actual, msg) + @pytest.mark.without_python2 + def test_python2_should_fail_without_optional_install(self) -> None: + # python 3.7 and below will install typed-ast and will be able to parse Python 2 + if sys.version_info < (3, 8): + return + 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") @@ -1338,7 +1406,14 @@ class BlackTestCase(BlackBaseTestCase): 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)) @@ -1360,8 +1435,9 @@ class BlackTestCase(BlackBaseTestCase): 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, @@ -1382,8 +1458,9 @@ class BlackTestCase(BlackBaseTestCase): 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, @@ -1405,8 +1482,9 @@ class BlackTestCase(BlackBaseTestCase): 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, @@ -1432,8 +1510,37 @@ class BlackTestCase(BlackBaseTestCase): 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, @@ -1457,9 +1564,10 @@ class BlackTestCase(BlackBaseTestCase): 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, ) @@ -1499,8 +1607,34 @@ class BlackTestCase(BlackBaseTestCase): 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: @@ -1524,7 +1658,7 @@ class BlackTestCase(BlackBaseTestCase): 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: @@ -1543,7 +1677,14 @@ class BlackTestCase(BlackBaseTestCase): 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)) @@ -1573,25 +1714,21 @@ class BlackTestCase(BlackBaseTestCase): 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( @@ -1599,7 +1736,8 @@ class BlackTestCase(BlackBaseTestCase): path.iterdir(), this_abs, re.compile(black.DEFAULT_INCLUDES), - empty, + re.compile(r"\.pyi$"), + re.compile(r"\.definitely_exclude"), None, report, gitignore, @@ -1607,8 +1745,8 @@ class BlackTestCase(BlackBaseTestCase): ) 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: @@ -1657,7 +1795,14 @@ class BlackTestCase(BlackBaseTestCase): 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: @@ -1671,7 +1816,14 @@ class BlackTestCase(BlackBaseTestCase): 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() @@ -1776,6 +1928,34 @@ class BlackTestCase(BlackBaseTestCase): 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 @@ -1798,6 +1978,49 @@ class BlackTestCase(BlackBaseTestCase): output = black.format_str(source, mode=DEFAULT_MODE) black.assert_stable(source, output, mode=DEFAULT_MODE) + def test_bpo_2142_workaround(self) -> None: + + # https://bugs.python.org/issue2142 + + source, _ = read_data("missing_final_newline.py") + # read_data adds a trailing newline + source = source.rstrip() + expected, _ = read_data("missing_final_newline.diff") + tmp_file = Path(black.dump_to_file(source, ensure_final_newline=False)) + diff_header = re.compile( + rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d " + 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)]) + self.assertEqual(result.exit_code, 0) + finally: + os.unlink(tmp_file) + actual = result.output + 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()