• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

psf / black / 7692220850

29 Jan 2024 06:46AM UTC coverage: 96.45%. Remained the same
7692220850

Pull #4192

github

web-flow
Bump peter-evans/create-or-update-comment from 3.1.0 to 4.0.0

Bumps [peter-evans/create-or-update-comment](https://github.com/peter-evans/create-or-update-comment) from 3.1.0 to 4.0.0.
- [Release notes](https://github.com/peter-evans/create-or-update-comment/releases)
- [Commits](https://github.com/peter-evans/create-or-update-comment/compare/23ff15729...71345be02)

---
updated-dependencies:
- dependency-name: peter-evans/create-or-update-comment
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Pull Request #4192: Bump peter-evans/create-or-update-comment from 3.1.0 to 4.0.0

3021 of 3232 branches covered (0.0%)

7145 of 7408 relevant lines covered (96.45%)

4.82 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.71
/tests/util.py
1
import argparse
5✔
2
import functools
5✔
3
import os
5✔
4
import shlex
5✔
5
import sys
5✔
6
import unittest
5✔
7
from contextlib import contextmanager
5✔
8
from dataclasses import dataclass, field, replace
5✔
9
from functools import partial
5✔
10
from pathlib import Path
5✔
11
from typing import Any, Collection, Iterator, List, Optional, Tuple
5✔
12

13
import black
5✔
14
from black.const import DEFAULT_LINE_LENGTH
5✔
15
from black.debug import DebugVisitor
5✔
16
from black.mode import TargetVersion
5✔
17
from black.output import diff, err, out
5✔
18
from black.ranges import parse_line_ranges
5✔
19

20
from . import conftest
5✔
21

22
PYTHON_SUFFIX = ".py"
5✔
23
ALLOWED_SUFFIXES = (PYTHON_SUFFIX, ".pyi", ".out", ".diff", ".ipynb")
5✔
24

25
THIS_DIR = Path(__file__).parent
5✔
26
DATA_DIR = THIS_DIR / "data"
5✔
27
PROJECT_ROOT = THIS_DIR.parent
5✔
28
EMPTY_LINE = "# EMPTY LINE WITH WHITESPACE" + " (this comment will be removed)"
5✔
29
DETERMINISTIC_HEADER = "[Deterministic header]"
5✔
30

31
PY36_VERSIONS = {
5✔
32
    TargetVersion.PY36,
33
    TargetVersion.PY37,
34
    TargetVersion.PY38,
35
    TargetVersion.PY39,
36
}
37

38
DEFAULT_MODE = black.Mode()
5✔
39
ff = partial(black.format_file_in_place, mode=DEFAULT_MODE, fast=True)
5✔
40
fs = partial(black.format_str, mode=DEFAULT_MODE)
5✔
41

42

43
@dataclass
5✔
44
class TestCaseArgs:
5✔
45
    mode: black.Mode = field(default_factory=black.Mode)
5✔
46
    fast: bool = False
5✔
47
    minimum_version: Optional[Tuple[int, int]] = None
5✔
48
    lines: Collection[Tuple[int, int]] = ()
5✔
49
    no_preview_line_length_1: bool = False
5✔
50

51

52
def _assert_format_equal(expected: str, actual: str) -> None:
5✔
53
    if actual != expected and (conftest.PRINT_FULL_TREE or conftest.PRINT_TREE_DIFF):
5✔
54
        bdv: DebugVisitor[Any]
55
        actual_out: str = ""
5✔
56
        expected_out: str = ""
5✔
57
        if conftest.PRINT_FULL_TREE:
5✔
58
            out("Expected tree:", fg="green")
5✔
59
        try:
5✔
60
            exp_node = black.lib2to3_parse(expected)
5✔
61
            bdv = DebugVisitor(print_output=conftest.PRINT_FULL_TREE)
5✔
62
            list(bdv.visit(exp_node))
5✔
63
            expected_out = "\n".join(bdv.list_output)
5✔
64
        except Exception as ve:
×
65
            err(str(ve))
×
66
        if conftest.PRINT_FULL_TREE:
5✔
67
            out("Actual tree:", fg="red")
5✔
68
        try:
5✔
69
            exp_node = black.lib2to3_parse(actual)
5✔
70
            bdv = DebugVisitor(print_output=conftest.PRINT_FULL_TREE)
5✔
71
            list(bdv.visit(exp_node))
5✔
72
            actual_out = "\n".join(bdv.list_output)
5✔
73
        except Exception as ve:
×
74
            err(str(ve))
×
75
        if conftest.PRINT_TREE_DIFF:
5✔
76
            out("Tree Diff:")
5✔
77
            out(
5✔
78
                diff(expected_out, actual_out, "expected tree", "actual tree")
79
                or "Trees do not differ"
80
            )
81

82
    if actual != expected:
5✔
83
        out(diff(expected, actual, "expected", "actual"))
5✔
84

85
    assert actual == expected
5✔
86

87

88
class FormatFailure(Exception):
5✔
89
    """Used to wrap failures when assert_format() runs in an extra mode."""
90

91

92
def assert_format(
5✔
93
    source: str,
94
    expected: str,
95
    mode: black.Mode = DEFAULT_MODE,
96
    *,
97
    fast: bool = False,
98
    minimum_version: Optional[Tuple[int, int]] = None,
99
    lines: Collection[Tuple[int, int]] = (),
100
    no_preview_line_length_1: bool = False,
101
) -> None:
102
    """Convenience function to check that Black formats as expected.
103

104
    You can pass @minimum_version if you're passing code with newer syntax to guard
105
    safety guards so they don't just crash with a SyntaxError. Please note this is
106
    separate from TargetVerson Mode configuration.
107
    """
108
    _assert_format_inner(
5✔
109
        source, expected, mode, fast=fast, minimum_version=minimum_version, lines=lines
110
    )
111

112
    # For both preview and non-preview tests, ensure that Black doesn't crash on
113
    # this code, but don't pass "expected" because the precise output may differ.
114
    try:
5✔
115
        if mode.unstable:
5✔
116
            new_mode = replace(mode, unstable=False, preview=False)
5✔
117
        else:
118
            new_mode = replace(mode, preview=not mode.preview)
5✔
119
        _assert_format_inner(
5✔
120
            source,
121
            None,
122
            new_mode,
123
            fast=fast,
124
            minimum_version=minimum_version,
125
            lines=lines,
126
        )
127
    except Exception as e:
×
128
        text = (
×
129
            "unstable"
130
            if mode.unstable
131
            else "non-preview" if mode.preview else "preview"
132
        )
133
        raise FormatFailure(
×
134
            f"Black crashed formatting this case in {text} mode."
135
        ) from e
136
    # Similarly, setting line length to 1 is a good way to catch
137
    # stability bugs. Some tests are known to be broken in preview mode with line length
138
    # of 1 though, and have marked that with a flag --no-preview-line-length-1
139
    preview_modes = [False]
5✔
140
    if not no_preview_line_length_1:
5✔
141
        preview_modes.append(True)
5✔
142

143
    for preview_mode in preview_modes:
5✔
144

145
        try:
5✔
146
            _assert_format_inner(
5✔
147
                source,
148
                None,
149
                replace(mode, preview=preview_mode, line_length=1, unstable=False),
150
                fast=fast,
151
                minimum_version=minimum_version,
152
                lines=lines,
153
            )
154
        except Exception as e:
×
155
            text = "preview" if preview_mode else "non-preview"
×
156
            raise FormatFailure(
×
157
                f"Black crashed formatting this case in {text} mode with line-length=1."
158
            ) from e
159

160

161
def _assert_format_inner(
5✔
162
    source: str,
163
    expected: Optional[str] = None,
164
    mode: black.Mode = DEFAULT_MODE,
165
    *,
166
    fast: bool = False,
167
    minimum_version: Optional[Tuple[int, int]] = None,
168
    lines: Collection[Tuple[int, int]] = (),
169
) -> None:
170
    actual = black.format_str(source, mode=mode, lines=lines)
5✔
171
    if expected is not None:
5✔
172
        _assert_format_equal(expected, actual)
5✔
173
    # It's not useful to run safety checks if we're expecting no changes anyway. The
174
    # assertion right above will raise if reality does actually make changes. This just
175
    # avoids wasted CPU cycles.
176
    if not fast and source != actual:
5✔
177
        # Unfortunately the AST equivalence check relies on the built-in ast module
178
        # being able to parse the code being formatted. This doesn't always work out
179
        # when checking modern code on older versions.
180
        if minimum_version is None or sys.version_info >= minimum_version:
5✔
181
            black.assert_equivalent(source, actual)
5✔
182
        black.assert_stable(source, actual, mode=mode, lines=lines)
5✔
183

184

185
def dump_to_stderr(*output: str) -> str:
5✔
186
    return "\n" + "\n".join(output) + "\n"
×
187

188

189
class BlackBaseTestCase(unittest.TestCase):
5✔
190
    def assertFormatEqual(self, expected: str, actual: str) -> None:
5✔
191
        _assert_format_equal(expected, actual)
5✔
192

193

194
def get_base_dir(data: bool) -> Path:
5✔
195
    return DATA_DIR if data else PROJECT_ROOT
5✔
196

197

198
def all_data_cases(subdir_name: str, data: bool = True) -> List[str]:
5✔
199
    cases_dir = get_base_dir(data) / subdir_name
5✔
200
    assert cases_dir.is_dir()
5✔
201
    return [case_path.stem for case_path in cases_dir.iterdir()]
5✔
202

203

204
def get_case_path(
5✔
205
    subdir_name: str, name: str, data: bool = True, suffix: str = PYTHON_SUFFIX
206
) -> Path:
207
    """Get case path from name"""
208
    case_path = get_base_dir(data) / subdir_name / name
5✔
209
    if not name.endswith(ALLOWED_SUFFIXES):
5✔
210
        case_path = case_path.with_suffix(suffix)
5✔
211
    assert case_path.is_file(), f"{case_path} is not a file."
5✔
212
    return case_path
5✔
213

214

215
def read_data_with_mode(
5✔
216
    subdir_name: str, name: str, data: bool = True
217
) -> Tuple[TestCaseArgs, str, str]:
218
    """read_data_with_mode('test_name') -> Mode(), 'input', 'output'"""
219
    return read_data_from_file(get_case_path(subdir_name, name, data))
5✔
220

221

222
def read_data(subdir_name: str, name: str, data: bool = True) -> Tuple[str, str]:
5✔
223
    """read_data('test_name') -> 'input', 'output'"""
224
    _, input, output = read_data_with_mode(subdir_name, name, data)
5✔
225
    return input, output
5✔
226

227

228
def _parse_minimum_version(version: str) -> Tuple[int, int]:
5✔
229
    major, minor = version.split(".")
5✔
230
    return int(major), int(minor)
5✔
231

232

233
@functools.lru_cache()
5✔
234
def get_flags_parser() -> argparse.ArgumentParser:
5✔
235
    parser = argparse.ArgumentParser()
5✔
236
    parser.add_argument(
5!
237
        "--target-version",
238
        action="append",
239
        type=lambda val: TargetVersion[val.upper()],
240
        default=(),
241
    )
242
    parser.add_argument("--line-length", default=DEFAULT_LINE_LENGTH, type=int)
5✔
243
    parser.add_argument(
5✔
244
        "--skip-string-normalization", default=False, action="store_true"
245
    )
246
    parser.add_argument("--pyi", default=False, action="store_true")
5✔
247
    parser.add_argument("--ipynb", default=False, action="store_true")
5✔
248
    parser.add_argument(
5✔
249
        "--skip-magic-trailing-comma", default=False, action="store_true"
250
    )
251
    parser.add_argument("--preview", default=False, action="store_true")
5✔
252
    parser.add_argument("--unstable", default=False, action="store_true")
5✔
253
    parser.add_argument("--fast", default=False, action="store_true")
5✔
254
    parser.add_argument(
5✔
255
        "--minimum-version",
256
        type=_parse_minimum_version,
257
        default=None,
258
        help=(
259
            "Minimum version of Python where this test case is parseable. If this is"
260
            " set, the test case will be run twice: once with the specified"
261
            " --target-version, and once with --target-version set to exactly the"
262
            " specified version. This ensures that Black's autodetection of the target"
263
            " version works correctly."
264
        ),
265
    )
266
    parser.add_argument("--line-ranges", action="append")
5✔
267
    parser.add_argument(
5✔
268
        "--no-preview-line-length-1",
269
        default=False,
270
        action="store_true",
271
        help=(
272
            "Don't run in preview mode with --line-length=1, as that's known to cause a"
273
            " crash"
274
        ),
275
    )
276
    return parser
5✔
277

278

279
def parse_mode(flags_line: str) -> TestCaseArgs:
5✔
280
    parser = get_flags_parser()
5✔
281
    args = parser.parse_args(shlex.split(flags_line))
5✔
282
    mode = black.Mode(
5✔
283
        target_versions=set(args.target_version),
284
        line_length=args.line_length,
285
        string_normalization=not args.skip_string_normalization,
286
        is_pyi=args.pyi,
287
        is_ipynb=args.ipynb,
288
        magic_trailing_comma=not args.skip_magic_trailing_comma,
289
        preview=args.preview,
290
        unstable=args.unstable,
291
    )
292
    if args.line_ranges:
5✔
293
        lines = parse_line_ranges(args.line_ranges)
5✔
294
    else:
295
        lines = []
5✔
296
    return TestCaseArgs(
5✔
297
        mode=mode,
298
        fast=args.fast,
299
        minimum_version=args.minimum_version,
300
        lines=lines,
301
        no_preview_line_length_1=args.no_preview_line_length_1,
302
    )
303

304

305
def read_data_from_file(file_name: Path) -> Tuple[TestCaseArgs, str, str]:
5✔
306
    with open(file_name, "r", encoding="utf8") as test:
5✔
307
        lines = test.readlines()
5✔
308
    _input: List[str] = []
5✔
309
    _output: List[str] = []
5✔
310
    result = _input
5✔
311
    mode = TestCaseArgs()
5✔
312
    for line in lines:
5✔
313
        if not _input and line.startswith("# flags: "):
5✔
314
            mode = parse_mode(line[len("# flags: ") :])
5✔
315
            if mode.lines:
5✔
316
                # Retain the `# flags: ` line when using --line-ranges=. This requires
317
                # the `# output` section to also include this line, but retaining the
318
                # line is important to make the line ranges match what you see in the
319
                # test file.
320
                result.append(line)
5✔
321
            continue
5✔
322
        line = line.replace(EMPTY_LINE, "")
5✔
323
        if line.rstrip() == "# output":
5✔
324
            result = _output
5✔
325
            continue
5✔
326

327
        result.append(line)
5✔
328
    if _input and not _output:
5✔
329
        # If there's no output marker, treat the entire file as already pre-formatted.
330
        _output = _input[:]
5✔
331
    return mode, "".join(_input).strip() + "\n", "".join(_output).strip() + "\n"
5✔
332

333

334
def read_jupyter_notebook(subdir_name: str, name: str, data: bool = True) -> str:
5✔
335
    return read_jupyter_notebook_from_file(
5✔
336
        get_case_path(subdir_name, name, data, suffix=".ipynb")
337
    )
338

339

340
def read_jupyter_notebook_from_file(file_name: Path) -> str:
5✔
341
    with open(file_name, mode="rb") as fd:
5✔
342
        content_bytes = fd.read()
5✔
343
    return content_bytes.decode()
5✔
344

345

346
@contextmanager
5✔
347
def change_directory(path: Path) -> Iterator[None]:
5✔
348
    """Context manager to temporarily chdir to a different directory."""
349
    previous_dir = os.getcwd()
5✔
350
    try:
5✔
351
        os.chdir(path)
5✔
352
        yield
5✔
353
    finally:
354
        os.chdir(previous_dir)
5✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc