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
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) -> 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 with open(THIS_DIR / name, "r", encoding="utf8") as test:
41 lines = test.readlines()
44 line = line.replace(EMPTY_LINE, "")
45 if line.rstrip() == "# output":
50 if _input and not _output:
51 # If there's no output marker, treat the entire file as already pre-formatted.
53 return "".join(_input).strip() + "\n", "".join(_output).strip() + "\n"
57 def cache_dir(exists: bool = True) -> Iterator[Path]:
58 with TemporaryDirectory() as workspace:
59 cache_dir = Path(workspace)
61 cache_dir = cache_dir / "new"
62 with patch("black.CACHE_DIR", cache_dir):
67 def event_loop(close: bool) -> Iterator[None]:
68 policy = asyncio.get_event_loop_policy()
69 old_loop = policy.get_event_loop()
70 loop = policy.new_event_loop()
71 asyncio.set_event_loop(loop)
76 policy.set_event_loop(old_loop)
81 class BlackRunner(CliRunner):
82 """Modify CliRunner so that stderr is not merged with stdout.
84 This is a hack that can be removed once we depend on Click 7.x"""
86 def __init__(self, stderrbuf: BinaryIO) -> None:
87 self.stderrbuf = stderrbuf
91 def isolation(self, *args: Any, **kwargs: Any) -> Generator[BinaryIO, None, None]:
92 with super().isolation(*args, **kwargs) as output:
94 hold_stderr = sys.stderr
95 sys.stderr = TextIOWrapper(self.stderrbuf, encoding=self.charset)
98 sys.stderr = hold_stderr
101 class BlackTestCase(unittest.TestCase):
104 def assertFormatEqual(self, expected: str, actual: str) -> None:
105 if actual != expected and not os.environ.get("SKIP_AST_PRINT"):
106 bdv: black.DebugVisitor[Any]
107 black.out("Expected tree:", fg="green")
109 exp_node = black.lib2to3_parse(expected)
110 bdv = black.DebugVisitor()
111 list(bdv.visit(exp_node))
112 except Exception as ve:
114 black.out("Actual tree:", fg="red")
116 exp_node = black.lib2to3_parse(actual)
117 bdv = black.DebugVisitor()
118 list(bdv.visit(exp_node))
119 except Exception as ve:
121 self.assertEqual(expected, actual)
123 @patch("black.dump_to_file", dump_to_stderr)
124 def test_empty(self) -> None:
125 source = expected = ""
127 self.assertFormatEqual(expected, actual)
128 black.assert_equivalent(source, actual)
129 black.assert_stable(source, actual, line_length=ll)
131 def test_empty_ff(self) -> None:
133 tmp_file = Path(black.dump_to_file())
135 self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES))
136 with open(tmp_file, encoding="utf8") as f:
140 self.assertFormatEqual(expected, actual)
142 @patch("black.dump_to_file", dump_to_stderr)
143 def test_self(self) -> None:
144 source, expected = read_data("test_black")
146 self.assertFormatEqual(expected, actual)
147 black.assert_equivalent(source, actual)
148 black.assert_stable(source, actual, line_length=ll)
149 self.assertFalse(ff(THIS_FILE))
151 @patch("black.dump_to_file", dump_to_stderr)
152 def test_black(self) -> None:
153 source, expected = read_data("../black")
155 self.assertFormatEqual(expected, actual)
156 black.assert_equivalent(source, actual)
157 black.assert_stable(source, actual, line_length=ll)
158 self.assertFalse(ff(THIS_DIR / ".." / "black.py"))
160 def test_piping(self) -> None:
161 source, expected = read_data("../black")
162 stderrbuf = BytesIO()
163 result = BlackRunner(stderrbuf).invoke(
164 black.main, ["-", "--fast", f"--line-length={ll}"], input=source
166 self.assertEqual(result.exit_code, 0)
167 self.assertFormatEqual(expected, result.output)
168 black.assert_equivalent(source, result.output)
169 black.assert_stable(source, result.output, line_length=ll)
171 def test_piping_diff(self) -> None:
172 diff_header = re.compile(
173 rf"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d "
174 rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
176 source, _ = read_data("expression.py")
177 expected, _ = read_data("expression.diff")
178 stderrbuf = BytesIO()
179 result = BlackRunner(stderrbuf).invoke(
180 black.main, ["-", "--fast", f"--line-length={ll}", "--diff"], input=source
182 self.assertEqual(result.exit_code, 0)
183 actual = diff_header.sub("[Deterministic header]", result.output)
184 actual = actual.rstrip() + "\n" # the diff output has a trailing space
185 self.assertEqual(expected, actual)
187 @patch("black.dump_to_file", dump_to_stderr)
188 def test_setup(self) -> None:
189 source, expected = read_data("../setup")
191 self.assertFormatEqual(expected, actual)
192 black.assert_equivalent(source, actual)
193 black.assert_stable(source, actual, line_length=ll)
194 self.assertFalse(ff(THIS_DIR / ".." / "setup.py"))
196 @patch("black.dump_to_file", dump_to_stderr)
197 def test_function(self) -> None:
198 source, expected = read_data("function")
200 self.assertFormatEqual(expected, actual)
201 black.assert_equivalent(source, actual)
202 black.assert_stable(source, actual, line_length=ll)
204 @patch("black.dump_to_file", dump_to_stderr)
205 def test_function2(self) -> None:
206 source, expected = read_data("function2")
208 self.assertFormatEqual(expected, actual)
209 black.assert_equivalent(source, actual)
210 black.assert_stable(source, actual, line_length=ll)
212 @patch("black.dump_to_file", dump_to_stderr)
213 def test_expression(self) -> None:
214 source, expected = read_data("expression")
216 self.assertFormatEqual(expected, actual)
217 black.assert_equivalent(source, actual)
218 black.assert_stable(source, actual, line_length=ll)
220 def test_expression_ff(self) -> None:
221 source, expected = read_data("expression")
222 tmp_file = Path(black.dump_to_file(source))
224 self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
225 with open(tmp_file, encoding="utf8") as f:
229 self.assertFormatEqual(expected, actual)
230 with patch("black.dump_to_file", dump_to_stderr):
231 black.assert_equivalent(source, actual)
232 black.assert_stable(source, actual, line_length=ll)
234 def test_expression_diff(self) -> None:
235 source, _ = read_data("expression.py")
236 expected, _ = read_data("expression.diff")
237 tmp_file = Path(black.dump_to_file(source))
238 diff_header = re.compile(
239 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
240 rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
242 stderrbuf = BytesIO()
244 result = BlackRunner(stderrbuf).invoke(
245 black.main, ["--diff", str(tmp_file)]
247 self.assertEqual(result.exit_code, 0)
250 actual = result.output
251 actual = diff_header.sub("[Deterministic header]", actual)
252 actual = actual.rstrip() + "\n" # the diff output has a trailing space
253 if expected != actual:
254 dump = black.dump_to_file(actual)
256 f"Expected diff isn't equal to the actual. If you made changes "
257 f"to expression.py and this is an anticipated difference, "
258 f"overwrite tests/expression.diff with {dump}"
260 self.assertEqual(expected, actual, msg)
262 @patch("black.dump_to_file", dump_to_stderr)
263 def test_fstring(self) -> None:
264 source, expected = read_data("fstring")
266 self.assertFormatEqual(expected, actual)
267 black.assert_equivalent(source, actual)
268 black.assert_stable(source, actual, line_length=ll)
270 @patch("black.dump_to_file", dump_to_stderr)
271 def test_string_quotes(self) -> None:
272 source, expected = read_data("string_quotes")
274 self.assertFormatEqual(expected, actual)
275 black.assert_equivalent(source, actual)
276 black.assert_stable(source, actual, line_length=ll)
277 mode = black.FileMode.NO_STRING_NORMALIZATION
278 not_normalized = fs(source, mode=mode)
279 self.assertFormatEqual(source, not_normalized)
280 black.assert_equivalent(source, not_normalized)
281 black.assert_stable(source, not_normalized, line_length=ll, mode=mode)
283 @patch("black.dump_to_file", dump_to_stderr)
284 def test_slices(self) -> None:
285 source, expected = read_data("slices")
287 self.assertFormatEqual(expected, actual)
288 black.assert_equivalent(source, actual)
289 black.assert_stable(source, actual, line_length=ll)
291 @patch("black.dump_to_file", dump_to_stderr)
292 def test_comments(self) -> None:
293 source, expected = read_data("comments")
295 self.assertFormatEqual(expected, actual)
296 black.assert_equivalent(source, actual)
297 black.assert_stable(source, actual, line_length=ll)
299 @patch("black.dump_to_file", dump_to_stderr)
300 def test_comments2(self) -> None:
301 source, expected = read_data("comments2")
303 self.assertFormatEqual(expected, actual)
304 black.assert_equivalent(source, actual)
305 black.assert_stable(source, actual, line_length=ll)
307 @patch("black.dump_to_file", dump_to_stderr)
308 def test_comments3(self) -> None:
309 source, expected = read_data("comments3")
311 self.assertFormatEqual(expected, actual)
312 black.assert_equivalent(source, actual)
313 black.assert_stable(source, actual, line_length=ll)
315 @patch("black.dump_to_file", dump_to_stderr)
316 def test_comments4(self) -> None:
317 source, expected = read_data("comments4")
319 self.assertFormatEqual(expected, actual)
320 black.assert_equivalent(source, actual)
321 black.assert_stable(source, actual, line_length=ll)
323 @patch("black.dump_to_file", dump_to_stderr)
324 def test_comments5(self) -> None:
325 source, expected = read_data("comments5")
327 self.assertFormatEqual(expected, actual)
328 black.assert_equivalent(source, actual)
329 black.assert_stable(source, actual, line_length=ll)
331 @patch("black.dump_to_file", dump_to_stderr)
332 def test_cantfit(self) -> None:
333 source, expected = read_data("cantfit")
335 self.assertFormatEqual(expected, actual)
336 black.assert_equivalent(source, actual)
337 black.assert_stable(source, actual, line_length=ll)
339 @patch("black.dump_to_file", dump_to_stderr)
340 def test_import_spacing(self) -> None:
341 source, expected = read_data("import_spacing")
343 self.assertFormatEqual(expected, actual)
344 black.assert_equivalent(source, actual)
345 black.assert_stable(source, actual, line_length=ll)
347 @patch("black.dump_to_file", dump_to_stderr)
348 def test_composition(self) -> None:
349 source, expected = read_data("composition")
351 self.assertFormatEqual(expected, actual)
352 black.assert_equivalent(source, actual)
353 black.assert_stable(source, actual, line_length=ll)
355 @patch("black.dump_to_file", dump_to_stderr)
356 def test_empty_lines(self) -> None:
357 source, expected = read_data("empty_lines")
359 self.assertFormatEqual(expected, actual)
360 black.assert_equivalent(source, actual)
361 black.assert_stable(source, actual, line_length=ll)
363 @patch("black.dump_to_file", dump_to_stderr)
364 def test_string_prefixes(self) -> None:
365 source, expected = read_data("string_prefixes")
367 self.assertFormatEqual(expected, actual)
368 black.assert_equivalent(source, actual)
369 black.assert_stable(source, actual, line_length=ll)
371 @patch("black.dump_to_file", dump_to_stderr)
372 def test_python2(self) -> None:
373 source, expected = read_data("python2")
375 self.assertFormatEqual(expected, actual)
376 # black.assert_equivalent(source, actual)
377 black.assert_stable(source, actual, line_length=ll)
379 @patch("black.dump_to_file", dump_to_stderr)
380 def test_python2_unicode_literals(self) -> None:
381 source, expected = read_data("python2_unicode_literals")
383 self.assertFormatEqual(expected, actual)
384 black.assert_stable(source, actual, line_length=ll)
386 @patch("black.dump_to_file", dump_to_stderr)
387 def test_stub(self) -> None:
388 mode = black.FileMode.PYI
389 source, expected = read_data("stub.pyi")
390 actual = fs(source, mode=mode)
391 self.assertFormatEqual(expected, actual)
392 black.assert_stable(source, actual, line_length=ll, mode=mode)
394 @patch("black.dump_to_file", dump_to_stderr)
395 def test_fmtonoff(self) -> None:
396 source, expected = read_data("fmtonoff")
398 self.assertFormatEqual(expected, actual)
399 black.assert_equivalent(source, actual)
400 black.assert_stable(source, actual, line_length=ll)
402 @patch("black.dump_to_file", dump_to_stderr)
403 def test_remove_empty_parentheses_after_class(self) -> None:
404 source, expected = read_data("class_blank_parentheses")
406 self.assertFormatEqual(expected, actual)
407 black.assert_equivalent(source, actual)
408 black.assert_stable(source, actual, line_length=ll)
410 @patch("black.dump_to_file", dump_to_stderr)
411 def test_new_line_between_class_and_code(self) -> None:
412 source, expected = read_data("class_methods_new_line")
414 self.assertFormatEqual(expected, actual)
415 black.assert_equivalent(source, actual)
416 black.assert_stable(source, actual, line_length=ll)
418 def test_report_verbose(self) -> None:
419 report = black.Report(verbose=True)
423 def out(msg: str, **kwargs: Any) -> None:
424 out_lines.append(msg)
426 def err(msg: str, **kwargs: Any) -> None:
427 err_lines.append(msg)
429 with patch("black.out", out), patch("black.err", err):
430 report.done(Path("f1"), black.Changed.NO)
431 self.assertEqual(len(out_lines), 1)
432 self.assertEqual(len(err_lines), 0)
433 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
434 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
435 self.assertEqual(report.return_code, 0)
436 report.done(Path("f2"), black.Changed.YES)
437 self.assertEqual(len(out_lines), 2)
438 self.assertEqual(len(err_lines), 0)
439 self.assertEqual(out_lines[-1], "reformatted f2")
441 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
443 report.done(Path("f3"), black.Changed.CACHED)
444 self.assertEqual(len(out_lines), 3)
445 self.assertEqual(len(err_lines), 0)
447 out_lines[-1], "f3 wasn't modified on disk since last run."
450 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
452 self.assertEqual(report.return_code, 0)
454 self.assertEqual(report.return_code, 1)
456 report.failed(Path("e1"), "boom")
457 self.assertEqual(len(out_lines), 3)
458 self.assertEqual(len(err_lines), 1)
459 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
461 unstyle(str(report)),
462 "1 file reformatted, 2 files left unchanged, "
463 "1 file failed to reformat.",
465 self.assertEqual(report.return_code, 123)
466 report.done(Path("f3"), black.Changed.YES)
467 self.assertEqual(len(out_lines), 4)
468 self.assertEqual(len(err_lines), 1)
469 self.assertEqual(out_lines[-1], "reformatted f3")
471 unstyle(str(report)),
472 "2 files reformatted, 2 files left unchanged, "
473 "1 file failed to reformat.",
475 self.assertEqual(report.return_code, 123)
476 report.failed(Path("e2"), "boom")
477 self.assertEqual(len(out_lines), 4)
478 self.assertEqual(len(err_lines), 2)
479 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
481 unstyle(str(report)),
482 "2 files reformatted, 2 files left unchanged, "
483 "2 files failed to reformat.",
485 self.assertEqual(report.return_code, 123)
486 report.path_ignored(Path("wat"), "no match")
487 self.assertEqual(len(out_lines), 5)
488 self.assertEqual(len(err_lines), 2)
489 self.assertEqual(out_lines[-1], "wat ignored: no match")
491 unstyle(str(report)),
492 "2 files reformatted, 2 files left unchanged, "
493 "2 files failed to reformat.",
495 self.assertEqual(report.return_code, 123)
496 report.done(Path("f4"), black.Changed.NO)
497 self.assertEqual(len(out_lines), 6)
498 self.assertEqual(len(err_lines), 2)
499 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
501 unstyle(str(report)),
502 "2 files reformatted, 3 files left unchanged, "
503 "2 files failed to reformat.",
505 self.assertEqual(report.return_code, 123)
508 unstyle(str(report)),
509 "2 files would be reformatted, 3 files would be left unchanged, "
510 "2 files would fail to reformat.",
513 def test_report_quiet(self) -> None:
514 report = black.Report(quiet=True)
518 def out(msg: str, **kwargs: Any) -> None:
519 out_lines.append(msg)
521 def err(msg: str, **kwargs: Any) -> None:
522 err_lines.append(msg)
524 with patch("black.out", out), patch("black.err", err):
525 report.done(Path("f1"), black.Changed.NO)
526 self.assertEqual(len(out_lines), 0)
527 self.assertEqual(len(err_lines), 0)
528 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
529 self.assertEqual(report.return_code, 0)
530 report.done(Path("f2"), black.Changed.YES)
531 self.assertEqual(len(out_lines), 0)
532 self.assertEqual(len(err_lines), 0)
534 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
536 report.done(Path("f3"), black.Changed.CACHED)
537 self.assertEqual(len(out_lines), 0)
538 self.assertEqual(len(err_lines), 0)
540 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
542 self.assertEqual(report.return_code, 0)
544 self.assertEqual(report.return_code, 1)
546 report.failed(Path("e1"), "boom")
547 self.assertEqual(len(out_lines), 0)
548 self.assertEqual(len(err_lines), 1)
549 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
551 unstyle(str(report)),
552 "1 file reformatted, 2 files left unchanged, "
553 "1 file failed to reformat.",
555 self.assertEqual(report.return_code, 123)
556 report.done(Path("f3"), black.Changed.YES)
557 self.assertEqual(len(out_lines), 0)
558 self.assertEqual(len(err_lines), 1)
560 unstyle(str(report)),
561 "2 files reformatted, 2 files left unchanged, "
562 "1 file failed to reformat.",
564 self.assertEqual(report.return_code, 123)
565 report.failed(Path("e2"), "boom")
566 self.assertEqual(len(out_lines), 0)
567 self.assertEqual(len(err_lines), 2)
568 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
570 unstyle(str(report)),
571 "2 files reformatted, 2 files left unchanged, "
572 "2 files failed to reformat.",
574 self.assertEqual(report.return_code, 123)
575 report.path_ignored(Path("wat"), "no match")
576 self.assertEqual(len(out_lines), 0)
577 self.assertEqual(len(err_lines), 2)
579 unstyle(str(report)),
580 "2 files reformatted, 2 files left unchanged, "
581 "2 files failed to reformat.",
583 self.assertEqual(report.return_code, 123)
584 report.done(Path("f4"), black.Changed.NO)
585 self.assertEqual(len(out_lines), 0)
586 self.assertEqual(len(err_lines), 2)
588 unstyle(str(report)),
589 "2 files reformatted, 3 files left unchanged, "
590 "2 files failed to reformat.",
592 self.assertEqual(report.return_code, 123)
595 unstyle(str(report)),
596 "2 files would be reformatted, 3 files would be left unchanged, "
597 "2 files would fail to reformat.",
600 def test_report_normal(self) -> None:
601 report = black.Report()
605 def out(msg: str, **kwargs: Any) -> None:
606 out_lines.append(msg)
608 def err(msg: str, **kwargs: Any) -> None:
609 err_lines.append(msg)
611 with patch("black.out", out), patch("black.err", err):
612 report.done(Path("f1"), black.Changed.NO)
613 self.assertEqual(len(out_lines), 0)
614 self.assertEqual(len(err_lines), 0)
615 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
616 self.assertEqual(report.return_code, 0)
617 report.done(Path("f2"), black.Changed.YES)
618 self.assertEqual(len(out_lines), 1)
619 self.assertEqual(len(err_lines), 0)
620 self.assertEqual(out_lines[-1], "reformatted f2")
622 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
624 report.done(Path("f3"), black.Changed.CACHED)
625 self.assertEqual(len(out_lines), 1)
626 self.assertEqual(len(err_lines), 0)
627 self.assertEqual(out_lines[-1], "reformatted f2")
629 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
631 self.assertEqual(report.return_code, 0)
633 self.assertEqual(report.return_code, 1)
635 report.failed(Path("e1"), "boom")
636 self.assertEqual(len(out_lines), 1)
637 self.assertEqual(len(err_lines), 1)
638 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
640 unstyle(str(report)),
641 "1 file reformatted, 2 files left unchanged, "
642 "1 file failed to reformat.",
644 self.assertEqual(report.return_code, 123)
645 report.done(Path("f3"), black.Changed.YES)
646 self.assertEqual(len(out_lines), 2)
647 self.assertEqual(len(err_lines), 1)
648 self.assertEqual(out_lines[-1], "reformatted f3")
650 unstyle(str(report)),
651 "2 files reformatted, 2 files left unchanged, "
652 "1 file failed to reformat.",
654 self.assertEqual(report.return_code, 123)
655 report.failed(Path("e2"), "boom")
656 self.assertEqual(len(out_lines), 2)
657 self.assertEqual(len(err_lines), 2)
658 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
660 unstyle(str(report)),
661 "2 files reformatted, 2 files left unchanged, "
662 "2 files failed to reformat.",
664 self.assertEqual(report.return_code, 123)
665 report.path_ignored(Path("wat"), "no match")
666 self.assertEqual(len(out_lines), 2)
667 self.assertEqual(len(err_lines), 2)
669 unstyle(str(report)),
670 "2 files reformatted, 2 files left unchanged, "
671 "2 files failed to reformat.",
673 self.assertEqual(report.return_code, 123)
674 report.done(Path("f4"), black.Changed.NO)
675 self.assertEqual(len(out_lines), 2)
676 self.assertEqual(len(err_lines), 2)
678 unstyle(str(report)),
679 "2 files reformatted, 3 files left unchanged, "
680 "2 files failed to reformat.",
682 self.assertEqual(report.return_code, 123)
685 unstyle(str(report)),
686 "2 files would be reformatted, 3 files would be left unchanged, "
687 "2 files would fail to reformat.",
690 def test_is_python36(self) -> None:
691 node = black.lib2to3_parse("def f(*, arg): ...\n")
692 self.assertFalse(black.is_python36(node))
693 node = black.lib2to3_parse("def f(*, arg,): ...\n")
694 self.assertTrue(black.is_python36(node))
695 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
696 self.assertTrue(black.is_python36(node))
697 source, expected = read_data("function")
698 node = black.lib2to3_parse(source)
699 self.assertTrue(black.is_python36(node))
700 node = black.lib2to3_parse(expected)
701 self.assertTrue(black.is_python36(node))
702 source, expected = read_data("expression")
703 node = black.lib2to3_parse(source)
704 self.assertFalse(black.is_python36(node))
705 node = black.lib2to3_parse(expected)
706 self.assertFalse(black.is_python36(node))
708 def test_get_future_imports(self) -> None:
709 node = black.lib2to3_parse("\n")
710 self.assertEqual(set(), black.get_future_imports(node))
711 node = black.lib2to3_parse("from __future__ import black\n")
712 self.assertEqual({"black"}, black.get_future_imports(node))
713 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
714 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
715 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
716 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
717 node = black.lib2to3_parse(
718 "from __future__ import multiple\nfrom __future__ import imports\n"
720 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
721 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
722 self.assertEqual({"black"}, black.get_future_imports(node))
723 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
724 self.assertEqual({"black"}, black.get_future_imports(node))
725 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
726 self.assertEqual(set(), black.get_future_imports(node))
727 node = black.lib2to3_parse("from some.module import black\n")
728 self.assertEqual(set(), black.get_future_imports(node))
730 def test_debug_visitor(self) -> None:
731 source, _ = read_data("debug_visitor.py")
732 expected, _ = read_data("debug_visitor.out")
736 def out(msg: str, **kwargs: Any) -> None:
737 out_lines.append(msg)
739 def err(msg: str, **kwargs: Any) -> None:
740 err_lines.append(msg)
742 with patch("black.out", out), patch("black.err", err):
743 black.DebugVisitor.show(source)
744 actual = "\n".join(out_lines) + "\n"
746 if expected != actual:
747 log_name = black.dump_to_file(*out_lines)
751 f"AST print out is different. Actual version dumped to {log_name}",
754 def test_format_file_contents(self) -> None:
756 with self.assertRaises(black.NothingChanged):
757 black.format_file_contents(empty, line_length=ll, fast=False)
759 with self.assertRaises(black.NothingChanged):
760 black.format_file_contents(just_nl, line_length=ll, fast=False)
761 same = "l = [1, 2, 3]\n"
762 with self.assertRaises(black.NothingChanged):
763 black.format_file_contents(same, line_length=ll, fast=False)
764 different = "l = [1,2,3]"
766 actual = black.format_file_contents(different, line_length=ll, fast=False)
767 self.assertEqual(expected, actual)
768 invalid = "return if you can"
769 with self.assertRaises(ValueError) as e:
770 black.format_file_contents(invalid, line_length=ll, fast=False)
771 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
773 def test_endmarker(self) -> None:
774 n = black.lib2to3_parse("\n")
775 self.assertEqual(n.type, black.syms.file_input)
776 self.assertEqual(len(n.children), 1)
777 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
779 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
780 def test_assertFormatEqual(self) -> None:
784 def out(msg: str, **kwargs: Any) -> None:
785 out_lines.append(msg)
787 def err(msg: str, **kwargs: Any) -> None:
788 err_lines.append(msg)
790 with patch("black.out", out), patch("black.err", err):
791 with self.assertRaises(AssertionError):
792 self.assertFormatEqual("l = [1, 2, 3]", "l = [1, 2, 3,]")
794 out_str = "".join(out_lines)
795 self.assertTrue("Expected tree:" in out_str)
796 self.assertTrue("Actual tree:" in out_str)
797 self.assertEqual("".join(err_lines), "")
799 def test_cache_broken_file(self) -> None:
800 mode = black.FileMode.AUTO_DETECT
801 with cache_dir() as workspace:
802 cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode)
803 with cache_file.open("w") as fobj:
804 fobj.write("this is not a pickle")
805 self.assertEqual(black.read_cache(black.DEFAULT_LINE_LENGTH, mode), {})
806 src = (workspace / "test.py").resolve()
807 with src.open("w") as fobj:
808 fobj.write("print('hello')")
809 result = CliRunner().invoke(black.main, [str(src)])
810 self.assertEqual(result.exit_code, 0)
811 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
812 self.assertIn(src, cache)
814 def test_cache_single_file_already_cached(self) -> None:
815 mode = black.FileMode.AUTO_DETECT
816 with cache_dir() as workspace:
817 src = (workspace / "test.py").resolve()
818 with src.open("w") as fobj:
819 fobj.write("print('hello')")
820 black.write_cache({}, [src], black.DEFAULT_LINE_LENGTH, mode)
821 result = CliRunner().invoke(black.main, [str(src)])
822 self.assertEqual(result.exit_code, 0)
823 with src.open("r") as fobj:
824 self.assertEqual(fobj.read(), "print('hello')")
826 @event_loop(close=False)
827 def test_cache_multiple_files(self) -> None:
828 mode = black.FileMode.AUTO_DETECT
829 with cache_dir() as workspace, patch(
830 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
832 one = (workspace / "one.py").resolve()
833 with one.open("w") as fobj:
834 fobj.write("print('hello')")
835 two = (workspace / "two.py").resolve()
836 with two.open("w") as fobj:
837 fobj.write("print('hello')")
838 black.write_cache({}, [one], black.DEFAULT_LINE_LENGTH, mode)
839 result = CliRunner().invoke(black.main, [str(workspace)])
840 self.assertEqual(result.exit_code, 0)
841 with one.open("r") as fobj:
842 self.assertEqual(fobj.read(), "print('hello')")
843 with two.open("r") as fobj:
844 self.assertEqual(fobj.read(), 'print("hello")\n')
845 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
846 self.assertIn(one, cache)
847 self.assertIn(two, cache)
849 def test_no_cache_when_writeback_diff(self) -> None:
850 mode = black.FileMode.AUTO_DETECT
851 with cache_dir() as workspace:
852 src = (workspace / "test.py").resolve()
853 with src.open("w") as fobj:
854 fobj.write("print('hello')")
855 result = CliRunner().invoke(black.main, [str(src), "--diff"])
856 self.assertEqual(result.exit_code, 0)
857 cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode)
858 self.assertFalse(cache_file.exists())
860 def test_no_cache_when_stdin(self) -> None:
861 mode = black.FileMode.AUTO_DETECT
863 result = CliRunner().invoke(black.main, ["-"], input="print('hello')")
864 self.assertEqual(result.exit_code, 0)
865 cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode)
866 self.assertFalse(cache_file.exists())
868 def test_read_cache_no_cachefile(self) -> None:
869 mode = black.FileMode.AUTO_DETECT
871 self.assertEqual(black.read_cache(black.DEFAULT_LINE_LENGTH, mode), {})
873 def test_write_cache_read_cache(self) -> None:
874 mode = black.FileMode.AUTO_DETECT
875 with cache_dir() as workspace:
876 src = (workspace / "test.py").resolve()
878 black.write_cache({}, [src], black.DEFAULT_LINE_LENGTH, mode)
879 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
880 self.assertIn(src, cache)
881 self.assertEqual(cache[src], black.get_cache_info(src))
883 def test_filter_cached(self) -> None:
884 with TemporaryDirectory() as workspace:
885 path = Path(workspace)
886 uncached = (path / "uncached").resolve()
887 cached = (path / "cached").resolve()
888 cached_but_changed = (path / "changed").resolve()
891 cached_but_changed.touch()
892 cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
893 todo, done = black.filter_cached(
894 cache, {uncached, cached, cached_but_changed}
896 self.assertEqual(todo, {uncached, cached_but_changed})
897 self.assertEqual(done, {cached})
899 def test_write_cache_creates_directory_if_needed(self) -> None:
900 mode = black.FileMode.AUTO_DETECT
901 with cache_dir(exists=False) as workspace:
902 self.assertFalse(workspace.exists())
903 black.write_cache({}, [], black.DEFAULT_LINE_LENGTH, mode)
904 self.assertTrue(workspace.exists())
906 @event_loop(close=False)
907 def test_failed_formatting_does_not_get_cached(self) -> None:
908 mode = black.FileMode.AUTO_DETECT
909 with cache_dir() as workspace, patch(
910 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
912 failing = (workspace / "failing.py").resolve()
913 with failing.open("w") as fobj:
914 fobj.write("not actually python")
915 clean = (workspace / "clean.py").resolve()
916 with clean.open("w") as fobj:
917 fobj.write('print("hello")\n')
918 result = CliRunner().invoke(black.main, [str(workspace)])
919 self.assertEqual(result.exit_code, 123)
920 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
921 self.assertNotIn(failing, cache)
922 self.assertIn(clean, cache)
924 def test_write_cache_write_fail(self) -> None:
925 mode = black.FileMode.AUTO_DETECT
926 with cache_dir(), patch.object(Path, "open") as mock:
927 mock.side_effect = OSError
928 black.write_cache({}, [], black.DEFAULT_LINE_LENGTH, mode)
930 @event_loop(close=False)
931 def test_check_diff_use_together(self) -> None:
933 # Files which will be reformatted.
934 src1 = (THIS_DIR / "string_quotes.py").resolve()
935 result = CliRunner().invoke(black.main, [str(src1), "--diff", "--check"])
936 self.assertEqual(result.exit_code, 1)
938 # Files which will not be reformatted.
939 src2 = (THIS_DIR / "composition.py").resolve()
940 result = CliRunner().invoke(black.main, [str(src2), "--diff", "--check"])
941 self.assertEqual(result.exit_code, 0)
943 # Multi file command.
944 result = CliRunner().invoke(
945 black.main, [str(src1), str(src2), "--diff", "--check"]
947 self.assertEqual(result.exit_code, 1, result.output)
949 def test_no_files(self) -> None:
951 # Without an argument, black exits with error code 0.
952 result = CliRunner().invoke(black.main, [])
953 self.assertEqual(result.exit_code, 0)
955 def test_broken_symlink(self) -> None:
956 with cache_dir() as workspace:
957 symlink = workspace / "broken_link.py"
959 symlink.symlink_to("nonexistent.py")
961 self.skipTest(f"Can't create symlinks: {e}")
962 result = CliRunner().invoke(black.main, [str(workspace.resolve())])
963 self.assertEqual(result.exit_code, 0)
965 def test_read_cache_line_lengths(self) -> None:
966 mode = black.FileMode.AUTO_DETECT
967 with cache_dir() as workspace:
968 path = (workspace / "file.py").resolve()
970 black.write_cache({}, [path], 1, mode)
971 one = black.read_cache(1, mode)
972 self.assertIn(path, one)
973 two = black.read_cache(2, mode)
974 self.assertNotIn(path, two)
976 def test_single_file_force_pyi(self) -> None:
977 reg_mode = black.FileMode.AUTO_DETECT
978 pyi_mode = black.FileMode.PYI
979 contents, expected = read_data("force_pyi")
980 with cache_dir() as workspace:
981 path = (workspace / "file.py").resolve()
982 with open(path, "w") as fh:
984 result = CliRunner().invoke(black.main, [str(path), "--pyi"])
985 self.assertEqual(result.exit_code, 0)
986 with open(path, "r") as fh:
988 # verify cache with --pyi is separate
989 pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, pyi_mode)
990 self.assertIn(path, pyi_cache)
991 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
992 self.assertNotIn(path, normal_cache)
993 self.assertEqual(actual, expected)
995 @event_loop(close=False)
996 def test_multi_file_force_pyi(self) -> None:
997 reg_mode = black.FileMode.AUTO_DETECT
998 pyi_mode = black.FileMode.PYI
999 contents, expected = read_data("force_pyi")
1000 with cache_dir() as workspace:
1002 (workspace / "file1.py").resolve(),
1003 (workspace / "file2.py").resolve(),
1006 with open(path, "w") as fh:
1008 result = CliRunner().invoke(black.main, [str(p) for p in paths] + ["--pyi"])
1009 self.assertEqual(result.exit_code, 0)
1011 with open(path, "r") as fh:
1013 self.assertEqual(actual, expected)
1014 # verify cache with --pyi is separate
1015 pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, pyi_mode)
1016 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1018 self.assertIn(path, pyi_cache)
1019 self.assertNotIn(path, normal_cache)
1021 def test_pipe_force_pyi(self) -> None:
1022 source, expected = read_data("force_pyi")
1023 result = CliRunner().invoke(black.main, ["-", "-q", "--pyi"], input=source)
1024 self.assertEqual(result.exit_code, 0)
1025 actual = result.output
1026 self.assertFormatEqual(actual, expected)
1028 def test_single_file_force_py36(self) -> None:
1029 reg_mode = black.FileMode.AUTO_DETECT
1030 py36_mode = black.FileMode.PYTHON36
1031 source, expected = read_data("force_py36")
1032 with cache_dir() as workspace:
1033 path = (workspace / "file.py").resolve()
1034 with open(path, "w") as fh:
1036 result = CliRunner().invoke(black.main, [str(path), "--py36"])
1037 self.assertEqual(result.exit_code, 0)
1038 with open(path, "r") as fh:
1040 # verify cache with --py36 is separate
1041 py36_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, py36_mode)
1042 self.assertIn(path, py36_cache)
1043 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1044 self.assertNotIn(path, normal_cache)
1045 self.assertEqual(actual, expected)
1047 @event_loop(close=False)
1048 def test_multi_file_force_py36(self) -> None:
1049 reg_mode = black.FileMode.AUTO_DETECT
1050 py36_mode = black.FileMode.PYTHON36
1051 source, expected = read_data("force_py36")
1052 with cache_dir() as workspace:
1054 (workspace / "file1.py").resolve(),
1055 (workspace / "file2.py").resolve(),
1058 with open(path, "w") as fh:
1060 result = CliRunner().invoke(
1061 black.main, [str(p) for p in paths] + ["--py36"]
1063 self.assertEqual(result.exit_code, 0)
1065 with open(path, "r") as fh:
1067 self.assertEqual(actual, expected)
1068 # verify cache with --py36 is separate
1069 pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, py36_mode)
1070 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1072 self.assertIn(path, pyi_cache)
1073 self.assertNotIn(path, normal_cache)
1075 def test_pipe_force_py36(self) -> None:
1076 source, expected = read_data("force_py36")
1077 result = CliRunner().invoke(black.main, ["-", "-q", "--py36"], input=source)
1078 self.assertEqual(result.exit_code, 0)
1079 actual = result.output
1080 self.assertFormatEqual(actual, expected)
1082 def test_include_exclude(self) -> None:
1083 path = THIS_DIR / "include_exclude_tests"
1084 include = re.compile(r"\.pyi?$")
1085 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1086 report = black.Report()
1087 sources: List[Path] = []
1089 Path(THIS_DIR / "include_exclude_tests/b/dont_exclude/a.py"),
1090 Path(THIS_DIR / "include_exclude_tests/b/dont_exclude/a.pyi"),
1092 this_abs = THIS_DIR.resolve()
1094 black.gen_python_files_in_dir(path, this_abs, include, exclude, report)
1096 self.assertEqual(sorted(expected), sorted(sources))
1098 def test_empty_include(self) -> None:
1099 path = THIS_DIR / "include_exclude_tests"
1100 report = black.Report()
1101 empty = re.compile(r"")
1102 sources: List[Path] = []
1104 Path(path / "b/exclude/a.pie"),
1105 Path(path / "b/exclude/a.py"),
1106 Path(path / "b/exclude/a.pyi"),
1107 Path(path / "b/dont_exclude/a.pie"),
1108 Path(path / "b/dont_exclude/a.py"),
1109 Path(path / "b/dont_exclude/a.pyi"),
1110 Path(path / "b/.definitely_exclude/a.pie"),
1111 Path(path / "b/.definitely_exclude/a.py"),
1112 Path(path / "b/.definitely_exclude/a.pyi"),
1114 this_abs = THIS_DIR.resolve()
1116 black.gen_python_files_in_dir(
1117 path, this_abs, empty, re.compile(black.DEFAULT_EXCLUDES), report
1120 self.assertEqual(sorted(expected), sorted(sources))
1122 def test_empty_exclude(self) -> None:
1123 path = THIS_DIR / "include_exclude_tests"
1124 report = black.Report()
1125 empty = re.compile(r"")
1126 sources: List[Path] = []
1128 Path(path / "b/dont_exclude/a.py"),
1129 Path(path / "b/dont_exclude/a.pyi"),
1130 Path(path / "b/exclude/a.py"),
1131 Path(path / "b/exclude/a.pyi"),
1132 Path(path / "b/.definitely_exclude/a.py"),
1133 Path(path / "b/.definitely_exclude/a.pyi"),
1135 this_abs = THIS_DIR.resolve()
1137 black.gen_python_files_in_dir(
1138 path, this_abs, re.compile(black.DEFAULT_INCLUDES), empty, report
1141 self.assertEqual(sorted(expected), sorted(sources))
1143 def test_invalid_include_exclude(self) -> None:
1144 for option in ["--include", "--exclude"]:
1145 result = CliRunner().invoke(black.main, ["-", option, "**()(!!*)"])
1146 self.assertEqual(result.exit_code, 2)
1148 def test_preserves_line_endings(self) -> None:
1149 with TemporaryDirectory() as workspace:
1150 test_file = Path(workspace) / "test.py"
1151 for nl in ["\n", "\r\n"]:
1152 contents = nl.join(["def f( ):", " pass"])
1153 test_file.write_bytes(contents.encode())
1154 ff(test_file, write_back=black.WriteBack.YES)
1155 updated_contents: bytes = test_file.read_bytes()
1156 self.assertIn(nl.encode(), updated_contents) # type: ignore
1158 self.assertNotIn(b"\r\n", updated_contents) # type: ignore
1160 def test_assert_equivalent_different_asts(self) -> None:
1161 with self.assertRaises(AssertionError):
1162 black.assert_equivalent("{}", "None")
1165 if __name__ == "__main__":