#!/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
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)
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:
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.
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)
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")
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")
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")
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()
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")