X-Git-Url: https://git.madduck.net/etc/vim.git/blobdiff_plain/883689366ce0f0e0ddd66d81360c61abfd19b01a..fd9f529d8be54a40106043b8135b1f0533420f90:/tests/test_black.py?ds=sidebyside diff --git a/tests/test_black.py b/tests/test_black.py index 9c798ca..92031ca 100644 --- a/tests/test_black.py +++ b/tests/test_black.py @@ -1,15 +1,25 @@ #!/usr/bin/env python3 import asyncio from concurrent.futures import ThreadPoolExecutor -from contextlib import contextmanager -from functools import partial +from contextlib import contextmanager, redirect_stderr +from functools import partial, wraps from io import BytesIO, TextIOWrapper import os from pathlib import Path import re import sys from tempfile import TemporaryDirectory -from typing import Any, BinaryIO, Generator, List, Tuple, Iterator +from typing import ( + Any, + BinaryIO, + Callable, + Coroutine, + Generator, + List, + Tuple, + Iterator, + TypeVar, +) import unittest from unittest.mock import patch, MagicMock @@ -18,6 +28,14 @@ from click.testing import CliRunner import black +try: + import blackd + from aiohttp.test_utils import TestClient, TestServer +except ImportError: + has_blackd_deps = False +else: + has_blackd_deps = True + ll = 88 ff = partial(black.format_file_in_place, line_length=ll, fast=True) @@ -25,6 +43,8 @@ fs = partial(black.format_str, line_length=ll) THIS_FILE = Path(__file__) THIS_DIR = THIS_FILE.parent EMPTY_LINE = "# EMPTY LINE WITH WHITESPACE" + " (this comment will be removed)" +T = TypeVar("T") +R = TypeVar("R") def dump_to_stderr(*output: str) -> str: @@ -79,13 +99,25 @@ def event_loop(close: bool) -> Iterator[None]: loop.close() +def async_test(f: Callable[..., Coroutine[Any, None, R]]) -> Callable[..., None]: + @event_loop(close=True) + @wraps(f) + def wrapper(*args: Any, **kwargs: Any) -> None: + asyncio.get_event_loop().run_until_complete(f(*args, **kwargs)) + + return wrapper + + 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, stderrbuf: BinaryIO) -> None: - self.stderrbuf = stderrbuf + def __init__(self) -> None: + self.stderrbuf = BytesIO() + self.stdoutbuf = BytesIO() + self.stdout_bytes = b"" + self.stderr_bytes = b"" super().__init__() @contextmanager @@ -96,6 +128,8 @@ class BlackRunner(CliRunner): 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 @@ -160,8 +194,7 @@ class BlackTestCase(unittest.TestCase): def test_piping(self) -> None: source, expected = read_data("../black", data=False) - stderrbuf = BytesIO() - result = BlackRunner(stderrbuf).invoke( + result = BlackRunner().invoke( black.main, ["-", "--fast", f"--line-length={ll}"], input=BytesIO(source.encode("utf8")), @@ -179,9 +212,8 @@ class BlackTestCase(unittest.TestCase): source, _ = read_data("expression.py") expected, _ = read_data("expression.diff") config = THIS_DIR / "data" / "empty_pyproject.toml" - stderrbuf = BytesIO() args = ["-", "--fast", f"--line-length={ll}", "--diff", f"--config={config}"] - result = BlackRunner(stderrbuf).invoke( + result = BlackRunner().invoke( black.main, args, input=BytesIO(source.encode("utf8")) ) self.assertEqual(result.exit_code, 0) @@ -244,11 +276,8 @@ 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" ) - stderrbuf = BytesIO() try: - result = BlackRunner(stderrbuf).invoke( - black.main, ["--diff", str(tmp_file)] - ) + result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)]) self.assertEqual(result.exit_code, 0) finally: os.unlink(tmp_file) @@ -260,7 +289,7 @@ class BlackTestCase(unittest.TestCase): msg = ( f"Expected diff isn't equal to the actual. If you made changes " f"to expression.py and this is an anticipated difference, " - f"overwrite tests/expression.diff with {dump}" + f"overwrite tests/data/expression.diff with {dump}" ) self.assertEqual(expected, actual, msg) @@ -333,6 +362,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_comments6(self) -> None: + source, expected = read_data("comments6") + 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_cantfit(self) -> None: source, expected = read_data("cantfit") @@ -381,6 +418,17 @@ 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_ignoring_underscores(self) -> None: + source, expected = read_data("numeric_literals_skip_underscores") + mode = ( + black.FileMode.PYTHON36 | black.FileMode.NO_NUMERIC_UNDERSCORE_NORMALIZATION + ) + actual = fs(source, mode=mode) + self.assertFormatEqual(expected, actual) + black.assert_equivalent(source, actual) + black.assert_stable(source, actual, line_length=ll, mode=mode) + @patch("black.dump_to_file", dump_to_stderr) def test_numeric_literals_py2(self) -> None: source, expected = read_data("numeric_literals_py2") @@ -453,6 +501,27 @@ 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_comment_indentation(self) -> None: + contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n" + contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n" + + self.assertFormatEqual(fs(contents_spc), contents_spc) + self.assertFormatEqual(fs(contents_tab), contents_spc) + + contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n" + contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n" + + self.assertFormatEqual(fs(contents_tab), contents_spc) + self.assertFormatEqual(fs(contents_spc), contents_spc) + def test_report_verbose(self) -> None: report = black.Report(verbose=True) out_lines = [] @@ -816,7 +885,7 @@ class BlackTestCase(unittest.TestCase): actual = black.format_file_contents(different, line_length=ll, fast=False) self.assertEqual(expected, actual) invalid = "return if you can" - with self.assertRaises(ValueError) as e: + with self.assertRaises(black.InvalidInput) as e: black.format_file_contents(invalid, line_length=ll, fast=False) self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can") @@ -1211,6 +1280,19 @@ class BlackTestCase(unittest.TestCase): if nl == "\n": 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") @@ -1230,7 +1312,7 @@ class BlackTestCase(unittest.TestCase): 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}") + self.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() @@ -1268,6 +1350,172 @@ class BlackTestCase(unittest.TestCase): except RuntimeError as re: self.fail(f"`patch_click()` failed, exception still raised: {re}") + @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed") + @async_test + async def test_blackd_request_needs_formatting(self) -> None: + app = blackd.make_app() + async with TestClient(TestServer(app)) as client: + response = await client.post("/", data=b"print('hello world')") + self.assertEqual(response.status, 200) + self.assertEqual(response.charset, "utf8") + self.assertEqual(await response.read(), b'print("hello world")\n') + + @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed") + @async_test + async def test_blackd_request_no_change(self) -> None: + app = blackd.make_app() + async with TestClient(TestServer(app)) as client: + response = await client.post("/", data=b'print("hello world")\n') + self.assertEqual(response.status, 204) + self.assertEqual(await response.read(), b"") + + @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed") + @async_test + async def test_blackd_request_syntax_error(self) -> None: + app = blackd.make_app() + async with TestClient(TestServer(app)) as client: + response = await client.post("/", data=b"what even ( is") + self.assertEqual(response.status, 400) + content = await response.text() + self.assertTrue( + content.startswith("Cannot parse"), + msg=f"Expected error to start with 'Cannot parse', got {repr(content)}", + ) + + @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed") + @async_test + async def test_blackd_unsupported_version(self) -> None: + app = blackd.make_app() + async with TestClient(TestServer(app)) as client: + response = await client.post( + "/", data=b"what", headers={blackd.VERSION_HEADER: "2"} + ) + self.assertEqual(response.status, 501) + + @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed") + @async_test + async def test_blackd_supported_version(self) -> None: + app = blackd.make_app() + async with TestClient(TestServer(app)) as client: + response = await client.post( + "/", data=b"what", headers={blackd.VERSION_HEADER: "1"} + ) + self.assertEqual(response.status, 200) + + @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed") + @async_test + async def test_blackd_invalid_python_variant(self) -> None: + app = blackd.make_app() + async with TestClient(TestServer(app)) as client: + response = await client.post( + "/", data=b"what", headers={blackd.PYTHON_VARIANT_HEADER: "lol"} + ) + self.assertEqual(response.status, 400) + + @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed") + @async_test + async def test_blackd_pyi(self) -> None: + app = blackd.make_app() + async with TestClient(TestServer(app)) as client: + source, expected = read_data("stub.pyi") + response = await client.post( + "/", data=source, headers={blackd.PYTHON_VARIANT_HEADER: "pyi"} + ) + self.assertEqual(response.status, 200) + self.assertEqual(await response.text(), expected) + + @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed") + @async_test + async def test_blackd_py36(self) -> None: + app = blackd.make_app() + async with TestClient(TestServer(app)) as client: + response = await client.post( + "/", + data=( + "def f(\n" + " and_has_a_bunch_of,\n" + " very_long_arguments_too,\n" + " and_lots_of_them_as_well_lol,\n" + " **and_very_long_keyword_arguments\n" + "):\n" + " pass\n" + ), + headers={blackd.PYTHON_VARIANT_HEADER: "3.6"}, + ) + self.assertEqual(response.status, 200) + response = await client.post( + "/", + data=( + "def f(\n" + " and_has_a_bunch_of,\n" + " very_long_arguments_too,\n" + " and_lots_of_them_as_well_lol,\n" + " **and_very_long_keyword_arguments\n" + "):\n" + " pass\n" + ), + headers={blackd.PYTHON_VARIANT_HEADER: "3.5"}, + ) + self.assertEqual(response.status, 204) + response = await client.post( + "/", + data=( + "def f(\n" + " and_has_a_bunch_of,\n" + " very_long_arguments_too,\n" + " and_lots_of_them_as_well_lol,\n" + " **and_very_long_keyword_arguments\n" + "):\n" + " pass\n" + ), + headers={blackd.PYTHON_VARIANT_HEADER: "2"}, + ) + self.assertEqual(response.status, 204) + + @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed") + @async_test + async def test_blackd_fast(self) -> None: + with open(os.devnull, "w") as dn, redirect_stderr(dn): + app = blackd.make_app() + async with TestClient(TestServer(app)) as client: + response = await client.post("/", data=b"ur'hello'") + self.assertEqual(response.status, 500) + self.assertIn("failed to parse source file", await response.text()) + response = await client.post( + "/", data=b"ur'hello'", headers={blackd.FAST_OR_SAFE_HEADER: "fast"} + ) + self.assertEqual(response.status, 200) + + @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed") + @async_test + async def test_blackd_line_length(self) -> None: + app = blackd.make_app() + async with TestClient(TestServer(app)) as client: + response = await client.post( + "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "7"} + ) + self.assertEqual(response.status, 200) + + @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed") + @async_test + async def test_blackd_invalid_line_length(self) -> None: + app = blackd.make_app() + async with TestClient(TestServer(app)) as client: + response = await client.post( + "/", + data=b'print("hello")\n', + headers={blackd.LINE_LENGTH_HEADER: "NaN"}, + ) + self.assertEqual(response.status, 400) + + @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed") + def test_blackd_main(self) -> None: + with patch("blackd.web.run_app"): + result = CliRunner().invoke(blackd.main, []) + if result.exception is not None: + raise result.exception + self.assertEqual(result.exit_code, 0) + if __name__ == "__main__": unittest.main(module="test_black")