From aa085ef9e8d47ef48b8063496e0edcf4e64d505c Mon Sep 17 00:00:00 2001 From: Pedro Mezacasa Muller Date: Thu, 21 May 2026 01:24:33 -0300 Subject: [PATCH 1/9] Add multi-cell fixture format to the test loader --- docs/contributing/the_basics.md | 37 ++++ tests/test_format.py | 67 +++++-- tests/util.py | 311 +++++++++++++++++++++++++++----- 3 files changed, 354 insertions(+), 61 deletions(-) diff --git a/docs/contributing/the_basics.md b/docs/contributing/the_basics.md index c6e0c788a19..8806e69960c 100644 --- a/docs/contributing/the_basics.md +++ b/docs/contributing/the_basics.md @@ -67,6 +67,43 @@ files in the `tests/data/cases` directory. These files consist of up to three pa - The line `# output`, followed by the output of _Black_ when run on the previous block. If this is omitted, the test asserts that _Black_ will leave the input code unchanged. +#### Multi-cell format + +Large fixture files can also group multiple cases inside one file using a cell header +inspired by [mypy's test data format][mypy-test-data]. The two formats coexist: a file +is detected as multi-cell only when its first non-blank non-comment line is a `[case ]` +header. Each cell starts at column 0 with `[case ]` and runs as an independent +test. Cell names match `[A-Za-z_][A-Za-z0-9_]*` and must be unique within the file. + +[mypy-test-data]: https://github.com/python/mypy/blob/master/mypy/test/data.py + +``` +[case empty_call] +foo() +# output +foo() + +[case nested_call_with_args] +# flags: --preview +foo(bar(x, y)) +# output +foo(bar(x, y)) +``` + +Rules: + +- Anything before the first `[case ]` is file-level prose and is ignored by the loader. +- Within a cell, the legacy rules apply: an optional `# flags: ` line on the first + non-blank line, then input, then optional `# output` followed by expected output. + Omitting `# output` asserts idempotency. +- Trailing whitespace on a `[case ]` line is tolerated; everything else on the + line is rejected. + +Pytest IDs reflect the format. Single-case files keep the legacy +`test_simple_format[]` shape; multi-cell files produce +`test_simple_format[::]`. On failure, the error message points at the +file path, the cell header line, and the cell's `# output` marker line. + _Black_ has two pytest command-line options affecting test files in `tests/data/` that are split into an input part, and an output part, separated by a line with `# output`. These can be passed to `pytest` through `tox`, or directly into pytest if not using diff --git a/tests/test_format.py b/tests/test_format.py index 2a9d6280143..6addb9f79d6 100644 --- a/tests/test_format.py +++ b/tests/test_format.py @@ -9,54 +9,83 @@ import black from black.mode import TargetVersion from tests.util import ( + CaseId, + FormatFailure, all_data_cases, assert_format, dump_to_stderr, + find_cell, + get_case_path, read_data, read_data_with_mode, ) +def _case_id(case: CaseId) -> str: + if isinstance(case, tuple): + stem, cell = case + return f"{stem}::{cell}" + return case + + @pytest.fixture(autouse=True) def patch_dump_to_file(request: Any) -> Iterator[None]: with patch("black.dump_to_file", dump_to_stderr): yield -def check_file(subdir: str, filename: str, *, data: bool = True) -> None: +def check_file(subdir: str, filename: CaseId, *, data: bool = True) -> None: args, source, expected = read_data_with_mode(subdir, filename, data=data) - assert_format( - source, - expected, - args.mode, - fast=args.fast, - minimum_version=args.minimum_version, - lines=args.lines, - no_preview_line_length_1=args.no_preview_line_length_1, - ) - if args.minimum_version is not None: - major, minor = args.minimum_version - target_version = TargetVersion[f"PY{major}{minor}"] - mode = replace(args.mode, target_versions={target_version}) + try: assert_format( source, expected, - mode, + args.mode, fast=args.fast, minimum_version=args.minimum_version, lines=args.lines, no_preview_line_length_1=args.no_preview_line_length_1, ) + if args.minimum_version is not None: + major, minor = args.minimum_version + target_version = TargetVersion[f"PY{major}{minor}"] + mode = replace(args.mode, target_versions={target_version}) + assert_format( + source, + expected, + mode, + fast=args.fast, + minimum_version=args.minimum_version, + lines=args.lines, + no_preview_line_length_1=args.no_preview_line_length_1, + ) + except (AssertionError, FormatFailure) as e: + if not isinstance(filename, tuple): + raise + stem, cell_name = filename + path = get_case_path(subdir, stem, data=data) + target = find_cell(path, cell_name) + if target is not None: + header = f"\ncell `{cell_name}` at {path}:{target.header_lineno}" + if target.output_lineno is not None: + header += f"\n# output marker at {path}:{target.output_lineno}" + else: + header += "\n(idempotency cell, no `# output` marker)" + else: + header = f"\ncell `{cell_name}` at {path}" + raise type(e)(f"{header}\n{e}") from e @pytest.mark.filterwarnings("ignore:invalid escape sequence.*:DeprecationWarning") -@pytest.mark.parametrize("filename", all_data_cases("cases")) -def test_simple_format(filename: str) -> None: +@pytest.mark.parametrize("filename", all_data_cases("cases"), ids=_case_id) +def test_simple_format(filename: CaseId) -> None: check_file("cases", filename) -@pytest.mark.parametrize("filename", all_data_cases("line_ranges_formatted")) -def test_line_ranges_line_by_line(filename: str) -> None: +@pytest.mark.parametrize( + "filename", all_data_cases("line_ranges_formatted"), ids=_case_id +) +def test_line_ranges_line_by_line(filename: CaseId) -> None: args, source, expected = read_data_with_mode("line_ranges_formatted", filename) assert ( source == expected diff --git a/tests/util.py b/tests/util.py index 26fd5aae720..3beb193159e 100644 --- a/tests/util.py +++ b/tests/util.py @@ -1,6 +1,7 @@ import argparse import functools import os +import re import shlex import sys import unittest @@ -197,34 +198,7 @@ def get_base_dir(data: bool) -> Path: return DATA_DIR if data else PROJECT_ROOT -def all_data_cases(subdir_name: str, data: bool = True) -> list[str]: - cases_dir = get_base_dir(data) / subdir_name - assert cases_dir.is_dir() - return [case_path.stem for case_path in cases_dir.iterdir()] - - -def get_case_path( - subdir_name: str, name: str, data: bool = True, suffix: str = PYTHON_SUFFIX -) -> Path: - """Get case path from name""" - case_path = get_base_dir(data) / subdir_name / name - if not name.endswith(ALLOWED_SUFFIXES): - case_path = case_path.with_suffix(suffix) - assert case_path.is_file(), f"{case_path} is not a file." - return case_path - - -def read_data_with_mode( - subdir_name: str, name: str, data: bool = True -) -> tuple[TestCaseArgs, str, str]: - """read_data_with_mode('test_name') -> Mode(), 'input', 'output'""" - return read_data_from_file(get_case_path(subdir_name, name, data)) - - -def read_data(subdir_name: str, name: str, data: bool = True) -> tuple[str, str]: - """read_data('test_name') -> 'input', 'output'""" - _, input, output = read_data_with_mode(subdir_name, name, data) - return input, output +CaseId = str | tuple[str, str] def _parse_minimum_version(version: str) -> tuple[int, int]: @@ -304,25 +278,36 @@ def parse_mode(flags_line: str) -> TestCaseArgs: ) -def read_data_from_file(file_name: Path) -> tuple[TestCaseArgs, str, str]: - with open(file_name, encoding="utf8") as test: - lines = test.readlines() +def _parse_cell_body( + lines: list[str], +) -> tuple[TestCaseArgs, str, str, int | None]: + """Parse one cell (or whole-file) body. Returns mode, input, output, and + the 0-based index into ``lines`` of the ``# output`` marker (or None for + idempotency cells).""" _input: list[str] = [] _output: list[str] = [] result = _input mode = TestCaseArgs() - for line in lines: - if not _input and line.startswith("# flags: "): - mode = parse_mode(line[len("# flags: ") :]) - if mode.lines: - # Retain the `# flags: ` line when using --line-ranges=. This requires - # the `# output` section to also include this line, but retaining the - # line is important to make the line ranges match what you see in the - # test file. - result.append(line) - continue + output_offset: int | None = None + for idx, line in enumerate(lines): + if not _input: + if not line.strip(): + # Tolerate leading blank lines before the optional `# flags: ` + # line (or before the first content line, when no flags are + # set). Matches the doc's "first non-blank line" promise. + continue + if line.startswith("# flags: "): + mode = parse_mode(line[len("# flags: ") :]) + if mode.lines: + # Retain the `# flags: ` line when using --line-ranges=. This requires + # the `# output` section to also include this line, but retaining the + # line is important to make the line ranges match what you see in the + # test file. + result.append(line) + continue line = line.replace(EMPTY_LINE, "") if line.rstrip() == "# output": + output_offset = idx result = _output continue @@ -330,7 +315,249 @@ def read_data_from_file(file_name: Path) -> tuple[TestCaseArgs, str, str]: if _input and not _output: # If there's no output marker, treat the entire file as already pre-formatted. _output = _input[:] - return mode, "".join(_input).strip() + "\n", "".join(_output).strip() + "\n" + return ( + mode, + "".join(_input).strip() + "\n", + "".join(_output).strip() + "\n", + output_offset, + ) + + +def read_data_from_file(file_name: Path) -> tuple[TestCaseArgs, str, str]: + with open(file_name, encoding="utf8") as test: + lines = test.readlines() + mode, input_text, output_text, _ = _parse_cell_body(lines) + return mode, input_text, output_text + + +CELL_HEADER_RE = re.compile(r"^\[case ([A-Za-z_][A-Za-z0-9_]*)\]$") + + +@dataclass(frozen=True) +class Cell: + name: str + args: TestCaseArgs + input: str + output: str + header_lineno: int + output_lineno: int | None + + +def is_multi_cell(lines: list[str]) -> bool: + for line in lines: + rstripped = line.rstrip() + if not rstripped: + continue + if rstripped.startswith("#"): + continue + return bool(CELL_HEADER_RE.match(rstripped)) + return False + + +def _build_cell( + name: str, + header_lineno: int, + block_start: int, + body_lines: list[str], +) -> Cell: + args, input_text, output_text, output_offset = _parse_cell_body(body_lines) + output_lineno = block_start + output_offset if output_offset is not None else None + return Cell( + name=name, + args=args, + input=input_text, + output=output_text, + header_lineno=header_lineno, + output_lineno=output_lineno, + ) + + +def parse_cells(text: str, path: Path) -> list[Cell] | None: + """Parse a multi-cell fixture file. + + Returns a list of Cell instances, or None if the file is single-case + (its first non-blank non-comment line is not a `[case ]` header). + Header lines tolerate trailing whitespace; everything else — including + leading whitespace — is rejected. + """ + lines = text.splitlines(keepends=True) + if not is_multi_cell(lines): + return None + + cells: list[Cell] = [] + current_name: str | None = None + current_header_lineno: int = 0 + current_lines: list[str] = [] + current_block_start: int = 0 # 1-based line number of first cell content line + + for idx, line in enumerate(lines, start=1): + match = CELL_HEADER_RE.match(line.rstrip()) + if match: + if current_name is not None: + cells.append( + _build_cell( + current_name, + current_header_lineno, + current_block_start, + current_lines, + ) + ) + current_name = match.group(1) + current_header_lineno = idx + current_lines = [] + current_block_start = idx + 1 + else: + if current_name is not None: + current_lines.append(line) + + if current_name is not None: + cells.append( + _build_cell( + current_name, + current_header_lineno, + current_block_start, + current_lines, + ) + ) + + assert cells, ( + f"unreachable: {path}: is_multi_cell said yes but no `[case ]`" + " headers were parsed" + ) + + seen: dict[str, int] = {} + for cell in cells: + if cell.name in seen: + raise ValueError( + f"Duplicate cell name `{cell.name}` in {path}:" + f" first at line {seen[cell.name]}, again at line" + f" {cell.header_lineno}" + ) + seen[cell.name] = cell.header_lineno + + return cells + + +@functools.lru_cache(maxsize=None) +def try_parse_cells(case_path: Path) -> tuple[Cell, ...] | None: + """Parse cells from `case_path`, cached on file path. + + Returns None for single-case files; returns an immutable tuple of cells for + multi-cell files. Cache is keyed on Path equality and lives for the whole + test process — file edits during a single run won't be picked up. + """ + if case_path.suffix not in (PYTHON_SUFFIX, ".pyi"): + return None + try: + text = case_path.read_text(encoding="utf8") + except (OSError, UnicodeDecodeError): + return None + cells = parse_cells(text, case_path) + if cells is None: + return None + return tuple(cells) + + +def find_cell(case_path: Path, cell_name: str) -> Cell | None: + """Return the named cell from a multi-cell fixture, or None if missing. + + Returns None for single-case files. + """ + cells = try_parse_cells(case_path) + if cells is None: + return None + for cell in cells: + if cell.name == cell_name: + return cell + return None + + +def all_data_cases(subdir_name: str, data: bool = True) -> list[CaseId]: + """Discover test cases under `subdir_name`. + + Single-case files contribute one `stem` entry; multi-cell files contribute + one `(stem, cell_name)` tuple per `[case ]` cell. + """ + cases_dir = get_base_dir(data) / subdir_name + assert cases_dir.is_dir() + result: list[CaseId] = [] + for case_path in cases_dir.iterdir(): + if not case_path.is_file(): + continue + # test_simple_format only consumes .py/.pyi fixtures; .diff/.out/.ipynb + # files are loaded by name (with extension) by other tests. + if case_path.suffix not in (PYTHON_SUFFIX, ".pyi"): + continue + cells = try_parse_cells(case_path) + if cells is None: + result.append(case_path.stem) + else: + for cell in cells: + result.append((case_path.stem, cell.name)) + return result + + +def _normalize_case(name: CaseId) -> tuple[str, str | None]: + """Split a `CaseId` into `(stem, cell_name_or_None)`. + + Centralizes the union-discrimination so callers don't sprinkle + `isinstance(name, tuple)` everywhere. + """ + if isinstance(name, tuple): + return name[0], name[1] + return name, None + + +def get_case_path( + subdir_name: str, + stem: str, + data: bool = True, + suffix: str = PYTHON_SUFFIX, +) -> Path: + """Get case path from a bare stem.""" + case_path = get_base_dir(data) / subdir_name / stem + if not stem.endswith(ALLOWED_SUFFIXES): + case_path = case_path.with_suffix(suffix) + assert case_path.is_file(), f"{case_path} is not a file." + return case_path + + +def read_data_with_mode( + subdir_name: str, name: CaseId, data: bool = True +) -> tuple[TestCaseArgs, str, str]: + """read_data_with_mode('test_name') -> Mode(), 'input', 'output'. + + `name` is either a bare stem (legacy single-case) or a `(stem, cell_name)` + tuple (multi-cell). Calling with a bare stem on a multi-cell file is an + error — there is no well-defined "whole-file" view of a multi-cell + fixture. + """ + stem, cell_name = _normalize_case(name) + path = get_case_path(subdir_name, stem, data) + cells = try_parse_cells(path) + if cell_name is not None: + if cells is None: + raise AssertionError( + f"{path}: requested cell `{cell_name}` but file is single-case." + ) + for cell in cells: + if cell.name == cell_name: + return cell.args, cell.input, cell.output + raise AssertionError(f"{path}: no cell named `{cell_name}`.") + if cells is not None: + raise AssertionError( + f"{path}: bare-stem read of a multi-cell file is not supported." + " Use a (stem, cell_name) tuple to address a specific cell." + ) + return read_data_from_file(path) + + +def read_data( + subdir_name: str, name: CaseId, data: bool = True +) -> tuple[str, str]: + """read_data('test_name') -> 'input', 'output'""" + _, input, output = read_data_with_mode(subdir_name, name, data) + return input, output def read_jupyter_notebook(subdir_name: str, name: str, data: bool = True) -> str: From a38f018d156a37ae9706d5f36f235cb548b3d4ce Mon Sep 17 00:00:00 2001 From: Pedro Mezacasa Muller Date: Thu, 21 May 2026 01:24:41 -0300 Subject: [PATCH 2/9] Migrate preview_long_strings__regression to multi-cell format --- .../cases/preview_long_strings__regression.py | 1463 ++++++++++------- 1 file changed, 890 insertions(+), 573 deletions(-) diff --git a/tests/data/cases/preview_long_strings__regression.py b/tests/data/cases/preview_long_strings__regression.py index 58fe32090e8..441df94f47d 100644 --- a/tests/data/cases/preview_long_strings__regression.py +++ b/tests/data/cases/preview_long_strings__regression.py @@ -1,576 +1,10 @@ -# flags: --unstable -class A: - def foo(): - result = type(message)("") - - -# Don't merge multiline (e.g. triple-quoted) strings. -def foo(): - query = ( - """SELECT xxxxxxxxxxxxxxxxxxxx(xxx)""" - """ FROM xxxxxxxxxxxxxxxx WHERE xxxxxxxxxx AND xxx <> xxxxxxxxxxxxxx()""") - -# There was a bug where tuples were being identified as long strings. -long_tuple = ('Apple', 'Berry', 'Cherry', 'Dill', 'Evergreen', 'Fig', - 'Grape', 'Harry', 'Iglu', 'Jaguar') - -stupid_format_method_bug = "Some really long string that just so happens to be the {} {} to force the 'format' method to hang over the line length boundary. This is pretty annoying.".format("perfect", "length") - -class A: - def foo(): - os.system("This is a regression test. xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxxx.".format("xxxxxxxxxx", "xxxxxx", "xxxxxxxxxx")) - - -class A: - def foo(): - XXXXXXXXXXXX.append( - ( - "xxx_xxxxxxxxxx(xxxxx={}, xxxx={}, xxxxx, xxxx_xxxx_xxxxxxxxxx={})".format( - xxxxx, xxxx, xxxx_xxxx_xxxxxxxxxx - ), - my_var, - my_other_var, - ) - ) - -class A: - class B: - def foo(): - bar( - ( - "[{}]: xxx_xxxxxxxxxx(xxxxx={}, xxxx={}, xxxxx={}" - " xxxx_xxxx_xxxxxxxxxx={}, xxxx={})" - .format(xxxx._xxxxxxxxxxxxxx, xxxxx, xxxx, xxxx_xxxx_xxxxxxxxxx, xxxxxxx) - ), - varX, - varY, - varZ, - ) - -def foo(xxxx): - for (xxx_xxxx, _xxx_xxx, _xxx_xxxxx, xxx_xxxx) in xxxx: - for xxx in xxx_xxxx: - assert ("x" in xxx) or ( - xxx in xxx_xxx_xxxxx - ), "{0} xxxxxxx xx {1}, xxx {1} xx xxx xx xxxx xx xxx xxxx: xxx xxxx {2}".format( - xxx_xxxx, xxx, xxxxxx.xxxxxxx(xxx_xxx_xxxxx) - ) - -class A: - def disappearing_comment(): - return ( - ( # xx -x xxxxxxx xx xxx xxxxxxx. - '{{xxx_xxxxxxxxxx_xxxxxxxx}} xxx xxxx' - ' {} {{xxxx}} >&2' - .format( - "{xxxx} {xxxxxx}" - if xxxxx.xx_xxxxxxxxxx - else ( # Disappearing Comment - "--xxxxxxx --xxxxxx=x --xxxxxx-xxxxx=xxxxxx" - " --xxxxxx-xxxx=xxxxxxxxxxx.xxx" - ) - ) - ), - (x, y, z), - ) - -class A: - class B: - def foo(): - xxxxx_xxxx( - xx, "\t" - "@xxxxxx '{xxxx_xxx}\t' > {xxxxxx_xxxx}.xxxxxxx;" - "{xxxx_xxx} >> {xxxxxx_xxxx}.xxxxxxx 2>&1; xx=$$?;" - "xxxx $$xx" - .format(xxxx_xxx=xxxx_xxxxxxx, xxxxxx_xxxx=xxxxxxx + "/" + xxxx_xxx_xxxx, x=xxx_xxxxx_xxxxx_xxx), - x, - y, - z, - ) - -func_call_where_string_arg_has_method_call_and_bad_parens( - ( - "A long string with {}. This string is so long that it is ridiculous. It can't fit on one line at alllll.".format("formatting") - ), -) - -func_call_where_string_arg_has_old_fmt_and_bad_parens( - ( - "A long string with {}. This string is so long that it is ridiculous. It can't fit on one line at alllll." % "formatting" - ), -) - -func_call_where_string_arg_has_old_fmt_and_bad_parens( - ( - "A long string with {}. This {} is so long that it is ridiculous. It can't fit on one line at alllll." % ("formatting", "string") - ), -) - -class A: - def append(self): - if True: - xxxx.xxxxxxx.xxxxx( ('xxxxxxxxxx xxxx xx xxxxxx(%x) xx %x xxxx xx xxx %x.xx' - % (len(self) + 1, - xxxx.xxxxxxxxxx, - xxxx.xxxxxxxxxx)) - + (' %.3f (%s) to %.3f (%s).\n' - % (xxxx.xxxxxxxxx, - xxxx.xxxxxxxxxxxxxx(xxxx.xxxxxxxxx), - x, - xxxx.xxxxxxxxxxxxxx( xx) - ))) - -class A: - def foo(): - some_func_call( - 'xxxxxxxxxx', - ( - "xx {xxxxxxxxxxx}/xxxxxxxxxxx.xxx xxxx.xxx && xxxxxx -x " - "\"xxxx xxxxxxx xxxxxx xxxx; xxxx xxxxxx_xxxxx xxxxxx xxxx; " - "xxxx.xxxx_xxxxxx(['xxxx.xxx'], xxxx.xxxxxxx().xxxxxxxxxx)\" " - ), - None, - ('xxxxxxxxxxx',), - ), - -class A: - def foo(): - some_func_call( - ( - "xx {xxxxxxxxxxx}/xxxxxxxxxxx.xxx xxxx.xxx && xxxxxx -x " - "xxxx, ('xxxxxxx xxxxxx xxxx, xxxx') xxxxxx_xxxxx xxxxxx xxxx; " - "xxxx.xxxx_xxxxxx(['xxxx.xxx'], xxxx.xxxxxxx().xxxxxxxxxx)\" " - ), - None, - ('xxxxxxxxxxx',), - ), - -xxxxxxx = { 'xx' : 'xxxx xxxxxxx xxxxxxxxx -x xxx -x /xxx/{0} -x xxx,xxx -xx {1} \ --xx {1} -xx xxx=xxx_xxxx,xxx_xx,xxx_xxx,xxx_xxxx,xxx_xx,xxx_xxx |\ - xxxxxx -x xxxxxxxx -x xxxxxxxx -x', - 'xx' : 'xxxx xxxxxxx xxxxxxxxx -x xxx -x /xxx/{0} -x xxx,xxx -xx {1} \ --xx {1} -xx xxx=xxx_xxxx_xxx_xxxx,xxx_xx_xxx_xxxx,xxx_xxxx_xxx_xxxx,\ -xxx_xx_xxxx_xxxx,xxx_xxx_xxxx,xxx_xxx_xxxx xxxx=xxx | xxxxxx -x xxxxxxxx -x xxxxxxxx -x' -} - -class A: - def foo(self): - if True: - xxxxx_xxxxxxxxxxxx('xxx xxxxxx xxx xxxxxxxxx.xx xx xxxxxxxx. xxx xxxxxxxxxxxxx.xx xxxxxxx ' - + 'xx xxxxxx xxxxxx xxxxxx xx xxxxxxx xxx xxx ${0} xx x xxxxxxxx xxxxx'.xxxxxx(xxxxxx_xxxxxx_xxx)) - -class A: - class B: - def foo(): - row = { - 'xxxxxxxxxxxxxxx' : xxxxxx_xxxxx_xxxx, - # 'xxxxxxxxxxxxxxxxxxxxxxx' - # 'xxxxxxxxxxxxxxxxxxxxxx' - # 'xxxxxxxxxxxxxxxxxx' - # 'xxxxxxxxxxxxxxxxx' - 'xxxxxxxxxx' : xxxxx_xxxxx, - } - -class A: - def xxxx_xxx_xx_xxxxxxxxxx_xxxx_xxxxxxxxx(xxxx): - xxxxxxxx = [ - xxxxxxxxxxxxxxxx( - 'xxxx', - xxxxxxxxxxx={ - 'xxxx' : 1.0, - }, - xxxxxx={'xxxxxx 1' : xxxxxx(xxxx='xxxxxx 1', xxxxxx=600.0)}, - xxxxxxxx_xxxxxxx=0.0, - ), - xxxxxxxxxxxxxxxx( - 'xxxxxxx', - xxxxxxxxxxx={ - 'xxxx' : 1.0, - }, - xxxxxx={'xxxxxx 1' : xxxxxx(xxxx='xxxxxx 1', xxxxxx=200.0)}, - xxxxxxxx_xxxxxxx=0.0, - ), - xxxxxxxxxxxxxxxx( - 'xxxx', - ), - ] - -some_dictionary = { - 'xxxxx006': ['xxx-xxx xxxxx3xxxx1xx2xxxxxxxxxxxxxx0xx6xxxxxxxxxx2xxxxxx9xxxxxxxxxx0xxxxx1xxx2x/xx9xx6+x+xxxxxxxxxxxxxx4xxxxxxxxxxxxxxxxxxxxx43xxx2xx2x4x++xxx6xxxxxxxxx+xxxxx/xx9x+xxxxxxxxxxxxxx8x15xxxxxxxxxxxxxxxxx82xx/xxxxxxxxxxxxxx/x5xxxxxxxxxxxxxx6xxxxxx74x4/xxx4x+xxxxxxxxx2xxxxxxxx87xxxxx4xxxxxxxx3xx0xxxxx4xxx1xx9xx5xxxxxxx/xxxxx5xx6xx4xxxx1x/x2xxxxxxxxxxxx64xxxxxxx1x0xx5xxxxxxxxxxxxxx== xxxxx000 xxxxxxxxxx\n', - 'xxx-xxx xxxxx3xxxx1xx2xxxxxxxxxxxxxx6xxxxxxxxxxxxxx9xxxxxxxxxxxxx3xxx9xxxxxxxxxxxxxxxx0xxxxxxxxxxxxxxxxx2xxxx2xxx6xxxxx/xx54xxxxxxxxx4xxx3xxxxxx9xx3xxxxx39xxxxxxxxx5xx91xxxx7xxxxxx8xxxxxxxxxxxxxxxx9xxx93xxxxxxxxxxxxxxxxx7xxx8xx8xx4/x1xxxxx1x3xxxxxxxxxxxxx3xxxxxx9xx4xx4x7xxxxxxxxxxxxx1xxxxxxxxx7xxxxxxxxxxxxxx4xx6xxxxxxxxx9xxx7xxxx2xxxxxxxxxxxxxxxxxxxxxx8xxxxxxxxxxxxxxxxxxxx6xx== xxxxx010 xxxxxxxxxx\n'], - 'xxxxx016': ['xxx-xxx xxxxx3xxxx1xx2xxxxxxxxxxxxxx0xx6xxxxxxxxxx2xxxxxx9xxxxxxxxxx0xxxxx1xxx2x/xx9xx6+x+xxxxxxxxxxxxxx4xxxxxxxxxxxxxxxxxxxxx43xxx2xx2x4x++xxx6xxxxxxxxx+xxxxx/xx9x+xxxxxxxxxxxxxx8x15xxxxxxxxxxxxxxxxx82xx/xxxxxxxxxxxxxx/x5xxxxxxxxxxxxxx6xxxxxx74x4/xxx4x+xxxxxxxxx2xxxxxxxx87xxxxx4xxxxxxxx3xx0xxxxx4xxx1xx9xx5xxxxxxx/xxxxx5xx6xx4xxxx1x/x2xxxxxxxxxxxx64xxxxxxx1x0xx5xxxxxxxxxxxxxx== xxxxx000 xxxxxxxxxx\n', - 'xxx-xxx xxxxx3xxxx1xx2xxxxxxxxxxxxxx6xxxxxxxxxxxxxx9xxxxxxxxxxxxx3xxx9xxxxxxxxxxxxxxxx0xxxxxxxxxxxxxxxxx2xxxx2xxx6xxxxx/xx54xxxxxxxxx4xxx3xxxxxx9xx3xxxxx39xxxxxxxxx5xx91xxxx7xxxxxx8xxxxxxxxxxxxxxxx9xxx93xxxxxxxxxxxxxxxxx7xxx8xx8xx4/x1xxxxx1x3xxxxxxxxxxxxx3xxxxxx9xx4xx4x7xxxxxxxxxxxxx1xxxxxxxxx7xxxxxxxxxxxxxx4xx6xxxxxxxxx9xxx7xxxx2xxxxxxxxxxxxxxxxxxxxxx8xxxxxxxxxxxxxxxxxxxx6xx== xxxxx010 xxxxxxxxxx\n'] -} - -def foo(): - xxx_xxx = ( - 'xxxx xxx xxxxxxxx_xxxx xx "xxxxxxxxxx".' - '\n xxx: xxxxxx xxxxxxxx_xxxx=xxxxxxxxxx' - ) # xxxx xxxxxxxxxx xxxx xx xxxx xx xxx xxxxxxxx xxxxxx xxxxx. - -some_tuple = ("some string", "some string" " which should be joined") - -some_commented_string = ( # This comment stays at the top. - "This string is long but not so long that it needs hahahah toooooo be so greatttt" - " {} that I just can't think of any more good words to say about it at" - " allllllllllll".format("ha") # comments here are fine -) - -some_commented_string = ( - "This string is long but not so long that it needs hahahah toooooo be so greatttt" # But these - " {} that I just can't think of any more good words to say about it at" # comments will stay - " allllllllllll".format("ha") # comments here are fine -) - -lpar_and_rpar_have_comments = func_call( # LPAR Comment - "Long really ridiculous type of string that shouldn't really even exist at all. I mean commmme onnn!!!", # Comma Comment -) # RPAR Comment - -cmd_fstring = ( - f"sudo -E deluge-console info --detailed --sort-reverse=time_added " - f"{'' if ID is None else ID} | perl -nE 'print if /^{field}:/'" -) - -cmd_fstring = f"sudo -E deluge-console info --detailed --sort-reverse=time_added {'' if ID is None else ID} | perl -nE 'print if /^{field}:/'" - -cmd_fstring = f"sudo -E deluge-console info --detailed --sort-reverse=time_added {'{{}}' if ID is None else ID} | perl -nE 'print if /^{field}:/'" - -cmd_fstring = f"sudo -E deluge-console info --detailed --sort-reverse=time_added {{'' if ID is None else ID}} | perl -nE 'print if /^{field}:/'" - -fstring = f"This string really doesn't need to be an {{{{fstring}}}}, but this one most certainly, absolutely {does}." - -fstring = ( - f"We have to remember to escape {braces}." - " Like {these}." - f" But not {this}." -) - -class A: - class B: - def foo(): - st_error = STError( - f"This string ({string_leaf.value}) appears to be pointless (i.e. has" - " no parent)." - ) - -def foo(): - user_regex = _lazy_re_compile( - r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*\Z" # dot-atom - r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"\Z)', # quoted-string - re.IGNORECASE) - -def foo(): - user_regex = _lazy_re_compile( - "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # dot-atom - 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', # quoted-string - xyz - ) - -def foo(): - user_regex = _lazy_re_compile( - "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # dot-atom - 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', # quoted-string - xyz - ) - -class A: - class B: - def foo(): - if not hasattr(module, name): - raise ValueError( - "Could not find object %s in %s.\n" - "Please note that you cannot serialize things like inner " - "classes. Please move the object into the main module " - "body to use migrations.\n" - "For more information, see " - "https://docs.djangoproject.com/en/%s/topics/migrations/#serializing-values" - % (name, module_name, get_docs_version())) - -class A: - class B: - def foo(): - if not hasattr(module, name): - raise ValueError( - "Could not find object %s in %s.\nPlease note that you cannot serialize things like inner classes. Please move the object into the main module body to use migrations.\nFor more information, see https://docs.djangoproject.com/en/%s/topics/migrations/#serializing-values" - % (name, module_name, get_docs_version())) - -x = ( - "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" - "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" - "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" - "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -) - -class Step(StepBase): - def who(self): - self.cmd = 'SR AAAA-CORRECT NAME IS {last_name} {first_name}{middle_name} {title}/P{passenger_association}'.format( - last_name=last_name, - first_name=first_name, - middle_name=middle_name, - title=title, - passenger_association=passenger_association, - ) - -xxxxxxx_xxxxxx_xxxxxxx = xxx( - [ - xxxxxxxxxxxx( - xxxxxx_xxxxxxx=( - '((x.aaaaaaaaa = "xxxxxx.xxxxxxxxxxxxxxxxxxxxx") || (x.xxxxxxxxx = "xxxxxxxxxxxx")) && ' - # xxxxx xxxxxxxxxxxx xxxx xxx (xxxxxxxxxxxxxxxx) xx x xxxxxxxxx xx xxxxxx. - "(x.bbbbbbbbbbbb.xxx != " - '"xxx:xxx:xxx::cccccccccccc:xxxxxxx-xxxx/xxxxxxxxxxx/xxxxxxxxxxxxxxxxx") && ' - ) - ) - ] -) - -if __name__ == "__main__": - for i in range(4, 8): - cmd = ( - r"for pid in $(ps aux | grep paster | grep -v grep | grep '\-%d' | awk '{print $2}'); do kill $pid; done" - % (i) - ) - -def A(): - def B(): - def C(): - def D(): - def E(): - def F(): - def G(): - assert ( - c_float(val[0][0] / val[0][1]).value - == c_float(value[0][0] / value[0][1]).value - ), "%s didn't roundtrip" % tag - -class xxxxxxxxxxxxxxxxxxxxx(xxxx.xxxxxxxxxxxxx): - def xxxxxxx_xxxxxx(xxxx): - assert xxxxxxx_xxxx in [ - x.xxxxx.xxxxxx.xxxxx.xxxxxx, - x.xxxxx.xxxxxx.xxxxx.xxxx, - ], ("xxxxxxxxxxx xxxxxxx xxxx (xxxxxx xxxx) %x xxx xxxxx" % xxxxxxx_xxxx) - -value.__dict__[ - key -] = "test" # set some Thrift field to non-None in the struct aa bb cc dd ee - -RE_ONE_BACKSLASH = { - "asdf_hjkl_jkl": re.compile( - r"(?>\n" -) - -assert str(suffix_arr) == ( - "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', " - "'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', " - "'o$', 'oo$', 'roo$', 'rykangaroo$', 'ykangaroo$']" -) -assert str(suffix_arr) != ( - "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', " - "'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', " - "'o$', 'oo$', 'roo$', 'rykangaroo$', 'ykangaroo$']" -) -assert str(suffix_arr) <= ( - "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', " - "'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', " - "'o$', 'oo$', 'roo$', 'rykangaroo$', 'ykangaroo$']" -) -assert str(suffix_arr) >= ( - "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', " - "'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', " - "'o$', 'oo$', 'roo$', 'rykangaroo$', 'ykangaroo$']" -) -assert str(suffix_arr) < ( - "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', " - "'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', " - "'o$', 'oo$', 'roo$', 'rykangaroo$', 'ykangaroo$']" -) -assert str(suffix_arr) > ( - "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', " - "'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', " - "'o$', 'oo$', 'roo$', 'rykangaroo$', 'ykangaroo$']" -) -assert str(suffix_arr) in "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', 'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', 'o$', 'oo$', 'roo$', 'rykangaroo$', 'ykangaroo$']" -assert str(suffix_arr) not in "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', 'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', 'o$', 'oo$', 'roo$', 'rykangaroo$', 'ykangaroo$']" -message = ( - f"1. Go to Google Developers Console and log in with your Google account." - "(https://console.developers.google.com/)" - "2. You should be prompted to create a new project (name does not matter)." - "3. Click on Enable APIs and Services at the top." - "4. In the list of APIs choose or search for YouTube Data API v3 and " - "click on it. Choose Enable." - "5. Click on Credentials on the left navigation bar." - "6. Click on Create Credential at the top." - '7. At the top click the link for "API key".' - "8. No application restrictions are needed. Click Create at the bottom." - "9. You now have a key to add to `{prefix}set api youtube api_key`" -) -message = ( - f"1. Go to Google Developers Console and log in with your Google account." - "(https://console.developers.google.com/)" - "2. You should be prompted to create a new project (name does not matter)." - f"3. Click on Enable APIs and Services at the top." - "4. In the list of APIs choose or search for YouTube Data API v3 and " - "click on it. Choose Enable." - f"5. Click on Credentials on the left navigation bar." - "6. Click on Create Credential at the top." - '7. At the top click the link for "API key".' - "8. No application restrictions are needed. Click Create at the bottom." - "9. You now have a key to add to `{prefix}set api youtube api_key`" -) -message = ( - f"1. Go to Google Developers Console and log in with your Google account." - "(https://console.developers.google.com/)" - "2. You should be prompted to create a new project (name does not matter)." - f"3. Click on Enable APIs and Services at the top." - "4. In the list of APIs choose or search for YouTube Data API v3 and " - "click on it. Choose Enable." - f"5. Click on Credentials on the left navigation bar." - "6. Click on Create Credential at the top." - '7. At the top click the link for "API key".' - "8. No application restrictions are needed. Click Create at the bottom." - f"9. You now have a key to add to `{prefix}set api youtube api_key`" -) - -# It shouldn't matter if the string prefixes are capitalized. -temp_msg = ( - F"{F'{humanize_number(pos)}.': <{pound_len+2}} " - F"{balance: <{bal_len + 5}} " - F"<<{author.display_name}>>\n" -) - -fstring = ( - F"We have to remember to escape {braces}." - " Like {these}." - F" But not {this}." -) - -welcome_to_programming = R"hello," R" world!" - -fstring = F"f-strings definitely make things more {difficult} than they need to be for {{black}}. But boy they sure are handy. The problem is that some lines will need to have the 'f' whereas others do not. This {line}, for example, needs one." - -x = F"This is a long string which contains an f-expr that should not split {{{[i for i in range(5)]}}}." - -x = ( - "\N{BLACK RIGHT-POINTING TRIANGLE WITH DOUBLE VERTICAL BAR}\N{VARIATION SELECTOR-16}" -) - -xxxxxx_xxx_xxxx_xx_xxxxx_xxxxxxxx_xxxxxxxx_xxxxxxxxxx_xxxx_xxxx_xxxxx = xxxx.xxxxxx.xxxxxxxxx.xxxxxxxxxxxxxxxxxxxx( - xx_xxxxxx={ - "x3_xxxxxxxx": "xxx3_xxxxx_xxxxxxxx_xxxxxxxx_xxxxxxxxxx_xxxxxxxx_xxxxxx_xxxxxxx", - }, -) - -# Regression test for https://github.com/psf/black/issues/3117. -some_dict = { - "something_something": - r"Lorem ipsum dolor sit amet, an sed convenire eloquentiam \t" - r"signiferumque, duo ea vocibus consetetur scriptorem. Facer \t", -} - -# Regression test for https://github.com/psf/black/issues/3459. -xxxx( - empty_str_as_first_split='' - f'xxxxxxx {xxxxxxxxxx} xxx xxxxxxxxxx xxxxx xxx xxx xx ' - 'xxxxx xxxxxxxxx xxxxxxx, xxx xxxxxxxxxxx xxx xxxxx. ' - f'xxxxxxxxxxxxx xxxx xx xxxxxxxxxx. xxxxx: {x.xxx}', - empty_u_str_as_first_split=u'' - f'xxxxxxx {xxxxxxxxxx} xxx xxxxxxxxxx xxxxx xxx xxx xx ' - 'xxxxx xxxxxxxxx xxxxxxx, xxx xxxxxxxxxxx xxx xxxxx. ' - f'xxxxxxxxxxxxx xxxx xx xxxxxxxxxx. xxxxx: {x.xxx}', -) - -# Regression test for https://github.com/psf/black/issues/3455. -a_dict = { - "/this/is/a/very/very/very/very/very/very/very/very/very/very/long/key/without/spaces": - # And there is a comment before the value - ("item1", "item2", "item3"), -} - -# Regression test for https://github.com/psf/black/issues/3506. -# Regressed again by https://github.com/psf/black/pull/4498 -s = ( - "With single quote: ' " - f" {my_dict['foo']}" - ' With double quote: " ' - f' {my_dict["bar"]}' -) - -s = f'Lorem Ipsum is simply dummy text of the printing and typesetting industry:\'{my_dict["foo"]}\'' - -# Regression test for https://github.com/psf/black/issues/4510. -# Don't merge multi-line strings when a pragma comment follows. -( - "A very very very very very very very very very very very very long string " - "annotated with a type ignore pragma gets merged into a single very long line " - "which is against the line length rule." -) # type: ignore - +# Migrated from single-case format. Source file: preview_long_strings__regression.py +[case class_a_1] +# flags: --unstable +class A: + def foo(): + result = type(message)("") # output @@ -578,6 +12,17 @@ class A: def foo(): result = type(message)("") +[case don_t_merge_multiline_e_g_triple_quoted_strings] +# flags: --unstable + + +# Don't merge multiline (e.g. triple-quoted) strings. +def foo(): + query = ( + """SELECT xxxxxxxxxxxxxxxxxxxx(xxx)""" + """ FROM xxxxxxxxxxxxxxxx WHERE xxxxxxxxxx AND xxx <> xxxxxxxxxxxxxx()""") +# output + # Don't merge multiline (e.g. triple-quoted) strings. def foo(): @@ -586,6 +31,14 @@ def foo(): """ FROM xxxxxxxxxxxxxxxx WHERE xxxxxxxxxx AND xxx <> xxxxxxxxxxxxxx()""" ) +[case there_was_a_bug_where_tuples_were_being_identified_as_long_s] +# flags: --unstable + +# There was a bug where tuples were being identified as long strings. +long_tuple = ('Apple', 'Berry', 'Cherry', 'Dill', 'Evergreen', 'Fig', + 'Grape', 'Harry', 'Iglu', 'Jaguar') +# output + # There was a bug where tuples were being identified as long strings. long_tuple = ( @@ -601,6 +54,12 @@ def foo(): "Jaguar", ) +[case stupid_format_method_bug] +# flags: --unstable + +stupid_format_method_bug = "Some really long string that just so happens to be the {} {} to force the 'format' method to hang over the line length boundary. This is pretty annoying.".format("perfect", "length") +# output + stupid_format_method_bug = ( "Some really long string that just so happens to be the {} {} to force the 'format'" " method to hang over the line length boundary. This is pretty annoying.".format( @@ -608,6 +67,14 @@ def foo(): ) ) +[case class_a_2] +# flags: --unstable + +class A: + def foo(): + os.system("This is a regression test. xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxx xxxx.".format("xxxxxxxxxx", "xxxxxx", "xxxxxxxxxx")) +# output + class A: def foo(): @@ -617,6 +84,23 @@ def foo(): " xxxx.".format("xxxxxxxxxx", "xxxxxx", "xxxxxxxxxx") ) +[case class_a_3] +# flags: --unstable + + +class A: + def foo(): + XXXXXXXXXXXX.append( + ( + "xxx_xxxxxxxxxx(xxxxx={}, xxxx={}, xxxxx, xxxx_xxxx_xxxxxxxxxx={})".format( + xxxxx, xxxx, xxxx_xxxx_xxxxxxxxxx + ), + my_var, + my_other_var, + ) + ) +# output + class A: def foo(): @@ -628,6 +112,24 @@ def foo(): my_other_var, )) +[case class_a_4] +# flags: --unstable + +class A: + class B: + def foo(): + bar( + ( + "[{}]: xxx_xxxxxxxxxx(xxxxx={}, xxxx={}, xxxxx={}" + " xxxx_xxxx_xxxxxxxxxx={}, xxxx={})" + .format(xxxx._xxxxxxxxxxxxxx, xxxxx, xxxx, xxxx_xxxx_xxxxxxxxxx, xxxxxxx) + ), + varX, + varY, + varZ, + ) +# output + class A: class B: @@ -642,6 +144,19 @@ def foo(): varZ, ) +[case fn_foo_1] +# flags: --unstable + +def foo(xxxx): + for (xxx_xxxx, _xxx_xxx, _xxx_xxxxx, xxx_xxxx) in xxxx: + for xxx in xxx_xxxx: + assert ("x" in xxx) or ( + xxx in xxx_xxx_xxxxx + ), "{0} xxxxxxx xx {1}, xxx {1} xx xxx xx xxxx xx xxx xxxx: xxx xxxx {2}".format( + xxx_xxxx, xxx, xxxxxx.xxxxxxx(xxx_xxx_xxxxx) + ) +# output + def foo(xxxx): for xxx_xxxx, _xxx_xxx, _xxx_xxxxx, xxx_xxxx in xxxx: @@ -651,6 +166,28 @@ def foo(xxxx): .format(xxx_xxxx, xxx, xxxxxx.xxxxxxx(xxx_xxx_xxxxx)) ) +[case class_a_5] +# flags: --unstable + +class A: + def disappearing_comment(): + return ( + ( # xx -x xxxxxxx xx xxx xxxxxxx. + '{{xxx_xxxxxxxxxx_xxxxxxxx}} xxx xxxx' + ' {} {{xxxx}} >&2' + .format( + "{xxxx} {xxxxxx}" + if xxxxx.xx_xxxxxxxxxx + else ( # Disappearing Comment + "--xxxxxxx --xxxxxx=x --xxxxxx-xxxxx=xxxxxx" + " --xxxxxx-xxxx=xxxxxxxxxxx.xxx" + ) + ) + ), + (x, y, z), + ) +# output + class A: def disappearing_comment(): @@ -668,6 +205,24 @@ def disappearing_comment(): (x, y, z), ) +[case class_a_6] +# flags: --unstable + +class A: + class B: + def foo(): + xxxxx_xxxx( + xx, "\t" + "@xxxxxx '{xxxx_xxx}\t' > {xxxxxx_xxxx}.xxxxxxx;" + "{xxxx_xxx} >> {xxxxxx_xxxx}.xxxxxxx 2>&1; xx=$$?;" + "xxxx $$xx" + .format(xxxx_xxx=xxxx_xxxxxxx, xxxxxx_xxxx=xxxxxxx + "/" + xxxx_xxx_xxxx, x=xxx_xxxxx_xxxxx_xxx), + x, + y, + z, + ) +# output + class A: class B: @@ -687,22 +242,70 @@ def foo(): z, ) +[case func_call_where_string_arg_has_method_call_and_bad_parens] +# flags: --unstable + +func_call_where_string_arg_has_method_call_and_bad_parens( + ( + "A long string with {}. This string is so long that it is ridiculous. It can't fit on one line at alllll.".format("formatting") + ), +) +# output + func_call_where_string_arg_has_method_call_and_bad_parens( "A long string with {}. This string is so long that it is ridiculous. It can't fit" " on one line at alllll.".format("formatting"), ) +[case func_call_where_string_arg_has_old_fmt_and_bad_parens] +# flags: --unstable + +func_call_where_string_arg_has_old_fmt_and_bad_parens( + ( + "A long string with {}. This string is so long that it is ridiculous. It can't fit on one line at alllll." % "formatting" + ), +) +# output + func_call_where_string_arg_has_old_fmt_and_bad_parens( "A long string with {}. This string is so long that it is ridiculous. It can't fit" " on one line at alllll." % "formatting", ) +[case func_call_where_string_arg_has_old_fmt_and_bad_parens_2] +# flags: --unstable + +func_call_where_string_arg_has_old_fmt_and_bad_parens( + ( + "A long string with {}. This {} is so long that it is ridiculous. It can't fit on one line at alllll." % ("formatting", "string") + ), +) +# output + func_call_where_string_arg_has_old_fmt_and_bad_parens( "A long string with {}. This {} is so long that it is ridiculous. It can't fit on" " one line at alllll." % ("formatting", "string"), ) +[case class_a_7] +# flags: --unstable + +class A: + def append(self): + if True: + xxxx.xxxxxxx.xxxxx( ('xxxxxxxxxx xxxx xx xxxxxx(%x) xx %x xxxx xx xxx %x.xx' + % (len(self) + 1, + xxxx.xxxxxxxxxx, + xxxx.xxxxxxxxxx)) + + (' %.3f (%s) to %.3f (%s).\n' + % (xxxx.xxxxxxxxx, + xxxx.xxxxxxxxxxxxxx(xxxx.xxxxxxxxx), + x, + xxxx.xxxxxxxxxxxxxx( xx) + ))) +# output + class A: def append(self): @@ -719,6 +322,23 @@ def append(self): ) ) +[case class_a_8] +# flags: --unstable + +class A: + def foo(): + some_func_call( + 'xxxxxxxxxx', + ( + "xx {xxxxxxxxxxx}/xxxxxxxxxxx.xxx xxxx.xxx && xxxxxx -x " + "\"xxxx xxxxxxx xxxxxx xxxx; xxxx xxxxxx_xxxxx xxxxxx xxxx; " + "xxxx.xxxx_xxxxxx(['xxxx.xxx'], xxxx.xxxxxxx().xxxxxxxxxx)\" " + ), + None, + ('xxxxxxxxxxx',), + ), +# output + class A: def foo(): @@ -731,6 +351,22 @@ def foo(): ("xxxxxxxxxxx",), ), +[case class_a_9] +# flags: --unstable + +class A: + def foo(): + some_func_call( + ( + "xx {xxxxxxxxxxx}/xxxxxxxxxxx.xxx xxxx.xxx && xxxxxx -x " + "xxxx, ('xxxxxxx xxxxxx xxxx, xxxx') xxxxxx_xxxxx xxxxxx xxxx; " + "xxxx.xxxx_xxxxxx(['xxxx.xxx'], xxxx.xxxxxxx().xxxxxxxxxx)\" " + ), + None, + ('xxxxxxxxxxx',), + ), +# output + class A: def foo(): @@ -742,6 +378,18 @@ def foo(): ("xxxxxxxxxxx",), ), +[case obfuscated_xxxxxxx] +# flags: --unstable + +xxxxxxx = { 'xx' : 'xxxx xxxxxxx xxxxxxxxx -x xxx -x /xxx/{0} -x xxx,xxx -xx {1} \ +-xx {1} -xx xxx=xxx_xxxx,xxx_xx,xxx_xxx,xxx_xxxx,xxx_xx,xxx_xxx |\ + xxxxxx -x xxxxxxxx -x xxxxxxxx -x', + 'xx' : 'xxxx xxxxxxx xxxxxxxxx -x xxx -x /xxx/{0} -x xxx,xxx -xx {1} \ +-xx {1} -xx xxx=xxx_xxxx_xxx_xxxx,xxx_xx_xxx_xxxx,xxx_xxxx_xxx_xxxx,\ +xxx_xx_xxxx_xxxx,xxx_xxx_xxxx,xxx_xxx_xxxx xxxx=xxx | xxxxxx -x xxxxxxxx -x xxxxxxxx -x' +} +# output + xxxxxxx = { "xx": ( @@ -756,6 +404,16 @@ def foo(): ), } +[case class_a_10] +# flags: --unstable + +class A: + def foo(self): + if True: + xxxxx_xxxxxxxxxxxx('xxx xxxxxx xxx xxxxxxxxx.xx xx xxxxxxxx. xxx xxxxxxxxxxxxx.xx xxxxxxx ' + + 'xx xxxxxx xxxxxx xxxxxx xx xxxxxxx xxx xxx ${0} xx x xxxxxxxx xxxxx'.xxxxxx(xxxxxx_xxxxxx_xxx)) +# output + class A: def foo(self): @@ -767,6 +425,22 @@ def foo(self): .xxxxxx(xxxxxx_xxxxxx_xxx) ) +[case class_a_11] +# flags: --unstable + +class A: + class B: + def foo(): + row = { + 'xxxxxxxxxxxxxxx' : xxxxxx_xxxxx_xxxx, + # 'xxxxxxxxxxxxxxxxxxxxxxx' + # 'xxxxxxxxxxxxxxxxxxxxxx' + # 'xxxxxxxxxxxxxxxxxx' + # 'xxxxxxxxxxxxxxxxx' + 'xxxxxxxxxx' : xxxxx_xxxxx, + } +# output + class A: class B: @@ -780,6 +454,34 @@ def foo(): "xxxxxxxxxx": xxxxx_xxxxx, } +[case class_a_12] +# flags: --unstable + +class A: + def xxxx_xxx_xx_xxxxxxxxxx_xxxx_xxxxxxxxx(xxxx): + xxxxxxxx = [ + xxxxxxxxxxxxxxxx( + 'xxxx', + xxxxxxxxxxx={ + 'xxxx' : 1.0, + }, + xxxxxx={'xxxxxx 1' : xxxxxx(xxxx='xxxxxx 1', xxxxxx=600.0)}, + xxxxxxxx_xxxxxxx=0.0, + ), + xxxxxxxxxxxxxxxx( + 'xxxxxxx', + xxxxxxxxxxx={ + 'xxxx' : 1.0, + }, + xxxxxx={'xxxxxx 1' : xxxxxx(xxxx='xxxxxx 1', xxxxxx=200.0)}, + xxxxxxxx_xxxxxxx=0.0, + ), + xxxxxxxxxxxxxxxx( + 'xxxx', + ), + ] +# output + class A: def xxxx_xxx_xx_xxxxxxxxxx_xxxx_xxxxxxxxx(xxxx): @@ -805,6 +507,17 @@ def xxxx_xxx_xx_xxxxxxxxxx_xxxx_xxxxxxxxx(xxxx): ), ] +[case some_dictionary_1] +# flags: --unstable + +some_dictionary = { + 'xxxxx006': ['xxx-xxx xxxxx3xxxx1xx2xxxxxxxxxxxxxx0xx6xxxxxxxxxx2xxxxxx9xxxxxxxxxx0xxxxx1xxx2x/xx9xx6+x+xxxxxxxxxxxxxx4xxxxxxxxxxxxxxxxxxxxx43xxx2xx2x4x++xxx6xxxxxxxxx+xxxxx/xx9x+xxxxxxxxxxxxxx8x15xxxxxxxxxxxxxxxxx82xx/xxxxxxxxxxxxxx/x5xxxxxxxxxxxxxx6xxxxxx74x4/xxx4x+xxxxxxxxx2xxxxxxxx87xxxxx4xxxxxxxx3xx0xxxxx4xxx1xx9xx5xxxxxxx/xxxxx5xx6xx4xxxx1x/x2xxxxxxxxxxxx64xxxxxxx1x0xx5xxxxxxxxxxxxxx== xxxxx000 xxxxxxxxxx\n', + 'xxx-xxx xxxxx3xxxx1xx2xxxxxxxxxxxxxx6xxxxxxxxxxxxxx9xxxxxxxxxxxxx3xxx9xxxxxxxxxxxxxxxx0xxxxxxxxxxxxxxxxx2xxxx2xxx6xxxxx/xx54xxxxxxxxx4xxx3xxxxxx9xx3xxxxx39xxxxxxxxx5xx91xxxx7xxxxxx8xxxxxxxxxxxxxxxx9xxx93xxxxxxxxxxxxxxxxx7xxx8xx8xx4/x1xxxxx1x3xxxxxxxxxxxxx3xxxxxx9xx4xx4x7xxxxxxxxxxxxx1xxxxxxxxx7xxxxxxxxxxxxxx4xx6xxxxxxxxx9xxx7xxxx2xxxxxxxxxxxxxxxxxxxxxx8xxxxxxxxxxxxxxxxxxxx6xx== xxxxx010 xxxxxxxxxx\n'], + 'xxxxx016': ['xxx-xxx xxxxx3xxxx1xx2xxxxxxxxxxxxxx0xx6xxxxxxxxxx2xxxxxx9xxxxxxxxxx0xxxxx1xxx2x/xx9xx6+x+xxxxxxxxxxxxxx4xxxxxxxxxxxxxxxxxxxxx43xxx2xx2x4x++xxx6xxxxxxxxx+xxxxx/xx9x+xxxxxxxxxxxxxx8x15xxxxxxxxxxxxxxxxx82xx/xxxxxxxxxxxxxx/x5xxxxxxxxxxxxxx6xxxxxx74x4/xxx4x+xxxxxxxxx2xxxxxxxx87xxxxx4xxxxxxxx3xx0xxxxx4xxx1xx9xx5xxxxxxx/xxxxx5xx6xx4xxxx1x/x2xxxxxxxxxxxx64xxxxxxx1x0xx5xxxxxxxxxxxxxx== xxxxx000 xxxxxxxxxx\n', + 'xxx-xxx xxxxx3xxxx1xx2xxxxxxxxxxxxxx6xxxxxxxxxxxxxx9xxxxxxxxxxxxx3xxx9xxxxxxxxxxxxxxxx0xxxxxxxxxxxxxxxxx2xxxx2xxx6xxxxx/xx54xxxxxxxxx4xxx3xxxxxx9xx3xxxxx39xxxxxxxxx5xx91xxxx7xxxxxx8xxxxxxxxxxxxxxxx9xxx93xxxxxxxxxxxxxxxxx7xxx8xx8xx4/x1xxxxx1x3xxxxxxxxxxxxx3xxxxxx9xx4xx4x7xxxxxxxxxxxxx1xxxxxxxxx7xxxxxxxxxxxxxx4xx6xxxxxxxxx9xxx7xxxx2xxxxxxxxxxxxxxxxxxxxxx8xxxxxxxxxxxxxxxxxxxx6xx== xxxxx010 xxxxxxxxxx\n'] +} +# output + some_dictionary = { "xxxxx006": [ @@ -833,59 +546,158 @@ def xxxx_xxx_xx_xxxxxxxxxx_xxxx_xxxxxxxxx(xxxx): ], } +[case fn_foo_2] +# flags: --unstable + +def foo(): + xxx_xxx = ( + 'xxxx xxx xxxxxxxx_xxxx xx "xxxxxxxxxx".' + '\n xxx: xxxxxx xxxxxxxx_xxxx=xxxxxxxxxx' + ) # xxxx xxxxxxxxxx xxxx xx xxxx xx xxx xxxxxxxx xxxxxx xxxxx. +# output + def foo(): xxx_xxx = ( # xxxx xxxxxxxxxx xxxx xx xxxx xx xxx xxxxxxxx xxxxxx xxxxx. 'xxxx xxx xxxxxxxx_xxxx xx "xxxxxxxxxx".\n xxx: xxxxxx xxxxxxxx_xxxx=xxxxxxxxxx' ) +[case some_tuple] +# flags: --unstable + +some_tuple = ("some string", "some string" " which should be joined") +# output + some_tuple = ("some string", "some string which should be joined") +[case some_commented_string] +# flags: --unstable + +some_commented_string = ( # This comment stays at the top. + "This string is long but not so long that it needs hahahah toooooo be so greatttt" + " {} that I just can't think of any more good words to say about it at" + " allllllllllll".format("ha") # comments here are fine +) +# output + some_commented_string = ( # This comment stays at the top. "This string is long but not so long that it needs hahahah toooooo be so greatttt" " {} that I just can't think of any more good words to say about it at" " allllllllllll".format("ha") # comments here are fine ) +[case some_commented_string_2] +# flags: --unstable + +some_commented_string = ( + "This string is long but not so long that it needs hahahah toooooo be so greatttt" # But these + " {} that I just can't think of any more good words to say about it at" # comments will stay + " allllllllllll".format("ha") # comments here are fine +) +# output + some_commented_string = ( "This string is long but not so long that it needs hahahah toooooo be so greatttt" # But these " {} that I just can't think of any more good words to say about it at" # comments will stay " allllllllllll".format("ha") # comments here are fine ) +[case lpar_and_rpar_have_comments] +# flags: --unstable + +lpar_and_rpar_have_comments = func_call( # LPAR Comment + "Long really ridiculous type of string that shouldn't really even exist at all. I mean commmme onnn!!!", # Comma Comment +) # RPAR Comment +# output + lpar_and_rpar_have_comments = func_call( # LPAR Comment "Long really ridiculous type of string that shouldn't really even exist at all. I" " mean commmme onnn!!!", # Comma Comment ) # RPAR Comment +[case cmd_fstring] +# flags: --unstable + +cmd_fstring = ( + f"sudo -E deluge-console info --detailed --sort-reverse=time_added " + f"{'' if ID is None else ID} | perl -nE 'print if /^{field}:/'" +) +# output + cmd_fstring = ( "sudo -E deluge-console info --detailed --sort-reverse=time_added " f"{'' if ID is None else ID} | perl -nE 'print if /^{field}:/'" ) +[case cmd_fstring_2] +# flags: --unstable + +cmd_fstring = f"sudo -E deluge-console info --detailed --sort-reverse=time_added {'' if ID is None else ID} | perl -nE 'print if /^{field}:/'" +# output + cmd_fstring = ( "sudo -E deluge-console info --detailed --sort-reverse=time_added" f" {'' if ID is None else ID} | perl -nE 'print if /^{field}:/'" ) +[case cmd_fstring_3] +# flags: --unstable + +cmd_fstring = f"sudo -E deluge-console info --detailed --sort-reverse=time_added {'{{}}' if ID is None else ID} | perl -nE 'print if /^{field}:/'" +# output + cmd_fstring = ( "sudo -E deluge-console info --detailed --sort-reverse=time_added" f" {'{{}}' if ID is None else ID} | perl -nE 'print if /^{field}:/'" ) +[case cmd_fstring_4] +# flags: --unstable + +cmd_fstring = f"sudo -E deluge-console info --detailed --sort-reverse=time_added {{'' if ID is None else ID}} | perl -nE 'print if /^{field}:/'" +# output + cmd_fstring = ( "sudo -E deluge-console info --detailed --sort-reverse=time_added {'' if ID is" f" None else ID}} | perl -nE 'print if /^{field}:/'" ) +[case fstring_1] +# flags: --unstable + +fstring = f"This string really doesn't need to be an {{{{fstring}}}}, but this one most certainly, absolutely {does}." +# output + fstring = ( "This string really doesn't need to be an {{fstring}}, but this one most" f" certainly, absolutely {does}." ) +[case fstring_2] +# flags: --unstable + +fstring = ( + f"We have to remember to escape {braces}." + " Like {these}." + f" But not {this}." +) +# output + fstring = f"We have to remember to escape {braces}. Like {{these}}. But not {this}." +[case class_a_13] +# flags: --unstable + +class A: + class B: + def foo(): + st_error = STError( + f"This string ({string_leaf.value}) appears to be pointless (i.e. has" + " no parent)." + ) +# output + class A: class B: @@ -895,6 +707,16 @@ def foo(): " no parent)." ) +[case fn_foo_3] +# flags: --unstable + +def foo(): + user_regex = _lazy_re_compile( + r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*\Z" # dot-atom + r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-\011\013\014\016-\177])*"\Z)', # quoted-string + re.IGNORECASE) +# output + def foo(): user_regex = _lazy_re_compile( @@ -903,6 +725,17 @@ def foo(): re.IGNORECASE, ) +[case fn_foo_4] +# flags: --unstable + +def foo(): + user_regex = _lazy_re_compile( + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # dot-atom + 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', # quoted-string + xyz + ) +# output + def foo(): user_regex = _lazy_re_compile( @@ -911,6 +744,17 @@ def foo(): xyz, ) +[case fn_foo_5] +# flags: --unstable + +def foo(): + user_regex = _lazy_re_compile( + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" # dot-atom + 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', # quoted-string + xyz + ) +# output + def foo(): user_regex = _lazy_re_compile( @@ -919,6 +763,23 @@ def foo(): xyz, ) +[case class_a_14] +# flags: --unstable + +class A: + class B: + def foo(): + if not hasattr(module, name): + raise ValueError( + "Could not find object %s in %s.\n" + "Please note that you cannot serialize things like inner " + "classes. Please move the object into the main module " + "body to use migrations.\n" + "For more information, see " + "https://docs.djangoproject.com/en/%s/topics/migrations/#serializing-values" + % (name, module_name, get_docs_version())) +# output + class A: class B: @@ -934,6 +795,18 @@ def foo(): % (name, module_name, get_docs_version()) ) +[case class_a_15] +# flags: --unstable + +class A: + class B: + def foo(): + if not hasattr(module, name): + raise ValueError( + "Could not find object %s in %s.\nPlease note that you cannot serialize things like inner classes. Please move the object into the main module body to use migrations.\nFor more information, see https://docs.djangoproject.com/en/%s/topics/migrations/#serializing-values" + % (name, module_name, get_docs_version())) +# output + class A: class B: @@ -947,6 +820,17 @@ def foo(): % (name, module_name, get_docs_version()) ) +[case assign_x_1] +# flags: --unstable + +x = ( + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" + "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +) +# output + x = ( "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -955,6 +839,20 @@ def foo(): "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" ) +[case assign_step] +# flags: --unstable + +class Step(StepBase): + def who(self): + self.cmd = 'SR AAAA-CORRECT NAME IS {last_name} {first_name}{middle_name} {title}/P{passenger_association}'.format( + last_name=last_name, + first_name=first_name, + middle_name=middle_name, + title=title, + passenger_association=passenger_association, + ) +# output + class Step(StepBase): def who(self): @@ -969,6 +867,23 @@ def who(self): ) ) +[case obfuscated_xxxxxxx_xxxxxx_xxxxxxx] +# flags: --unstable + +xxxxxxx_xxxxxx_xxxxxxx = xxx( + [ + xxxxxxxxxxxx( + xxxxxx_xxxxxxx=( + '((x.aaaaaaaaa = "xxxxxx.xxxxxxxxxxxxxxxxxxxxx") || (x.xxxxxxxxx = "xxxxxxxxxxxx")) && ' + # xxxxx xxxxxxxxxxxx xxxx xxx (xxxxxxxxxxxxxxxx) xx x xxxxxxxxx xx xxxxxx. + "(x.bbbbbbbbbbbb.xxx != " + '"xxx:xxx:xxx::cccccccccccc:xxxxxxx-xxxx/xxxxxxxxxxx/xxxxxxxxxxxxxxxxx") && ' + ) + ) + ] +) +# output + xxxxxxx_xxxxxx_xxxxxxx = xxx([ xxxxxxxxxxxx( @@ -982,6 +897,17 @@ def who(self): ) ]) +[case conditional_string] +# flags: --unstable + +if __name__ == "__main__": + for i in range(4, 8): + cmd = ( + r"for pid in $(ps aux | grep paster | grep -v grep | grep '\-%d' | awk '{print $2}'); do kill $pid; done" + % (i) + ) +# output + if __name__ == "__main__": for i in range(4, 8): cmd = ( @@ -989,6 +915,22 @@ def who(self): r" '{print $2}'); do kill $pid; done" % (i) ) +[case class_a_16] +# flags: --unstable + +def A(): + def B(): + def C(): + def D(): + def E(): + def F(): + def G(): + assert ( + c_float(val[0][0] / val[0][1]).value + == c_float(value[0][0] / value[0][1]).value + ), "%s didn't roundtrip" % tag +# output + def A(): def B(): @@ -1002,6 +944,17 @@ def G(): == c_float(value[0][0] / value[0][1]).value ), "%s didn't roundtrip" % tag +[case obfuscated_xxxxxxxxxxxxxxxxxxxxx] +# flags: --unstable + +class xxxxxxxxxxxxxxxxxxxxx(xxxx.xxxxxxxxxxxxx): + def xxxxxxx_xxxxxx(xxxx): + assert xxxxxxx_xxxx in [ + x.xxxxx.xxxxxx.xxxxx.xxxxxx, + x.xxxxx.xxxxxx.xxxxx.xxxx, + ], ("xxxxxxxxxxx xxxxxxx xxxx (xxxxxx xxxx) %x xxx xxxxx" % xxxxxxx_xxxx) +# output + class xxxxxxxxxxxxxxxxxxxxx(xxxx.xxxxxxxxxxxxx): def xxxxxxx_xxxxxx(xxxx): @@ -1012,16 +965,44 @@ def xxxxxxx_xxxxxx(xxxx): "xxxxxxxxxxx xxxxxxx xxxx (xxxxxx xxxx) %x xxx xxxxx" % xxxxxxx_xxxx ) +[case assign_value] +# flags: --unstable + +value.__dict__[ + key +] = "test" # set some Thrift field to non-None in the struct aa bb cc dd ee +# output + value.__dict__[key] = ( "test" # set some Thrift field to non-None in the struct aa bb cc dd ee ) +[case re_one_backslash] +# flags: --unstable + RE_ONE_BACKSLASH = { "asdf_hjkl_jkl": re.compile( r"(?>\n" +) +# output temp_msg = ( @@ -1094,54 +1169,136 @@ async def foo(self): f"<<{author.display_name}>>\n" ) +[case assert_stmt_1] +# flags: --unstable + +assert str(suffix_arr) == ( + "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', " + "'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', " + "'o$', 'oo$', 'roo$', 'rykangaroo$', 'ykangaroo$']" +) +# output + assert ( str(suffix_arr) == "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', " "'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', " "'o$', 'oo$', 'roo$', 'rykangaroo$', 'ykangaroo$']" ) + +[case assert_stmt_2] +# flags: --unstable +assert str(suffix_arr) != ( + "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', " + "'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', " + "'o$', 'oo$', 'roo$', 'rykangaroo$', 'ykangaroo$']" +) +# output assert ( str(suffix_arr) != "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', " "'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', " "'o$', 'oo$', 'roo$', 'rykangaroo$', 'ykangaroo$']" ) + +[case assert_stmt_3] +# flags: --unstable +assert str(suffix_arr) <= ( + "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', " + "'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', " + "'o$', 'oo$', 'roo$', 'rykangaroo$', 'ykangaroo$']" +) +# output assert ( str(suffix_arr) <= "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', " "'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', " "'o$', 'oo$', 'roo$', 'rykangaroo$', 'ykangaroo$']" ) + +[case assert_stmt_4] +# flags: --unstable +assert str(suffix_arr) >= ( + "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', " + "'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', " + "'o$', 'oo$', 'roo$', 'rykangaroo$', 'ykangaroo$']" +) +# output assert ( str(suffix_arr) >= "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', " "'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', " "'o$', 'oo$', 'roo$', 'rykangaroo$', 'ykangaroo$']" ) + +[case assert_stmt_5] +# flags: --unstable +assert str(suffix_arr) < ( + "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', " + "'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', " + "'o$', 'oo$', 'roo$', 'rykangaroo$', 'ykangaroo$']" +) +# output assert ( str(suffix_arr) < "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', " "'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', " "'o$', 'oo$', 'roo$', 'rykangaroo$', 'ykangaroo$']" ) + +[case assert_stmt_6] +# flags: --unstable +assert str(suffix_arr) > ( + "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', " + "'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', " + "'o$', 'oo$', 'roo$', 'rykangaroo$', 'ykangaroo$']" +) +# output assert ( str(suffix_arr) > "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', " "'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', " "'o$', 'oo$', 'roo$', 'rykangaroo$', 'ykangaroo$']" ) + +[case assert_stmt_7] +# flags: --unstable +assert str(suffix_arr) in "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', 'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', 'o$', 'oo$', 'roo$', 'rykangaroo$', 'ykangaroo$']" +# output assert ( str(suffix_arr) in "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', 'grykangaroo$'," " 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', 'o$', 'oo$', 'roo$', 'rykangaroo$'," " 'ykangaroo$']" ) + +[case assert_stmt_8] +# flags: --unstable +assert str(suffix_arr) not in "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', 'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', 'o$', 'oo$', 'roo$', 'rykangaroo$', 'ykangaroo$']" +# output assert ( str(suffix_arr) not in "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', 'grykangaroo$'," " 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', 'o$', 'oo$', 'roo$'," " 'rykangaroo$', 'ykangaroo$']" ) + +[case assign_message_1] +# flags: --unstable +message = ( + f"1. Go to Google Developers Console and log in with your Google account." + "(https://console.developers.google.com/)" + "2. You should be prompted to create a new project (name does not matter)." + "3. Click on Enable APIs and Services at the top." + "4. In the list of APIs choose or search for YouTube Data API v3 and " + "click on it. Choose Enable." + "5. Click on Credentials on the left navigation bar." + "6. Click on Create Credential at the top." + '7. At the top click the link for "API key".' + "8. No application restrictions are needed. Click Create at the bottom." + "9. You now have a key to add to `{prefix}set api youtube api_key`" +) +# output message = ( f"1. Go to Google Developers Console and log in with your Google account." f"(https://console.developers.google.com/)" @@ -1155,6 +1312,23 @@ async def foo(self): f"8. No application restrictions are needed. Click Create at the bottom." f"9. You now have a key to add to `{{prefix}}set api youtube api_key`" ) + +[case assign_message_2] +# flags: --unstable +message = ( + f"1. Go to Google Developers Console and log in with your Google account." + "(https://console.developers.google.com/)" + "2. You should be prompted to create a new project (name does not matter)." + f"3. Click on Enable APIs and Services at the top." + "4. In the list of APIs choose or search for YouTube Data API v3 and " + "click on it. Choose Enable." + f"5. Click on Credentials on the left navigation bar." + "6. Click on Create Credential at the top." + '7. At the top click the link for "API key".' + "8. No application restrictions are needed. Click Create at the bottom." + "9. You now have a key to add to `{prefix}set api youtube api_key`" +) +# output message = ( f"1. Go to Google Developers Console and log in with your Google account." f"(https://console.developers.google.com/)" @@ -1168,6 +1342,23 @@ async def foo(self): f"8. No application restrictions are needed. Click Create at the bottom." f"9. You now have a key to add to `{{prefix}}set api youtube api_key`" ) + +[case assign_message_3] +# flags: --unstable +message = ( + f"1. Go to Google Developers Console and log in with your Google account." + "(https://console.developers.google.com/)" + "2. You should be prompted to create a new project (name does not matter)." + f"3. Click on Enable APIs and Services at the top." + "4. In the list of APIs choose or search for YouTube Data API v3 and " + "click on it. Choose Enable." + f"5. Click on Credentials on the left navigation bar." + "6. Click on Create Credential at the top." + '7. At the top click the link for "API key".' + "8. No application restrictions are needed. Click Create at the bottom." + f"9. You now have a key to add to `{prefix}set api youtube api_key`" +) +# output message = ( "1. Go to Google Developers Console and log in with your Google account." "(https://console.developers.google.com/)" @@ -1182,6 +1373,17 @@ async def foo(self): f"9. You now have a key to add to `{prefix}set api youtube api_key`" ) +[case it_shouldn_t_matter_if_the_string_prefixes_are_capitalized] +# flags: --unstable + +# It shouldn't matter if the string prefixes are capitalized. +temp_msg = ( + F"{F'{humanize_number(pos)}.': <{pound_len+2}} " + F"{balance: <{bal_len + 5}} " + F"<<{author.display_name}>>\n" +) +# output + # It shouldn't matter if the string prefixes are capitalized. temp_msg = ( f"{F'{humanize_number(pos)}.': <{pound_len+2}} " @@ -1189,25 +1391,71 @@ async def foo(self): f"<<{author.display_name}>>\n" ) +[case fstring_3] +# flags: --unstable + +fstring = ( + F"We have to remember to escape {braces}." + " Like {these}." + F" But not {this}." +) +# output + fstring = f"We have to remember to escape {braces}. Like {{these}}. But not {this}." +[case welcome_to_programming] +# flags: --unstable + +welcome_to_programming = R"hello," R" world!" +# output + welcome_to_programming = R"hello," R" world!" +[case fstring_4] +# flags: --unstable + +fstring = F"f-strings definitely make things more {difficult} than they need to be for {{black}}. But boy they sure are handy. The problem is that some lines will need to have the 'f' whereas others do not. This {line}, for example, needs one." +# output + fstring = ( f"f-strings definitely make things more {difficult} than they need to be for" " {black}. But boy they sure are handy. The problem is that some lines will need" f" to have the 'f' whereas others do not. This {line}, for example, needs one." ) +[case assign_x_4] +# flags: --unstable + +x = F"This is a long string which contains an f-expr that should not split {{{[i for i in range(5)]}}}." +# output + x = ( "This is a long string which contains an f-expr that should not split" f" {{{[i for i in range(5)]}}}." ) +[case assign_x_5] +# flags: --unstable + +x = ( + "\N{BLACK RIGHT-POINTING TRIANGLE WITH DOUBLE VERTICAL BAR}\N{VARIATION SELECTOR-16}" +) +# output + x = ( "\N{BLACK RIGHT-POINTING TRIANGLE WITH DOUBLE VERTICAL BAR}\N{VARIATION SELECTOR-16}" ) +[case obfuscated_long] +# flags: --unstable + +xxxxxx_xxx_xxxx_xx_xxxxx_xxxxxxxx_xxxxxxxx_xxxxxxxxxx_xxxx_xxxx_xxxxx = xxxx.xxxxxx.xxxxxxxxx.xxxxxxxxxxxxxxxxxxxx( + xx_xxxxxx={ + "x3_xxxxxxxx": "xxx3_xxxxx_xxxxxxxx_xxxxxxxx_xxxxxxxxxx_xxxxxxxx_xxxxxx_xxxxxxx", + }, +) +# output + xxxxxx_xxx_xxxx_xx_xxxxx_xxxxxxxx_xxxxxxxx_xxxxxxxxxx_xxxx_xxxx_xxxxx = xxxx.xxxxxx.xxxxxxxxx.xxxxxxxxxxxxxxxxxxxx( xx_xxxxxx={ "x3_xxxxxxxx": ( @@ -1216,6 +1464,17 @@ async def foo(self): }, ) +[case issue_3117_regression] +# flags: --unstable + +# Regression test for https://github.com/psf/black/issues/3117. +some_dict = { + "something_something": + r"Lorem ipsum dolor sit amet, an sed convenire eloquentiam \t" + r"signiferumque, duo ea vocibus consetetur scriptorem. Facer \t", +} +# output + # Regression test for https://github.com/psf/black/issues/3117. some_dict = { "something_something": ( @@ -1224,6 +1483,22 @@ async def foo(self): ), } +[case issue_3459_regression] +# flags: --unstable + +# Regression test for https://github.com/psf/black/issues/3459. +xxxx( + empty_str_as_first_split='' + f'xxxxxxx {xxxxxxxxxx} xxx xxxxxxxxxx xxxxx xxx xxx xx ' + 'xxxxx xxxxxxxxx xxxxxxx, xxx xxxxxxxxxxx xxx xxxxx. ' + f'xxxxxxxxxxxxx xxxx xx xxxxxxxxxx. xxxxx: {x.xxx}', + empty_u_str_as_first_split=u'' + f'xxxxxxx {xxxxxxxxxx} xxx xxxxxxxxxx xxxxx xxx xxx xx ' + 'xxxxx xxxxxxxxx xxxxxxx, xxx xxxxxxxxxxx xxx xxxxx. ' + f'xxxxxxxxxxxxx xxxx xx xxxxxxxxxx. xxxxx: {x.xxx}', +) +# output + # Regression test for https://github.com/psf/black/issues/3459. xxxx( empty_str_as_first_split=( @@ -1240,6 +1515,17 @@ async def foo(self): ), ) +[case issue_3455_regression] +# flags: --unstable + +# Regression test for https://github.com/psf/black/issues/3455. +a_dict = { + "/this/is/a/very/very/very/very/very/very/very/very/very/very/long/key/without/spaces": + # And there is a comment before the value + ("item1", "item2", "item3"), +} +# output + # Regression test for https://github.com/psf/black/issues/3455. a_dict = { "/this/is/a/very/very/very/very/very/very/very/very/very/very/long/key/without/spaces": @@ -1247,6 +1533,19 @@ async def foo(self): ("item1", "item2", "item3"), } +[case issue_3506_regression] +# flags: --unstable + +# Regression test for https://github.com/psf/black/issues/3506. +# Regressed again by https://github.com/psf/black/pull/4498 +s = ( + "With single quote: ' " + f" {my_dict['foo']}" + ' With double quote: " ' + f' {my_dict["bar"]}' +) +# output + # Regression test for https://github.com/psf/black/issues/3506. # Regressed again by https://github.com/psf/black/pull/4498 s = ( @@ -1256,15 +1555,33 @@ async def foo(self): f' {my_dict["bar"]}' ) +[case assign_s] +# flags: --unstable + +s = f'Lorem Ipsum is simply dummy text of the printing and typesetting industry:\'{my_dict["foo"]}\'' +# output + s = ( "Lorem Ipsum is simply dummy text of the printing and typesetting" f' industry:\'{my_dict["foo"]}\'' ) +[case issue_4510_regression] +# flags: --unstable + +# Regression test for https://github.com/psf/black/issues/4510. +# Don't merge multi-line strings when a pragma comment follows. +( + "A very very very very very very very very very very very very long string " + "annotated with a type ignore pragma gets merged into a single very long line " + "which is against the line length rule." +) # type: ignore +# output + # Regression test for https://github.com/psf/black/issues/4510. # Don't merge multi-line strings when a pragma comment follows. ( "A very very very very very very very very very very very very long string " "annotated with a type ignore pragma gets merged into a single very long line " "which is against the line length rule." -) # type: ignore \ No newline at end of file +) # type: ignore From b4c95b101797c322700f23677341b0f361e81e63 Mon Sep 17 00:00:00 2001 From: Pedro Mezacasa Muller Date: Thu, 21 May 2026 01:24:47 -0300 Subject: [PATCH 3/9] Migrate preview_long_strings to multi-cell format --- tests/data/cases/preview_long_strings.py | 1104 +++++++++++++++------- 1 file changed, 745 insertions(+), 359 deletions(-) diff --git a/tests/data/cases/preview_long_strings.py b/tests/data/cases/preview_long_strings.py index 177f866be0a..2041a292dc1 100644 --- a/tests/data/cases/preview_long_strings.py +++ b/tests/data/cases/preview_long_strings.py @@ -1,351 +1,8 @@ +# Migrated from single-case format. Source file: preview_long_strings.py + +[case assign_x_1] # flags: --unstable x = "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." - -x += "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." - -y = ( - 'Short string' -) - -print('This is a really long string inside of a print statement with extra arguments attached at the end of it.', x, y, z) - -print("This is a really long string inside of a print statement with no extra arguments attached at the end of it.") - -D1 = {"The First": "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a dictionary, so formatting is more difficult.", "The Second": "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a dictionary."} - -D2 = {1.0: "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a dictionary, so formatting is more difficult.", 2.0: "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a dictionary."} - -D3 = {x: "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a dictionary, so formatting is more difficult.", y: "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a dictionary."} - -D4 = {"A long and ridiculous {}".format(string_key): "This is a really really really long string that has to go i,side of a dictionary. It is soooo bad.", some_func("calling", "some", "stuff"): "This is a really really really long string that has to go inside of a dictionary. It is {soooo} bad (#{x}).".format(sooo="soooo", x=2), "A %s %s" % ("formatted", "string"): "This is a really really really long string that has to go inside of a dictionary. It is %s bad (#%d)." % ("soooo", 2)} - -D5 = { # Test for https://github.com/psf/black/issues/3261 - ("This is a really long string that can't be expected to fit in one line and is used as a nested dict's key"): {"inner": "value"}, -} - -D6 = { # Test for https://github.com/psf/black/issues/3261 - ("This is a really long string that can't be expected to fit in one line and is used as a dict's key"): ["value1", "value2"], -} - -L1 = ["The is a short string", "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a list literal, so it's expected to be wrapped in parens when splitting to avoid implicit str concatenation.", short_call("arg", {"key": "value"}), "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a list literal.", ("parens should be stripped for short string in list")] - -L2 = ["This is a really long string that can't be expected to fit in one line and is the only child of a list literal."] - -S1 = {"The is a short string", "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a set literal, so it's expected to be wrapped in parens when splitting to avoid implicit str concatenation.", short_call("arg", {"key": "value"}), "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a set literal.", ("parens should be stripped for short string in set")} - -S2 = {"This is a really long string that can't be expected to fit in one line and is the only child of a set literal."} - -T1 = ("The is a short string", "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a tuple literal, so it's expected to be wrapped in parens when splitting to avoid implicit str concatenation.", short_call("arg", {"key": "value"}), "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a tuple literal.", ("parens should be stripped for short string in list")) - -T2 = ("This is a really long string that can't be expected to fit in one line and is the only child of a tuple literal.",) - -# Test case for https://github.com/psf/black/issues/4912 - unassigned long string with trailing comma -"A long string literal that is not assigned to a variable, exceeds line length when string-processing is enabled, and has a trailing comma (to make it a one-item tuple)", - -func_with_keywords(my_arg, my_kwarg="Long keyword strings also need to be wrapped, but they will probably need to be handled a little bit differently.") - -bad_split1 = ( - 'But what should happen when code has already been formatted but in the wrong way? Like' - " with a space at the end instead of the beginning. Or what about when it is split too soon?" -) - -bad_split2 = "But what should happen when code has already " \ - "been formatted but in the wrong way? Like " \ - "with a space at the end instead of the " \ - "beginning. Or what about when it is split too " \ - "soon? In the case of a split that is too " \ - "short, black will try to honer the custom " \ - "split." - -bad_split3 = ( - "What if we have inline comments on " # First Comment - "each line of a bad split? In that " # Second Comment - "case, we should just leave it alone." # Third Comment -) - -bad_split_func1( - "But what should happen when code has already " - "been formatted but in the wrong way? Like " - "with a space at the end instead of the " - "beginning. Or what about when it is split too " - "soon? In the case of a split that is too " - "short, black will try to honer the custom " - "split.", - xxx, yyy, zzz -) - -bad_split_func2( - xxx, yyy, zzz, - long_string_kwarg="But what should happen when code has already been formatted but in the wrong way? Like " - "with a space at the end instead of the beginning. Or what about when it is split too " - "soon?", -) - -bad_split_func3( - ( - "But what should happen when code has already " - r"been formatted but in the wrong way? Like " - "with a space at the end instead of the " - r"beginning. Or what about when it is split too " - r"soon? In the case of a split that is too " - "short, black will try to honer the custom " - "split." - ), - xxx, - yyy, - zzz, -) - -inline_comments_func1( - "if there are inline " - "comments in the middle " - # Here is the standard alone comment. - "of the implicitly concatenated " - "string, we should handle " - "them correctly", - xxx, -) - -inline_comments_func2( - "what if the string is very very very very very very very very very very long and this part does " - "not fit into a single line? " - # Here is the standard alone comment. - "then the string should still be properly handled by merging and splitting " - "it into parts that fit in line length.", - xxx, -) - -raw_string = r"This is a long raw string. When re-formatting this string, black needs to make sure it prepends the 'r' onto the new string." - -fmt_string1 = "We also need to be sure to preserve any and all {} which may or may not be attached to the string in question.".format("method calls") - -fmt_string2 = "But what about when the string is {} but {}".format("short", "the method call is really really really really really really really really long?") - -old_fmt_string1 = "While we are on the topic of %s, we should also note that old-style formatting must also be preserved, since some %s still uses it." % ("formatting", "code") - -old_fmt_string2 = "This is a %s %s %s %s" % ("really really really really really", "old", "way to format strings!", "Use f-strings instead!") - -old_fmt_string3 = "Whereas only the strings after the percent sign were long in the last example, this example uses a long initial string as well. This is another %s %s %s %s" % ("really really really really really", "old", "way to format strings!", "Use f-strings instead!") - -fstring = f"f-strings definitely make things more {difficult} than they need to be for {{black}}. But boy they sure are handy. The problem is that some lines will need to have the 'f' whereas others do not. This {line}, for example, needs one." - -fstring_with_no_fexprs = f"Some regular string that needs to get split certainly but is NOT an fstring by any means whatsoever." - -comment_string = "Long lines with inline comments should have their comments appended to the reformatted string's enclosing right parentheses." # This comment gets thrown to the top. - -arg_comment_string = print("Long lines with inline comments which are apart of (and not the only member of) an argument list should have their comments appended to the reformatted string's enclosing left parentheses.", # This comment gets thrown to the top. - "Arg #2", "Arg #3", "Arg #4", "Arg #5") - -pragma_comment_string1 = "Lines which end with an inline pragma comment of the form `# : <...>` should be left alone." # noqa: E501 - -pragma_comment_string2 = "Lines which end with an inline pragma comment of the form `# : <...>` should be left alone." # noqa - -"""This is a really really really long triple quote string and it should not be touched.""" - -triple_quote_string = """This is a really really really long triple quote string assignment and it should not be touched.""" - -assert some_type_of_boolean_expression, "Followed by a really really really long string that is used to provide context to the AssertionError exception." - -assert some_type_of_boolean_expression, "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic string {}.".format("formatting") - -assert some_type_of_boolean_expression, "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic string %s." % "formatting" - -assert some_type_of_boolean_expression, "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic %s %s." % ("string", "formatting") - -some_function_call("With a reallly generic name and with a really really long string that is, at some point down the line, " + added + " to a variable and then added to another string.") - -some_function_call("With a reallly generic name and with a really really long string that is, at some point down the line, " + added + " to a variable and then added to another string. But then what happens when the final string is also supppppperrrrr long?! Well then that second (realllllllly long) string should be split too.", "and a second argument", and_a_third) - -return "A really really really really really really really really really really really really really long {} {}".format("return", "value") - -func_with_bad_comma( - "This is a really long string argument to a function that has a trailing comma which should NOT be there.", -) - -func_with_bad_comma( - "This is a really long string argument to a function that has a trailing comma which should NOT be there.", # comment after comma -) - -func_with_bad_comma( - ( - "This is a really long string argument to a function that has a trailing comma" - " which should NOT be there." - ), -) - -func_with_bad_comma( - ( - "This is a really long string argument to a function that has a trailing comma" - " which should NOT be there." - ), # comment after comma -) - -func_with_bad_parens_that_wont_fit_in_one_line( - ("short string that should have parens stripped"), - x, - y, - z -) - -func_with_bad_parens_that_wont_fit_in_one_line( - x, - y, - ("short string that should have parens stripped"), - z -) - -func_with_bad_parens( - ("short string that should have parens stripped"), - x, - y, - z, -) - -func_with_bad_parens( - x, - y, - ("short string that should have parens stripped"), - z, -) - -annotated_variable: Final = "This is a large " + STRING + " that has been " + CONCATENATED + "using the '+' operator." -annotated_variable: Final = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped." -annotated_variable: Literal["fakse_literal"] = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped." - -backslashes = "This is a really long string with \"embedded\" double quotes and 'single' quotes that also handles checking for an even number of backslashes \\" -backslashes = "This is a really long string with \"embedded\" double quotes and 'single' quotes that also handles checking for an even number of backslashes \\\\" -backslashes = "This is a really 'long' string with \"embedded double quotes\" and 'single' quotes that also handles checking for an odd number of backslashes \\\", like this...\\\\\\" - -short_string = ( - "Hi" - " there." -) - -func_call( - short_string=( - "Hi" - " there." - ) -) - -raw_strings = r"Don't" " get" r" merged" " unless they are all raw." - -def foo(): - yield "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." - -x = f"This is a {{really}} long string that needs to be split without a doubt (i.e. most definitely). In short, this {string} that can't possibly be {{expected}} to fit all together on one line. In {fact} it may even take up three or more lines... like four or five... but probably just four." - -long_unmergable_string_with_pragma = ( - "This is a really long string that can't be merged because it has a likely pragma at the end" # type: ignore - " of it." -) - -long_unmergable_string_with_pragma = ( - "This is a really long string that can't be merged because it has a likely pragma at the end" # noqa - " of it." -) - -long_unmergable_string_with_pragma = ( - "This is a really long string that can't be merged because it has a likely pragma at the end" # pylint: disable=some-pylint-check - " of it." -) - -string_with_nameescape = ( - "........................................................................ \N{LAO KO LA}" -) - -string_with_nameescape = ( - "........................................................................... \N{LAO KO LA}" -) - -string_with_nameescape = ( - "............................................................................ \N{LAO KO LA}" -) - -string_with_nameescape_and_escaped_backslash = ( - "...................................................................... \\\N{LAO KO LA}" -) - -string_with_nameescape_and_escaped_backslash = ( - "......................................................................... \\\N{LAO KO LA}" -) - -string_with_nameescape_and_escaped_backslash = ( - ".......................................................................... \\\N{LAO KO LA}" -) - -string_with_escaped_nameescape = ( - "........................................................................ \\N{LAO KO LA}" -) - -string_with_escaped_nameescape = ( - "........................................................................... \\N{LAO KO LA}" -) - -msg = lambda x: f"this is a very very very very long lambda value {x} that doesn't fit on a single line" - -dict_with_lambda_values = { - "join": lambda j: ( - f"{j.__class__.__name__}({some_function_call(j.left)}, " - f"{some_function_call(j.right)})" - ), -} - -# Complex string concatenations with a method call in the middle. -code = ( - (" return [\n") - + ( - ", \n".join( - " (%r, self.%s, visitor.%s)" - % (attrname, attrname, visit_name) - for attrname, visit_name in names - ) - ) - + ("\n ]\n") -) - - -# Test case of an outer string' parens enclose an inner string's parens. -call(body=("%s %s" % ((",".join(items)), suffix))) - -log.info(f'Skipping: {desc["db_id"]=} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]=} {desc["exposure_max"]=}') - -log.info(f"Skipping: {desc['db_id']=} {desc['ms_name']} {money=} {dte=} {pos_share=} {desc['status']=} {desc['exposure_max']=}") - -log.info(f'Skipping: {desc["db_id"]} {foo("bar",x=123)} {"foo" != "bar"} {(x := "abc=")} {pos_share=} {desc["status"]} {desc["exposure_max"]}') - -log.info(f'Skipping: {desc["db_id"]} {desc["ms_name"]} {money=} {(x := "abc=")=} {pos_share=} {desc["status"]} {desc["exposure_max"]}') - -log.info(f'Skipping: {desc["db_id"]} {foo("bar",x=123)=} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}') - -log.info(f'Skipping: {foo("asdf")=} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}') - -log.info(f'Skipping: {"a" == "b" == "c" == "d"} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}') - -log.info(f'Skipping: {"a" == "b" == "c" == "d"=} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}') - -log.info(f'Skipping: { longer_longer_longer_longer_longer_longer_name [ "db_id" ] [ "another_key" ] = : .3f }') - -log.info(f'''Skipping: {"a" == 'b'} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}''') - -log.info(f'''Skipping: {'a' == "b"=} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}''') - -log.info(f"""Skipping: {'a' == 'b'} {desc['ms_name']} {money=} {dte=} {pos_share=} {desc['status']} {desc['exposure_max']}""") - -x = { - "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": ( - "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxx{xx}xxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx" - ) -} -x = { - "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxx{xx}xxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx", -} -x = { - "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": ( - "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxx" - ) -} - # output @@ -355,14 +12,34 @@ def foo(): " five... but probably just three." ) +[case assign_x_2] +# flags: --unstable + +x += "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." +# output + x += ( "This is a really long string that can't possibly be expected to fit all together" " on one line. In fact it may even take up three or more lines... like four or" " five... but probably just three." ) +[case assign_y] +# flags: --unstable + +y = ( + 'Short string' +) +# output + y = "Short string" +[case print] +# flags: --unstable + +print('This is a really long string inside of a print statement with extra arguments attached at the end of it.', x, y, z) +# output + print( "This is a really long string inside of a print statement with extra arguments" " attached at the end of it.", @@ -371,11 +48,23 @@ def foo(): z, ) +[case print_2] +# flags: --unstable + +print("This is a really long string inside of a print statement with no extra arguments attached at the end of it.") +# output + print( "This is a really long string inside of a print statement with no extra arguments" " attached at the end of it." ) +[case d1] +# flags: --unstable + +D1 = {"The First": "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a dictionary, so formatting is more difficult.", "The Second": "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a dictionary."} +# output + D1 = { "The First": ( "This is a really long string that can't possibly be expected to fit all" @@ -389,6 +78,12 @@ def foo(): ), } +[case d2] +# flags: --unstable + +D2 = {1.0: "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a dictionary, so formatting is more difficult.", 2.0: "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a dictionary."} +# output + D2 = { 1.0: ( "This is a really long string that can't possibly be expected to fit all" @@ -402,6 +97,12 @@ def foo(): ), } +[case d3] +# flags: --unstable + +D3 = {x: "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a dictionary, so formatting is more difficult.", y: "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a dictionary."} +# output + D3 = { x: ( "This is a really long string that can't possibly be expected to fit all" @@ -415,6 +116,12 @@ def foo(): ), } +[case d4] +# flags: --unstable + +D4 = {"A long and ridiculous {}".format(string_key): "This is a really really really long string that has to go i,side of a dictionary. It is soooo bad.", some_func("calling", "some", "stuff"): "This is a really really really long string that has to go inside of a dictionary. It is {soooo} bad (#{x}).".format(sooo="soooo", x=2), "A %s %s" % ("formatted", "string"): "This is a really really really long string that has to go inside of a dictionary. It is %s bad (#%d)." % ("soooo", 2)} +# output + D4 = { "A long and ridiculous {}".format(string_key): ( "This is a really really really long string that has to go i,side of a" @@ -431,12 +138,28 @@ def foo(): ), } +[case d5] +# flags: --unstable + +D5 = { # Test for https://github.com/psf/black/issues/3261 + ("This is a really long string that can't be expected to fit in one line and is used as a nested dict's key"): {"inner": "value"}, +} +# output + D5 = { # Test for https://github.com/psf/black/issues/3261 "This is a really long string that can't be expected to fit in one line and is used as a nested dict's key": { "inner": "value" }, } +[case d6] +# flags: --unstable + +D6 = { # Test for https://github.com/psf/black/issues/3261 + ("This is a really long string that can't be expected to fit in one line and is used as a dict's key"): ["value1", "value2"], +} +# output + D6 = { # Test for https://github.com/psf/black/issues/3261 "This is a really long string that can't be expected to fit in one line and is used as a dict's key": [ "value1", @@ -444,6 +167,12 @@ def foo(): ], } +[case l1] +# flags: --unstable + +L1 = ["The is a short string", "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a list literal, so it's expected to be wrapped in parens when splitting to avoid implicit str concatenation.", short_call("arg", {"key": "value"}), "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a list literal.", ("parens should be stripped for short string in list")] +# output + L1 = [ "The is a short string", ( @@ -460,11 +189,23 @@ def foo(): "parens should be stripped for short string in list", ] +[case l2] +# flags: --unstable + +L2 = ["This is a really long string that can't be expected to fit in one line and is the only child of a list literal."] +# output + L2 = [ "This is a really long string that can't be expected to fit in one line and is the" " only child of a list literal." ] +[case s1] +# flags: --unstable + +S1 = {"The is a short string", "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a set literal, so it's expected to be wrapped in parens when splitting to avoid implicit str concatenation.", short_call("arg", {"key": "value"}), "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a set literal.", ("parens should be stripped for short string in set")} +# output + S1 = { "The is a short string", ( @@ -481,11 +222,23 @@ def foo(): "parens should be stripped for short string in set", } +[case s2] +# flags: --unstable + +S2 = {"This is a really long string that can't be expected to fit in one line and is the only child of a set literal."} +# output + S2 = { "This is a really long string that can't be expected to fit in one line and is the" " only child of a set literal." } +[case t1] +# flags: --unstable + +T1 = ("The is a short string", "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a tuple literal, so it's expected to be wrapped in parens when splitting to avoid implicit str concatenation.", short_call("arg", {"key": "value"}), "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a tuple literal.", ("parens should be stripped for short string in list")) +# output + T1 = ( "The is a short string", ( @@ -502,6 +255,12 @@ def foo(): "parens should be stripped for short string in list", ) +[case t2] +# flags: --unstable + +T2 = ("This is a really long string that can't be expected to fit in one line and is the only child of a tuple literal.",) +# output + T2 = ( ( "This is a really long string that can't be expected to fit in one line and is" @@ -509,6 +268,13 @@ def foo(): ), ) +[case test_case_for_https_github_com_psf_black_issues_4912_unassig] +# flags: --unstable + +# Test case for https://github.com/psf/black/issues/4912 - unassigned long string with trailing comma +"A long string literal that is not assigned to a variable, exceeds line length when string-processing is enabled, and has a trailing comma (to make it a one-item tuple)", +# output + # Test case for https://github.com/psf/black/issues/4912 - unassigned long string with trailing comma ( "A long string literal that is not assigned to a variable, exceeds line length when" @@ -516,6 +282,12 @@ def foo(): " tuple)" ), +[case func_with_keywords] +# flags: --unstable + +func_with_keywords(my_arg, my_kwarg="Long keyword strings also need to be wrapped, but they will probably need to be handled a little bit differently.") +# output + func_with_keywords( my_arg, my_kwarg=( @@ -524,27 +296,73 @@ def foo(): ), ) -bad_split1 = ( - "But what should happen when code has already been formatted but in the wrong way?" - " Like with a space at the end instead of the beginning. Or what about when it is" - " split too soon?" -) +[case bad_split1] +# flags: --unstable + +bad_split1 = ( + 'But what should happen when code has already been formatted but in the wrong way? Like' + " with a space at the end instead of the beginning. Or what about when it is split too soon?" +) +# output + +bad_split1 = ( + "But what should happen when code has already been formatted but in the wrong way?" + " Like with a space at the end instead of the beginning. Or what about when it is" + " split too soon?" +) + +[case bad_split2] +# flags: --unstable + +bad_split2 = "But what should happen when code has already " \ + "been formatted but in the wrong way? Like " \ + "with a space at the end instead of the " \ + "beginning. Or what about when it is split too " \ + "soon? In the case of a split that is too " \ + "short, black will try to honer the custom " \ + "split." +# output + +bad_split2 = ( + "But what should happen when code has already " + "been formatted but in the wrong way? Like " + "with a space at the end instead of the " + "beginning. Or what about when it is split too " + "soon? In the case of a split that is too " + "short, black will try to honer the custom " + "split." +) + +[case bad_split3] +# flags: --unstable + +bad_split3 = ( + "What if we have inline comments on " # First Comment + "each line of a bad split? In that " # Second Comment + "case, we should just leave it alone." # Third Comment +) +# output + +bad_split3 = ( + "What if we have inline comments on " # First Comment + "each line of a bad split? In that " # Second Comment + "case, we should just leave it alone." # Third Comment +) + +[case bad_split_func1] +# flags: --unstable -bad_split2 = ( +bad_split_func1( "But what should happen when code has already " "been formatted but in the wrong way? Like " "with a space at the end instead of the " "beginning. Or what about when it is split too " "soon? In the case of a split that is too " "short, black will try to honer the custom " - "split." -) - -bad_split3 = ( - "What if we have inline comments on " # First Comment - "each line of a bad split? In that " # Second Comment - "case, we should just leave it alone." # Third Comment + "split.", + xxx, yyy, zzz ) +# output bad_split_func1( "But what should happen when code has already " @@ -559,6 +377,17 @@ def foo(): zzz, ) +[case bad_split_func2] +# flags: --unstable + +bad_split_func2( + xxx, yyy, zzz, + long_string_kwarg="But what should happen when code has already been formatted but in the wrong way? Like " + "with a space at the end instead of the beginning. Or what about when it is split too " + "soon?", +) +# output + bad_split_func2( xxx, yyy, @@ -570,6 +399,25 @@ def foo(): ), ) +[case bad_split_func3] +# flags: --unstable + +bad_split_func3( + ( + "But what should happen when code has already " + r"been formatted but in the wrong way? Like " + "with a space at the end instead of the " + r"beginning. Or what about when it is split too " + r"soon? In the case of a split that is too " + "short, black will try to honer the custom " + "split." + ), + xxx, + yyy, + zzz, +) +# output + bad_split_func3( ( "But what should happen when code has already " @@ -585,6 +433,20 @@ def foo(): zzz, ) +[case inline_comments_func1] +# flags: --unstable + +inline_comments_func1( + "if there are inline " + "comments in the middle " + # Here is the standard alone comment. + "of the implicitly concatenated " + "string, we should handle " + "them correctly", + xxx, +) +# output + inline_comments_func1( "if there are inline comments in the middle " # Here is the standard alone comment. @@ -592,6 +454,19 @@ def foo(): xxx, ) +[case inline_comments_func2] +# flags: --unstable + +inline_comments_func2( + "what if the string is very very very very very very very very very very long and this part does " + "not fit into a single line? " + # Here is the standard alone comment. + "then the string should still be properly handled by merging and splitting " + "it into parts that fit in line length.", + xxx, +) +# output + inline_comments_func2( "what if the string is very very very very very very very very very very long and" " this part does not fit into a single line? " @@ -601,26 +476,56 @@ def foo(): xxx, ) +[case raw_string] +# flags: --unstable + +raw_string = r"This is a long raw string. When re-formatting this string, black needs to make sure it prepends the 'r' onto the new string." +# output + raw_string = ( r"This is a long raw string. When re-formatting this string, black needs to make" r" sure it prepends the 'r' onto the new string." ) +[case fmt_string1] +# flags: --unstable + +fmt_string1 = "We also need to be sure to preserve any and all {} which may or may not be attached to the string in question.".format("method calls") +# output + fmt_string1 = ( "We also need to be sure to preserve any and all {} which may or may not be" " attached to the string in question.".format("method calls") ) +[case fmt_string2] +# flags: --unstable + +fmt_string2 = "But what about when the string is {} but {}".format("short", "the method call is really really really really really really really really long?") +# output + fmt_string2 = "But what about when the string is {} but {}".format( "short", "the method call is really really really really really really really really long?", ) +[case old_fmt_string1] +# flags: --unstable + +old_fmt_string1 = "While we are on the topic of %s, we should also note that old-style formatting must also be preserved, since some %s still uses it." % ("formatting", "code") +# output + old_fmt_string1 = ( "While we are on the topic of %s, we should also note that old-style formatting" " must also be preserved, since some %s still uses it." % ("formatting", "code") ) +[case old_fmt_string2] +# flags: --unstable + +old_fmt_string2 = "This is a %s %s %s %s" % ("really really really really really", "old", "way to format strings!", "Use f-strings instead!") +# output + old_fmt_string2 = "This is a %s %s %s %s" % ( "really really really really really", "old", @@ -628,6 +533,12 @@ def foo(): "Use f-strings instead!", ) +[case old_fmt_string3] +# flags: --unstable + +old_fmt_string3 = "Whereas only the strings after the percent sign were long in the last example, this example uses a long initial string as well. This is another %s %s %s %s" % ("really really really really really", "old", "way to format strings!", "Use f-strings instead!") +# output + old_fmt_string3 = ( "Whereas only the strings after the percent sign were long in the last example," " this example uses a long initial string as well. This is another %s %s %s %s" @@ -639,22 +550,47 @@ def foo(): ) ) +[case fstring] +# flags: --unstable + +fstring = f"f-strings definitely make things more {difficult} than they need to be for {{black}}. But boy they sure are handy. The problem is that some lines will need to have the 'f' whereas others do not. This {line}, for example, needs one." +# output + fstring = ( f"f-strings definitely make things more {difficult} than they need to be for" " {black}. But boy they sure are handy. The problem is that some lines will need" f" to have the 'f' whereas others do not. This {line}, for example, needs one." ) +[case fstring_with_no_fexprs] +# flags: --unstable + +fstring_with_no_fexprs = f"Some regular string that needs to get split certainly but is NOT an fstring by any means whatsoever." +# output + fstring_with_no_fexprs = ( f"Some regular string that needs to get split certainly but is NOT an fstring by" f" any means whatsoever." ) +[case comment_string] +# flags: --unstable + +comment_string = "Long lines with inline comments should have their comments appended to the reformatted string's enclosing right parentheses." # This comment gets thrown to the top. +# output + comment_string = ( # This comment gets thrown to the top. "Long lines with inline comments should have their comments appended to the" " reformatted string's enclosing right parentheses." ) +[case arg_comment_string] +# flags: --unstable + +arg_comment_string = print("Long lines with inline comments which are apart of (and not the only member of) an argument list should have their comments appended to the reformatted string's enclosing left parentheses.", # This comment gets thrown to the top. + "Arg #2", "Arg #3", "Arg #4", "Arg #5") +# output + arg_comment_string = print( "Long lines with inline comments which are apart of (and not the only member of) an" " argument list should have their comments appended to the reformatted string's" @@ -665,35 +601,89 @@ def foo(): "Arg #5", ) +[case pragma_comment_string1] +# flags: --unstable + pragma_comment_string1 = "Lines which end with an inline pragma comment of the form `# : <...>` should be left alone." # noqa: E501 +# output + +pragma_comment_string1 = "Lines which end with an inline pragma comment of the form `# : <...>` should be left alone." # noqa: E501 + +[case pragma_comment_string2] +# flags: --unstable + +pragma_comment_string2 = "Lines which end with an inline pragma comment of the form `# : <...>` should be left alone." # noqa +# output pragma_comment_string2 = "Lines which end with an inline pragma comment of the form `# : <...>` should be left alone." # noqa +[case this] +# flags: --unstable + +"""This is a really really really long triple quote string and it should not be touched.""" +# output + """This is a really really really long triple quote string and it should not be touched.""" +[case triple_quote_string] +# flags: --unstable + +triple_quote_string = """This is a really really really long triple quote string assignment and it should not be touched.""" +# output + triple_quote_string = """This is a really really really long triple quote string assignment and it should not be touched.""" +[case assert] +# flags: --unstable + +assert some_type_of_boolean_expression, "Followed by a really really really long string that is used to provide context to the AssertionError exception." +# output + assert some_type_of_boolean_expression, ( "Followed by a really really really long string that is used to provide context to" " the AssertionError exception." ) +[case assert_2] +# flags: --unstable + +assert some_type_of_boolean_expression, "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic string {}.".format("formatting") +# output + assert some_type_of_boolean_expression, ( "Followed by a really really really long string that is used to provide context to" " the AssertionError exception, which uses dynamic string {}.".format("formatting") ) +[case assert_3] +# flags: --unstable + +assert some_type_of_boolean_expression, "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic string %s." % "formatting" +# output + assert some_type_of_boolean_expression, ( "Followed by a really really really long string that is used to provide context to" " the AssertionError exception, which uses dynamic string %s." % "formatting" ) +[case assert_4] +# flags: --unstable + +assert some_type_of_boolean_expression, "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic %s %s." % ("string", "formatting") +# output + assert some_type_of_boolean_expression, ( "Followed by a really really really long string that is used to provide context to" " the AssertionError exception, which uses dynamic %s %s." % ("string", "formatting") ) +[case some_function_call] +# flags: --unstable + +some_function_call("With a reallly generic name and with a really really long string that is, at some point down the line, " + added + " to a variable and then added to another string.") +# output + some_function_call( "With a reallly generic name and with a really really long string that is, at some" " point down the line, " @@ -701,6 +691,12 @@ def foo(): + " to a variable and then added to another string." ) +[case some_function_call_2] +# flags: --unstable + +some_function_call("With a reallly generic name and with a really really long string that is, at some point down the line, " + added + " to a variable and then added to another string. But then what happens when the final string is also supppppperrrrr long?! Well then that second (realllllllly long) string should be split too.", "and a second argument", and_a_third) +# output + some_function_call( "With a reallly generic name and with a really really long string that is, at some" " point down the line, " @@ -712,39 +708,116 @@ def foo(): and_a_third, ) +[case return] +# flags: --unstable + +return "A really really really really really really really really really really really really really long {} {}".format("return", "value") +# output + return ( "A really really really really really really really really really really really" " really really long {} {}".format("return", "value") ) +[case func_with_bad_comma] +# flags: --unstable + +func_with_bad_comma( + "This is a really long string argument to a function that has a trailing comma which should NOT be there.", +) +# output + func_with_bad_comma( "This is a really long string argument to a function that has a trailing comma" " which should NOT be there.", ) +[case func_with_bad_comma_2] +# flags: --unstable + +func_with_bad_comma( + "This is a really long string argument to a function that has a trailing comma which should NOT be there.", # comment after comma +) +# output + func_with_bad_comma( "This is a really long string argument to a function that has a trailing comma" " which should NOT be there.", # comment after comma ) +[case func_with_bad_comma_3] +# flags: --unstable + +func_with_bad_comma( + ( + "This is a really long string argument to a function that has a trailing comma" + " which should NOT be there." + ), +) +# output + func_with_bad_comma( "This is a really long string argument to a function that has a trailing comma" " which should NOT be there.", ) +[case func_with_bad_comma_4] +# flags: --unstable + +func_with_bad_comma( + ( + "This is a really long string argument to a function that has a trailing comma" + " which should NOT be there." + ), # comment after comma +) +# output + func_with_bad_comma( "This is a really long string argument to a function that has a trailing comma" " which should NOT be there.", # comment after comma ) +[case func_with_bad_parens_that_wont_fit_in_one_line] +# flags: --unstable + +func_with_bad_parens_that_wont_fit_in_one_line( + ("short string that should have parens stripped"), + x, + y, + z +) +# output + func_with_bad_parens_that_wont_fit_in_one_line( "short string that should have parens stripped", x, y, z ) +[case func_with_bad_parens_that_wont_fit_in_one_line_2] +# flags: --unstable + +func_with_bad_parens_that_wont_fit_in_one_line( + x, + y, + ("short string that should have parens stripped"), + z +) +# output + func_with_bad_parens_that_wont_fit_in_one_line( x, y, "short string that should have parens stripped", z ) +[case func_with_bad_parens] +# flags: --unstable + +func_with_bad_parens( + ("short string that should have parens stripped"), + x, + y, + z, +) +# output + func_with_bad_parens( "short string that should have parens stripped", x, @@ -752,6 +825,17 @@ def foo(): z, ) +[case func_with_bad_parens_2] +# flags: --unstable + +func_with_bad_parens( + x, + y, + ("short string that should have parens stripped"), + z, +) +# output + func_with_bad_parens( x, y, @@ -759,6 +843,12 @@ def foo(): z, ) +[case annotated_variable] +# flags: --unstable + +annotated_variable: Final = "This is a large " + STRING + " that has been " + CONCATENATED + "using the '+' operator." +# output + annotated_variable: Final = ( "This is a large " + STRING @@ -766,35 +856,94 @@ def foo(): + CONCATENATED + "using the '+' operator." ) + +[case annotated_variable_2] +# flags: --unstable +annotated_variable: Final = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped." +# output annotated_variable: Final = ( "This is a large string that has a type annotation attached to it. A type" " annotation should NOT stop a long string from being wrapped." ) + +[case annotated_variable_3] +# flags: --unstable +annotated_variable: Literal["fakse_literal"] = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped." +# output annotated_variable: Literal["fakse_literal"] = ( "This is a large string that has a type annotation attached to it. A type" " annotation should NOT stop a long string from being wrapped." ) +[case backslashes] +# flags: --unstable + +backslashes = "This is a really long string with \"embedded\" double quotes and 'single' quotes that also handles checking for an even number of backslashes \\" +# output + backslashes = ( "This is a really long string with \"embedded\" double quotes and 'single' quotes" " that also handles checking for an even number of backslashes \\" ) + +[case backslashes_2] +# flags: --unstable +backslashes = "This is a really long string with \"embedded\" double quotes and 'single' quotes that also handles checking for an even number of backslashes \\\\" +# output backslashes = ( "This is a really long string with \"embedded\" double quotes and 'single' quotes" " that also handles checking for an even number of backslashes \\\\" ) + +[case backslashes_3] +# flags: --unstable +backslashes = "This is a really 'long' string with \"embedded double quotes\" and 'single' quotes that also handles checking for an odd number of backslashes \\\", like this...\\\\\\" +# output backslashes = ( "This is a really 'long' string with \"embedded double quotes\" and 'single' quotes" ' that also handles checking for an odd number of backslashes \\", like' " this...\\\\\\" ) +[case short_string] +# flags: --unstable + +short_string = ( + "Hi" + " there." +) +# output + short_string = "Hi there." +[case func_call] +# flags: --unstable + +func_call( + short_string=( + "Hi" + " there." + ) +) +# output + func_call(short_string="Hi there.") +[case raw_strings] +# flags: --unstable + +raw_strings = r"Don't" " get" r" merged" " unless they are all raw." +# output + raw_strings = r"Don't" " get" r" merged" " unless they are all raw." +[case fn_foo] +# flags: --unstable + +def foo(): + yield "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." +# output + def foo(): yield ( @@ -803,6 +952,12 @@ def foo(): " four or five... but probably just three." ) +[case assign_x_3] +# flags: --unstable + +x = f"This is a {{really}} long string that needs to be split without a doubt (i.e. most definitely). In short, this {string} that can't possibly be {{expected}} to fit all together on one line. In {fact} it may even take up three or more lines... like four or five... but probably just four." +# output + x = ( "This is a {really} long string that needs to be split without a doubt (i.e." @@ -811,66 +966,174 @@ def foo(): " lines... like four or five... but probably just four." ) +[case long_unmergable_string_with_pragma] +# flags: --unstable + +long_unmergable_string_with_pragma = ( + "This is a really long string that can't be merged because it has a likely pragma at the end" # type: ignore + " of it." +) +# output + long_unmergable_string_with_pragma = ( "This is a really long string that can't be merged because it has a likely pragma at the end" # type: ignore " of it." ) +[case long_unmergable_string_with_pragma_2] +# flags: --unstable + +long_unmergable_string_with_pragma = ( + "This is a really long string that can't be merged because it has a likely pragma at the end" # noqa + " of it." +) +# output + long_unmergable_string_with_pragma = ( "This is a really long string that can't be merged because it has a likely pragma at the end" # noqa " of it." ) +[case long_unmergable_string_with_pragma_3] +# flags: --unstable + +long_unmergable_string_with_pragma = ( + "This is a really long string that can't be merged because it has a likely pragma at the end" # pylint: disable=some-pylint-check + " of it." +) +# output + long_unmergable_string_with_pragma = ( "This is a really long string that can't be merged because it has a likely pragma at the end" # pylint: disable=some-pylint-check " of it." ) +[case string_with_nameescape] +# flags: --unstable + +string_with_nameescape = ( + "........................................................................ \N{LAO KO LA}" +) +# output + string_with_nameescape = ( "........................................................................" " \N{LAO KO LA}" ) +[case string_with_nameescape_2] +# flags: --unstable + +string_with_nameescape = ( + "........................................................................... \N{LAO KO LA}" +) +# output + string_with_nameescape = ( "..........................................................................." " \N{LAO KO LA}" ) +[case string_with_nameescape_3] +# flags: --unstable + +string_with_nameescape = ( + "............................................................................ \N{LAO KO LA}" +) +# output + string_with_nameescape = ( "............................................................................" " \N{LAO KO LA}" ) +[case string_with_nameescape_and_escaped_backslash] +# flags: --unstable + +string_with_nameescape_and_escaped_backslash = ( + "...................................................................... \\\N{LAO KO LA}" +) +# output + string_with_nameescape_and_escaped_backslash = ( "......................................................................" " \\\N{LAO KO LA}" ) +[case string_with_nameescape_and_escaped_backslash_2] +# flags: --unstable + +string_with_nameescape_and_escaped_backslash = ( + "......................................................................... \\\N{LAO KO LA}" +) +# output + string_with_nameescape_and_escaped_backslash = ( "........................................................................." " \\\N{LAO KO LA}" ) +[case string_with_nameescape_and_escaped_backslash_3] +# flags: --unstable + +string_with_nameescape_and_escaped_backslash = ( + ".......................................................................... \\\N{LAO KO LA}" +) +# output + string_with_nameescape_and_escaped_backslash = ( ".........................................................................." " \\\N{LAO KO LA}" ) +[case string_with_escaped_nameescape] +# flags: --unstable + +string_with_escaped_nameescape = ( + "........................................................................ \\N{LAO KO LA}" +) +# output + string_with_escaped_nameescape = ( "........................................................................ \\N{LAO" " KO LA}" ) +[case string_with_escaped_nameescape_2] +# flags: --unstable + +string_with_escaped_nameescape = ( + "........................................................................... \\N{LAO KO LA}" +) +# output + string_with_escaped_nameescape = ( "..........................................................................." " \\N{LAO KO LA}" ) +[case msg] +# flags: --unstable + +msg = lambda x: f"this is a very very very very long lambda value {x} that doesn't fit on a single line" +# output + msg = lambda x: ( f"this is a very very very very long lambda value {x} that doesn't fit on a" " single line" ) +[case dict_with_lambda_values] +# flags: --unstable + +dict_with_lambda_values = { + "join": lambda j: ( + f"{j.__class__.__name__}({some_function_call(j.left)}, " + f"{some_function_call(j.right)})" + ), +} +# output + dict_with_lambda_values = { "join": lambda j: ( f"{j.__class__.__name__}({some_function_call(j.left)}, " @@ -878,6 +1141,23 @@ def foo(): ), } +[case complex_string_concatenations_with_a_method_call_in_the_midd] +# flags: --unstable + +# Complex string concatenations with a method call in the middle. +code = ( + (" return [\n") + + ( + ", \n".join( + " (%r, self.%s, visitor.%s)" + % (attrname, attrname, visit_name) + for attrname, visit_name in names + ) + ) + + ("\n ]\n") +) +# output + # Complex string concatenations with a method call in the middle. code = ( " return [\n" @@ -888,77 +1168,183 @@ def foo(): + "\n ]\n" ) +[case test_case_of_an_outer_string_parens_enclose_an_inner_string] +# flags: --unstable + + +# Test case of an outer string' parens enclose an inner string's parens. +call(body=("%s %s" % ((",".join(items)), suffix))) +# output + # Test case of an outer string' parens enclose an inner string's parens. call(body="%s %s" % (",".join(items), suffix)) +[case log] +# flags: --unstable + +log.info(f'Skipping: {desc["db_id"]=} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]=} {desc["exposure_max"]=}') +# output + log.info( f'Skipping: {desc["db_id"]=} {desc["ms_name"]} {money=} {dte=} {pos_share=}' f' {desc["status"]=} {desc["exposure_max"]=}' ) +[case log_2] +# flags: --unstable + +log.info(f"Skipping: {desc['db_id']=} {desc['ms_name']} {money=} {dte=} {pos_share=} {desc['status']=} {desc['exposure_max']=}") +# output + log.info( f"Skipping: {desc['db_id']=} {desc['ms_name']} {money=} {dte=} {pos_share=}" f" {desc['status']=} {desc['exposure_max']=}" ) +[case log_3] +# flags: --unstable + +log.info(f'Skipping: {desc["db_id"]} {foo("bar",x=123)} {"foo" != "bar"} {(x := "abc=")} {pos_share=} {desc["status"]} {desc["exposure_max"]}') +# output + log.info( f'Skipping: {desc["db_id"]} {foo("bar",x=123)} {"foo" != "bar"} {(x := "abc=")}' f' {pos_share=} {desc["status"]} {desc["exposure_max"]}' ) +[case log_4] +# flags: --unstable + +log.info(f'Skipping: {desc["db_id"]} {desc["ms_name"]} {money=} {(x := "abc=")=} {pos_share=} {desc["status"]} {desc["exposure_max"]}') +# output + log.info( f'Skipping: {desc["db_id"]} {desc["ms_name"]} {money=} {(x := "abc=")=}' f' {pos_share=} {desc["status"]} {desc["exposure_max"]}' ) +[case log_5] +# flags: --unstable + +log.info(f'Skipping: {desc["db_id"]} {foo("bar",x=123)=} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}') +# output + log.info( f'Skipping: {desc["db_id"]} {foo("bar",x=123)=} {money=} {dte=} {pos_share=}' f' {desc["status"]} {desc["exposure_max"]}' ) +[case log_6] +# flags: --unstable + +log.info(f'Skipping: {foo("asdf")=} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}') +# output + log.info( f'Skipping: {foo("asdf")=} {desc["ms_name"]} {money=} {dte=} {pos_share=}' f' {desc["status"]} {desc["exposure_max"]}' ) +[case log_7] +# flags: --unstable + +log.info(f'Skipping: {"a" == "b" == "c" == "d"} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}') +# output + log.info( f'Skipping: {"a" == "b" == "c" == "d"} {desc["ms_name"]} {money=} {dte=}' f' {pos_share=} {desc["status"]} {desc["exposure_max"]}' ) +[case log_8] +# flags: --unstable + +log.info(f'Skipping: {"a" == "b" == "c" == "d"=} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}') +# output + log.info( f'Skipping: {"a" == "b" == "c" == "d"=} {desc["ms_name"]} {money=} {dte=}' f' {pos_share=} {desc["status"]} {desc["exposure_max"]}' ) +[case log_9] +# flags: --unstable + +log.info(f'Skipping: { longer_longer_longer_longer_longer_longer_name [ "db_id" ] [ "another_key" ] = : .3f }') +# output + log.info( "Skipping:" f' { longer_longer_longer_longer_longer_longer_name [ "db_id" ] [ "another_key" ] = : .3f }' ) +[case log_10] +# flags: --unstable + +log.info(f'''Skipping: {"a" == 'b'} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}''') +# output + log.info( f"""Skipping: {"a" == 'b'} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}""" ) +[case log_11] +# flags: --unstable + +log.info(f'''Skipping: {'a' == "b"=} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}''') +# output + log.info( f"""Skipping: {'a' == "b"=} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}""" ) +[case log_12] +# flags: --unstable + +log.info(f"""Skipping: {'a' == 'b'} {desc['ms_name']} {money=} {dte=} {pos_share=} {desc['status']} {desc['exposure_max']}""") +# output + log.info( f"""Skipping: {'a' == 'b'} {desc['ms_name']} {money=} {dte=} {pos_share=} {desc['status']} {desc['exposure_max']}""" ) +[case assign_x_4] +# flags: --unstable + +x = { + "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": ( + "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxx{xx}xxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx" + ) +} +# output + x = { "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": ( "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxx{xx}xxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx" ) } + +[case assign_x_5] +# flags: --unstable +x = { + "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxx{xx}xxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx", +} +# output x = { "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": ( "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxx{xx}xxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx" ), } + +[case assign_x_6] +# flags: --unstable +x = { + "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": ( + "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxx" + ) +} +# output x = { "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxx" } From 004ffb52fc203f9d3be57d52a91071a204af1459 Mon Sep 17 00:00:00 2001 From: Pedro Mezacasa Muller Date: Thu, 21 May 2026 01:24:52 -0300 Subject: [PATCH 4/9] Add unit tests for the multi-cell fixture loader --- tests/test_data_loader.py | 402 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 402 insertions(+) create mode 100644 tests/test_data_loader.py diff --git a/tests/test_data_loader.py b/tests/test_data_loader.py new file mode 100644 index 00000000000..6ec2b2b3ce1 --- /dev/null +++ b/tests/test_data_loader.py @@ -0,0 +1,402 @@ +"""Unit tests for the multi-cell fixture loader in tests/util.py. + +These exercise the loader against in-memory file content rather than the real +fixtures under tests/data/cases/, so regressions in cell parsing show up here +without depending on a specific fixture file. +""" + +from __future__ import annotations + +import textwrap +from collections.abc import Iterator +from pathlib import Path + +import pytest + +from tests import util +from tests.util import ( + Cell, + find_cell, + is_multi_cell, + parse_cells, + read_data, + read_data_with_mode, + try_parse_cells, +) + + +@pytest.fixture(autouse=True) +def _clear_parse_cache() -> Iterator[None]: + """`try_parse_cells` caches parse results by Path identity for the test + session. Tests here reuse `tmp_path`-derived filenames across runs, so the + cache is cleared before and after each test.""" + try_parse_cells.cache_clear() + yield + try_parse_cells.cache_clear() + + +@pytest.fixture +def patch_data_dir( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> Path: + """Point `util.DATA_DIR` at a fresh temp dir for `read_data_with_mode` + callers, with automatic restoration. Also pre-creates the subdirs that + test_format.py's module-load parametrize decorators expect, so that + importing it from within a test (e.g. to reach `check_file`) does not + blow up on a missing `cases/` or `line_ranges_formatted/`.""" + monkeypatch.setattr(util, "DATA_DIR", tmp_path) + (tmp_path / "cases").mkdir(exist_ok=True) + (tmp_path / "line_ranges_formatted").mkdir(exist_ok=True) + return tmp_path + + +def write(target_dir: Path, name: str, body: str) -> Path: + path = target_dir / name + path.write_text(textwrap.dedent(body).lstrip("\n"), encoding="utf8") + return path + + +def test_single_case_file_returns_none(tmp_path: Path) -> None: + path = write( + tmp_path, + "fix.py", + """ + # flags: --preview + x = 1 + # output + x = 1 + """, + ) + assert parse_cells(path.read_text(encoding="utf8"), path) is None + + +def test_file_with_case_header_not_at_top_is_single_case(tmp_path: Path) -> None: + path = write( + tmp_path, + "fix.py", + """ + x = 1 + [case nope] + y = 2 + """, + ) + assert parse_cells(path.read_text(encoding="utf8"), path) is None + + +def test_two_cells_parsed(tmp_path: Path) -> None: + path = write( + tmp_path, + "fix.py", + """ + # file-level prose + + [case alpha] + import foo + # output + import foo + + [case beta] + # flags: --pyi + x: int + # output + x: int + """, + ) + cells = parse_cells(path.read_text(encoding="utf8"), path) + assert cells is not None and len(cells) == 2 + assert cells[0].name == "alpha" + assert cells[1].name == "beta" + assert cells[0].args.mode.is_pyi is False + assert cells[1].args.mode.is_pyi is True + + +def test_multi_cell_outputs_do_not_leak_between_cells(tmp_path: Path) -> None: + """Each cell's `# output` marker bounds that cell's output. A second + `[case ]` header ends the prior cell's output even if there's no blank + line before it.""" + path = write( + tmp_path, + "fix.py", + """ + [case first] + a = 1 + # output + a = 1 + + [case second] + b = 2 + # output + b = 2 + """, + ) + cells = parse_cells(path.read_text(encoding="utf8"), path) + assert cells is not None and len(cells) == 2 + assert cells[0].input.strip() == "a = 1" + assert cells[0].output.strip() == "a = 1" + assert cells[1].input.strip() == "b = 2" + assert cells[1].output.strip() == "b = 2" + + +def test_idempotency_cell_has_no_output_marker(tmp_path: Path) -> None: + path = write( + tmp_path, + "fix.py", + """ + [case lone] + import bar + """, + ) + cells = parse_cells(path.read_text(encoding="utf8"), path) + assert cells is not None and len(cells) == 1 + assert cells[0].input == cells[0].output + assert cells[0].output_lineno is None + + +def test_duplicate_cell_name_raises(tmp_path: Path) -> None: + path = write( + tmp_path, + "fix.py", + """ + [case dup] + x = 1 + [case dup] + y = 2 + """, + ) + with pytest.raises(ValueError, match="Duplicate cell name `dup`"): + parse_cells(path.read_text(encoding="utf8"), path) + + +def test_header_tolerates_trailing_whitespace(tmp_path: Path) -> None: + # Trailing spaces on a header should still be recognized — silently + # treating the file as single-case is the worst failure mode. + path = write( + tmp_path, + "fix.py", + "[case foo] \nx = 1\n", + ) + cells = parse_cells(path.read_text(encoding="utf8"), path) + assert cells is not None and len(cells) == 1 + assert cells[0].name == "foo" + + +def test_cell_flags_tolerate_leading_blank_lines(tmp_path: Path) -> None: + # The docs promise `# flags: ` on the first *non-blank* line of a cell + # body. A blank line between the `[case ]` header and the flags line must + # not silently swallow the flags by parsing them as Python source. + path = write( + tmp_path, + "fix.py", + """ + [case foo] + + # flags: --preview + x = 1 + # output + x = 1 + """, + ) + cells = parse_cells(path.read_text(encoding="utf8"), path) + assert cells is not None and len(cells) == 1 + assert cells[0].args.mode.preview is True + assert cells[0].input.strip() == "x = 1" + + +def test_single_case_flags_tolerate_leading_blank_lines(tmp_path: Path) -> None: + # Single-case files share the same body parser. A blank line before the + # `# flags: ` line at the top of a legacy single-case file must also be + # tolerated. + path = write( + tmp_path, + "fix.py", + """ + + # flags: --preview + x = 1 + # output + x = 1 + """, + ) + from tests.util import read_data_from_file + + mode, input_text, output_text = read_data_from_file(path) + assert mode.mode.preview is True + assert input_text.strip() == "x = 1" + assert output_text.strip() == "x = 1" + + +def test_header_accepts_mixed_case_and_numbers(tmp_path: Path) -> None: + # Case names allow uppercase and trailing digits so contributors can name + # cases after PEPs or issue numbers (`PEP_695`, `issue_4296_regression`). + path = write( + tmp_path, + "fix.py", + """ + [case PEP_695] + x = 1 + [case issue_4296_regression] + y = 2 + """, + ) + cells = parse_cells(path.read_text(encoding="utf8"), path) + assert cells is not None and [c.name for c in cells] == [ + "PEP_695", + "issue_4296_regression", + ] + + +def test_header_rejects_leading_whitespace(tmp_path: Path) -> None: + # Leading whitespace must NOT make a header. Otherwise the detector and + # the parser would disagree (detector says multi-cell, parser finds zero + # headers), and all_data_cases would silently emit no tests. + path = write( + tmp_path, + "fix.py", + " [case foo]\nx = 1\n", + ) + assert parse_cells(path.read_text(encoding="utf8"), path) is None + + +def test_is_multi_cell_skips_leading_comments() -> None: + lines = ["# a file-level comment\n", "\n", "[case x]\n"] + assert is_multi_cell(lines) is True + + +def test_find_cell_returns_named_cell(tmp_path: Path) -> None: + path = write( + tmp_path, + "fix.py", + """ + [case alpha] + x = 1 + [case beta] + y = 2 + """, + ) + cell = find_cell(path, "beta") + assert cell is not None + assert cell.name == "beta" + assert cell.input.strip() == "y = 2" + + +def test_find_cell_missing_returns_none(tmp_path: Path) -> None: + path = write( + tmp_path, + "fix.py", + """ + [case alpha] + x = 1 + """, + ) + assert find_cell(path, "nope") is None + + +def test_find_cell_on_single_case_returns_none(tmp_path: Path) -> None: + path = write(tmp_path, "fix.py", "x = 1\n") + assert find_cell(path, "anything") is None + + +def test_try_parse_cells_returns_immutable_tuple(tmp_path: Path) -> None: + path = write( + tmp_path, + "fix.py", + """ + [case alpha] + x = 1 + """, + ) + result = try_parse_cells(path) + assert result is not None + assert isinstance(result, tuple) + assert all(isinstance(c, Cell) for c in result) + + +def test_bare_stem_read_on_multi_cell_fails_loudly(patch_data_dir: Path) -> None: + """Reading a multi-cell file by bare stem must raise. Callers have to + pick a cell with the (stem, cell_name) tuple form.""" + cases_dir = patch_data_dir / "cases" + write( + cases_dir, + "fix.py", + """ + [case alpha] + x = 1 + """, + ) + with pytest.raises(AssertionError, match="bare-stem read of a multi-cell"): + read_data_with_mode("cases", "fix") + + +def test_tuple_lookup_missing_cell_fails(patch_data_dir: Path) -> None: + cases_dir = patch_data_dir / "cases" + write( + cases_dir, + "fix.py", + """ + [case alpha] + x = 1 + """, + ) + with pytest.raises(AssertionError, match="no cell named `missing`"): + read_data_with_mode("cases", ("fix", "missing")) + + +def test_tuple_lookup_on_single_case_fails(patch_data_dir: Path) -> None: + cases_dir = patch_data_dir / "cases" + write(cases_dir, "fix.py", "x = 1\n") + with pytest.raises(AssertionError, match="file is single-case"): + read_data_with_mode("cases", ("fix", "alpha")) + + +def test_failing_cell_message_includes_cell_header( + patch_data_dir: Path, +) -> None: + """When a cell fails, the wrapped AssertionError must include the cell + name and file location so the pytest traceback (which reads e.args[0]) + points the contributor at the right line.""" + cases_dir = patch_data_dir / "cases" + fixture_path = write( + cases_dir, + "fix.py", + """ + [case bad_cell] + x = 1 + # output + x = 2 + """, + ) + # Lazy import to avoid module-level dependency from a unit-test file + # onto the format harness. + from tests.test_format import check_file + + with pytest.raises(AssertionError) as exc_info: + check_file("cases", ("fix", "bad_cell")) + + message = str(exc_info.value) + assert "cell `bad_cell`" in message + assert str(fixture_path) in message + assert "# output marker" in message + + +def test_per_cell_lookup_returns_isolated_input_output( + patch_data_dir: Path, +) -> None: + cases_dir = patch_data_dir / "cases" + write( + cases_dir, + "fix.py", + """ + [case first] + a = 1 + # output + a = 1 + + [case second] + b = 2 + # output + b = 2 + """, + ) + src, out = read_data("cases", ("fix", "second")) + assert src.strip() == "b = 2" + assert out.strip() == "b = 2" From 536830e9020f02814578271ba7a4002949f8a4c2 Mon Sep 17 00:00:00 2001 From: Pedro Mezacasa Muller Date: Thu, 21 May 2026 01:24:59 -0300 Subject: [PATCH 5/9] Add changelog entry for multi-cell fixture format (#5147) --- CHANGES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index a7d912cd466..da674f1a762 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -58,6 +58,12 @@ +- Allow grouping multiple cases in one fixture file under `tests/data/cases/` using + `[case ]` headers, inspired by mypy's test data format. Files without a header + continue to work unchanged. Failing cells report their file path, cell header line, + and `# output` marker line. Migrates `preview_long_strings.py` and + `preview_long_strings__regression.py` as the first consumers (#5147) + ## Version 26.5.1 ### Stable style From e6fdab0997c202a70112acbaf2d8e87501a95f31 Mon Sep 17 00:00:00 2001 From: Pedro Mezacasa Muller Date: Thu, 21 May 2026 11:00:49 -0300 Subject: [PATCH 6/9] Allow a top-level `# flags:` in multi-cell fixtures --- docs/contributing/the_basics.md | 47 +++++- tests/data/cases/preview_long_strings.py | 99 +---------- .../cases/preview_long_strings__regression.py | 82 +-------- tests/test_data_loader.py | 155 ++++++++++++++++++ tests/util.py | 92 +++++++++-- 5 files changed, 284 insertions(+), 191 deletions(-) diff --git a/docs/contributing/the_basics.md b/docs/contributing/the_basics.md index 8806e69960c..4dbef91ca51 100644 --- a/docs/contributing/the_basics.md +++ b/docs/contributing/the_basics.md @@ -92,13 +92,58 @@ foo(bar(x, y)) Rules: -- Anything before the first `[case ]` is file-level prose and is ignored by the loader. +- Anything before the first `[case ]` is file-level prose and is ignored by the loader, + with one exception: a single `# flags: ` line in that region is taken as the + file-level flag set and merged into every cell. - Within a cell, the legacy rules apply: an optional `# flags: ` line on the first non-blank line, then input, then optional `# output` followed by expected output. Omitting `# output` asserts idempotency. - Trailing whitespace on a `[case ]` line is tolerated; everything else on the line is rejected. +#### File-level flags + +When most cells in a file share the same flags, lifting them above the first `[case ]` +keeps the cells terse: + +``` +# flags: --preview --line-length=88 + +[case keep_short_strings] +x = "abc" +# output +x = "abc" + +[case override_per_cell] +# flags: --preview --line-length=120 +x = "the quick brown fox jumps over the lazy dog repeatedly" +# output +x = "the quick brown fox jumps over the lazy dog repeatedly" + +[case narrow_line_range] +# flags: --line-ranges=2-2 +x = "first" +x = "second longer string that would normally wrap" +# output +x = "first" +x = "second longer string that would normally wrap" +``` + +Merge semantics, driven by argparse: + +- Scalar / store flags (`--line-length`, `--preview`, `--pyi`, `--target-version`, …): + the cell wins when it sets the same flag. So `keep_short_strings` runs at + `--line-length=88`, and `override_per_cell` runs at `--line-length=120`. +- `--line-ranges` is rejected at the file level. Line numbers are per-cell, so a + file-level range would apply to every cell against unrelated content. Set it inside + the relevant `[case ]` instead. +- Once a `store_true` flag is set at the file level, a cell cannot un-set it (there is + no `--no-preview`). If a file has both preview and stable cases, leave `--preview` off + the file-level line and set it per cell. + +Only one file-level `# flags: ` line is allowed; multiples raise at load time. A +malformed file-level flag string fails fast with the file path attached. + Pytest IDs reflect the format. Single-case files keep the legacy `test_simple_format[]` shape; multi-cell files produce `test_simple_format[::]`. On failure, the error message points at the diff --git a/tests/data/cases/preview_long_strings.py b/tests/data/cases/preview_long_strings.py index 2041a292dc1..daa2ff2cb0c 100644 --- a/tests/data/cases/preview_long_strings.py +++ b/tests/data/cases/preview_long_strings.py @@ -1,7 +1,6 @@ -# Migrated from single-case format. Source file: preview_long_strings.py +# flags: --unstable [case assign_x_1] -# flags: --unstable x = "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." # output @@ -13,7 +12,6 @@ ) [case assign_x_2] -# flags: --unstable x += "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." # output @@ -25,7 +23,6 @@ ) [case assign_y] -# flags: --unstable y = ( 'Short string' @@ -35,7 +32,6 @@ y = "Short string" [case print] -# flags: --unstable print('This is a really long string inside of a print statement with extra arguments attached at the end of it.', x, y, z) # output @@ -49,7 +45,6 @@ ) [case print_2] -# flags: --unstable print("This is a really long string inside of a print statement with no extra arguments attached at the end of it.") # output @@ -60,7 +55,6 @@ ) [case d1] -# flags: --unstable D1 = {"The First": "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a dictionary, so formatting is more difficult.", "The Second": "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a dictionary."} # output @@ -79,7 +73,6 @@ } [case d2] -# flags: --unstable D2 = {1.0: "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a dictionary, so formatting is more difficult.", 2.0: "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a dictionary."} # output @@ -98,7 +91,6 @@ } [case d3] -# flags: --unstable D3 = {x: "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a dictionary, so formatting is more difficult.", y: "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a dictionary."} # output @@ -117,7 +109,6 @@ } [case d4] -# flags: --unstable D4 = {"A long and ridiculous {}".format(string_key): "This is a really really really long string that has to go i,side of a dictionary. It is soooo bad.", some_func("calling", "some", "stuff"): "This is a really really really long string that has to go inside of a dictionary. It is {soooo} bad (#{x}).".format(sooo="soooo", x=2), "A %s %s" % ("formatted", "string"): "This is a really really really long string that has to go inside of a dictionary. It is %s bad (#%d)." % ("soooo", 2)} # output @@ -139,7 +130,6 @@ } [case d5] -# flags: --unstable D5 = { # Test for https://github.com/psf/black/issues/3261 ("This is a really long string that can't be expected to fit in one line and is used as a nested dict's key"): {"inner": "value"}, @@ -153,7 +143,6 @@ } [case d6] -# flags: --unstable D6 = { # Test for https://github.com/psf/black/issues/3261 ("This is a really long string that can't be expected to fit in one line and is used as a dict's key"): ["value1", "value2"], @@ -168,7 +157,6 @@ } [case l1] -# flags: --unstable L1 = ["The is a short string", "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a list literal, so it's expected to be wrapped in parens when splitting to avoid implicit str concatenation.", short_call("arg", {"key": "value"}), "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a list literal.", ("parens should be stripped for short string in list")] # output @@ -190,7 +178,6 @@ ] [case l2] -# flags: --unstable L2 = ["This is a really long string that can't be expected to fit in one line and is the only child of a list literal."] # output @@ -201,7 +188,6 @@ ] [case s1] -# flags: --unstable S1 = {"The is a short string", "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a set literal, so it's expected to be wrapped in parens when splitting to avoid implicit str concatenation.", short_call("arg", {"key": "value"}), "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a set literal.", ("parens should be stripped for short string in set")} # output @@ -223,7 +209,6 @@ } [case s2] -# flags: --unstable S2 = {"This is a really long string that can't be expected to fit in one line and is the only child of a set literal."} # output @@ -234,7 +219,6 @@ } [case t1] -# flags: --unstable T1 = ("The is a short string", "This is a really long string that can't possibly be expected to fit all together on one line. Also it is inside a tuple literal, so it's expected to be wrapped in parens when splitting to avoid implicit str concatenation.", short_call("arg", {"key": "value"}), "This is another really really (not really) long string that also can't be expected to fit on one line and is, like the other string, inside a tuple literal.", ("parens should be stripped for short string in list")) # output @@ -256,7 +240,6 @@ ) [case t2] -# flags: --unstable T2 = ("This is a really long string that can't be expected to fit in one line and is the only child of a tuple literal.",) # output @@ -269,7 +252,6 @@ ) [case test_case_for_https_github_com_psf_black_issues_4912_unassig] -# flags: --unstable # Test case for https://github.com/psf/black/issues/4912 - unassigned long string with trailing comma "A long string literal that is not assigned to a variable, exceeds line length when string-processing is enabled, and has a trailing comma (to make it a one-item tuple)", @@ -283,7 +265,6 @@ ), [case func_with_keywords] -# flags: --unstable func_with_keywords(my_arg, my_kwarg="Long keyword strings also need to be wrapped, but they will probably need to be handled a little bit differently.") # output @@ -297,7 +278,6 @@ ) [case bad_split1] -# flags: --unstable bad_split1 = ( 'But what should happen when code has already been formatted but in the wrong way? Like' @@ -312,7 +292,6 @@ ) [case bad_split2] -# flags: --unstable bad_split2 = "But what should happen when code has already " \ "been formatted but in the wrong way? Like " \ @@ -334,7 +313,6 @@ ) [case bad_split3] -# flags: --unstable bad_split3 = ( "What if we have inline comments on " # First Comment @@ -350,7 +328,6 @@ ) [case bad_split_func1] -# flags: --unstable bad_split_func1( "But what should happen when code has already " @@ -378,7 +355,6 @@ ) [case bad_split_func2] -# flags: --unstable bad_split_func2( xxx, yyy, zzz, @@ -400,7 +376,6 @@ ) [case bad_split_func3] -# flags: --unstable bad_split_func3( ( @@ -434,7 +409,6 @@ ) [case inline_comments_func1] -# flags: --unstable inline_comments_func1( "if there are inline " @@ -455,7 +429,6 @@ ) [case inline_comments_func2] -# flags: --unstable inline_comments_func2( "what if the string is very very very very very very very very very very long and this part does " @@ -477,7 +450,6 @@ ) [case raw_string] -# flags: --unstable raw_string = r"This is a long raw string. When re-formatting this string, black needs to make sure it prepends the 'r' onto the new string." # output @@ -488,7 +460,6 @@ ) [case fmt_string1] -# flags: --unstable fmt_string1 = "We also need to be sure to preserve any and all {} which may or may not be attached to the string in question.".format("method calls") # output @@ -499,7 +470,6 @@ ) [case fmt_string2] -# flags: --unstable fmt_string2 = "But what about when the string is {} but {}".format("short", "the method call is really really really really really really really really long?") # output @@ -510,7 +480,6 @@ ) [case old_fmt_string1] -# flags: --unstable old_fmt_string1 = "While we are on the topic of %s, we should also note that old-style formatting must also be preserved, since some %s still uses it." % ("formatting", "code") # output @@ -521,7 +490,6 @@ ) [case old_fmt_string2] -# flags: --unstable old_fmt_string2 = "This is a %s %s %s %s" % ("really really really really really", "old", "way to format strings!", "Use f-strings instead!") # output @@ -534,7 +502,6 @@ ) [case old_fmt_string3] -# flags: --unstable old_fmt_string3 = "Whereas only the strings after the percent sign were long in the last example, this example uses a long initial string as well. This is another %s %s %s %s" % ("really really really really really", "old", "way to format strings!", "Use f-strings instead!") # output @@ -551,7 +518,6 @@ ) [case fstring] -# flags: --unstable fstring = f"f-strings definitely make things more {difficult} than they need to be for {{black}}. But boy they sure are handy. The problem is that some lines will need to have the 'f' whereas others do not. This {line}, for example, needs one." # output @@ -563,7 +529,6 @@ ) [case fstring_with_no_fexprs] -# flags: --unstable fstring_with_no_fexprs = f"Some regular string that needs to get split certainly but is NOT an fstring by any means whatsoever." # output @@ -574,7 +539,6 @@ ) [case comment_string] -# flags: --unstable comment_string = "Long lines with inline comments should have their comments appended to the reformatted string's enclosing right parentheses." # This comment gets thrown to the top. # output @@ -585,7 +549,6 @@ ) [case arg_comment_string] -# flags: --unstable arg_comment_string = print("Long lines with inline comments which are apart of (and not the only member of) an argument list should have their comments appended to the reformatted string's enclosing left parentheses.", # This comment gets thrown to the top. "Arg #2", "Arg #3", "Arg #4", "Arg #5") @@ -602,7 +565,6 @@ ) [case pragma_comment_string1] -# flags: --unstable pragma_comment_string1 = "Lines which end with an inline pragma comment of the form `# : <...>` should be left alone." # noqa: E501 # output @@ -610,7 +572,6 @@ pragma_comment_string1 = "Lines which end with an inline pragma comment of the form `# : <...>` should be left alone." # noqa: E501 [case pragma_comment_string2] -# flags: --unstable pragma_comment_string2 = "Lines which end with an inline pragma comment of the form `# : <...>` should be left alone." # noqa # output @@ -618,7 +579,6 @@ pragma_comment_string2 = "Lines which end with an inline pragma comment of the form `# : <...>` should be left alone." # noqa [case this] -# flags: --unstable """This is a really really really long triple quote string and it should not be touched.""" # output @@ -626,7 +586,6 @@ """This is a really really really long triple quote string and it should not be touched.""" [case triple_quote_string] -# flags: --unstable triple_quote_string = """This is a really really really long triple quote string assignment and it should not be touched.""" # output @@ -634,7 +593,6 @@ triple_quote_string = """This is a really really really long triple quote string assignment and it should not be touched.""" [case assert] -# flags: --unstable assert some_type_of_boolean_expression, "Followed by a really really really long string that is used to provide context to the AssertionError exception." # output @@ -645,7 +603,6 @@ ) [case assert_2] -# flags: --unstable assert some_type_of_boolean_expression, "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic string {}.".format("formatting") # output @@ -656,7 +613,6 @@ ) [case assert_3] -# flags: --unstable assert some_type_of_boolean_expression, "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic string %s." % "formatting" # output @@ -667,7 +623,6 @@ ) [case assert_4] -# flags: --unstable assert some_type_of_boolean_expression, "Followed by a really really really long string that is used to provide context to the AssertionError exception, which uses dynamic %s %s." % ("string", "formatting") # output @@ -679,7 +634,6 @@ ) [case some_function_call] -# flags: --unstable some_function_call("With a reallly generic name and with a really really long string that is, at some point down the line, " + added + " to a variable and then added to another string.") # output @@ -692,7 +646,6 @@ ) [case some_function_call_2] -# flags: --unstable some_function_call("With a reallly generic name and with a really really long string that is, at some point down the line, " + added + " to a variable and then added to another string. But then what happens when the final string is also supppppperrrrr long?! Well then that second (realllllllly long) string should be split too.", "and a second argument", and_a_third) # output @@ -709,7 +662,6 @@ ) [case return] -# flags: --unstable return "A really really really really really really really really really really really really really long {} {}".format("return", "value") # output @@ -720,7 +672,6 @@ ) [case func_with_bad_comma] -# flags: --unstable func_with_bad_comma( "This is a really long string argument to a function that has a trailing comma which should NOT be there.", @@ -733,7 +684,6 @@ ) [case func_with_bad_comma_2] -# flags: --unstable func_with_bad_comma( "This is a really long string argument to a function that has a trailing comma which should NOT be there.", # comment after comma @@ -746,7 +696,6 @@ ) [case func_with_bad_comma_3] -# flags: --unstable func_with_bad_comma( ( @@ -762,7 +711,6 @@ ) [case func_with_bad_comma_4] -# flags: --unstable func_with_bad_comma( ( @@ -778,7 +726,6 @@ ) [case func_with_bad_parens_that_wont_fit_in_one_line] -# flags: --unstable func_with_bad_parens_that_wont_fit_in_one_line( ("short string that should have parens stripped"), @@ -793,7 +740,6 @@ ) [case func_with_bad_parens_that_wont_fit_in_one_line_2] -# flags: --unstable func_with_bad_parens_that_wont_fit_in_one_line( x, @@ -808,7 +754,6 @@ ) [case func_with_bad_parens] -# flags: --unstable func_with_bad_parens( ("short string that should have parens stripped"), @@ -826,7 +771,6 @@ ) [case func_with_bad_parens_2] -# flags: --unstable func_with_bad_parens( x, @@ -844,7 +788,6 @@ ) [case annotated_variable] -# flags: --unstable annotated_variable: Final = "This is a large " + STRING + " that has been " + CONCATENATED + "using the '+' operator." # output @@ -858,7 +801,6 @@ ) [case annotated_variable_2] -# flags: --unstable annotated_variable: Final = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped." # output annotated_variable: Final = ( @@ -867,7 +809,6 @@ ) [case annotated_variable_3] -# flags: --unstable annotated_variable: Literal["fakse_literal"] = "This is a large string that has a type annotation attached to it. A type annotation should NOT stop a long string from being wrapped." # output annotated_variable: Literal["fakse_literal"] = ( @@ -876,7 +817,6 @@ ) [case backslashes] -# flags: --unstable backslashes = "This is a really long string with \"embedded\" double quotes and 'single' quotes that also handles checking for an even number of backslashes \\" # output @@ -887,7 +827,6 @@ ) [case backslashes_2] -# flags: --unstable backslashes = "This is a really long string with \"embedded\" double quotes and 'single' quotes that also handles checking for an even number of backslashes \\\\" # output backslashes = ( @@ -896,7 +835,6 @@ ) [case backslashes_3] -# flags: --unstable backslashes = "This is a really 'long' string with \"embedded double quotes\" and 'single' quotes that also handles checking for an odd number of backslashes \\\", like this...\\\\\\" # output backslashes = ( @@ -906,7 +844,6 @@ ) [case short_string] -# flags: --unstable short_string = ( "Hi" @@ -917,7 +854,6 @@ short_string = "Hi there." [case func_call] -# flags: --unstable func_call( short_string=( @@ -930,7 +866,6 @@ func_call(short_string="Hi there.") [case raw_strings] -# flags: --unstable raw_strings = r"Don't" " get" r" merged" " unless they are all raw." # output @@ -938,7 +873,6 @@ raw_strings = r"Don't" " get" r" merged" " unless they are all raw." [case fn_foo] -# flags: --unstable def foo(): yield "This is a really long string that can't possibly be expected to fit all together on one line. In fact it may even take up three or more lines... like four or five... but probably just three." @@ -953,7 +887,6 @@ def foo(): ) [case assign_x_3] -# flags: --unstable x = f"This is a {{really}} long string that needs to be split without a doubt (i.e. most definitely). In short, this {string} that can't possibly be {{expected}} to fit all together on one line. In {fact} it may even take up three or more lines... like four or five... but probably just four." # output @@ -967,7 +900,6 @@ def foo(): ) [case long_unmergable_string_with_pragma] -# flags: --unstable long_unmergable_string_with_pragma = ( "This is a really long string that can't be merged because it has a likely pragma at the end" # type: ignore @@ -981,7 +913,6 @@ def foo(): ) [case long_unmergable_string_with_pragma_2] -# flags: --unstable long_unmergable_string_with_pragma = ( "This is a really long string that can't be merged because it has a likely pragma at the end" # noqa @@ -995,7 +926,6 @@ def foo(): ) [case long_unmergable_string_with_pragma_3] -# flags: --unstable long_unmergable_string_with_pragma = ( "This is a really long string that can't be merged because it has a likely pragma at the end" # pylint: disable=some-pylint-check @@ -1009,7 +939,6 @@ def foo(): ) [case string_with_nameescape] -# flags: --unstable string_with_nameescape = ( "........................................................................ \N{LAO KO LA}" @@ -1022,7 +951,6 @@ def foo(): ) [case string_with_nameescape_2] -# flags: --unstable string_with_nameescape = ( "........................................................................... \N{LAO KO LA}" @@ -1035,7 +963,6 @@ def foo(): ) [case string_with_nameescape_3] -# flags: --unstable string_with_nameescape = ( "............................................................................ \N{LAO KO LA}" @@ -1048,7 +975,6 @@ def foo(): ) [case string_with_nameescape_and_escaped_backslash] -# flags: --unstable string_with_nameescape_and_escaped_backslash = ( "...................................................................... \\\N{LAO KO LA}" @@ -1061,7 +987,6 @@ def foo(): ) [case string_with_nameescape_and_escaped_backslash_2] -# flags: --unstable string_with_nameescape_and_escaped_backslash = ( "......................................................................... \\\N{LAO KO LA}" @@ -1074,7 +999,6 @@ def foo(): ) [case string_with_nameescape_and_escaped_backslash_3] -# flags: --unstable string_with_nameescape_and_escaped_backslash = ( ".......................................................................... \\\N{LAO KO LA}" @@ -1087,7 +1011,6 @@ def foo(): ) [case string_with_escaped_nameescape] -# flags: --unstable string_with_escaped_nameescape = ( "........................................................................ \\N{LAO KO LA}" @@ -1100,7 +1023,6 @@ def foo(): ) [case string_with_escaped_nameescape_2] -# flags: --unstable string_with_escaped_nameescape = ( "........................................................................... \\N{LAO KO LA}" @@ -1113,7 +1035,6 @@ def foo(): ) [case msg] -# flags: --unstable msg = lambda x: f"this is a very very very very long lambda value {x} that doesn't fit on a single line" # output @@ -1124,7 +1045,6 @@ def foo(): ) [case dict_with_lambda_values] -# flags: --unstable dict_with_lambda_values = { "join": lambda j: ( @@ -1142,7 +1062,6 @@ def foo(): } [case complex_string_concatenations_with_a_method_call_in_the_midd] -# flags: --unstable # Complex string concatenations with a method call in the middle. code = ( @@ -1169,7 +1088,6 @@ def foo(): ) [case test_case_of_an_outer_string_parens_enclose_an_inner_string] -# flags: --unstable # Test case of an outer string' parens enclose an inner string's parens. @@ -1181,7 +1099,6 @@ def foo(): call(body="%s %s" % (",".join(items), suffix)) [case log] -# flags: --unstable log.info(f'Skipping: {desc["db_id"]=} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]=} {desc["exposure_max"]=}') # output @@ -1192,7 +1109,6 @@ def foo(): ) [case log_2] -# flags: --unstable log.info(f"Skipping: {desc['db_id']=} {desc['ms_name']} {money=} {dte=} {pos_share=} {desc['status']=} {desc['exposure_max']=}") # output @@ -1203,7 +1119,6 @@ def foo(): ) [case log_3] -# flags: --unstable log.info(f'Skipping: {desc["db_id"]} {foo("bar",x=123)} {"foo" != "bar"} {(x := "abc=")} {pos_share=} {desc["status"]} {desc["exposure_max"]}') # output @@ -1214,7 +1129,6 @@ def foo(): ) [case log_4] -# flags: --unstable log.info(f'Skipping: {desc["db_id"]} {desc["ms_name"]} {money=} {(x := "abc=")=} {pos_share=} {desc["status"]} {desc["exposure_max"]}') # output @@ -1225,7 +1139,6 @@ def foo(): ) [case log_5] -# flags: --unstable log.info(f'Skipping: {desc["db_id"]} {foo("bar",x=123)=} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}') # output @@ -1236,7 +1149,6 @@ def foo(): ) [case log_6] -# flags: --unstable log.info(f'Skipping: {foo("asdf")=} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}') # output @@ -1247,7 +1159,6 @@ def foo(): ) [case log_7] -# flags: --unstable log.info(f'Skipping: {"a" == "b" == "c" == "d"} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}') # output @@ -1258,7 +1169,6 @@ def foo(): ) [case log_8] -# flags: --unstable log.info(f'Skipping: {"a" == "b" == "c" == "d"=} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}') # output @@ -1269,7 +1179,6 @@ def foo(): ) [case log_9] -# flags: --unstable log.info(f'Skipping: { longer_longer_longer_longer_longer_longer_name [ "db_id" ] [ "another_key" ] = : .3f }') # output @@ -1280,7 +1189,6 @@ def foo(): ) [case log_10] -# flags: --unstable log.info(f'''Skipping: {"a" == 'b'} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}''') # output @@ -1290,7 +1198,6 @@ def foo(): ) [case log_11] -# flags: --unstable log.info(f'''Skipping: {'a' == "b"=} {desc["ms_name"]} {money=} {dte=} {pos_share=} {desc["status"]} {desc["exposure_max"]}''') # output @@ -1300,7 +1207,6 @@ def foo(): ) [case log_12] -# flags: --unstable log.info(f"""Skipping: {'a' == 'b'} {desc['ms_name']} {money=} {dte=} {pos_share=} {desc['status']} {desc['exposure_max']}""") # output @@ -1310,7 +1216,6 @@ def foo(): ) [case assign_x_4] -# flags: --unstable x = { "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": ( @@ -1326,7 +1231,6 @@ def foo(): } [case assign_x_5] -# flags: --unstable x = { "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxxx{xx}xxx_xxxxx_xxxxxxxxx_xxxxxxxxxxxx_xxxx", } @@ -1338,7 +1242,6 @@ def foo(): } [case assign_x_6] -# flags: --unstable x = { "xx_xxxxx_xxxxxxxxxx_xxxxxxxxx_xx": ( "xx:xxxxxxxxxxxxxxxxx_xxxxx_xxxxxxx_xxxxxxxxxx" diff --git a/tests/data/cases/preview_long_strings__regression.py b/tests/data/cases/preview_long_strings__regression.py index 441df94f47d..cc79766bad2 100644 --- a/tests/data/cases/preview_long_strings__regression.py +++ b/tests/data/cases/preview_long_strings__regression.py @@ -1,7 +1,6 @@ -# Migrated from single-case format. Source file: preview_long_strings__regression.py +# flags: --unstable [case class_a_1] -# flags: --unstable class A: def foo(): result = type(message)("") @@ -13,7 +12,6 @@ def foo(): result = type(message)("") [case don_t_merge_multiline_e_g_triple_quoted_strings] -# flags: --unstable # Don't merge multiline (e.g. triple-quoted) strings. @@ -32,7 +30,6 @@ def foo(): ) [case there_was_a_bug_where_tuples_were_being_identified_as_long_s] -# flags: --unstable # There was a bug where tuples were being identified as long strings. long_tuple = ('Apple', 'Berry', 'Cherry', 'Dill', 'Evergreen', 'Fig', @@ -55,7 +52,6 @@ def foo(): ) [case stupid_format_method_bug] -# flags: --unstable stupid_format_method_bug = "Some really long string that just so happens to be the {} {} to force the 'format' method to hang over the line length boundary. This is pretty annoying.".format("perfect", "length") # output @@ -68,7 +64,6 @@ def foo(): ) [case class_a_2] -# flags: --unstable class A: def foo(): @@ -85,7 +80,6 @@ def foo(): ) [case class_a_3] -# flags: --unstable class A: @@ -113,7 +107,6 @@ def foo(): )) [case class_a_4] -# flags: --unstable class A: class B: @@ -145,7 +138,6 @@ def foo(): ) [case fn_foo_1] -# flags: --unstable def foo(xxxx): for (xxx_xxxx, _xxx_xxx, _xxx_xxxxx, xxx_xxxx) in xxxx: @@ -167,7 +159,6 @@ def foo(xxxx): ) [case class_a_5] -# flags: --unstable class A: def disappearing_comment(): @@ -206,7 +197,6 @@ def disappearing_comment(): ) [case class_a_6] -# flags: --unstable class A: class B: @@ -243,7 +233,6 @@ def foo(): ) [case func_call_where_string_arg_has_method_call_and_bad_parens] -# flags: --unstable func_call_where_string_arg_has_method_call_and_bad_parens( ( @@ -259,7 +248,6 @@ def foo(): ) [case func_call_where_string_arg_has_old_fmt_and_bad_parens] -# flags: --unstable func_call_where_string_arg_has_old_fmt_and_bad_parens( ( @@ -274,7 +262,6 @@ def foo(): ) [case func_call_where_string_arg_has_old_fmt_and_bad_parens_2] -# flags: --unstable func_call_where_string_arg_has_old_fmt_and_bad_parens( ( @@ -289,7 +276,6 @@ def foo(): ) [case class_a_7] -# flags: --unstable class A: def append(self): @@ -323,7 +309,6 @@ def append(self): ) [case class_a_8] -# flags: --unstable class A: def foo(): @@ -352,7 +337,6 @@ def foo(): ), [case class_a_9] -# flags: --unstable class A: def foo(): @@ -379,7 +363,6 @@ def foo(): ), [case obfuscated_xxxxxxx] -# flags: --unstable xxxxxxx = { 'xx' : 'xxxx xxxxxxx xxxxxxxxx -x xxx -x /xxx/{0} -x xxx,xxx -xx {1} \ -xx {1} -xx xxx=xxx_xxxx,xxx_xx,xxx_xxx,xxx_xxxx,xxx_xx,xxx_xxx |\ @@ -405,7 +388,6 @@ def foo(): } [case class_a_10] -# flags: --unstable class A: def foo(self): @@ -426,7 +408,6 @@ def foo(self): ) [case class_a_11] -# flags: --unstable class A: class B: @@ -455,7 +436,6 @@ def foo(): } [case class_a_12] -# flags: --unstable class A: def xxxx_xxx_xx_xxxxxxxxxx_xxxx_xxxxxxxxx(xxxx): @@ -508,7 +488,6 @@ def xxxx_xxx_xx_xxxxxxxxxx_xxxx_xxxxxxxxx(xxxx): ] [case some_dictionary_1] -# flags: --unstable some_dictionary = { 'xxxxx006': ['xxx-xxx xxxxx3xxxx1xx2xxxxxxxxxxxxxx0xx6xxxxxxxxxx2xxxxxx9xxxxxxxxxx0xxxxx1xxx2x/xx9xx6+x+xxxxxxxxxxxxxx4xxxxxxxxxxxxxxxxxxxxx43xxx2xx2x4x++xxx6xxxxxxxxx+xxxxx/xx9x+xxxxxxxxxxxxxx8x15xxxxxxxxxxxxxxxxx82xx/xxxxxxxxxxxxxx/x5xxxxxxxxxxxxxx6xxxxxx74x4/xxx4x+xxxxxxxxx2xxxxxxxx87xxxxx4xxxxxxxx3xx0xxxxx4xxx1xx9xx5xxxxxxx/xxxxx5xx6xx4xxxx1x/x2xxxxxxxxxxxx64xxxxxxx1x0xx5xxxxxxxxxxxxxx== xxxxx000 xxxxxxxxxx\n', @@ -547,7 +526,6 @@ def xxxx_xxx_xx_xxxxxxxxxx_xxxx_xxxxxxxxx(xxxx): } [case fn_foo_2] -# flags: --unstable def foo(): xxx_xxx = ( @@ -563,7 +541,6 @@ def foo(): ) [case some_tuple] -# flags: --unstable some_tuple = ("some string", "some string" " which should be joined") # output @@ -572,7 +549,6 @@ def foo(): some_tuple = ("some string", "some string which should be joined") [case some_commented_string] -# flags: --unstable some_commented_string = ( # This comment stays at the top. "This string is long but not so long that it needs hahahah toooooo be so greatttt" @@ -588,7 +564,6 @@ def foo(): ) [case some_commented_string_2] -# flags: --unstable some_commented_string = ( "This string is long but not so long that it needs hahahah toooooo be so greatttt" # But these @@ -604,7 +579,6 @@ def foo(): ) [case lpar_and_rpar_have_comments] -# flags: --unstable lpar_and_rpar_have_comments = func_call( # LPAR Comment "Long really ridiculous type of string that shouldn't really even exist at all. I mean commmme onnn!!!", # Comma Comment @@ -617,7 +591,6 @@ def foo(): ) # RPAR Comment [case cmd_fstring] -# flags: --unstable cmd_fstring = ( f"sudo -E deluge-console info --detailed --sort-reverse=time_added " @@ -631,7 +604,6 @@ def foo(): ) [case cmd_fstring_2] -# flags: --unstable cmd_fstring = f"sudo -E deluge-console info --detailed --sort-reverse=time_added {'' if ID is None else ID} | perl -nE 'print if /^{field}:/'" # output @@ -642,7 +614,6 @@ def foo(): ) [case cmd_fstring_3] -# flags: --unstable cmd_fstring = f"sudo -E deluge-console info --detailed --sort-reverse=time_added {'{{}}' if ID is None else ID} | perl -nE 'print if /^{field}:/'" # output @@ -653,7 +624,6 @@ def foo(): ) [case cmd_fstring_4] -# flags: --unstable cmd_fstring = f"sudo -E deluge-console info --detailed --sort-reverse=time_added {{'' if ID is None else ID}} | perl -nE 'print if /^{field}:/'" # output @@ -664,7 +634,6 @@ def foo(): ) [case fstring_1] -# flags: --unstable fstring = f"This string really doesn't need to be an {{{{fstring}}}}, but this one most certainly, absolutely {does}." # output @@ -675,7 +644,6 @@ def foo(): ) [case fstring_2] -# flags: --unstable fstring = ( f"We have to remember to escape {braces}." @@ -687,7 +655,6 @@ def foo(): fstring = f"We have to remember to escape {braces}. Like {{these}}. But not {this}." [case class_a_13] -# flags: --unstable class A: class B: @@ -708,7 +675,6 @@ def foo(): ) [case fn_foo_3] -# flags: --unstable def foo(): user_regex = _lazy_re_compile( @@ -726,7 +692,6 @@ def foo(): ) [case fn_foo_4] -# flags: --unstable def foo(): user_regex = _lazy_re_compile( @@ -745,7 +710,6 @@ def foo(): ) [case fn_foo_5] -# flags: --unstable def foo(): user_regex = _lazy_re_compile( @@ -764,7 +728,6 @@ def foo(): ) [case class_a_14] -# flags: --unstable class A: class B: @@ -796,7 +759,6 @@ def foo(): ) [case class_a_15] -# flags: --unstable class A: class B: @@ -821,7 +783,6 @@ def foo(): ) [case assign_x_1] -# flags: --unstable x = ( "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" @@ -840,7 +801,6 @@ def foo(): ) [case assign_step] -# flags: --unstable class Step(StepBase): def who(self): @@ -868,7 +828,6 @@ def who(self): ) [case obfuscated_xxxxxxx_xxxxxx_xxxxxxx] -# flags: --unstable xxxxxxx_xxxxxx_xxxxxxx = xxx( [ @@ -898,7 +857,6 @@ def who(self): ]) [case conditional_string] -# flags: --unstable if __name__ == "__main__": for i in range(4, 8): @@ -916,7 +874,6 @@ def who(self): ) [case class_a_16] -# flags: --unstable def A(): def B(): @@ -945,7 +902,6 @@ def G(): ), "%s didn't roundtrip" % tag [case obfuscated_xxxxxxxxxxxxxxxxxxxxx] -# flags: --unstable class xxxxxxxxxxxxxxxxxxxxx(xxxx.xxxxxxxxxxxxx): def xxxxxxx_xxxxxx(xxxx): @@ -966,7 +922,6 @@ def xxxxxxx_xxxxxx(xxxx): ) [case assign_value] -# flags: --unstable value.__dict__[ key @@ -979,7 +934,6 @@ def xxxxxxx_xxxxxx(xxxx): ) [case re_one_backslash] -# flags: --unstable RE_ONE_BACKSLASH = { "asdf_hjkl_jkl": re.compile( @@ -995,7 +949,6 @@ def xxxxxxx_xxxxxx(xxxx): } [case re_two_backslashes] -# flags: --unstable RE_TWO_BACKSLASHES = { "asdf_hjkl_jkl": re.compile( @@ -1012,7 +965,6 @@ def xxxxxxx_xxxxxx(xxxx): } [case re_three_backslashes] -# flags: --unstable RE_THREE_BACKSLASHES = { "asdf_hjkl_jkl": re.compile( @@ -1028,7 +980,6 @@ def xxxxxxx_xxxxxx(xxxx): } [case we_do_not_split_on_f_string_expressions] -# flags: --unstable # We do NOT split on f-string expressions. print(f"Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam. {[f'{i}' for i in range(10)]}") @@ -1041,7 +992,6 @@ def xxxxxxx_xxxxxx(xxxx): ) [case assign_x_2] -# flags: --unstable x = f"This is a long string which contains an f-expr that should not split {{{[i for i in range(5)]}}}." # output x = ( @@ -1050,7 +1000,6 @@ def xxxxxxx_xxxxxx(xxxx): ) [case the_parens_should_not_be_removed_in_this_case] -# flags: --unstable # The parens should NOT be removed in this case. ( @@ -1066,7 +1015,6 @@ def xxxxxxx_xxxxxx(xxxx): ) [case the_parens_should_not_be_removed_in_this_case_2] -# flags: --unstable # The parens should NOT be removed in this case. ( @@ -1082,7 +1030,6 @@ def xxxxxxx_xxxxxx(xxxx): ) [case the_parens_should_not_be_removed_in_this_case_3] -# flags: --unstable # The parens should NOT be removed in this case. ( @@ -1104,7 +1051,6 @@ def xxxxxxx_xxxxxx(xxxx): ) [case legacy_listen_examples] -# flags: --unstable def _legacy_listen_examples(): @@ -1128,7 +1074,6 @@ def _legacy_listen_examples(): ) [case assign_x_3] -# flags: --unstable class X: @@ -1152,7 +1097,6 @@ async def foo(self): ) [case assign_temp_msg] -# flags: --unstable temp_msg = ( @@ -1170,7 +1114,6 @@ async def foo(self): ) [case assert_stmt_1] -# flags: --unstable assert str(suffix_arr) == ( "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', " @@ -1187,7 +1130,6 @@ async def foo(self): ) [case assert_stmt_2] -# flags: --unstable assert str(suffix_arr) != ( "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', " "'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', " @@ -1202,7 +1144,6 @@ async def foo(self): ) [case assert_stmt_3] -# flags: --unstable assert str(suffix_arr) <= ( "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', " "'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', " @@ -1217,7 +1158,6 @@ async def foo(self): ) [case assert_stmt_4] -# flags: --unstable assert str(suffix_arr) >= ( "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', " "'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', " @@ -1232,7 +1172,6 @@ async def foo(self): ) [case assert_stmt_5] -# flags: --unstable assert str(suffix_arr) < ( "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', " "'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', " @@ -1247,7 +1186,6 @@ async def foo(self): ) [case assert_stmt_6] -# flags: --unstable assert str(suffix_arr) > ( "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', " "'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', " @@ -1262,7 +1200,6 @@ async def foo(self): ) [case assert_stmt_7] -# flags: --unstable assert str(suffix_arr) in "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', 'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', 'o$', 'oo$', 'roo$', 'rykangaroo$', 'ykangaroo$']" # output assert ( @@ -1273,7 +1210,6 @@ async def foo(self): ) [case assert_stmt_8] -# flags: --unstable assert str(suffix_arr) not in "['$', 'angaroo$', 'angrykangaroo$', 'aroo$', 'garoo$', 'grykangaroo$', 'kangaroo$', 'ngaroo$', 'ngrykangaroo$', 'o$', 'oo$', 'roo$', 'rykangaroo$', 'ykangaroo$']" # output assert ( @@ -1284,7 +1220,6 @@ async def foo(self): ) [case assign_message_1] -# flags: --unstable message = ( f"1. Go to Google Developers Console and log in with your Google account." "(https://console.developers.google.com/)" @@ -1314,7 +1249,6 @@ async def foo(self): ) [case assign_message_2] -# flags: --unstable message = ( f"1. Go to Google Developers Console and log in with your Google account." "(https://console.developers.google.com/)" @@ -1344,7 +1278,6 @@ async def foo(self): ) [case assign_message_3] -# flags: --unstable message = ( f"1. Go to Google Developers Console and log in with your Google account." "(https://console.developers.google.com/)" @@ -1374,7 +1307,6 @@ async def foo(self): ) [case it_shouldn_t_matter_if_the_string_prefixes_are_capitalized] -# flags: --unstable # It shouldn't matter if the string prefixes are capitalized. temp_msg = ( @@ -1392,7 +1324,6 @@ async def foo(self): ) [case fstring_3] -# flags: --unstable fstring = ( F"We have to remember to escape {braces}." @@ -1404,7 +1335,6 @@ async def foo(self): fstring = f"We have to remember to escape {braces}. Like {{these}}. But not {this}." [case welcome_to_programming] -# flags: --unstable welcome_to_programming = R"hello," R" world!" # output @@ -1412,7 +1342,6 @@ async def foo(self): welcome_to_programming = R"hello," R" world!" [case fstring_4] -# flags: --unstable fstring = F"f-strings definitely make things more {difficult} than they need to be for {{black}}. But boy they sure are handy. The problem is that some lines will need to have the 'f' whereas others do not. This {line}, for example, needs one." # output @@ -1424,7 +1353,6 @@ async def foo(self): ) [case assign_x_4] -# flags: --unstable x = F"This is a long string which contains an f-expr that should not split {{{[i for i in range(5)]}}}." # output @@ -1435,7 +1363,6 @@ async def foo(self): ) [case assign_x_5] -# flags: --unstable x = ( "\N{BLACK RIGHT-POINTING TRIANGLE WITH DOUBLE VERTICAL BAR}\N{VARIATION SELECTOR-16}" @@ -1447,7 +1374,6 @@ async def foo(self): ) [case obfuscated_long] -# flags: --unstable xxxxxx_xxx_xxxx_xx_xxxxx_xxxxxxxx_xxxxxxxx_xxxxxxxxxx_xxxx_xxxx_xxxxx = xxxx.xxxxxx.xxxxxxxxx.xxxxxxxxxxxxxxxxxxxx( xx_xxxxxx={ @@ -1465,7 +1391,6 @@ async def foo(self): ) [case issue_3117_regression] -# flags: --unstable # Regression test for https://github.com/psf/black/issues/3117. some_dict = { @@ -1484,7 +1409,6 @@ async def foo(self): } [case issue_3459_regression] -# flags: --unstable # Regression test for https://github.com/psf/black/issues/3459. xxxx( @@ -1516,7 +1440,6 @@ async def foo(self): ) [case issue_3455_regression] -# flags: --unstable # Regression test for https://github.com/psf/black/issues/3455. a_dict = { @@ -1534,7 +1457,6 @@ async def foo(self): } [case issue_3506_regression] -# flags: --unstable # Regression test for https://github.com/psf/black/issues/3506. # Regressed again by https://github.com/psf/black/pull/4498 @@ -1556,7 +1478,6 @@ async def foo(self): ) [case assign_s] -# flags: --unstable s = f'Lorem Ipsum is simply dummy text of the printing and typesetting industry:\'{my_dict["foo"]}\'' # output @@ -1567,7 +1488,6 @@ async def foo(self): ) [case issue_4510_regression] -# flags: --unstable # Regression test for https://github.com/psf/black/issues/4510. # Don't merge multi-line strings when a pragma comment follows. diff --git a/tests/test_data_loader.py b/tests/test_data_loader.py index 6ec2b2b3ce1..3f14c49715f 100644 --- a/tests/test_data_loader.py +++ b/tests/test_data_loader.py @@ -378,6 +378,161 @@ def test_failing_cell_message_includes_cell_header( assert "# output marker" in message +def test_file_level_flags_apply_to_all_cells(tmp_path: Path) -> None: + path = write( + tmp_path, + "fix.py", + """ + # flags: --preview --line-length=88 + + [case one] + x = 1 + # output + x = 1 + + [case two] + y = 2 + # output + y = 2 + """, + ) + cells = parse_cells(path.read_text(encoding="utf8"), path) + assert cells is not None and len(cells) == 2 + for cell in cells: + assert cell.args.mode.preview is True + assert cell.args.mode.line_length == 88 + + +def test_cell_flags_override_file_level_scalars(tmp_path: Path) -> None: + path = write( + tmp_path, + "fix.py", + """ + # flags: --line-length=88 + + [case default] + x = 1 + # output + x = 1 + + [case wider] + # flags: --line-length=120 + y = 2 + # output + y = 2 + """, + ) + cells = parse_cells(path.read_text(encoding="utf8"), path) + assert cells is not None + by_name = {c.name: c for c in cells} + assert by_name["default"].args.mode.line_length == 88 + assert by_name["wider"].args.mode.line_length == 120 + + +def test_cell_overrides_file_level_target_version(tmp_path: Path) -> None: + # `--target-version` uses `action="store"` in `get_flags_parser`, so the + # cell value wins outright rather than appending to file-level. Documented + # explicitly here so a future switch to `action="append"` shows up as a + # test break, not a silent semantic shift. + import black + + path = write( + tmp_path, + "fix.py", + """ + # flags: --target-version=PY310 + + [case bare] + x = 1 + # output + x = 1 + + [case override_to_311] + # flags: --target-version=PY311 + y = 2 + # output + y = 2 + """, + ) + cells = parse_cells(path.read_text(encoding="utf8"), path) + assert cells is not None + by_name = {c.name: c for c in cells} + assert by_name["bare"].args.mode.target_versions == {black.TargetVersion.PY310} + assert by_name["override_to_311"].args.mode.target_versions == { + black.TargetVersion.PY311 + } + + +def test_file_level_line_ranges_rejected(tmp_path: Path) -> None: + # Line numbers in `--line-ranges` are absolute within each cell's own + # input, so a file-level range can never share meaning across cells. + # Reject at load time rather than silently apply it to every cell. + path = write( + tmp_path, + "fix.py", + """ + # flags: --line-ranges=1-1 + + [case combines] + # flags: --line-ranges=2-3 + a = 1 + b = 2 + c = 3 + """, + ) + with pytest.raises( + ValueError, match="`--line-ranges` is not allowed at the file level" + ): + parse_cells(path.read_text(encoding="utf8"), path) + + +def test_duplicate_file_level_flags_raises(tmp_path: Path) -> None: + path = write( + tmp_path, + "fix.py", + """ + # flags: --preview + # flags: --pyi + + [case x] + x = 1 + """, + ) + with pytest.raises(ValueError, match="multiple top-level `# flags: ` lines"): + parse_cells(path.read_text(encoding="utf8"), path) + + +def test_malformed_file_level_flags_raises(tmp_path: Path) -> None: + path = write( + tmp_path, + "fix.py", + """ + # flags: --not-a-real-flag + + [case x] + x = 1 + """, + ) + with pytest.raises(ValueError, match="malformed top-level"): + parse_cells(path.read_text(encoding="utf8"), path) + + +def test_file_level_flags_line_after_cell_header_is_ignored(tmp_path: Path) -> None: + # `# flags: ` after the first `[case ]` belongs to that cell, not the file. + path = write( + tmp_path, + "fix.py", + """ + [case alpha] + # flags: --pyi + x: int + """, + ) + cells = parse_cells(path.read_text(encoding="utf8"), path) + assert cells is not None and len(cells) == 1 + assert cells[0].args.mode.is_pyi is True + + def test_per_cell_lookup_returns_isolated_input_output( patch_data_dir: Path, ) -> None: diff --git a/tests/util.py b/tests/util.py index 3beb193159e..8bed6ca6e30 100644 --- a/tests/util.py +++ b/tests/util.py @@ -280,30 +280,35 @@ def parse_mode(flags_line: str) -> TestCaseArgs: def _parse_cell_body( lines: list[str], + file_flags: str = "", ) -> tuple[TestCaseArgs, str, str, int | None]: """Parse one cell (or whole-file) body. Returns mode, input, output, and the 0-based index into ``lines`` of the ``# output`` marker (or None for - idempotency cells).""" + idempotency cells). + + ``file_flags`` carries an optional multi-cell file-level ``# flags: `` + string (already stripped of the ``# flags: `` prefix and trailing + newline). It is concatenated *before* the cell's own ``# flags: `` line, + so scalar/store flags follow later-wins (cell beats file). ``--line-ranges`` + is rejected at the file level by ``_extract_file_flags`` and so only ever + appears here from a cell. + """ _input: list[str] = [] _output: list[str] = [] result = _input - mode = TestCaseArgs() output_offset: int | None = None + cell_flags: str | None = None + cell_flag_line: str | None = None for idx, line in enumerate(lines): - if not _input: + if not _input and cell_flags is None: if not line.strip(): # Tolerate leading blank lines before the optional `# flags: ` # line (or before the first content line, when no flags are # set). Matches the doc's "first non-blank line" promise. continue if line.startswith("# flags: "): - mode = parse_mode(line[len("# flags: ") :]) - if mode.lines: - # Retain the `# flags: ` line when using --line-ranges=. This requires - # the `# output` section to also include this line, but retaining the - # line is important to make the line ranges match what you see in the - # test file. - result.append(line) + cell_flags = line[len("# flags: ") :].rstrip("\n") + cell_flag_line = line continue line = line.replace(EMPTY_LINE, "") if line.rstrip() == "# output": @@ -312,6 +317,19 @@ def _parse_cell_body( continue result.append(line) + + if cell_flags is not None or file_flags: + combined = f"{file_flags} {cell_flags or ''}".strip() + mode = parse_mode(combined) + else: + mode = TestCaseArgs() + + if cell_flag_line is not None and mode.lines: + # Retain the cell's `# flags: ` line when --line-ranges is in effect so + # the input's line numbers match the ranges. Output must include the + # same line; that's a fixture-author responsibility. + _input.insert(0, cell_flag_line) + if _input and not _output: # If there's no output marker, treat the entire file as already pre-formatted. _output = _input[:] @@ -331,6 +349,7 @@ def read_data_from_file(file_name: Path) -> tuple[TestCaseArgs, str, str]: CELL_HEADER_RE = re.compile(r"^\[case ([A-Za-z_][A-Za-z0-9_]*)\]$") +FILE_FLAGS_RE = re.compile(r"^# flags: (.*)$") @dataclass(frozen=True) @@ -359,8 +378,11 @@ def _build_cell( header_lineno: int, block_start: int, body_lines: list[str], + file_flags: str = "", ) -> Cell: - args, input_text, output_text, output_offset = _parse_cell_body(body_lines) + args, input_text, output_text, output_offset = _parse_cell_body( + body_lines, file_flags=file_flags + ) output_lineno = block_start + output_offset if output_offset is not None else None return Cell( name=name, @@ -372,6 +394,50 @@ def _build_cell( ) +def _extract_file_flags(lines: list[str], path: Path) -> str: + """Pull a top-level ``# flags: ...`` line from the prose region before the + first ``[case ]`` header. At most one such line is allowed; multiples + raise. Returns the flag string (without the ``# flags: `` prefix), or an + empty string if none is present. + """ + found: str | None = None + found_lineno: int = 0 + for idx, line in enumerate(lines, start=1): + if CELL_HEADER_RE.match(line.rstrip()): + break + match = FILE_FLAGS_RE.match(line.rstrip("\n")) + if not match: + continue + if found is not None: + raise ValueError( + f"{path}: multiple top-level `# flags: ` lines" + f" (first at line {found_lineno}, again at line {idx});" + " only one is allowed" + ) + found = match.group(1) + found_lineno = idx + if found is None: + return "" + # Parse-validate at file load time so a malformed top-level surfaces with + # the file path attached, not buried in a per-cell argparse trace. + try: + parsed = parse_mode(found) + except SystemExit as exc: + raise ValueError( + f"{path}: malformed top-level `# flags: ` on line {found_lineno}:" + f" {found!r}" + ) from exc + if parsed.lines: + raise ValueError( + f"{path}: `--line-ranges` is not allowed at the file level" + f" (line {found_lineno}). Line numbers are per-cell, so a" + " file-level range would apply to every cell against unrelated" + " content. Set `--line-ranges` inside the relevant `[case ]`" + " instead." + ) + return found + + def parse_cells(text: str, path: Path) -> list[Cell] | None: """Parse a multi-cell fixture file. @@ -384,6 +450,8 @@ def parse_cells(text: str, path: Path) -> list[Cell] | None: if not is_multi_cell(lines): return None + file_flags = _extract_file_flags(lines, path) + cells: list[Cell] = [] current_name: str | None = None current_header_lineno: int = 0 @@ -400,6 +468,7 @@ def parse_cells(text: str, path: Path) -> list[Cell] | None: current_header_lineno, current_block_start, current_lines, + file_flags=file_flags, ) ) current_name = match.group(1) @@ -417,6 +486,7 @@ def parse_cells(text: str, path: Path) -> list[Cell] | None: current_header_lineno, current_block_start, current_lines, + file_flags=file_flags, ) ) From a38fd6a1dadb9a364332c21ea5d9c04e20b77d06 Mon Sep 17 00:00:00 2001 From: Pedro Mezacasa Muller Date: Sat, 23 May 2026 17:39:05 -0300 Subject: [PATCH 7/9] Update PR number in changelog --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index da674f1a762..9782758e54e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -62,7 +62,7 @@ `[case ]` headers, inspired by mypy's test data format. Files without a header continue to work unchanged. Failing cells report their file path, cell header line, and `# output` marker line. Migrates `preview_long_strings.py` and - `preview_long_strings__regression.py` as the first consumers (#5147) + `preview_long_strings__regression.py` as the first consumers (#5149) ## Version 26.5.1 From bd062f32640f46a152c460e5efe1d8de395fa92a Mon Sep 17 00:00:00 2001 From: Pedro Mezacasa Muller Date: Sat, 23 May 2026 18:09:13 -0300 Subject: [PATCH 8/9] Apply self-format --- tests/test_data_loader.py | 4 +--- tests/util.py | 7 ++----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/tests/test_data_loader.py b/tests/test_data_loader.py index 3f14c49715f..21e9e7675d6 100644 --- a/tests/test_data_loader.py +++ b/tests/test_data_loader.py @@ -36,9 +36,7 @@ def _clear_parse_cache() -> Iterator[None]: @pytest.fixture -def patch_data_dir( - tmp_path: Path, monkeypatch: pytest.MonkeyPatch -) -> Path: +def patch_data_dir(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path: """Point `util.DATA_DIR` at a fresh temp dir for `read_data_with_mode` callers, with automatic restoration. Also pre-creates the subdirs that test_format.py's module-load parametrize decorators expect, so that diff --git a/tests/util.py b/tests/util.py index 8bed6ca6e30..5ca593bbcf3 100644 --- a/tests/util.py +++ b/tests/util.py @@ -424,8 +424,7 @@ def _extract_file_flags(lines: list[str], path: Path) -> str: parsed = parse_mode(found) except SystemExit as exc: raise ValueError( - f"{path}: malformed top-level `# flags: ` on line {found_lineno}:" - f" {found!r}" + f"{path}: malformed top-level `# flags: ` on line {found_lineno}: {found!r}" ) from exc if parsed.lines: raise ValueError( @@ -622,9 +621,7 @@ def read_data_with_mode( return read_data_from_file(path) -def read_data( - subdir_name: str, name: CaseId, data: bool = True -) -> tuple[str, str]: +def read_data(subdir_name: str, name: CaseId, data: bool = True) -> tuple[str, str]: """read_data('test_name') -> 'input', 'output'""" _, input, output = read_data_with_mode(subdir_name, name, data) return input, output From 71af7a3cf4f343a30f371956c32e80ca0e6b636a Mon Sep 17 00:00:00 2001 From: Pedro Mezacasa Muller <114496585+Pedro-Muller29@users.noreply.github.com> Date: Sun, 5 Jul 2026 21:40:22 -0300 Subject: [PATCH 9/9] Update CHANGES.md Co-authored-by: GiGaGon <107241144+MeGaGiGaGon@users.noreply.github.com> --- CHANGES.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 9782758e54e..a7d912cd466 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -58,12 +58,6 @@ -- Allow grouping multiple cases in one fixture file under `tests/data/cases/` using - `[case ]` headers, inspired by mypy's test data format. Files without a header - continue to work unchanged. Failing cells report their file path, cell header line, - and `# output` marker line. Migrates `preview_long_strings.py` and - `preview_long_strings__regression.py` as the first consumers (#5149) - ## Version 26.5.1 ### Stable style