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.
3 from concurrent.futures import ThreadPoolExecutor
4 from contextlib import contextmanager
5 from functools import partial
6 from io import BytesIO, TextIOWrapper
8 from pathlib import Path
11 from tempfile import TemporaryDirectory
12 from typing import Any, BinaryIO, Generator, List, Tuple, Iterator
14 from unittest.mock import patch, MagicMock
16 from click import unstyle
17 from click.testing import CliRunner
23 ff = partial(black.format_file_in_place, line_length=ll, fast=True)
24 fs = partial(black.format_str, line_length=ll)
25 THIS_FILE = Path(__file__)
26 THIS_DIR = THIS_FILE.parent
27 EMPTY_LINE = "# EMPTY LINE WITH WHITESPACE" + " (this comment will be removed)"
30 def dump_to_stderr(*output: str) -> str:
31 return "\n" + "\n".join(output) + "\n"
34 def read_data(name: str, data: bool = True) -> Tuple[str, str]:
35 """read_data('test_name') -> 'input', 'output'"""
36 if not name.endswith((".py", ".pyi", ".out", ".diff")):
38 _input: List[str] = []
39 _output: List[str] = []
40 base_dir = THIS_DIR / "data" if data else THIS_DIR
41 with open(base_dir / name, "r", encoding="utf8") as test:
42 lines = test.readlines()
45 line = line.replace(EMPTY_LINE, "")
46 if line.rstrip() == "# output":
51 if _input and not _output:
52 # If there's no output marker, treat the entire file as already pre-formatted.
54 return "".join(_input).strip() + "\n", "".join(_output).strip() + "\n"
58 def cache_dir(exists: bool = True) -> Iterator[Path]:
59 with TemporaryDirectory() as workspace:
60 cache_dir = Path(workspace)
62 cache_dir = cache_dir / "new"
63 with patch("black.CACHE_DIR", cache_dir):
68 def event_loop(close: bool) -> Iterator[None]:
69 policy = asyncio.get_event_loop_policy()
70 old_loop = policy.get_event_loop()
71 loop = policy.new_event_loop()
72 asyncio.set_event_loop(loop)
77 policy.set_event_loop(old_loop)
82 class BlackRunner(CliRunner):
83 """Modify CliRunner so that stderr is not merged with stdout.
85 This is a hack that can be removed once we depend on Click 7.x"""
87 def __init__(self, stderrbuf: BinaryIO) -> None:
88 self.stderrbuf = stderrbuf
92 def isolation(self, *args: Any, **kwargs: Any) -> Generator[BinaryIO, None, None]:
93 with super().isolation(*args, **kwargs) as output:
95 hold_stderr = sys.stderr
96 sys.stderr = TextIOWrapper(self.stderrbuf, encoding=self.charset)
99 sys.stderr = hold_stderr
102 class BlackTestCase(unittest.TestCase):
105 def assertFormatEqual(self, expected: str, actual: str) -> None:
106 if actual != expected and not os.environ.get("SKIP_AST_PRINT"):
107 bdv: black.DebugVisitor[Any]
108 black.out("Expected tree:", fg="green")
110 exp_node = black.lib2to3_parse(expected)
111 bdv = black.DebugVisitor()
112 list(bdv.visit(exp_node))
113 except Exception as ve:
115 black.out("Actual tree:", fg="red")
117 exp_node = black.lib2to3_parse(actual)
118 bdv = black.DebugVisitor()
119 list(bdv.visit(exp_node))
120 except Exception as ve:
122 self.assertEqual(expected, actual)
124 @patch("black.dump_to_file", dump_to_stderr)
125 def test_empty(self) -> None:
126 source = expected = ""
128 self.assertFormatEqual(expected, actual)
129 black.assert_equivalent(source, actual)
130 black.assert_stable(source, actual, line_length=ll)
132 def test_empty_ff(self) -> None:
134 tmp_file = Path(black.dump_to_file())
136 self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES))
137 with open(tmp_file, encoding="utf8") as f:
141 self.assertFormatEqual(expected, actual)
143 @patch("black.dump_to_file", dump_to_stderr)
144 def test_self(self) -> None:
145 source, expected = read_data("test_black", data=False)
147 self.assertFormatEqual(expected, actual)
148 black.assert_equivalent(source, actual)
149 black.assert_stable(source, actual, line_length=ll)
150 self.assertFalse(ff(THIS_FILE))
152 @patch("black.dump_to_file", dump_to_stderr)
153 def test_black(self) -> None:
154 source, expected = read_data("../black", data=False)
156 self.assertFormatEqual(expected, actual)
157 black.assert_equivalent(source, actual)
158 black.assert_stable(source, actual, line_length=ll)
159 self.assertFalse(ff(THIS_DIR / ".." / "black.py"))
161 def test_piping(self) -> None:
162 source, expected = read_data("../black", data=False)
163 stderrbuf = BytesIO()
164 result = BlackRunner(stderrbuf).invoke(
166 ["-", "--fast", f"--line-length={ll}"],
167 input=BytesIO(source.encode("utf8")),
169 self.assertEqual(result.exit_code, 0)
170 self.assertFormatEqual(expected, result.output)
171 black.assert_equivalent(source, result.output)
172 black.assert_stable(source, result.output, line_length=ll)
174 def test_piping_diff(self) -> None:
175 diff_header = re.compile(
176 rf"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d "
177 rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
179 source, _ = read_data("expression.py")
180 expected, _ = read_data("expression.diff")
181 config = THIS_DIR / "data" / "empty_pyproject.toml"
182 stderrbuf = BytesIO()
183 args = ["-", "--fast", f"--line-length={ll}", "--diff", f"--config={config}"]
184 result = BlackRunner(stderrbuf).invoke(
185 black.main, args, input=BytesIO(source.encode("utf8"))
187 self.assertEqual(result.exit_code, 0)
188 actual = diff_header.sub("[Deterministic header]", result.output)
189 actual = actual.rstrip() + "\n" # the diff output has a trailing space
190 self.assertEqual(expected, actual)
192 @patch("black.dump_to_file", dump_to_stderr)
193 def test_setup(self) -> None:
194 source, expected = read_data("../setup", data=False)
196 self.assertFormatEqual(expected, actual)
197 black.assert_equivalent(source, actual)
198 black.assert_stable(source, actual, line_length=ll)
199 self.assertFalse(ff(THIS_DIR / ".." / "setup.py"))
201 @patch("black.dump_to_file", dump_to_stderr)
202 def test_function(self) -> None:
203 source, expected = read_data("function")
205 self.assertFormatEqual(expected, actual)
206 black.assert_equivalent(source, actual)
207 black.assert_stable(source, actual, line_length=ll)
209 @patch("black.dump_to_file", dump_to_stderr)
210 def test_function2(self) -> None:
211 source, expected = read_data("function2")
213 self.assertFormatEqual(expected, actual)
214 black.assert_equivalent(source, actual)
215 black.assert_stable(source, actual, line_length=ll)
217 @patch("black.dump_to_file", dump_to_stderr)
218 def test_expression(self) -> None:
219 source, expected = read_data("expression")
221 self.assertFormatEqual(expected, actual)
222 black.assert_equivalent(source, actual)
223 black.assert_stable(source, actual, line_length=ll)
225 def test_expression_ff(self) -> None:
226 source, expected = read_data("expression")
227 tmp_file = Path(black.dump_to_file(source))
229 self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
230 with open(tmp_file, encoding="utf8") as f:
234 self.assertFormatEqual(expected, actual)
235 with patch("black.dump_to_file", dump_to_stderr):
236 black.assert_equivalent(source, actual)
237 black.assert_stable(source, actual, line_length=ll)
239 def test_expression_diff(self) -> None:
240 source, _ = read_data("expression.py")
241 expected, _ = read_data("expression.diff")
242 tmp_file = Path(black.dump_to_file(source))
243 diff_header = re.compile(
244 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
245 rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
247 stderrbuf = BytesIO()
249 result = BlackRunner(stderrbuf).invoke(
250 black.main, ["--diff", str(tmp_file)]
252 self.assertEqual(result.exit_code, 0)
255 actual = result.output
256 actual = diff_header.sub("[Deterministic header]", actual)
257 actual = actual.rstrip() + "\n" # the diff output has a trailing space
258 if expected != actual:
259 dump = black.dump_to_file(actual)
261 f"Expected diff isn't equal to the actual. If you made changes "
262 f"to expression.py and this is an anticipated difference, "
263 f"overwrite tests/expression.diff with {dump}"
265 self.assertEqual(expected, actual, msg)
267 @patch("black.dump_to_file", dump_to_stderr)
268 def test_fstring(self) -> None:
269 source, expected = read_data("fstring")
271 self.assertFormatEqual(expected, actual)
272 black.assert_equivalent(source, actual)
273 black.assert_stable(source, actual, line_length=ll)
275 @patch("black.dump_to_file", dump_to_stderr)
276 def test_string_quotes(self) -> None:
277 source, expected = read_data("string_quotes")
279 self.assertFormatEqual(expected, actual)
280 black.assert_equivalent(source, actual)
281 black.assert_stable(source, actual, line_length=ll)
282 mode = black.FileMode.NO_STRING_NORMALIZATION
283 not_normalized = fs(source, mode=mode)
284 self.assertFormatEqual(source, not_normalized)
285 black.assert_equivalent(source, not_normalized)
286 black.assert_stable(source, not_normalized, line_length=ll, mode=mode)
288 @patch("black.dump_to_file", dump_to_stderr)
289 def test_slices(self) -> None:
290 source, expected = read_data("slices")
292 self.assertFormatEqual(expected, actual)
293 black.assert_equivalent(source, actual)
294 black.assert_stable(source, actual, line_length=ll)
296 @patch("black.dump_to_file", dump_to_stderr)
297 def test_comments(self) -> None:
298 source, expected = read_data("comments")
300 self.assertFormatEqual(expected, actual)
301 black.assert_equivalent(source, actual)
302 black.assert_stable(source, actual, line_length=ll)
304 @patch("black.dump_to_file", dump_to_stderr)
305 def test_comments2(self) -> None:
306 source, expected = read_data("comments2")
308 self.assertFormatEqual(expected, actual)
309 black.assert_equivalent(source, actual)
310 black.assert_stable(source, actual, line_length=ll)
312 @patch("black.dump_to_file", dump_to_stderr)
313 def test_comments3(self) -> None:
314 source, expected = read_data("comments3")
316 self.assertFormatEqual(expected, actual)
317 black.assert_equivalent(source, actual)
318 black.assert_stable(source, actual, line_length=ll)
320 @patch("black.dump_to_file", dump_to_stderr)
321 def test_comments4(self) -> None:
322 source, expected = read_data("comments4")
324 self.assertFormatEqual(expected, actual)
325 black.assert_equivalent(source, actual)
326 black.assert_stable(source, actual, line_length=ll)
328 @patch("black.dump_to_file", dump_to_stderr)
329 def test_comments5(self) -> None:
330 source, expected = read_data("comments5")
332 self.assertFormatEqual(expected, actual)
333 black.assert_equivalent(source, actual)
334 black.assert_stable(source, actual, line_length=ll)
336 @patch("black.dump_to_file", dump_to_stderr)
337 def test_cantfit(self) -> None:
338 source, expected = read_data("cantfit")
340 self.assertFormatEqual(expected, actual)
341 black.assert_equivalent(source, actual)
342 black.assert_stable(source, actual, line_length=ll)
344 @patch("black.dump_to_file", dump_to_stderr)
345 def test_import_spacing(self) -> None:
346 source, expected = read_data("import_spacing")
348 self.assertFormatEqual(expected, actual)
349 black.assert_equivalent(source, actual)
350 black.assert_stable(source, actual, line_length=ll)
352 @patch("black.dump_to_file", dump_to_stderr)
353 def test_composition(self) -> None:
354 source, expected = read_data("composition")
356 self.assertFormatEqual(expected, actual)
357 black.assert_equivalent(source, actual)
358 black.assert_stable(source, actual, line_length=ll)
360 @patch("black.dump_to_file", dump_to_stderr)
361 def test_empty_lines(self) -> None:
362 source, expected = read_data("empty_lines")
364 self.assertFormatEqual(expected, actual)
365 black.assert_equivalent(source, actual)
366 black.assert_stable(source, actual, line_length=ll)
368 @patch("black.dump_to_file", dump_to_stderr)
369 def test_string_prefixes(self) -> None:
370 source, expected = read_data("string_prefixes")
372 self.assertFormatEqual(expected, actual)
373 black.assert_equivalent(source, actual)
374 black.assert_stable(source, actual, line_length=ll)
376 @patch("black.dump_to_file", dump_to_stderr)
377 def test_numeric_literals(self) -> None:
378 source, expected = read_data("numeric_literals")
379 actual = fs(source, mode=black.FileMode.PYTHON36)
380 self.assertFormatEqual(expected, actual)
381 black.assert_equivalent(source, actual)
382 black.assert_stable(source, actual, line_length=ll)
384 @patch("black.dump_to_file", dump_to_stderr)
385 def test_numeric_literals_py2(self) -> None:
386 source, expected = read_data("numeric_literals_py2")
388 self.assertFormatEqual(expected, actual)
389 black.assert_stable(source, actual, line_length=ll)
391 @patch("black.dump_to_file", dump_to_stderr)
392 def test_python2(self) -> None:
393 source, expected = read_data("python2")
395 self.assertFormatEqual(expected, actual)
396 # black.assert_equivalent(source, actual)
397 black.assert_stable(source, actual, line_length=ll)
399 @patch("black.dump_to_file", dump_to_stderr)
400 def test_python2_unicode_literals(self) -> None:
401 source, expected = read_data("python2_unicode_literals")
403 self.assertFormatEqual(expected, actual)
404 black.assert_stable(source, actual, line_length=ll)
406 @patch("black.dump_to_file", dump_to_stderr)
407 def test_stub(self) -> None:
408 mode = black.FileMode.PYI
409 source, expected = read_data("stub.pyi")
410 actual = fs(source, mode=mode)
411 self.assertFormatEqual(expected, actual)
412 black.assert_stable(source, actual, line_length=ll, mode=mode)
414 @patch("black.dump_to_file", dump_to_stderr)
415 def test_python37(self) -> None:
416 source, expected = read_data("python37")
418 self.assertFormatEqual(expected, actual)
419 major, minor = sys.version_info[:2]
420 if major > 3 or (major == 3 and minor >= 7):
421 black.assert_equivalent(source, actual)
422 black.assert_stable(source, actual, line_length=ll)
424 @patch("black.dump_to_file", dump_to_stderr)
425 def test_fmtonoff(self) -> None:
426 source, expected = read_data("fmtonoff")
428 self.assertFormatEqual(expected, actual)
429 black.assert_equivalent(source, actual)
430 black.assert_stable(source, actual, line_length=ll)
432 @patch("black.dump_to_file", dump_to_stderr)
433 def test_fmtonoff2(self) -> None:
434 source, expected = read_data("fmtonoff2")
436 self.assertFormatEqual(expected, actual)
437 black.assert_equivalent(source, actual)
438 black.assert_stable(source, actual, line_length=ll)
440 @patch("black.dump_to_file", dump_to_stderr)
441 def test_remove_empty_parentheses_after_class(self) -> None:
442 source, expected = read_data("class_blank_parentheses")
444 self.assertFormatEqual(expected, actual)
445 black.assert_equivalent(source, actual)
446 black.assert_stable(source, actual, line_length=ll)
448 @patch("black.dump_to_file", dump_to_stderr)
449 def test_new_line_between_class_and_code(self) -> None:
450 source, expected = read_data("class_methods_new_line")
452 self.assertFormatEqual(expected, actual)
453 black.assert_equivalent(source, actual)
454 black.assert_stable(source, actual, line_length=ll)
456 @patch("black.dump_to_file", dump_to_stderr)
457 def test_bracket_match(self) -> None:
458 source, expected = read_data("bracketmatch")
460 self.assertFormatEqual(expected, actual)
461 black.assert_equivalent(source, actual)
462 black.assert_stable(source, actual, line_length=ll)
464 def test_report_verbose(self) -> None:
465 report = black.Report(verbose=True)
469 def out(msg: str, **kwargs: Any) -> None:
470 out_lines.append(msg)
472 def err(msg: str, **kwargs: Any) -> None:
473 err_lines.append(msg)
475 with patch("black.out", out), patch("black.err", err):
476 report.done(Path("f1"), black.Changed.NO)
477 self.assertEqual(len(out_lines), 1)
478 self.assertEqual(len(err_lines), 0)
479 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
480 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
481 self.assertEqual(report.return_code, 0)
482 report.done(Path("f2"), black.Changed.YES)
483 self.assertEqual(len(out_lines), 2)
484 self.assertEqual(len(err_lines), 0)
485 self.assertEqual(out_lines[-1], "reformatted f2")
487 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
489 report.done(Path("f3"), black.Changed.CACHED)
490 self.assertEqual(len(out_lines), 3)
491 self.assertEqual(len(err_lines), 0)
493 out_lines[-1], "f3 wasn't modified on disk since last run."
496 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
498 self.assertEqual(report.return_code, 0)
500 self.assertEqual(report.return_code, 1)
502 report.failed(Path("e1"), "boom")
503 self.assertEqual(len(out_lines), 3)
504 self.assertEqual(len(err_lines), 1)
505 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
507 unstyle(str(report)),
508 "1 file reformatted, 2 files left unchanged, "
509 "1 file failed to reformat.",
511 self.assertEqual(report.return_code, 123)
512 report.done(Path("f3"), black.Changed.YES)
513 self.assertEqual(len(out_lines), 4)
514 self.assertEqual(len(err_lines), 1)
515 self.assertEqual(out_lines[-1], "reformatted f3")
517 unstyle(str(report)),
518 "2 files reformatted, 2 files left unchanged, "
519 "1 file failed to reformat.",
521 self.assertEqual(report.return_code, 123)
522 report.failed(Path("e2"), "boom")
523 self.assertEqual(len(out_lines), 4)
524 self.assertEqual(len(err_lines), 2)
525 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
527 unstyle(str(report)),
528 "2 files reformatted, 2 files left unchanged, "
529 "2 files failed to reformat.",
531 self.assertEqual(report.return_code, 123)
532 report.path_ignored(Path("wat"), "no match")
533 self.assertEqual(len(out_lines), 5)
534 self.assertEqual(len(err_lines), 2)
535 self.assertEqual(out_lines[-1], "wat ignored: no match")
537 unstyle(str(report)),
538 "2 files reformatted, 2 files left unchanged, "
539 "2 files failed to reformat.",
541 self.assertEqual(report.return_code, 123)
542 report.done(Path("f4"), black.Changed.NO)
543 self.assertEqual(len(out_lines), 6)
544 self.assertEqual(len(err_lines), 2)
545 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
547 unstyle(str(report)),
548 "2 files reformatted, 3 files left unchanged, "
549 "2 files failed to reformat.",
551 self.assertEqual(report.return_code, 123)
554 unstyle(str(report)),
555 "2 files would be reformatted, 3 files would be left unchanged, "
556 "2 files would fail to reformat.",
559 def test_report_quiet(self) -> None:
560 report = black.Report(quiet=True)
564 def out(msg: str, **kwargs: Any) -> None:
565 out_lines.append(msg)
567 def err(msg: str, **kwargs: Any) -> None:
568 err_lines.append(msg)
570 with patch("black.out", out), patch("black.err", err):
571 report.done(Path("f1"), black.Changed.NO)
572 self.assertEqual(len(out_lines), 0)
573 self.assertEqual(len(err_lines), 0)
574 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
575 self.assertEqual(report.return_code, 0)
576 report.done(Path("f2"), black.Changed.YES)
577 self.assertEqual(len(out_lines), 0)
578 self.assertEqual(len(err_lines), 0)
580 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
582 report.done(Path("f3"), black.Changed.CACHED)
583 self.assertEqual(len(out_lines), 0)
584 self.assertEqual(len(err_lines), 0)
586 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
588 self.assertEqual(report.return_code, 0)
590 self.assertEqual(report.return_code, 1)
592 report.failed(Path("e1"), "boom")
593 self.assertEqual(len(out_lines), 0)
594 self.assertEqual(len(err_lines), 1)
595 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
597 unstyle(str(report)),
598 "1 file reformatted, 2 files left unchanged, "
599 "1 file failed to reformat.",
601 self.assertEqual(report.return_code, 123)
602 report.done(Path("f3"), black.Changed.YES)
603 self.assertEqual(len(out_lines), 0)
604 self.assertEqual(len(err_lines), 1)
606 unstyle(str(report)),
607 "2 files reformatted, 2 files left unchanged, "
608 "1 file failed to reformat.",
610 self.assertEqual(report.return_code, 123)
611 report.failed(Path("e2"), "boom")
612 self.assertEqual(len(out_lines), 0)
613 self.assertEqual(len(err_lines), 2)
614 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
616 unstyle(str(report)),
617 "2 files reformatted, 2 files left unchanged, "
618 "2 files failed to reformat.",
620 self.assertEqual(report.return_code, 123)
621 report.path_ignored(Path("wat"), "no match")
622 self.assertEqual(len(out_lines), 0)
623 self.assertEqual(len(err_lines), 2)
625 unstyle(str(report)),
626 "2 files reformatted, 2 files left unchanged, "
627 "2 files failed to reformat.",
629 self.assertEqual(report.return_code, 123)
630 report.done(Path("f4"), black.Changed.NO)
631 self.assertEqual(len(out_lines), 0)
632 self.assertEqual(len(err_lines), 2)
634 unstyle(str(report)),
635 "2 files reformatted, 3 files left unchanged, "
636 "2 files failed to reformat.",
638 self.assertEqual(report.return_code, 123)
641 unstyle(str(report)),
642 "2 files would be reformatted, 3 files would be left unchanged, "
643 "2 files would fail to reformat.",
646 def test_report_normal(self) -> None:
647 report = black.Report()
651 def out(msg: str, **kwargs: Any) -> None:
652 out_lines.append(msg)
654 def err(msg: str, **kwargs: Any) -> None:
655 err_lines.append(msg)
657 with patch("black.out", out), patch("black.err", err):
658 report.done(Path("f1"), black.Changed.NO)
659 self.assertEqual(len(out_lines), 0)
660 self.assertEqual(len(err_lines), 0)
661 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
662 self.assertEqual(report.return_code, 0)
663 report.done(Path("f2"), black.Changed.YES)
664 self.assertEqual(len(out_lines), 1)
665 self.assertEqual(len(err_lines), 0)
666 self.assertEqual(out_lines[-1], "reformatted f2")
668 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
670 report.done(Path("f3"), black.Changed.CACHED)
671 self.assertEqual(len(out_lines), 1)
672 self.assertEqual(len(err_lines), 0)
673 self.assertEqual(out_lines[-1], "reformatted f2")
675 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
677 self.assertEqual(report.return_code, 0)
679 self.assertEqual(report.return_code, 1)
681 report.failed(Path("e1"), "boom")
682 self.assertEqual(len(out_lines), 1)
683 self.assertEqual(len(err_lines), 1)
684 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
686 unstyle(str(report)),
687 "1 file reformatted, 2 files left unchanged, "
688 "1 file failed to reformat.",
690 self.assertEqual(report.return_code, 123)
691 report.done(Path("f3"), black.Changed.YES)
692 self.assertEqual(len(out_lines), 2)
693 self.assertEqual(len(err_lines), 1)
694 self.assertEqual(out_lines[-1], "reformatted f3")
696 unstyle(str(report)),
697 "2 files reformatted, 2 files left unchanged, "
698 "1 file failed to reformat.",
700 self.assertEqual(report.return_code, 123)
701 report.failed(Path("e2"), "boom")
702 self.assertEqual(len(out_lines), 2)
703 self.assertEqual(len(err_lines), 2)
704 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
706 unstyle(str(report)),
707 "2 files reformatted, 2 files left unchanged, "
708 "2 files failed to reformat.",
710 self.assertEqual(report.return_code, 123)
711 report.path_ignored(Path("wat"), "no match")
712 self.assertEqual(len(out_lines), 2)
713 self.assertEqual(len(err_lines), 2)
715 unstyle(str(report)),
716 "2 files reformatted, 2 files left unchanged, "
717 "2 files failed to reformat.",
719 self.assertEqual(report.return_code, 123)
720 report.done(Path("f4"), black.Changed.NO)
721 self.assertEqual(len(out_lines), 2)
722 self.assertEqual(len(err_lines), 2)
724 unstyle(str(report)),
725 "2 files reformatted, 3 files left unchanged, "
726 "2 files failed to reformat.",
728 self.assertEqual(report.return_code, 123)
731 unstyle(str(report)),
732 "2 files would be reformatted, 3 files would be left unchanged, "
733 "2 files would fail to reformat.",
736 def test_is_python36(self) -> None:
737 node = black.lib2to3_parse("def f(*, arg): ...\n")
738 self.assertFalse(black.is_python36(node))
739 node = black.lib2to3_parse("def f(*, arg,): ...\n")
740 self.assertTrue(black.is_python36(node))
741 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
742 self.assertTrue(black.is_python36(node))
743 node = black.lib2to3_parse("123_456\n")
744 self.assertTrue(black.is_python36(node))
745 node = black.lib2to3_parse("123456\n")
746 self.assertFalse(black.is_python36(node))
747 source, expected = read_data("function")
748 node = black.lib2to3_parse(source)
749 self.assertTrue(black.is_python36(node))
750 node = black.lib2to3_parse(expected)
751 self.assertTrue(black.is_python36(node))
752 source, expected = read_data("expression")
753 node = black.lib2to3_parse(source)
754 self.assertFalse(black.is_python36(node))
755 node = black.lib2to3_parse(expected)
756 self.assertFalse(black.is_python36(node))
758 def test_get_future_imports(self) -> None:
759 node = black.lib2to3_parse("\n")
760 self.assertEqual(set(), black.get_future_imports(node))
761 node = black.lib2to3_parse("from __future__ import black\n")
762 self.assertEqual({"black"}, black.get_future_imports(node))
763 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
764 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
765 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
766 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
767 node = black.lib2to3_parse(
768 "from __future__ import multiple\nfrom __future__ import imports\n"
770 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
771 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
772 self.assertEqual({"black"}, black.get_future_imports(node))
773 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
774 self.assertEqual({"black"}, black.get_future_imports(node))
775 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
776 self.assertEqual(set(), black.get_future_imports(node))
777 node = black.lib2to3_parse("from some.module import black\n")
778 self.assertEqual(set(), black.get_future_imports(node))
779 node = black.lib2to3_parse(
780 "from __future__ import unicode_literals as _unicode_literals"
782 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
783 node = black.lib2to3_parse(
784 "from __future__ import unicode_literals as _lol, print"
786 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
788 def test_debug_visitor(self) -> None:
789 source, _ = read_data("debug_visitor.py")
790 expected, _ = read_data("debug_visitor.out")
794 def out(msg: str, **kwargs: Any) -> None:
795 out_lines.append(msg)
797 def err(msg: str, **kwargs: Any) -> None:
798 err_lines.append(msg)
800 with patch("black.out", out), patch("black.err", err):
801 black.DebugVisitor.show(source)
802 actual = "\n".join(out_lines) + "\n"
804 if expected != actual:
805 log_name = black.dump_to_file(*out_lines)
809 f"AST print out is different. Actual version dumped to {log_name}",
812 def test_format_file_contents(self) -> None:
814 with self.assertRaises(black.NothingChanged):
815 black.format_file_contents(empty, line_length=ll, fast=False)
817 with self.assertRaises(black.NothingChanged):
818 black.format_file_contents(just_nl, line_length=ll, fast=False)
819 same = "l = [1, 2, 3]\n"
820 with self.assertRaises(black.NothingChanged):
821 black.format_file_contents(same, line_length=ll, fast=False)
822 different = "l = [1,2,3]"
824 actual = black.format_file_contents(different, line_length=ll, fast=False)
825 self.assertEqual(expected, actual)
826 invalid = "return if you can"
827 with self.assertRaises(ValueError) as e:
828 black.format_file_contents(invalid, line_length=ll, fast=False)
829 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
831 def test_endmarker(self) -> None:
832 n = black.lib2to3_parse("\n")
833 self.assertEqual(n.type, black.syms.file_input)
834 self.assertEqual(len(n.children), 1)
835 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
837 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
838 def test_assertFormatEqual(self) -> None:
842 def out(msg: str, **kwargs: Any) -> None:
843 out_lines.append(msg)
845 def err(msg: str, **kwargs: Any) -> None:
846 err_lines.append(msg)
848 with patch("black.out", out), patch("black.err", err):
849 with self.assertRaises(AssertionError):
850 self.assertFormatEqual("l = [1, 2, 3]", "l = [1, 2, 3,]")
852 out_str = "".join(out_lines)
853 self.assertTrue("Expected tree:" in out_str)
854 self.assertTrue("Actual tree:" in out_str)
855 self.assertEqual("".join(err_lines), "")
857 def test_cache_broken_file(self) -> None:
858 mode = black.FileMode.AUTO_DETECT
859 with cache_dir() as workspace:
860 cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode)
861 with cache_file.open("w") as fobj:
862 fobj.write("this is not a pickle")
863 self.assertEqual(black.read_cache(black.DEFAULT_LINE_LENGTH, mode), {})
864 src = (workspace / "test.py").resolve()
865 with src.open("w") as fobj:
866 fobj.write("print('hello')")
867 result = CliRunner().invoke(black.main, [str(src)])
868 self.assertEqual(result.exit_code, 0)
869 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
870 self.assertIn(src, cache)
872 def test_cache_single_file_already_cached(self) -> None:
873 mode = black.FileMode.AUTO_DETECT
874 with cache_dir() as workspace:
875 src = (workspace / "test.py").resolve()
876 with src.open("w") as fobj:
877 fobj.write("print('hello')")
878 black.write_cache({}, [src], black.DEFAULT_LINE_LENGTH, mode)
879 result = CliRunner().invoke(black.main, [str(src)])
880 self.assertEqual(result.exit_code, 0)
881 with src.open("r") as fobj:
882 self.assertEqual(fobj.read(), "print('hello')")
884 @event_loop(close=False)
885 def test_cache_multiple_files(self) -> None:
886 mode = black.FileMode.AUTO_DETECT
887 with cache_dir() as workspace, patch(
888 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
890 one = (workspace / "one.py").resolve()
891 with one.open("w") as fobj:
892 fobj.write("print('hello')")
893 two = (workspace / "two.py").resolve()
894 with two.open("w") as fobj:
895 fobj.write("print('hello')")
896 black.write_cache({}, [one], black.DEFAULT_LINE_LENGTH, mode)
897 result = CliRunner().invoke(black.main, [str(workspace)])
898 self.assertEqual(result.exit_code, 0)
899 with one.open("r") as fobj:
900 self.assertEqual(fobj.read(), "print('hello')")
901 with two.open("r") as fobj:
902 self.assertEqual(fobj.read(), 'print("hello")\n')
903 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
904 self.assertIn(one, cache)
905 self.assertIn(two, cache)
907 def test_no_cache_when_writeback_diff(self) -> None:
908 mode = black.FileMode.AUTO_DETECT
909 with cache_dir() as workspace:
910 src = (workspace / "test.py").resolve()
911 with src.open("w") as fobj:
912 fobj.write("print('hello')")
913 result = CliRunner().invoke(black.main, [str(src), "--diff"])
914 self.assertEqual(result.exit_code, 0)
915 cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode)
916 self.assertFalse(cache_file.exists())
918 def test_no_cache_when_stdin(self) -> None:
919 mode = black.FileMode.AUTO_DETECT
921 result = CliRunner().invoke(
922 black.main, ["-"], input=BytesIO(b"print('hello')")
924 self.assertEqual(result.exit_code, 0)
925 cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode)
926 self.assertFalse(cache_file.exists())
928 def test_read_cache_no_cachefile(self) -> None:
929 mode = black.FileMode.AUTO_DETECT
931 self.assertEqual(black.read_cache(black.DEFAULT_LINE_LENGTH, mode), {})
933 def test_write_cache_read_cache(self) -> None:
934 mode = black.FileMode.AUTO_DETECT
935 with cache_dir() as workspace:
936 src = (workspace / "test.py").resolve()
938 black.write_cache({}, [src], black.DEFAULT_LINE_LENGTH, mode)
939 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
940 self.assertIn(src, cache)
941 self.assertEqual(cache[src], black.get_cache_info(src))
943 def test_filter_cached(self) -> None:
944 with TemporaryDirectory() as workspace:
945 path = Path(workspace)
946 uncached = (path / "uncached").resolve()
947 cached = (path / "cached").resolve()
948 cached_but_changed = (path / "changed").resolve()
951 cached_but_changed.touch()
952 cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
953 todo, done = black.filter_cached(
954 cache, {uncached, cached, cached_but_changed}
956 self.assertEqual(todo, {uncached, cached_but_changed})
957 self.assertEqual(done, {cached})
959 def test_write_cache_creates_directory_if_needed(self) -> None:
960 mode = black.FileMode.AUTO_DETECT
961 with cache_dir(exists=False) as workspace:
962 self.assertFalse(workspace.exists())
963 black.write_cache({}, [], black.DEFAULT_LINE_LENGTH, mode)
964 self.assertTrue(workspace.exists())
966 @event_loop(close=False)
967 def test_failed_formatting_does_not_get_cached(self) -> None:
968 mode = black.FileMode.AUTO_DETECT
969 with cache_dir() as workspace, patch(
970 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
972 failing = (workspace / "failing.py").resolve()
973 with failing.open("w") as fobj:
974 fobj.write("not actually python")
975 clean = (workspace / "clean.py").resolve()
976 with clean.open("w") as fobj:
977 fobj.write('print("hello")\n')
978 result = CliRunner().invoke(black.main, [str(workspace)])
979 self.assertEqual(result.exit_code, 123)
980 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
981 self.assertNotIn(failing, cache)
982 self.assertIn(clean, cache)
984 def test_write_cache_write_fail(self) -> None:
985 mode = black.FileMode.AUTO_DETECT
986 with cache_dir(), patch.object(Path, "open") as mock:
987 mock.side_effect = OSError
988 black.write_cache({}, [], black.DEFAULT_LINE_LENGTH, mode)
990 @event_loop(close=False)
991 def test_check_diff_use_together(self) -> None:
993 # Files which will be reformatted.
994 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
995 result = CliRunner().invoke(black.main, [str(src1), "--diff", "--check"])
996 self.assertEqual(result.exit_code, 1, result.output)
997 # Files which will not be reformatted.
998 src2 = (THIS_DIR / "data" / "composition.py").resolve()
999 result = CliRunner().invoke(black.main, [str(src2), "--diff", "--check"])
1000 self.assertEqual(result.exit_code, 0, result.output)
1001 # Multi file command.
1002 result = CliRunner().invoke(
1003 black.main, [str(src1), str(src2), "--diff", "--check"]
1005 self.assertEqual(result.exit_code, 1, result.output)
1007 def test_no_files(self) -> None:
1009 # Without an argument, black exits with error code 0.
1010 result = CliRunner().invoke(black.main, [])
1011 self.assertEqual(result.exit_code, 0)
1013 def test_broken_symlink(self) -> None:
1014 with cache_dir() as workspace:
1015 symlink = workspace / "broken_link.py"
1017 symlink.symlink_to("nonexistent.py")
1018 except OSError as e:
1019 self.skipTest(f"Can't create symlinks: {e}")
1020 result = CliRunner().invoke(black.main, [str(workspace.resolve())])
1021 self.assertEqual(result.exit_code, 0)
1023 def test_read_cache_line_lengths(self) -> None:
1024 mode = black.FileMode.AUTO_DETECT
1025 with cache_dir() as workspace:
1026 path = (workspace / "file.py").resolve()
1028 black.write_cache({}, [path], 1, mode)
1029 one = black.read_cache(1, mode)
1030 self.assertIn(path, one)
1031 two = black.read_cache(2, mode)
1032 self.assertNotIn(path, two)
1034 def test_single_file_force_pyi(self) -> None:
1035 reg_mode = black.FileMode.AUTO_DETECT
1036 pyi_mode = black.FileMode.PYI
1037 contents, expected = read_data("force_pyi")
1038 with cache_dir() as workspace:
1039 path = (workspace / "file.py").resolve()
1040 with open(path, "w") as fh:
1042 result = CliRunner().invoke(black.main, [str(path), "--pyi"])
1043 self.assertEqual(result.exit_code, 0)
1044 with open(path, "r") as fh:
1046 # verify cache with --pyi is separate
1047 pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, pyi_mode)
1048 self.assertIn(path, pyi_cache)
1049 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1050 self.assertNotIn(path, normal_cache)
1051 self.assertEqual(actual, expected)
1053 @event_loop(close=False)
1054 def test_multi_file_force_pyi(self) -> None:
1055 reg_mode = black.FileMode.AUTO_DETECT
1056 pyi_mode = black.FileMode.PYI
1057 contents, expected = read_data("force_pyi")
1058 with cache_dir() as workspace:
1060 (workspace / "file1.py").resolve(),
1061 (workspace / "file2.py").resolve(),
1064 with open(path, "w") as fh:
1066 result = CliRunner().invoke(black.main, [str(p) for p in paths] + ["--pyi"])
1067 self.assertEqual(result.exit_code, 0)
1069 with open(path, "r") as fh:
1071 self.assertEqual(actual, expected)
1072 # verify cache with --pyi is separate
1073 pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, pyi_mode)
1074 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1076 self.assertIn(path, pyi_cache)
1077 self.assertNotIn(path, normal_cache)
1079 def test_pipe_force_pyi(self) -> None:
1080 source, expected = read_data("force_pyi")
1081 result = CliRunner().invoke(
1082 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1084 self.assertEqual(result.exit_code, 0)
1085 actual = result.output
1086 self.assertFormatEqual(actual, expected)
1088 def test_single_file_force_py36(self) -> None:
1089 reg_mode = black.FileMode.AUTO_DETECT
1090 py36_mode = black.FileMode.PYTHON36
1091 source, expected = read_data("force_py36")
1092 with cache_dir() as workspace:
1093 path = (workspace / "file.py").resolve()
1094 with open(path, "w") as fh:
1096 result = CliRunner().invoke(black.main, [str(path), "--py36"])
1097 self.assertEqual(result.exit_code, 0)
1098 with open(path, "r") as fh:
1100 # verify cache with --py36 is separate
1101 py36_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, py36_mode)
1102 self.assertIn(path, py36_cache)
1103 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1104 self.assertNotIn(path, normal_cache)
1105 self.assertEqual(actual, expected)
1107 @event_loop(close=False)
1108 def test_multi_file_force_py36(self) -> None:
1109 reg_mode = black.FileMode.AUTO_DETECT
1110 py36_mode = black.FileMode.PYTHON36
1111 source, expected = read_data("force_py36")
1112 with cache_dir() as workspace:
1114 (workspace / "file1.py").resolve(),
1115 (workspace / "file2.py").resolve(),
1118 with open(path, "w") as fh:
1120 result = CliRunner().invoke(
1121 black.main, [str(p) for p in paths] + ["--py36"]
1123 self.assertEqual(result.exit_code, 0)
1125 with open(path, "r") as fh:
1127 self.assertEqual(actual, expected)
1128 # verify cache with --py36 is separate
1129 pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, py36_mode)
1130 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1132 self.assertIn(path, pyi_cache)
1133 self.assertNotIn(path, normal_cache)
1135 def test_pipe_force_py36(self) -> None:
1136 source, expected = read_data("force_py36")
1137 result = CliRunner().invoke(
1138 black.main, ["-", "-q", "--py36"], input=BytesIO(source.encode("utf8"))
1140 self.assertEqual(result.exit_code, 0)
1141 actual = result.output
1142 self.assertFormatEqual(actual, expected)
1144 def test_include_exclude(self) -> None:
1145 path = THIS_DIR / "data" / "include_exclude_tests"
1146 include = re.compile(r"\.pyi?$")
1147 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1148 report = black.Report()
1149 sources: List[Path] = []
1151 Path(path / "b/dont_exclude/a.py"),
1152 Path(path / "b/dont_exclude/a.pyi"),
1154 this_abs = THIS_DIR.resolve()
1156 black.gen_python_files_in_dir(path, this_abs, include, exclude, report)
1158 self.assertEqual(sorted(expected), sorted(sources))
1160 def test_empty_include(self) -> None:
1161 path = THIS_DIR / "data" / "include_exclude_tests"
1162 report = black.Report()
1163 empty = re.compile(r"")
1164 sources: List[Path] = []
1166 Path(path / "b/exclude/a.pie"),
1167 Path(path / "b/exclude/a.py"),
1168 Path(path / "b/exclude/a.pyi"),
1169 Path(path / "b/dont_exclude/a.pie"),
1170 Path(path / "b/dont_exclude/a.py"),
1171 Path(path / "b/dont_exclude/a.pyi"),
1172 Path(path / "b/.definitely_exclude/a.pie"),
1173 Path(path / "b/.definitely_exclude/a.py"),
1174 Path(path / "b/.definitely_exclude/a.pyi"),
1176 this_abs = THIS_DIR.resolve()
1178 black.gen_python_files_in_dir(
1179 path, this_abs, empty, re.compile(black.DEFAULT_EXCLUDES), report
1182 self.assertEqual(sorted(expected), sorted(sources))
1184 def test_empty_exclude(self) -> None:
1185 path = THIS_DIR / "data" / "include_exclude_tests"
1186 report = black.Report()
1187 empty = re.compile(r"")
1188 sources: List[Path] = []
1190 Path(path / "b/dont_exclude/a.py"),
1191 Path(path / "b/dont_exclude/a.pyi"),
1192 Path(path / "b/exclude/a.py"),
1193 Path(path / "b/exclude/a.pyi"),
1194 Path(path / "b/.definitely_exclude/a.py"),
1195 Path(path / "b/.definitely_exclude/a.pyi"),
1197 this_abs = THIS_DIR.resolve()
1199 black.gen_python_files_in_dir(
1200 path, this_abs, re.compile(black.DEFAULT_INCLUDES), empty, report
1203 self.assertEqual(sorted(expected), sorted(sources))
1205 def test_invalid_include_exclude(self) -> None:
1206 for option in ["--include", "--exclude"]:
1207 result = CliRunner().invoke(black.main, ["-", option, "**()(!!*)"])
1208 self.assertEqual(result.exit_code, 2)
1210 def test_preserves_line_endings(self) -> None:
1211 with TemporaryDirectory() as workspace:
1212 test_file = Path(workspace) / "test.py"
1213 for nl in ["\n", "\r\n"]:
1214 contents = nl.join(["def f( ):", " pass"])
1215 test_file.write_bytes(contents.encode())
1216 ff(test_file, write_back=black.WriteBack.YES)
1217 updated_contents: bytes = test_file.read_bytes()
1218 self.assertIn(nl.encode(), updated_contents)
1220 self.assertNotIn(b"\r\n", updated_contents)
1222 def test_assert_equivalent_different_asts(self) -> None:
1223 with self.assertRaises(AssertionError):
1224 black.assert_equivalent("{}", "None")
1226 def test_symlink_out_of_root_directory(self) -> None:
1230 include = re.compile(black.DEFAULT_INCLUDES)
1231 exclude = re.compile(black.DEFAULT_EXCLUDES)
1232 report = black.Report()
1233 # `child` should behave like a symlink which resolved path is clearly
1234 # outside of the `root` directory.
1235 path.iterdir.return_value = [child]
1236 child.resolve.return_value = Path("/a/b/c")
1237 child.is_symlink.return_value = True
1239 list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1240 except ValueError as ve:
1241 self.fail("`get_python_files_in_dir()` failed: {ve}")
1242 path.iterdir.assert_called_once()
1243 child.resolve.assert_called_once()
1244 child.is_symlink.assert_called_once()
1245 # `child` should behave like a strange file which resolved path is clearly
1246 # outside of the `root` directory.
1247 child.is_symlink.return_value = False
1248 with self.assertRaises(ValueError):
1249 list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1250 path.iterdir.assert_called()
1251 self.assertEqual(path.iterdir.call_count, 2)
1252 child.resolve.assert_called()
1253 self.assertEqual(child.resolve.call_count, 2)
1254 child.is_symlink.assert_called()
1255 self.assertEqual(child.is_symlink.call_count, 2)
1257 def test_shhh_click(self) -> None:
1259 from click import _unicodefun # type: ignore
1260 except ModuleNotFoundError:
1261 self.skipTest("Incompatible Click version")
1262 if not hasattr(_unicodefun, "_verify_python3_env"):
1263 self.skipTest("Incompatible Click version")
1264 # First, let's see if Click is crashing with a preferred ASCII charset.
1265 with patch("locale.getpreferredencoding") as gpe:
1266 gpe.return_value = "ASCII"
1267 with self.assertRaises(RuntimeError):
1268 _unicodefun._verify_python3_env()
1269 # Now, let's silence Click...
1271 # ...and confirm it's silent.
1272 with patch("locale.getpreferredencoding") as gpe:
1273 gpe.return_value = "ASCII"
1275 _unicodefun._verify_python3_env()
1276 except RuntimeError as re:
1277 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1280 if __name__ == "__main__":
1281 unittest.main(module="test_black")