X-Git-Url: https://git.madduck.net/etc/vim.git/blobdiff_plain/728e5a2f1ed16e2cfe0ca5586edac6c10da436c5..25795c9ff5d2e388bcef026d8642a6699208ce37:/tests/test_black.py?ds=inline

diff --git a/tests/test_black.py b/tests/test_black.py
index f6952c7..85ab2b6 100644
--- a/tests/test_black.py
+++ b/tests/test_black.py
@@ -9,9 +9,9 @@ from pathlib import Path
 import re
 import sys
 from tempfile import TemporaryDirectory
-from typing import Any, List, Tuple, Iterator
+from typing import Any, BinaryIO, Generator, List, Tuple, Iterator
 import unittest
-from unittest.mock import patch
+from unittest.mock import patch, MagicMock
 
 from click import unstyle
 from click.testing import CliRunner
@@ -31,13 +31,14 @@ def dump_to_stderr(*output: str) -> str:
     return "\n" + "\n".join(output) + "\n"
 
 
-def read_data(name: str) -> Tuple[str, str]:
+def read_data(name: str, data: bool = True) -> Tuple[str, str]:
     """read_data('test_name') -> 'input', 'output'"""
     if not name.endswith((".py", ".pyi", ".out", ".diff")):
         name += ".py"
     _input: List[str] = []
     _output: List[str] = []
-    with open(THIS_DIR / name, "r", encoding="utf8") as test:
+    base_dir = THIS_DIR / "data" if data else THIS_DIR
+    with open(base_dir / name, "r", encoding="utf8") as test:
         lines = test.readlines()
     result = _input
     for line in lines:
@@ -78,6 +79,31 @@ def event_loop(close: bool) -> Iterator[None]:
             loop.close()
 
 
+class BlackRunner(CliRunner):
+    """Modify CliRunner so that stderr is not merged with stdout.
+
+    This is a hack that can be removed once we depend on Click 7.x"""
+
+    def __init__(self) -> None:
+        self.stderrbuf = BytesIO()
+        self.stdoutbuf = BytesIO()
+        self.stdout_bytes = b""
+        self.stderr_bytes = b""
+        super().__init__()
+
+    @contextmanager
+    def isolation(self, *args: Any, **kwargs: Any) -> Generator[BinaryIO, None, None]:
+        with super().isolation(*args, **kwargs) as output:
+            try:
+                hold_stderr = sys.stderr
+                sys.stderr = TextIOWrapper(self.stderrbuf, encoding=self.charset)
+                yield output
+            finally:
+                self.stdout_bytes = sys.stdout.buffer.getvalue()  # type: ignore
+                self.stderr_bytes = sys.stderr.buffer.getvalue()  # type: ignore
+                sys.stderr = hold_stderr
+
+
 class BlackTestCase(unittest.TestCase):
     maxDiff = None
 
@@ -100,9 +126,28 @@ class BlackTestCase(unittest.TestCase):
                 black.err(str(ve))
         self.assertEqual(expected, actual)
 
+    @patch("black.dump_to_file", dump_to_stderr)
+    def test_empty(self) -> None:
+        source = expected = ""
+        actual = fs(source)
+        self.assertFormatEqual(expected, actual)
+        black.assert_equivalent(source, actual)
+        black.assert_stable(source, actual, line_length=ll)
+
+    def test_empty_ff(self) -> None:
+        expected = ""
+        tmp_file = Path(black.dump_to_file())
+        try:
+            self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES))
+            with open(tmp_file, encoding="utf8") as f:
+                actual = f.read()
+        finally:
+            os.unlink(tmp_file)
+        self.assertFormatEqual(expected, actual)
+
     @patch("black.dump_to_file", dump_to_stderr)
     def test_self(self) -> None:
-        source, expected = read_data("test_black")
+        source, expected = read_data("test_black", data=False)
         actual = fs(source)
         self.assertFormatEqual(expected, actual)
         black.assert_equivalent(source, actual)
@@ -111,7 +156,7 @@ class BlackTestCase(unittest.TestCase):
 
     @patch("black.dump_to_file", dump_to_stderr)
     def test_black(self) -> None:
-        source, expected = read_data("../black")
+        source, expected = read_data("../black", data=False)
         actual = fs(source)
         self.assertFormatEqual(expected, actual)
         black.assert_equivalent(source, actual)
@@ -119,22 +164,16 @@ class BlackTestCase(unittest.TestCase):
         self.assertFalse(ff(THIS_DIR / ".." / "black.py"))
 
     def test_piping(self) -> None:
-        source, expected = read_data("../black")
-        hold_stdin, hold_stdout = sys.stdin, sys.stdout
-        try:
-            sys.stdin = TextIOWrapper(BytesIO(source.encode("utf8")), encoding="utf8")
-            sys.stdout = TextIOWrapper(BytesIO(), encoding="utf8")
-            sys.stdin.buffer.name = "<stdin>"  # type: ignore
-            black.format_stdin_to_stdout(
-                line_length=ll, fast=True, write_back=black.WriteBack.YES
-            )
-            sys.stdout.seek(0)
-            actual = sys.stdout.read()
-        finally:
-            sys.stdin, sys.stdout = hold_stdin, hold_stdout
-        self.assertFormatEqual(expected, actual)
-        black.assert_equivalent(source, actual)
-        black.assert_stable(source, actual, line_length=ll)
+        source, expected = read_data("../black", data=False)
+        result = BlackRunner().invoke(
+            black.main,
+            ["-", "--fast", f"--line-length={ll}"],
+            input=BytesIO(source.encode("utf8")),
+        )
+        self.assertEqual(result.exit_code, 0)
+        self.assertFormatEqual(expected, result.output)
+        black.assert_equivalent(source, result.output)
+        black.assert_stable(source, result.output, line_length=ll)
 
     def test_piping_diff(self) -> None:
         diff_header = re.compile(
@@ -143,24 +182,19 @@ class BlackTestCase(unittest.TestCase):
         )
         source, _ = read_data("expression.py")
         expected, _ = read_data("expression.diff")
-        hold_stdin, hold_stdout = sys.stdin, sys.stdout
-        try:
-            sys.stdin = TextIOWrapper(BytesIO(source.encode("utf8")), encoding="utf8")
-            sys.stdout = TextIOWrapper(BytesIO(), encoding="utf8")
-            black.format_stdin_to_stdout(
-                line_length=ll, fast=True, write_back=black.WriteBack.DIFF
-            )
-            sys.stdout.seek(0)
-            actual = sys.stdout.read()
-            actual = diff_header.sub("[Deterministic header]", actual)
-        finally:
-            sys.stdin, sys.stdout = hold_stdin, hold_stdout
+        config = THIS_DIR / "data" / "empty_pyproject.toml"
+        args = ["-", "--fast", f"--line-length={ll}", "--diff", f"--config={config}"]
+        result = BlackRunner().invoke(
+            black.main, args, input=BytesIO(source.encode("utf8"))
+        )
+        self.assertEqual(result.exit_code, 0)
+        actual = diff_header.sub("[Deterministic header]", result.output)
         actual = actual.rstrip() + "\n"  # the diff output has a trailing space
         self.assertEqual(expected, actual)
 
     @patch("black.dump_to_file", dump_to_stderr)
     def test_setup(self) -> None:
-        source, expected = read_data("../setup")
+        source, expected = read_data("../setup", data=False)
         actual = fs(source)
         self.assertFormatEqual(expected, actual)
         black.assert_equivalent(source, actual)
@@ -213,16 +247,13 @@ class BlackTestCase(unittest.TestCase):
             rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
             rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
         )
-        hold_stdout = sys.stdout
         try:
-            sys.stdout = TextIOWrapper(BytesIO(), encoding="utf8")
-            self.assertTrue(ff(tmp_file, write_back=black.WriteBack.DIFF))
-            sys.stdout.seek(0)
-            actual = sys.stdout.read()
-            actual = diff_header.sub("[Deterministic header]", actual)
+            result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
+            self.assertEqual(result.exit_code, 0)
         finally:
-            sys.stdout = hold_stdout
             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)
@@ -342,6 +373,21 @@ class BlackTestCase(unittest.TestCase):
         black.assert_equivalent(source, actual)
         black.assert_stable(source, actual, line_length=ll)
 
+    @patch("black.dump_to_file", dump_to_stderr)
+    def test_numeric_literals(self) -> None:
+        source, expected = read_data("numeric_literals")
+        actual = fs(source, mode=black.FileMode.PYTHON36)
+        self.assertFormatEqual(expected, actual)
+        black.assert_equivalent(source, actual)
+        black.assert_stable(source, actual, line_length=ll)
+
+    @patch("black.dump_to_file", dump_to_stderr)
+    def test_numeric_literals_py2(self) -> None:
+        source, expected = read_data("numeric_literals_py2")
+        actual = fs(source)
+        self.assertFormatEqual(expected, actual)
+        black.assert_stable(source, actual, line_length=ll)
+
     @patch("black.dump_to_file", dump_to_stderr)
     def test_python2(self) -> None:
         source, expected = read_data("python2")
@@ -365,6 +411,16 @@ class BlackTestCase(unittest.TestCase):
         self.assertFormatEqual(expected, actual)
         black.assert_stable(source, actual, line_length=ll, mode=mode)
 
+    @patch("black.dump_to_file", dump_to_stderr)
+    def test_python37(self) -> None:
+        source, expected = read_data("python37")
+        actual = fs(source)
+        self.assertFormatEqual(expected, actual)
+        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, line_length=ll)
+
     @patch("black.dump_to_file", dump_to_stderr)
     def test_fmtonoff(self) -> None:
         source, expected = read_data("fmtonoff")
