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(
165 black.main, ["-", "--fast", f"--line-length={ll}"], input=source
167 self.assertEqual(result.exit_code, 0)
168 self.assertFormatEqual(expected, result.output)
169 black.assert_equivalent(source, result.output)
170 black.assert_stable(source, result.output, line_length=ll)
172 def test_piping_diff(self) -> None:
173 diff_header = re.compile(
174 rf"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d "
175 rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
177 source, _ = read_data("expression.py")
178 expected, _ = read_data("expression.diff")
179 config = THIS_DIR / "data" / "empty_pyproject.toml"
180 stderrbuf = BytesIO()
181 args = ["-", "--fast", f"--line-length={ll}", "--diff", f"--config={config}"]
182 result = BlackRunner(stderrbuf).invoke(black.main, args, input=source)
183 self.assertEqual(result.exit_code, 0)
184 actual = diff_header.sub("[Deterministic header]", result.output)
185 actual = actual.rstrip() + "\n" # the diff output has a trailing space
186 self.assertEqual(expected, actual)
188 @patch("black.dump_to_file", dump_to_stderr)
189 def test_setup(self) -> None:
190 source, expected = read_data("../setup", data=False)
192 self.assertFormatEqual(expected, actual)
193 black.assert_equivalent(source, actual)
194 black.assert_stable(source, actual, line_length=ll)
195 self.assertFalse(ff(THIS_DIR / ".." / "setup.py"))
197 @patch("black.dump_to_file", dump_to_stderr)
198 def test_function(self) -> None:
199 source, expected = read_data("function")
201 self.assertFormatEqual(expected, actual)
202 black.assert_equivalent(source, actual)
203 black.assert_stable(source, actual, line_length=ll)
205 @patch("black.dump_to_file", dump_to_stderr)
206 def test_function2(self) -> None:
207 source, expected = read_data("function2")
209 self.assertFormatEqual(expected, actual)
210 black.assert_equivalent(source, actual)
211 black.assert_stable(source, actual, line_length=ll)
213 @patch("black.dump_to_file", dump_to_stderr)
214 def test_expression(self) -> None:
215 source, expected = read_data("expression")
217 self.assertFormatEqual(expected, actual)
218 black.assert_equivalent(source, actual)
219 black.assert_stable(source, actual, line_length=ll)
221 def test_expression_ff(self) -> None:
222 source, expected = read_data("expression")
223 tmp_file = Path(black.dump_to_file(source))
225 self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
226 with open(tmp_file, encoding="utf8") as f:
230 self.assertFormatEqual(expected, actual)
231 with patch("black.dump_to_file", dump_to_stderr):
232 black.assert_equivalent(source, actual)
233 black.assert_stable(source, actual, line_length=ll)
235 def test_expression_diff(self) -> None:
236 source, _ = read_data("expression.py")
237 expected, _ = read_data("expression.diff")
238 tmp_file = Path(black.dump_to_file(source))
239 diff_header = re.compile(
240 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
241 rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
243 stderrbuf = BytesIO()
245 result = BlackRunner(stderrbuf).invoke(
246 black.main, ["--diff", str(tmp_file)]
248 self.assertEqual(result.exit_code, 0)
251 actual = result.output
252 actual = diff_header.sub("[Deterministic header]", actual)
253 actual = actual.rstrip() + "\n" # the diff output has a trailing space
254 if expected != actual:
255 dump = black.dump_to_file(actual)
257 f"Expected diff isn't equal to the actual. If you made changes "
258 f"to expression.py and this is an anticipated difference, "
259 f"overwrite tests/expression.diff with {dump}"
261 self.assertEqual(expected, actual, msg)
263 @patch("black.dump_to_file", dump_to_stderr)
264 def test_fstring(self) -> None:
265 source, expected = read_data("fstring")
267 self.assertFormatEqual(expected, actual)
268 black.assert_equivalent(source, actual)
269 black.assert_stable(source, actual, line_length=ll)
271 @patch("black.dump_to_file", dump_to_stderr)
272 def test_string_quotes(self) -> None:
273 source, expected = read_data("string_quotes")
275 self.assertFormatEqual(expected, actual)
276 black.assert_equivalent(source, actual)
277 black.assert_stable(source, actual, line_length=ll)
278 mode = black.FileMode.NO_STRING_NORMALIZATION
279 not_normalized = fs(source, mode=mode)
280 self.assertFormatEqual(source, not_normalized)
281 black.assert_equivalent(source, not_normalized)
282 black.assert_stable(source, not_normalized, line_length=ll, mode=mode)
284 @patch("black.dump_to_file", dump_to_stderr)
285 def test_slices(self) -> None:
286 source, expected = read_data("slices")
288 self.assertFormatEqual(expected, actual)
289 black.assert_equivalent(source, actual)
290 black.assert_stable(source, actual, line_length=ll)
292 @patch("black.dump_to_file", dump_to_stderr)
293 def test_comments(self) -> None:
294 source, expected = read_data("comments")
296 self.assertFormatEqual(expected, actual)
297 black.assert_equivalent(source, actual)
298 black.assert_stable(source, actual, line_length=ll)
300 @patch("black.dump_to_file", dump_to_stderr)
301 def test_comments2(self) -> None:
302 source, expected = read_data("comments2")
304 self.assertFormatEqual(expected, actual)
305 black.assert_equivalent(source, actual)
306 black.assert_stable(source, actual, line_length=ll)
308 @patch("black.dump_to_file", dump_to_stderr)
309 def test_comments3(self) -> None:
310 source, expected = read_data("comments3")
312 self.assertFormatEqual(expected, actual)
313 black.assert_equivalent(source, actual)
314 black.assert_stable(source, actual, line_length=ll)
316 @patch("black.dump_to_file", dump_to_stderr)
317 def test_comments4(self) -> None:
318 source, expected = read_data("comments4")
320 self.assertFormatEqual(expected, actual)
321 black.assert_equivalent(source, actual)
322 black.assert_stable(source, actual, line_length=ll)
324 @patch("black.dump_to_file", dump_to_stderr)
325 def test_comments5(self) -> None:
326 source, expected = read_data("comments5")
328 self.assertFormatEqual(expected, actual)
329 black.assert_equivalent(source, actual)
330 black.assert_stable(source, actual, line_length=ll)
332 @patch("black.dump_to_file", dump_to_stderr)
333 def test_cantfit(self) -> None:
334 source, expected = read_data("cantfit")
336 self.assertFormatEqual(expected, actual)
337 black.assert_equivalent(source, actual)
338 black.assert_stable(source, actual, line_length=ll)
340 @patch("black.dump_to_file", dump_to_stderr)
341 def test_import_spacing(self) -> None:
342 source, expected = read_data("import_spacing")
344 self.assertFormatEqual(expected, actual)
345 black.assert_equivalent(source, actual)
346 black.assert_stable(source, actual, line_length=ll)
348 @patch("black.dump_to_file", dump_to_stderr)
349 def test_composition(self) -> None:
350 source, expected = read_data("composition")
352 self.assertFormatEqual(expected, actual)
353 black.assert_equivalent(source, actual)
354 black.assert_stable(source, actual, line_length=ll)
356 @patch("black.dump_to_file", dump_to_stderr)
357 def test_empty_lines(self) -> None:
358 source, expected = read_data("empty_lines")
360 self.assertFormatEqual(expected, actual)
361 black.assert_equivalent(source, actual)
362 black.assert_stable(source, actual, line_length=ll)
364 @patch("black.dump_to_file", dump_to_stderr)
365 def test_string_prefixes(self) -> None:
366 source, expected = read_data("string_prefixes")
368 self.assertFormatEqual(expected, actual)
369 black.assert_equivalent(source, actual)
370 black.assert_stable(source, actual, line_length=ll)
372 @patch("black.dump_to_file", dump_to_stderr)
373 def test_python2(self) -> None:
374 source, expected = read_data("python2")
376 self.assertFormatEqual(expected, actual)
377 # black.assert_equivalent(source, actual)
378 black.assert_stable(source, actual, line_length=ll)
380 @patch("black.dump_to_file", dump_to_stderr)
381 def test_python2_unicode_literals(self) -> None:
382 source, expected = read_data("python2_unicode_literals")
384 self.assertFormatEqual(expected, actual)
385 black.assert_stable(source, actual, line_length=ll)
387 @patch("black.dump_to_file", dump_to_stderr)
388 def test_stub(self) -> None:
389 mode = black.FileMode.PYI
390 source, expected = read_data("stub.pyi")
391 actual = fs(source, mode=mode)
392 self.assertFormatEqual(expected, actual)
393 black.assert_stable(source, actual, line_length=ll, mode=mode)
395 @patch("black.dump_to_file", dump_to_stderr)
396 def test_fmtonoff(self) -> None:
397 source, expected = read_data("fmtonoff")
399 self.assertFormatEqual(expected, actual)
400 black.assert_equivalent(source, actual)
401 black.assert_stable(source, actual, line_length=ll)
403 @patch("black.dump_to_file", dump_to_stderr)
404 def test_remove_empty_parentheses_after_class(self) -> None:
405 source, expected = read_data("class_blank_parentheses")
407 self.assertFormatEqual(expected, actual)
408 black.assert_equivalent(source, actual)
409 black.assert_stable(source, actual, line_length=ll)
411 @patch("black.dump_to_file", dump_to_stderr)
412 def test_new_line_between_class_and_code(self) -> None:
413 source, expected = read_data("class_methods_new_line")
415 self.assertFormatEqual(expected, actual)
416 black.assert_equivalent(source, actual)
417 black.assert_stable(source, actual, line_length=ll)
419 def test_report_verbose(self) -> None:
420 report = black.Report(verbose=True)
424 def out(msg: str, **kwargs: Any) -> None:
425 out_lines.append(msg)
427 def err(msg: str, **kwargs: Any) -> None:
428 err_lines.append(msg)
430 with patch("black.out", out), patch("black.err", err):
431 report.done(Path("f1"), black.Changed.NO)
432 self.assertEqual(len(out_lines), 1)
433 self.assertEqual(len(err_lines), 0)
434 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
435 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
436 self.assertEqual(report.return_code, 0)
437 report.done(Path("f2"), black.Changed.YES)
438 self.assertEqual(len(out_lines), 2)
439 self.assertEqual(len(err_lines), 0)
440 self.assertEqual(out_lines[-1], "reformatted f2")
442 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
444 report.done(Path("f3"), black.Changed.CACHED)
445 self.assertEqual(len(out_lines), 3)
446 self.assertEqual(len(err_lines), 0)
448 out_lines[-1], "f3 wasn't modified on disk since last run."
451 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
453 self.assertEqual(report.return_code, 0)
455 self.assertEqual(report.return_code, 1)
457 report.failed(Path("e1"), "boom")
458 self.assertEqual(len(out_lines), 3)
459 self.assertEqual(len(err_lines), 1)
460 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
462 unstyle(str(report)),
463 "1 file reformatted, 2 files left unchanged, "
464 "1 file failed to reformat.",
466 self.assertEqual(report.return_code, 123)
467 report.done(Path("f3"), black.Changed.YES)
468 self.assertEqual(len(out_lines), 4)
469 self.assertEqual(len(err_lines), 1)
470 self.assertEqual(out_lines[-1], "reformatted f3")
472 unstyle(str(report)),
473 "2 files reformatted, 2 files left unchanged, "
474 "1 file failed to reformat.",
476 self.assertEqual(report.return_code, 123)
477 report.failed(Path("e2"), "boom")
478 self.assertEqual(len(out_lines), 4)
479 self.assertEqual(len(err_lines), 2)
480 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
482 unstyle(str(report)),
483 "2 files reformatted, 2 files left unchanged, "
484 "2 files failed to reformat.",
486 self.assertEqual(report.return_code, 123)
487 report.path_ignored(Path("wat"), "no match")
488 self.assertEqual(len(out_lines), 5)
489 self.assertEqual(len(err_lines), 2)
490 self.assertEqual(out_lines[-1], "wat ignored: no match")
492 unstyle(str(report)),
493 "2 files reformatted, 2 files left unchanged, "
494 "2 files failed to reformat.",
496 self.assertEqual(report.return_code, 123)
497 report.done(Path("f4"), black.Changed.NO)
498 self.assertEqual(len(out_lines), 6)
499 self.assertEqual(len(err_lines), 2)
500 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
502 unstyle(str(report)),
503 "2 files reformatted, 3 files left unchanged, "
504 "2 files failed to reformat.",
506 self.assertEqual(report.return_code, 123)
509 unstyle(str(report)),
510 "2 files would be reformatted, 3 files would be left unchanged, "
511 "2 files would fail to reformat.",
514 def test_report_quiet(self) -> None:
515 report = black.Report(quiet=True)
519 def out(msg: str, **kwargs: Any) -> None:
520 out_lines.append(msg)
522 def err(msg: str, **kwargs: Any) -> None:
523 err_lines.append(msg)
525 with patch("black.out", out), patch("black.err", err):
526 report.done(Path("f1"), black.Changed.NO)
527 self.assertEqual(len(out_lines), 0)
528 self.assertEqual(len(err_lines), 0)
529 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
530 self.assertEqual(report.return_code, 0)
531 report.done(Path("f2"), black.Changed.YES)
532 self.assertEqual(len(out_lines), 0)
533 self.assertEqual(len(err_lines), 0)
535 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
537 report.done(Path("f3"), black.Changed.CACHED)
538 self.assertEqual(len(out_lines), 0)
539 self.assertEqual(len(err_lines), 0)
541 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
543 self.assertEqual(report.return_code, 0)
545 self.assertEqual(report.return_code, 1)
547 report.failed(Path("e1"), "boom")
548 self.assertEqual(len(out_lines), 0)
549 self.assertEqual(len(err_lines), 1)
550 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
552 unstyle(str(report)),
553 "1 file reformatted, 2 files left unchanged, "
554 "1 file failed to reformat.",
556 self.assertEqual(report.return_code, 123)
557 report.done(Path("f3"), black.Changed.YES)
558 self.assertEqual(len(out_lines), 0)
559 self.assertEqual(len(err_lines), 1)
561 unstyle(str(report)),
562 "2 files reformatted, 2 files left unchanged, "
563 "1 file failed to reformat.",
565 self.assertEqual(report.return_code, 123)
566 report.failed(Path("e2"), "boom")
567 self.assertEqual(len(out_lines), 0)
568 self.assertEqual(len(err_lines), 2)
569 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
571 unstyle(str(report)),
572 "2 files reformatted, 2 files left unchanged, "
573 "2 files failed to reformat.",
575 self.assertEqual(report.return_code, 123)
576 report.path_ignored(Path("wat"), "no match")
577 self.assertEqual(len(out_lines), 0)
578 self.assertEqual(len(err_lines), 2)
580 unstyle(str(report)),
581 "2 files reformatted, 2 files left unchanged, "
582 "2 files failed to reformat.",
584 self.assertEqual(report.return_code, 123)
585 report.done(Path("f4"), black.Changed.NO)
586 self.assertEqual(len(out_lines), 0)
587 self.assertEqual(len(err_lines), 2)
589 unstyle(str(report)),
590 "2 files reformatted, 3 files left unchanged, "
591 "2 files failed to reformat.",
593 self.assertEqual(report.return_code, 123)
596 unstyle(str(report)),
597 "2 files would be reformatted, 3 files would be left unchanged, "
598 "2 files would fail to reformat.",
601 def test_report_normal(self) -> None:
602 report = black.Report()
606 def out(msg: str, **kwargs: Any) -> None:
607 out_lines.append(msg)
609 def err(msg: str, **kwargs: Any) -> None:
610 err_lines.append(msg)
612 with patch("black.out", out), patch("black.err", err):
613 report.done(Path("f1"), black.Changed.NO)
614 self.assertEqual(len(out_lines), 0)
615 self.assertEqual(len(err_lines), 0)
616 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
617 self.assertEqual(report.return_code, 0)
618 report.done(Path("f2"), black.Changed.YES)
619 self.assertEqual(len(out_lines), 1)
620 self.assertEqual(len(err_lines), 0)
621 self.assertEqual(out_lines[-1], "reformatted f2")
623 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
625 report.done(Path("f3"), black.Changed.CACHED)
626 self.assertEqual(len(out_lines), 1)
627 self.assertEqual(len(err_lines), 0)
628 self.assertEqual(out_lines[-1], "reformatted f2")
630 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
632 self.assertEqual(report.return_code, 0)
634 self.assertEqual(report.return_code, 1)
636 report.failed(Path("e1"), "boom")
637 self.assertEqual(len(out_lines), 1)
638 self.assertEqual(len(err_lines), 1)
639 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
641 unstyle(str(report)),
642 "1 file reformatted, 2 files left unchanged, "
643 "1 file failed to reformat.",
645 self.assertEqual(report.return_code, 123)
646 report.done(Path("f3"), black.Changed.YES)
647 self.assertEqual(len(out_lines), 2)
648 self.assertEqual(len(err_lines), 1)
649 self.assertEqual(out_lines[-1], "reformatted f3")
651 unstyle(str(report)),
652 "2 files reformatted, 2 files left unchanged, "
653 "1 file failed to reformat.",
655 self.assertEqual(report.return_code, 123)
656 report.failed(Path("e2"), "boom")
657 self.assertEqual(len(out_lines), 2)
658 self.assertEqual(len(err_lines), 2)
659 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
661 unstyle(str(report)),
662 "2 files reformatted, 2 files left unchanged, "
663 "2 files failed to reformat.",
665 self.assertEqual(report.return_code, 123)
666 report.path_ignored(Path("wat"), "no match")
667 self.assertEqual(len(out_lines), 2)
668 self.assertEqual(len(err_lines), 2)
670 unstyle(str(report)),
671 "2 files reformatted, 2 files left unchanged, "
672 "2 files failed to reformat.",
674 self.assertEqual(report.return_code, 123)
675 report.done(Path("f4"), black.Changed.NO)
676 self.assertEqual(len(out_lines), 2)
677 self.assertEqual(len(err_lines), 2)
679 unstyle(str(report)),
680 "2 files reformatted, 3 files left unchanged, "
681 "2 files failed to reformat.",
683 self.assertEqual(report.return_code, 123)
686 unstyle(str(report)),
687 "2 files would be reformatted, 3 files would be left unchanged, "
688 "2 files would fail to reformat.",
691 def test_is_python36(self) -> None:
692 node = black.lib2to3_parse("def f(*, arg): ...\n")
693 self.assertFalse(black.is_python36(node))
694 node = black.lib2to3_parse("def f(*, arg,): ...\n")
695 self.assertTrue(black.is_python36(node))
696 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
697 self.assertTrue(black.is_python36(node))
698 source, expected = read_data("function")
699 node = black.lib2to3_parse(source)
700 self.assertTrue(black.is_python36(node))
701 node = black.lib2to3_parse(expected)
702 self.assertTrue(black.is_python36(node))
703 source, expected = read_data("expression")
704 node = black.lib2to3_parse(source)
705 self.assertFalse(black.is_python36(node))
706 node = black.lib2to3_parse(expected)
707 self.assertFalse(black.is_python36(node))
709 def test_get_future_imports(self) -> None:
710 node = black.lib2to3_parse("\n")
711 self.assertEqual(set(), black.get_future_imports(node))
712 node = black.lib2to3_parse("from __future__ import black\n")
713 self.assertEqual({"black"}, black.get_future_imports(node))
714 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
715 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
716 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
717 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
718 node = black.lib2to3_parse(
719 "from __future__ import multiple\nfrom __future__ import imports\n"
721 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
722 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
723 self.assertEqual({"black"}, black.get_future_imports(node))
724 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
725 self.assertEqual({"black"}, black.get_future_imports(node))
726 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
727 self.assertEqual(set(), black.get_future_imports(node))
728 node = black.lib2to3_parse("from some.module import black\n")
729 self.assertEqual(set(), black.get_future_imports(node))
731 def test_debug_visitor(self) -> None:
732 source, _ = read_data("debug_visitor.py")
733 expected, _ = read_data("debug_visitor.out")
737 def out(msg: str, **kwargs: Any) -> None:
738 out_lines.append(msg)
740 def err(msg: str, **kwargs: Any) -> None:
741 err_lines.append(msg)
743 with patch("black.out", out), patch("black.err", err):
744 black.DebugVisitor.show(source)
745 actual = "\n".join(out_lines) + "\n"
747 if expected != actual:
748 log_name = black.dump_to_file(*out_lines)
752 f"AST print out is different. Actual version dumped to {log_name}",
755 def test_format_file_contents(self) -> None:
757 with self.assertRaises(black.NothingChanged):
758 black.format_file_contents(empty, line_length=ll, fast=False)
760 with self.assertRaises(black.NothingChanged):
761 black.format_file_contents(just_nl, line_length=ll, fast=False)
762 same = "l = [1, 2, 3]\n"
763 with self.assertRaises(black.NothingChanged):
764 black.format_file_contents(same, line_length=ll, fast=False)
765 different = "l = [1,2,3]"
767 actual = black.format_file_contents(different, line_length=ll, fast=False)
768 self.assertEqual(expected, actual)
769 invalid = "return if you can"
770 with self.assertRaises(ValueError) as e:
771 black.format_file_contents(invalid, line_length=ll, fast=False)
772 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
774 def test_endmarker(self) -> None:
775 n = black.lib2to3_parse("\n")
776 self.assertEqual(n.type, black.syms.file_input)
777 self.assertEqual(len(n.children), 1)
778 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
780 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
781 def test_assertFormatEqual(self) -> None:
785 def out(msg: str, **kwargs: Any) -> None:
786 out_lines.append(msg)
788 def err(msg: str, **kwargs: Any) -> None:
789 err_lines.append(msg)
791 with patch("black.out", out), patch("black.err", err):
792 with self.assertRaises(AssertionError):
793 self.assertFormatEqual("l = [1, 2, 3]", "l = [1, 2, 3,]")
795 out_str = "".join(out_lines)
796 self.assertTrue("Expected tree:" in out_str)
797 self.assertTrue("Actual tree:" in out_str)
798 self.assertEqual("".join(err_lines), "")
800 def test_cache_broken_file(self) -> None:
801 mode = black.FileMode.AUTO_DETECT
802 with cache_dir() as workspace:
803 cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode)
804 with cache_file.open("w") as fobj:
805 fobj.write("this is not a pickle")
806 self.assertEqual(black.read_cache(black.DEFAULT_LINE_LENGTH, mode), {})
807 src = (workspace / "test.py").resolve()
808 with src.open("w") as fobj:
809 fobj.write("print('hello')")
810 result = CliRunner().invoke(black.main, [str(src)])
811 self.assertEqual(result.exit_code, 0)
812 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
813 self.assertIn(src, cache)
815 def test_cache_single_file_already_cached(self) -> None:
816 mode = black.FileMode.AUTO_DETECT
817 with cache_dir() as workspace:
818 src = (workspace / "test.py").resolve()
819 with src.open("w") as fobj:
820 fobj.write("print('hello')")
821 black.write_cache({}, [src], black.DEFAULT_LINE_LENGTH, mode)
822 result = CliRunner().invoke(black.main, [str(src)])
823 self.assertEqual(result.exit_code, 0)
824 with src.open("r") as fobj:
825 self.assertEqual(fobj.read(), "print('hello')")
827 @event_loop(close=False)
828 def test_cache_multiple_files(self) -> None:
829 mode = black.FileMode.AUTO_DETECT
830 with cache_dir() as workspace, patch(
831 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
833 one = (workspace / "one.py").resolve()
834 with one.open("w") as fobj:
835 fobj.write("print('hello')")
836 two = (workspace / "two.py").resolve()
837 with two.open("w") as fobj:
838 fobj.write("print('hello')")
839 black.write_cache({}, [one], black.DEFAULT_LINE_LENGTH, mode)
840 result = CliRunner().invoke(black.main, [str(workspace)])
841 self.assertEqual(result.exit_code, 0)
842 with one.open("r") as fobj:
843 self.assertEqual(fobj.read(), "print('hello')")
844 with two.open("r") as fobj:
845 self.assertEqual(fobj.read(), 'print("hello")\n')
846 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
847 self.assertIn(one, cache)
848 self.assertIn(two, cache)
850 def test_no_cache_when_writeback_diff(self) -> None:
851 mode = black.FileMode.AUTO_DETECT
852 with cache_dir() as workspace:
853 src = (workspace / "test.py").resolve()
854 with src.open("w") as fobj:
855 fobj.write("print('hello')")
856 result = CliRunner().invoke(black.main, [str(src), "--diff"])
857 self.assertEqual(result.exit_code, 0)
858 cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode)
859 self.assertFalse(cache_file.exists())
861 def test_no_cache_when_stdin(self) -> None:
862 mode = black.FileMode.AUTO_DETECT
864 result = CliRunner().invoke(black.main, ["-"], input="print('hello')")
865 self.assertEqual(result.exit_code, 0)
866 cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode)
867 self.assertFalse(cache_file.exists())
869 def test_read_cache_no_cachefile(self) -> None:
870 mode = black.FileMode.AUTO_DETECT
872 self.assertEqual(black.read_cache(black.DEFAULT_LINE_LENGTH, mode), {})
874 def test_write_cache_read_cache(self) -> None:
875 mode = black.FileMode.AUTO_DETECT
876 with cache_dir() as workspace:
877 src = (workspace / "test.py").resolve()
879 black.write_cache({}, [src], black.DEFAULT_LINE_LENGTH, mode)
880 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
881 self.assertIn(src, cache)
882 self.assertEqual(cache[src], black.get_cache_info(src))
884 def test_filter_cached(self) -> None:
885 with TemporaryDirectory() as workspace:
886 path = Path(workspace)
887 uncached = (path / "uncached").resolve()
888 cached = (path / "cached").resolve()
889 cached_but_changed = (path / "changed").resolve()
892 cached_but_changed.touch()
893 cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
894 todo, done = black.filter_cached(
895 cache, {uncached, cached, cached_but_changed}
897 self.assertEqual(todo, {uncached, cached_but_changed})
898 self.assertEqual(done, {cached})
900 def test_write_cache_creates_directory_if_needed(self) -> None:
901 mode = black.FileMode.AUTO_DETECT
902 with cache_dir(exists=False) as workspace:
903 self.assertFalse(workspace.exists())
904 black.write_cache({}, [], black.DEFAULT_LINE_LENGTH, mode)
905 self.assertTrue(workspace.exists())
907 @event_loop(close=False)
908 def test_failed_formatting_does_not_get_cached(self) -> None:
909 mode = black.FileMode.AUTO_DETECT
910 with cache_dir() as workspace, patch(
911 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
913 failing = (workspace / "failing.py").resolve()
914 with failing.open("w") as fobj:
915 fobj.write("not actually python")
916 clean = (workspace / "clean.py").resolve()
917 with clean.open("w") as fobj:
918 fobj.write('print("hello")\n')
919 result = CliRunner().invoke(black.main, [str(workspace)])
920 self.assertEqual(result.exit_code, 123)
921 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
922 self.assertNotIn(failing, cache)
923 self.assertIn(clean, cache)
925 def test_write_cache_write_fail(self) -> None:
926 mode = black.FileMode.AUTO_DETECT
927 with cache_dir(), patch.object(Path, "open") as mock:
928 mock.side_effect = OSError
929 black.write_cache({}, [], black.DEFAULT_LINE_LENGTH, mode)
931 @event_loop(close=False)
932 def test_check_diff_use_together(self) -> None:
934 # Files which will be reformatted.
935 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
936 result = CliRunner().invoke(black.main, [str(src1), "--diff", "--check"])
937 self.assertEqual(result.exit_code, 1, result.output)
939 # Files which will not be reformatted.
940 src2 = (THIS_DIR / "data" / "composition.py").resolve()
941 result = CliRunner().invoke(black.main, [str(src2), "--diff", "--check"])
942 self.assertEqual(result.exit_code, 0, result.output)
944 # Multi file command.
945 result = CliRunner().invoke(
946 black.main, [str(src1), str(src2), "--diff", "--check"]
948 self.assertEqual(result.exit_code, 1, result.output)
950 def test_no_files(self) -> None:
952 # Without an argument, black exits with error code 0.
953 result = CliRunner().invoke(black.main, [])
954 self.assertEqual(result.exit_code, 0)
956 def test_broken_symlink(self) -> None:
957 with cache_dir() as workspace:
958 symlink = workspace / "broken_link.py"
960 symlink.symlink_to("nonexistent.py")
962 self.skipTest(f"Can't create symlinks: {e}")
963 result = CliRunner().invoke(black.main, [str(workspace.resolve())])
964 self.assertEqual(result.exit_code, 0)
966 def test_read_cache_line_lengths(self) -> None:
967 mode = black.FileMode.AUTO_DETECT
968 with cache_dir() as workspace:
969 path = (workspace / "file.py").resolve()
971 black.write_cache({}, [path], 1, mode)
972 one = black.read_cache(1, mode)
973 self.assertIn(path, one)
974 two = black.read_cache(2, mode)
975 self.assertNotIn(path, two)
977 def test_single_file_force_pyi(self) -> None:
978 reg_mode = black.FileMode.AUTO_DETECT
979 pyi_mode = black.FileMode.PYI
980 contents, expected = read_data("force_pyi")
981 with cache_dir() as workspace:
982 path = (workspace / "file.py").resolve()
983 with open(path, "w") as fh:
985 result = CliRunner().invoke(black.main, [str(path), "--pyi"])
986 self.assertEqual(result.exit_code, 0)
987 with open(path, "r") as fh:
989 # verify cache with --pyi is separate
990 pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, pyi_mode)
991 self.assertIn(path, pyi_cache)
992 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
993 self.assertNotIn(path, normal_cache)
994 self.assertEqual(actual, expected)
996 @event_loop(close=False)
997 def test_multi_file_force_pyi(self) -> None:
998 reg_mode = black.FileMode.AUTO_DETECT
999 pyi_mode = black.FileMode.PYI
1000 contents, expected = read_data("force_pyi")
1001 with cache_dir() as workspace:
1003 (workspace / "file1.py").resolve(),
1004 (workspace / "file2.py").resolve(),
1007 with open(path, "w") as fh:
1009 result = CliRunner().invoke(black.main, [str(p) for p in paths] + ["--pyi"])
1010 self.assertEqual(result.exit_code, 0)
1012 with open(path, "r") as fh:
1014 self.assertEqual(actual, expected)
1015 # verify cache with --pyi is separate
1016 pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, pyi_mode)
1017 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1019 self.assertIn(path, pyi_cache)
1020 self.assertNotIn(path, normal_cache)
1022 def test_pipe_force_pyi(self) -> None:
1023 source, expected = read_data("force_pyi")
1024 result = CliRunner().invoke(black.main, ["-", "-q", "--pyi"], input=source)
1025 self.assertEqual(result.exit_code, 0)
1026 actual = result.output
1027 self.assertFormatEqual(actual, expected)
1029 def test_single_file_force_py36(self) -> None:
1030 reg_mode = black.FileMode.AUTO_DETECT
1031 py36_mode = black.FileMode.PYTHON36
1032 source, expected = read_data("force_py36")
1033 with cache_dir() as workspace:
1034 path = (workspace / "file.py").resolve()
1035 with open(path, "w") as fh:
1037 result = CliRunner().invoke(black.main, [str(path), "--py36"])
1038 self.assertEqual(result.exit_code, 0)
1039 with open(path, "r") as fh:
1041 # verify cache with --py36 is separate
1042 py36_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, py36_mode)
1043 self.assertIn(path, py36_cache)
1044 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1045 self.assertNotIn(path, normal_cache)
1046 self.assertEqual(actual, expected)
1048 @event_loop(close=False)
1049 def test_multi_file_force_py36(self) -> None:
1050 reg_mode = black.FileMode.AUTO_DETECT
1051 py36_mode = black.FileMode.PYTHON36
1052 source, expected = read_data("force_py36")
1053 with cache_dir() as workspace:
1055 (workspace / "file1.py").resolve(),
1056 (workspace / "file2.py").resolve(),
1059 with open(path, "w") as fh:
1061 result = CliRunner().invoke(
1062 black.main, [str(p) for p in paths] + ["--py36"]
1064 self.assertEqual(result.exit_code, 0)
1066 with open(path, "r") as fh:
1068 self.assertEqual(actual, expected)
1069 # verify cache with --py36 is separate
1070 pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, py36_mode)
1071 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1073 self.assertIn(path, pyi_cache)
1074 self.assertNotIn(path, normal_cache)
1076 def test_pipe_force_py36(self) -> None:
1077 source, expected = read_data("force_py36")
1078 result = CliRunner().invoke(black.main, ["-", "-q", "--py36"], input=source)
1079 self.assertEqual(result.exit_code, 0)
1080 actual = result.output
1081 self.assertFormatEqual(actual, expected)
1083 def test_include_exclude(self) -> None:
1084 path = THIS_DIR / "data" / "include_exclude_tests"
1085 include = re.compile(r"\.pyi?$")
1086 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1087 report = black.Report()
1088 sources: List[Path] = []
1090 Path(path / "b/dont_exclude/a.py"),
1091 Path(path / "b/dont_exclude/a.pyi"),
1093 this_abs = THIS_DIR.resolve()
1095 black.gen_python_files_in_dir(path, this_abs, include, exclude, report)
1097 self.assertEqual(sorted(expected), sorted(sources))
1099 def test_empty_include(self) -> None:
1100 path = THIS_DIR / "data" / "include_exclude_tests"
1101 report = black.Report()
1102 empty = re.compile(r"")
1103 sources: List[Path] = []
1105 Path(path / "b/exclude/a.pie"),
1106 Path(path / "b/exclude/a.py"),
1107 Path(path / "b/exclude/a.pyi"),
1108 Path(path / "b/dont_exclude/a.pie"),
1109 Path(path / "b/dont_exclude/a.py"),
1110 Path(path / "b/dont_exclude/a.pyi"),
1111 Path(path / "b/.definitely_exclude/a.pie"),
1112 Path(path / "b/.definitely_exclude/a.py"),
1113 Path(path / "b/.definitely_exclude/a.pyi"),
1115 this_abs = THIS_DIR.resolve()
1117 black.gen_python_files_in_dir(
1118 path, this_abs, empty, re.compile(black.DEFAULT_EXCLUDES), report
1121 self.assertEqual(sorted(expected), sorted(sources))
1123 def test_empty_exclude(self) -> None:
1124 path = THIS_DIR / "data" / "include_exclude_tests"
1125 report = black.Report()
1126 empty = re.compile(r"")
1127 sources: List[Path] = []
1129 Path(path / "b/dont_exclude/a.py"),
1130 Path(path / "b/dont_exclude/a.pyi"),
1131 Path(path / "b/exclude/a.py"),
1132 Path(path / "b/exclude/a.pyi"),
1133 Path(path / "b/.definitely_exclude/a.py"),
1134 Path(path / "b/.definitely_exclude/a.pyi"),
1136 this_abs = THIS_DIR.resolve()
1138 black.gen_python_files_in_dir(
1139 path, this_abs, re.compile(black.DEFAULT_INCLUDES), empty, report
1142 self.assertEqual(sorted(expected), sorted(sources))
1144 def test_invalid_include_exclude(self) -> None:
1145 for option in ["--include", "--exclude"]:
1146 result = CliRunner().invoke(black.main, ["-", option, "**()(!!*)"])
1147 self.assertEqual(result.exit_code, 2)
1149 def test_preserves_line_endings(self) -> None:
1150 with TemporaryDirectory() as workspace:
1151 test_file = Path(workspace) / "test.py"
1152 for nl in ["\n", "\r\n"]:
1153 contents = nl.join(["def f( ):", " pass"])
1154 test_file.write_bytes(contents.encode())
1155 ff(test_file, write_back=black.WriteBack.YES)
1156 updated_contents: bytes = test_file.read_bytes()
1157 self.assertIn(nl.encode(), updated_contents) # type: ignore
1159 self.assertNotIn(b"\r\n", updated_contents) # type: ignore
1161 def test_assert_equivalent_different_asts(self) -> None:
1162 with self.assertRaises(AssertionError):
1163 black.assert_equivalent("{}", "None")
1165 def test_symlink_out_of_root_directory(self) -> None:
1170 include = re.compile(black.DEFAULT_INCLUDES)
1171 exclude = re.compile(black.DEFAULT_EXCLUDES)
1172 report = black.Report()
1174 # set the behavior of mock arguments
1175 # child should behave like a symlink which resolved path is clearly
1176 # outside of the root directory
1177 path.iterdir.return_value = [child]
1178 child.resolve.return_value = Path("/a/b/c")
1179 child.is_symlink.return_value = True
1182 # it should not raise any error
1183 list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1185 # check the call of the methods of the mock objects
1186 path.iterdir.assert_called_once()
1187 child.resolve.assert_called_once()
1188 child.is_symlink.assert_called_once()
1190 # set the behavior of mock arguments
1191 # child should behave like a strange file which resolved path is clearly
1192 # outside of the root directory
1193 child.is_symlink.return_value = False
1196 # it should raise a ValueError
1197 with self.assertRaises(ValueError):
1198 list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1200 # check the call of the methods of the mock objects
1201 path.iterdir.assert_called()
1202 self.assertEqual(path.iterdir.call_count, 2)
1203 child.resolve.assert_called()
1204 self.assertEqual(child.resolve.call_count, 2)
1205 child.is_symlink.assert_called()
1206 self.assertEqual(child.is_symlink.call_count, 2)
1209 if __name__ == "__main__":
1210 unittest.main(module="test_black")