All patches and comments are welcome. Please squash your changes to logical
commits before using git-format-patch and git-send-email to
patches@git.madduck.net.
If you'd read over the Git project's submission guidelines and adhered to them,
I'd be especially grateful.
4 from contextlib import contextmanager
5 from functools import partial
6 from pathlib import Path
7 from typing import Any, Iterator, List, Optional, Tuple
10 from black.debug import DebugVisitor
11 from black.mode import TargetVersion
12 from black.output import diff, err, out
14 THIS_DIR = Path(__file__).parent
15 DATA_DIR = THIS_DIR / "data"
16 PROJECT_ROOT = THIS_DIR.parent
17 EMPTY_LINE = "# EMPTY LINE WITH WHITESPACE" + " (this comment will be removed)"
18 DETERMINISTIC_HEADER = "[Deterministic header]"
27 DEFAULT_MODE = black.Mode()
28 ff = partial(black.format_file_in_place, mode=DEFAULT_MODE, fast=True)
29 fs = partial(black.format_str, mode=DEFAULT_MODE)
32 def _assert_format_equal(expected: str, actual: str) -> None:
33 if actual != expected and not os.environ.get("SKIP_AST_PRINT"):
34 bdv: DebugVisitor[Any]
35 out("Expected tree:", fg="green")
37 exp_node = black.lib2to3_parse(expected)
39 list(bdv.visit(exp_node))
40 except Exception as ve:
42 out("Actual tree:", fg="red")
44 exp_node = black.lib2to3_parse(actual)
46 list(bdv.visit(exp_node))
47 except Exception as ve:
50 if actual != expected:
51 out(diff(expected, actual, "expected", "actual"))
53 assert actual == expected
59 mode: black.Mode = DEFAULT_MODE,
62 minimum_version: Optional[Tuple[int, int]] = None,
64 """Convenience function to check that Black formats as expected.
66 You can pass @minimum_version if you're passing code with newer syntax to guard
67 safety guards so they don't just crash with a SyntaxError. Please note this is
68 separate from TargetVerson Mode configuration.
70 actual = black.format_str(source, mode=mode)
71 _assert_format_equal(expected, actual)
72 # It's not useful to run safety checks if we're expecting no changes anyway. The
73 # assertion right above will raise if reality does actually make changes. This just
74 # avoids wasted CPU cycles.
75 if not fast and source != expected:
76 # Unfortunately the AST equivalence check relies on the built-in ast module
77 # being able to parse the code being formatted. This doesn't always work out
78 # when checking modern code on older versions.
79 if minimum_version is None or sys.version_info >= minimum_version:
80 black.assert_equivalent(source, actual)
81 black.assert_stable(source, actual, mode=mode)
84 def dump_to_stderr(*output: str) -> str:
85 return "\n" + "\n".join(output) + "\n"
88 class BlackBaseTestCase(unittest.TestCase):
89 def assertFormatEqual(self, expected: str, actual: str) -> None:
90 _assert_format_equal(expected, actual)
93 def all_data_cases(dir_name: str, data: bool = True) -> List[str]:
94 base_dir = DATA_DIR if data else PROJECT_ROOT
95 cases_dir = base_dir / dir_name
96 assert cases_dir.is_dir()
97 return [f"{dir_name}/{case_path.stem}" for case_path in cases_dir.iterdir()]
100 def read_data(name: str, data: bool = True) -> Tuple[str, str]:
101 """read_data('test_name') -> 'input', 'output'"""
102 if not name.endswith((".py", ".pyi", ".out", ".diff")):
104 base_dir = DATA_DIR if data else PROJECT_ROOT
105 case_path = base_dir / name
106 assert case_path.is_file(), f"{case_path} is not a file."
107 return read_data_from_file(case_path)
110 def read_data_from_file(file_name: Path) -> Tuple[str, str]:
111 with open(file_name, "r", encoding="utf8") as test:
112 lines = test.readlines()
113 _input: List[str] = []
114 _output: List[str] = []
117 line = line.replace(EMPTY_LINE, "")
118 if line.rstrip() == "# output":
123 if _input and not _output:
124 # If there's no output marker, treat the entire file as already pre-formatted.
126 return "".join(_input).strip() + "\n", "".join(_output).strip() + "\n"
130 def change_directory(path: Path) -> Iterator[None]:
131 """Context manager to temporarily chdir to a different directory."""
132 previous_dir = os.getcwd()
137 os.chdir(previous_dir)