@@ -373,6 +429,14 @@ class BlackTestCase(unittest.TestCase):
         black.assert_equivalent(source, actual)
         black.assert_stable(source, actual, line_length=ll)
 
+    @patch("black.dump_to_file", dump_to_stderr)
+    def test_fmtonoff2(self) -> None:
+        source, expected = read_data("fmtonoff2")
+        actual = fs(source)
+        self.assertFormatEqual(expected, actual)
+        black.assert_equivalent(source, actual)
+        black.assert_stable(source, actual, line_length=ll)
+
     @patch("black.dump_to_file", dump_to_stderr)
     def test_remove_empty_parentheses_after_class(self) -> None:
         source, expected = read_data("class_blank_parentheses")
@@ -389,6 +453,14 @@ class BlackTestCase(unittest.TestCase):
         black.assert_equivalent(source, actual)
         black.assert_stable(source, actual, line_length=ll)
 
+    @patch("black.dump_to_file", dump_to_stderr)
+    def test_bracket_match(self) -> None:
+        source, expected = read_data("bracketmatch")
+        actual = fs(source)
+        self.assertFormatEqual(expected, actual)
+        black.assert_equivalent(source, actual)
+        black.assert_stable(source, actual, line_length=ll)
+
     def test_report_verbose(self) -> None:
         report = black.Report(verbose=True)
         out_lines = []
@@ -668,6 +740,10 @@ class BlackTestCase(unittest.TestCase):
         self.assertTrue(black.is_python36(node))
         node = black.lib2to3_parse("def f(*, arg): f'string'\n")
         self.assertTrue(black.is_python36(node))
+        node = black.lib2to3_parse("123_456\n")
+        self.assertTrue(black.is_python36(node))
+        node = black.lib2to3_parse("123456\n")
+        self.assertFalse(black.is_python36(node))
         source, expected = read_data("function")
         node = black.lib2to3_parse(source)
         self.assertTrue(black.is_python36(node))
@@ -700,6 +776,14 @@ class BlackTestCase(unittest.TestCase):
         self.assertEqual(set(), black.get_future_imports(node))
         node = black.lib2to3_parse("from some.module import black\n")
         self.assertEqual(set(), black.get_future_imports(node))
+        node = black.lib2to3_parse(
+            "from __future__ import unicode_literals as _unicode_literals"
+        )
+        self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
+        node = black.lib2to3_parse(
+            "from __future__ import unicode_literals as _lol, print"
+        )
+        self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
 
     def test_debug_visitor(self) -> None:
         source, _ = read_data("debug_visitor.py")
@@ -834,7 +918,9 @@ class BlackTestCase(unittest.TestCase):
     def test_no_cache_when_stdin(self) -> None:
         mode = black.FileMode.AUTO_DETECT
         with cache_dir():
-            result = CliRunner().invoke(black.main, ["-"], input="print('hello')")
+            result = CliRunner().invoke(
+                black.main, ["-"], input=BytesIO(b"print('hello')")
+            )
             self.assertEqual(result.exit_code, 0)
             cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode)
             self.assertFalse(cache_file.exists())
@@ -905,15 +991,13 @@ class BlackTestCase(unittest.TestCase):
     def test_check_diff_use_together(self) -> None:
         with cache_dir():
             # Files which will be reformatted.
-            src1 = (THIS_DIR / "string_quotes.py").resolve()
+            src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
             result = CliRunner().invoke(black.main, [str(src1), "--diff", "--check"])
-            self.assertEqual(result.exit_code, 1)
-
+            self.assertEqual(result.exit_code, 1, result.output)
             # Files which will not be reformatted.
-            src2 = (THIS_DIR / "composition.py").resolve()
+            src2 = (THIS_DIR / "data" / "composition.py").resolve()
             result = CliRunner().invoke(black.main, [str(src2), "--diff", "--check"])
