Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 9 commits into from Dec 17, 2022
2 changes: 2 additions & 0 deletions CHANGES.md
Expand Up @@ -17,6 +17,8 @@
<!-- Changes that affect Black's preview style -->

- 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
too long (#3430)
- Long values in dict literals are now wrapped in parentheses; correspondingly
Expand Down
2 changes: 2 additions & 0 deletions src/black/linegen.py
Expand Up @@ -1268,6 +1268,8 @@ def maybe_make_parens_invisible_in_atom(
syms.expr_stmt,
syms.assert_stmt,
syms.return_stmt,
syms.except_clause,
syms.funcdef,
# these ones aren't useful to end users, but they do please fuzzers
syms.for_stmt,
syms.del_stmt,
Expand Down
2 changes: 1 addition & 1 deletion tests/data/py_310/pattern_matching_extras.py
Expand Up @@ -114,6 +114,6 @@ def func(match: case, case: match) -> case:

match bar1:
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
55 changes: 53 additions & 2 deletions tests/util.py
Expand Up @@ -2,6 +2,7 @@
import sys
import unittest
from contextlib import contextmanager
from dataclasses import replace
from functools import partial
from pathlib import Path
from typing import Any, Iterator, List, Optional, Tuple
Expand Down Expand Up @@ -56,6 +57,10 @@ def _assert_format_equal(expected: str, actual: str) -> None:
assert actual == expected


class FormatFailure(Exception):
"""Used to wrap failures when assert_format() runs in an extra mode."""


def assert_format(
source: str,
expected: str,
Expand All @@ -70,12 +75,58 @@ def assert_format(
safety guards so they don't just crash with a SyntaxError. Please note this is
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:
text = "non-preview" if mode.preview else "preview"
JelleZijlstra marked this conversation as resolved.
Show resolved Hide resolved
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)
_assert_format_equal(expected, actual)
if expected is not None:
_assert_format_equal(expected, actual)
# 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
# avoids wasted CPU cycles.
Comment on lines 125 to 127
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# 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
# avoids wasted CPU cycles.

Unfortunately this optimization is now gone and I can't find a way to bring it back :(

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why? The optimization still applies.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, you are right. My bad.

if not fast and source != expected:
if not fast and source != actual:
# 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
# when checking modern code on older versions.
Expand Down