Check stability for both preview and non-preview styles (#3423)

And fix parens-related test failures this found.

Co-authored-by: Richard Si <63936253+ichard26@users.noreply.github.com>
This commit is contained in:
Jelle Zijlstra 2022-12-17 10:31:47 -08:00 committed by GitHub
parent c0089ef19d
commit 159984a735
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 57 additions and 3 deletions

View File

@ -17,6 +17,8 @@
<!-- Changes that affect Black's preview style --> <!-- Changes that affect Black's preview style -->
- Fix a crash in preview style with assert + parenthesized string (#3415) - Fix a crash in preview style with assert + parenthesized string (#3415)
- Fix crashes in preview style with walrus operators used in function return annotations
and except clauses (#3423)
- Do not put the closing quotes in a docstring on a separate line, even if the line is - Do not put the closing quotes in a docstring on a separate line, even if the line is
too long (#3430) too long (#3430)
- Long values in dict literals are now wrapped in parentheses; correspondingly - Long values in dict literals are now wrapped in parentheses; correspondingly

View File

@ -1268,6 +1268,8 @@ def maybe_make_parens_invisible_in_atom(
syms.expr_stmt, syms.expr_stmt,
syms.assert_stmt, syms.assert_stmt,
syms.return_stmt, syms.return_stmt,
syms.except_clause,
syms.funcdef,
# these ones aren't useful to end users, but they do please fuzzers # these ones aren't useful to end users, but they do please fuzzers
syms.for_stmt, syms.for_stmt,
syms.del_stmt, syms.del_stmt,

View File

@ -114,6 +114,6 @@ def func(match: case, case: match) -> case:
match bar1: match bar1:
case Foo( case Foo(
normal=x, perhaps=[list, {an: d, dict: 1.0}] as y, otherwise=something, q=t as u normal=x, perhaps=[list, {"x": d, "y": 1.0}] as y, otherwise=something, q=t as u
): ):
pass pass

View File

@ -2,6 +2,7 @@
import sys import sys
import unittest import unittest
from contextlib import contextmanager from contextlib import contextmanager
from dataclasses import replace
from functools import partial from functools import partial
from pathlib import Path from pathlib import Path
from typing import Any, Iterator, List, Optional, Tuple from typing import Any, Iterator, List, Optional, Tuple
@ -56,6 +57,10 @@ def _assert_format_equal(expected: str, actual: str) -> None:
assert actual == expected assert actual == expected
class FormatFailure(Exception):
"""Used to wrap failures when assert_format() runs in an extra mode."""
def assert_format( def assert_format(
source: str, source: str,
expected: str, expected: str,
@ -70,12 +75,57 @@ def assert_format(
safety guards so they don't just crash with a SyntaxError. Please note this is safety guards so they don't just crash with a SyntaxError. Please note this is
separate from TargetVerson Mode configuration. separate from TargetVerson Mode configuration.
""" """
_assert_format_inner(
source, expected, mode, fast=fast, minimum_version=minimum_version
)
# For both preview and non-preview tests, ensure that Black doesn't crash on
# this code, but don't pass "expected" because the precise output may differ.
try:
_assert_format_inner(
source,
None,
replace(mode, preview=not mode.preview),
fast=fast,
minimum_version=minimum_version,
)
except Exception as e:
text = "non-preview" if mode.preview else "preview"
raise FormatFailure(
f"Black crashed formatting this case in {text} mode."
) from e
# Similarly, setting line length to 1 is a good way to catch
# stability bugs. But only in non-preview mode because preview mode
# currently has a lot of line length 1 bugs.
try:
_assert_format_inner(
source,
None,
replace(mode, preview=False, line_length=1),
fast=fast,
minimum_version=minimum_version,
)
except Exception as e:
raise FormatFailure(
"Black crashed formatting this case with line-length set to 1."
) from e
def _assert_format_inner(
source: str,
expected: Optional[str] = None,
mode: black.Mode = DEFAULT_MODE,
*,
fast: bool = False,
minimum_version: Optional[Tuple[int, int]] = None,
) -> None:
actual = black.format_str(source, mode=mode) actual = black.format_str(source, mode=mode)
if expected is not None:
_assert_format_equal(expected, actual) _assert_format_equal(expected, actual)
# It's not useful to run safety checks if we're expecting no changes anyway. The # It's not useful to run safety checks if we're expecting no changes anyway. The
# assertion right above will raise if reality does actually make changes. This just # assertion right above will raise if reality does actually make changes. This just
# avoids wasted CPU cycles. # avoids wasted CPU cycles.
if not fast and source != expected: if not fast and source != actual:
# Unfortunately the AST equivalence check relies on the built-in ast module # Unfortunately the AST equivalence check relies on the built-in ast module
# being able to parse the code being formatted. This doesn't always work out # being able to parse the code being formatted. This doesn't always work out
# when checking modern code on older versions. # when checking modern code on older versions.