-            self.assertEqual(result.exit_code, 0)
-
+            self.assertEqual(result.exit_code, 0, result.output)
             # Multi file command.
             result = CliRunner().invoke(
                 black.main, [str(src1), str(src2), "--diff", "--check"]
@@ -994,7 +1078,9 @@ class BlackTestCase(unittest.TestCase):
 
     def test_pipe_force_pyi(self) -> None:
         source, expected = read_data("force_pyi")
-        result = CliRunner().invoke(black.main, ["-", "-q", "--pyi"], input=source)
+        result = CliRunner().invoke(
+            black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
+        )
         self.assertEqual(result.exit_code, 0)
         actual = result.output
         self.assertFormatEqual(actual, expected)
@@ -1048,20 +1134,22 @@ class BlackTestCase(unittest.TestCase):
 
     def test_pipe_force_py36(self) -> None:
         source, expected = read_data("force_py36")
-        result = CliRunner().invoke(black.main, ["-", "-q", "--py36"], input=source)
+        result = CliRunner().invoke(
+            black.main, ["-", "-q", "--py36"], input=BytesIO(source.encode("utf8"))
+        )
         self.assertEqual(result.exit_code, 0)
         actual = result.output
         self.assertFormatEqual(actual, expected)
 
     def test_include_exclude(self) -> None:
-        path = THIS_DIR / "include_exclude_tests"
+        path = THIS_DIR / "data" / "include_exclude_tests"
         include = re.compile(r"\.pyi?$")
         exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
         report = black.Report()
         sources: List[Path] = []
         expected = [
-            Path(THIS_DIR / "include_exclude_tests/b/dont_exclude/a.py"),
-            Path(THIS_DIR / "include_exclude_tests/b/dont_exclude/a.pyi"),
+            Path(path / "b/dont_exclude/a.py"),
+            Path(path / "b/dont_exclude/a.pyi"),
         ]
         this_abs = THIS_DIR.resolve()
         sources.extend(
@@ -1070,7 +1158,7 @@ class BlackTestCase(unittest.TestCase):
         self.assertEqual(sorted(expected), sorted(sources))
 
     def test_empty_include(self) -> None:
-        path = THIS_DIR / "include_exclude_tests"
+        path = THIS_DIR / "data" / "include_exclude_tests"
         report = black.Report()
         empty = re.compile(r"")
         sources: List[Path] = []
@@ -1094,7 +1182,7 @@ class BlackTestCase(unittest.TestCase):
         self.assertEqual(sorted(expected), sorted(sources))
 
     def test_empty_exclude(self) -> None:
-        path = THIS_DIR / "include_exclude_tests"
+        path = THIS_DIR / "data" / "include_exclude_tests"
         report = black.Report()
         empty = re.compile(r"")
         sources: List[Path] = []
@@ -1127,10 +1215,80 @@ class BlackTestCase(unittest.TestCase):
                 test_file.write_bytes(contents.encode())
                 ff(test_file, write_back=black.WriteBack.YES)
                 updated_contents: bytes = test_file.read_bytes()
-                self.assertIn(nl.encode(), updated_contents)  # type: ignore
+                self.assertIn(nl.encode(), updated_contents)
                 if nl == "\n":
-                    self.assertNotIn(b"\r\n", updated_contents)  # type: ignore
+                    self.assertNotIn(b"\r\n", updated_contents)
+
+    def test_preserves_line_endings_via_stdin(self) -> None:
+        for nl in ["\n", "\r\n"]:
+            contents = nl.join(["def f(  ):", "    pass"])
+            runner = BlackRunner()
+            result = runner.invoke(
+                black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
+            )
+            self.assertEqual(result.exit_code, 0)
+            output = runner.stdout_bytes
+            self.assertIn(nl.encode("utf8"), output)
+            if nl == "\n":
+                self.assertNotIn(b"\r\n", output)
+
+    def test_assert_equivalent_different_asts(self) -> None:
+        with self.assertRaises(AssertionError):
+            black.assert_equivalent("{}", "None")
+
+    def test_symlink_out_of_root_directory(self) -> None:
+        path = MagicMock()
+        root = THIS_DIR
+        child = MagicMock()
+        include = re.compile(black.DEFAULT_INCLUDES)
+        exclude = re.compile(black.DEFAULT_EXCLUDES)
+        report = black.Report()
+        # `child` should behave like a symlink which resolved path is clearly
+        # outside of the `root` directory.
+        path.iterdir.return_value = [child]
+        child.resolve.return_value = Path("/a/b/c")
+        child.is_symlink.return_value = True
+        try:
+            list(black.gen_python_files_in_dir(path, root, include, exclude, report))
+        except ValueError as ve:
+            self.fail("`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 self.assertRaises(ValueError):
+            list(black.gen_python_files_in_dir(path, root, include, exclude, report))
+        path.iterdir.assert_called()
+        self.assertEqual(path.iterdir.call_count, 2)
+        child.resolve.assert_called()
+        self.assertEqual(child.resolve.call_count, 2)
+        child.is_symlink.assert_called()
+        self.assertEqual(child.is_symlink.call_count, 2)
+
+    def test_shhh_click(self) -> None:
+        try:
+            from click import _unicodefun  # type: ignore
+        except ModuleNotFoundError:
+            self.skipTest("Incompatible Click version")
+        if not hasattr(_unicodefun, "_verify_python3_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()
+        # 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()
+            except RuntimeError as re:
+                self.fail(f"`patch_click()` failed, exception still raised: {re}")
 
 
 if __name__ == "__main__":
-    unittest.main()
+    unittest.main(module="test_black")