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_fmtonoff2(self) -> None:
405 source, expected = read_data("fmtonoff2")
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_remove_empty_parentheses_after_class(self) -> None:
413 source, expected = read_data("class_blank_parentheses")
415 self.assertFormatEqual(expected, actual)
416 black.assert_equivalent(source, actual)
417 black.assert_stable(source, actual, line_length=ll)
419 @patch("black.dump_to_file", dump_to_stderr)
420 def test_new_line_between_class_and_code(self) -> None:
421 source, expected = read_data("class_methods_new_line")
423 self.assertFormatEqual(expected, actual)
424 black.assert_equivalent(source, actual)
425 black.assert_stable(source, actual, line_length=ll)
427 def test_report_verbose(self) -> None:
428 report = black.Report(verbose=True)
432 def out(msg: str, **kwargs: Any) -> None:
433 out_lines.append(msg)
435 def err(msg: str, **kwargs: Any) -> None:
436 err_lines.append(msg)
438 with patch("black.out", out), patch("black.err", err):
439 report.done(Path("f1"), black.Changed.NO)
440 self.assertEqual(len(out_lines), 1)
441 self.assertEqual(len(err_lines), 0)
442 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
443 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
444 self.assertEqual(report.return_code, 0)
445 report.done(Path("f2"), black.Changed.YES)
446 self.assertEqual(len(out_lines), 2)
447 self.assertEqual(len(err_lines), 0)
448 self.assertEqual(out_lines[-1], "reformatted f2")
450 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
452 report.done(Path("f3"), black.Changed.CACHED)
453 self.assertEqual(len(out_lines), 3)
454 self.assertEqual(len(err_lines), 0)
456 out_lines[-1], "f3 wasn't modified on disk since last run."
459 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
461 self.assertEqual(report.return_code, 0)
463 self.assertEqual(report.return_code, 1)
465 report.failed(Path("e1"), "boom")
466 self.assertEqual(len(out_lines), 3)
467 self.assertEqual(len(err_lines), 1)
468 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
470 unstyle(str(report)),
471 "1 file reformatted, 2 files left unchanged, "
472 "1 file failed to reformat.",
474 self.assertEqual(report.return_code, 123)
475 report.done(Path("f3"), black.Changed.YES)
476 self.assertEqual(len(out_lines), 4)
477 self.assertEqual(len(err_lines), 1)
478 self.assertEqual(out_lines[-1], "reformatted f3")
480 unstyle(str(report)),
481 "2 files reformatted, 2 files left unchanged, "
482 "1 file failed to reformat.",
484 self.assertEqual(report.return_code, 123)
485 report.failed(Path("e2"), "boom")
486 self.assertEqual(len(out_lines), 4)
487 self.assertEqual(len(err_lines), 2)
488 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
490 unstyle(str(report)),
491 "2 files reformatted, 2 files left unchanged, "
492 "2 files failed to reformat.",
494 self.assertEqual(report.return_code, 123)
495 report.path_ignored(Path("wat"), "no match")
496 self.assertEqual(len(out_lines), 5)
497 self.assertEqual(len(err_lines), 2)
498 self.assertEqual(out_lines[-1], "wat ignored: no match")
500 unstyle(str(report)),
501 "2 files reformatted, 2 files left unchanged, "
502 "2 files failed to reformat.",
504 self.assertEqual(report.return_code, 123)
505 report.done(Path("f4"), black.Changed.NO)
506 self.assertEqual(len(out_lines), 6)
507 self.assertEqual(len(err_lines), 2)
508 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
510 unstyle(str(report)),
511 "2 files reformatted, 3 files left unchanged, "
512 "2 files failed to reformat.",
514 self.assertEqual(report.return_code, 123)
517 unstyle(str(report)),
518 "2 files would be reformatted, 3 files would be left unchanged, "
519 "2 files would fail to reformat.",
522 def test_report_quiet(self) -> None:
523 report = black.Report(quiet=True)
527 def out(msg: str, **kwargs: Any) -> None:
528 out_lines.append(msg)
530 def err(msg: str, **kwargs: Any) -> None:
531 err_lines.append(msg)
533 with patch("black.out", out), patch("black.err", err):
534 report.done(Path("f1"), black.Changed.NO)
535 self.assertEqual(len(out_lines), 0)
536 self.assertEqual(len(err_lines), 0)
537 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
538 self.assertEqual(report.return_code, 0)
539 report.done(Path("f2"), black.Changed.YES)
540 self.assertEqual(len(out_lines), 0)
541 self.assertEqual(len(err_lines), 0)
543 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
545 report.done(Path("f3"), black.Changed.CACHED)
546 self.assertEqual(len(out_lines), 0)
547 self.assertEqual(len(err_lines), 0)
549 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
551 self.assertEqual(report.return_code, 0)
553 self.assertEqual(report.return_code, 1)
555 report.failed(Path("e1"), "boom")
556 self.assertEqual(len(out_lines), 0)
557 self.assertEqual(len(err_lines), 1)
558 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
560 unstyle(str(report)),
561 "1 file reformatted, 2 files left unchanged, "
562 "1 file failed to reformat.",
564 self.assertEqual(report.return_code, 123)
565 report.done(Path("f3"), black.Changed.YES)
566 self.assertEqual(len(out_lines), 0)
567 self.assertEqual(len(err_lines), 1)
569 unstyle(str(report)),
570 "2 files reformatted, 2 files left unchanged, "
571 "1 file failed to reformat.",
573 self.assertEqual(report.return_code, 123)
574 report.failed(Path("e2"), "boom")
575 self.assertEqual(len(out_lines), 0)
576 self.assertEqual(len(err_lines), 2)
577 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
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.path_ignored(Path("wat"), "no match")
585 self.assertEqual(len(out_lines), 0)
586 self.assertEqual(len(err_lines), 2)
588 unstyle(str(report)),
589 "2 files reformatted, 2 files left unchanged, "
590 "2 files failed to reformat.",
592 self.assertEqual(report.return_code, 123)
593 report.done(Path("f4"), black.Changed.NO)
594 self.assertEqual(len(out_lines), 0)
595 self.assertEqual(len(err_lines), 2)
597 unstyle(str(report)),
598 "2 files reformatted, 3 files left unchanged, "
599 "2 files failed to reformat.",
601 self.assertEqual(report.return_code, 123)
604 unstyle(str(report)),
605 "2 files would be reformatted, 3 files would be left unchanged, "
606 "2 files would fail to reformat.",
609 def test_report_normal(self) -> None:
610 report = black.Report()
614 def out(msg: str, **kwargs: Any) -> None:
615 out_lines.append(msg)
617 def err(msg: str, **kwargs: Any) -> None:
618 err_lines.append(msg)
620 with patch("black.out", out), patch("black.err", err):
621 report.done(Path("f1"), black.Changed.NO)
622 self.assertEqual(len(out_lines), 0)
623 self.assertEqual(len(err_lines), 0)
624 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
625 self.assertEqual(report.return_code, 0)
626 report.done(Path("f2"), black.Changed.YES)
627 self.assertEqual(len(out_lines), 1)
628 self.assertEqual(len(err_lines), 0)
629 self.assertEqual(out_lines[-1], "reformatted f2")
631 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
633 report.done(Path("f3"), black.Changed.CACHED)
634 self.assertEqual(len(out_lines), 1)
635 self.assertEqual(len(err_lines), 0)
636 self.assertEqual(out_lines[-1], "reformatted f2")
638 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
640 self.assertEqual(report.return_code, 0)
642 self.assertEqual(report.return_code, 1)
644 report.failed(Path("e1"), "boom")
645 self.assertEqual(len(out_lines), 1)
646 self.assertEqual(len(err_lines), 1)
647 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
649 unstyle(str(report)),
650 "1 file reformatted, 2 files left unchanged, "
651 "1 file failed to reformat.",
653 self.assertEqual(report.return_code, 123)
654 report.done(Path("f3"), black.Changed.YES)
655 self.assertEqual(len(out_lines), 2)
656 self.assertEqual(len(err_lines), 1)
657 self.assertEqual(out_lines[-1], "reformatted f3")
659 unstyle(str(report)),
660 "2 files reformatted, 2 files left unchanged, "
661 "1 file failed to reformat.",
663 self.assertEqual(report.return_code, 123)
664 report.failed(Path("e2"), "boom")
665 self.assertEqual(len(out_lines), 2)
666 self.assertEqual(len(err_lines), 2)
667 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
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.path_ignored(Path("wat"), "no match")
675 self.assertEqual(len(out_lines), 2)
676 self.assertEqual(len(err_lines), 2)
678 unstyle(str(report)),
679 "2 files reformatted, 2 files left unchanged, "
680 "2 files failed to reformat.",
682 self.assertEqual(report.return_code, 123)
683 report.done(Path("f4"), black.Changed.NO)
684 self.assertEqual(len(out_lines), 2)
685 self.assertEqual(len(err_lines), 2)
687 unstyle(str(report)),
688 "2 files reformatted, 3 files left unchanged, "
689 "2 files failed to reformat.",
691 self.assertEqual(report.return_code, 123)
694 unstyle(str(report)),
695 "2 files would be reformatted, 3 files would be left unchanged, "
696 "2 files would fail to reformat.",
699 def test_is_python36(self) -> None:
700 node = black.lib2to3_parse("def f(*, arg): ...\n")
701 self.assertFalse(black.is_python36(node))
702 node = black.lib2to3_parse("def f(*, arg,): ...\n")
703 self.assertTrue(black.is_python36(node))
704 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
705 self.assertTrue(black.is_python36(node))
706 source, expected = read_data("function")
707 node = black.lib2to3_parse(source)
708 self.assertTrue(black.is_python36(node))
709 node = black.lib2to3_parse(expected)
710 self.assertTrue(black.is_python36(node))
711 source, expected = read_data("expression")
712 node = black.lib2to3_parse(source)
713 self.assertFalse(black.is_python36(node))
714 node = black.lib2to3_parse(expected)
715 self.assertFalse(black.is_python36(node))
717 def test_get_future_imports(self) -> None:
718 node = black.lib2to3_parse("\n")
719 self.assertEqual(set(), black.get_future_imports(node))
720 node = black.lib2to3_parse("from __future__ import black\n")
721 self.assertEqual({"black"}, black.get_future_imports(node))
722 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
723 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
724 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
725 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
726 node = black.lib2to3_parse(
727 "from __future__ import multiple\nfrom __future__ import imports\n"
729 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
730 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
731 self.assertEqual({"black"}, black.get_future_imports(node))
732 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
733 self.assertEqual({"black"}, black.get_future_imports(node))
734 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
735 self.assertEqual(set(), black.get_future_imports(node))
736 node = black.lib2to3_parse("from some.module import black\n")
737 self.assertEqual(set(), black.get_future_imports(node))
739 def test_debug_visitor(self) -> None:
740 source, _ = read_data("debug_visitor.py")
741 expected, _ = read_data("debug_visitor.out")
745 def out(msg: str, **kwargs: Any) -> None:
746 out_lines.append(msg)
748 def err(msg: str, **kwargs: Any) -> None:
749 err_lines.append(msg)
751 with patch("black.out", out), patch("black.err", err):
752 black.DebugVisitor.show(source)
753 actual = "\n".join(out_lines) + "\n"
755 if expected != actual:
756 log_name = black.dump_to_file(*out_lines)
760 f"AST print out is different. Actual version dumped to {log_name}",
763 def test_format_file_contents(self) -> None:
765 with self.assertRaises(black.NothingChanged):
766 black.format_file_contents(empty, line_length=ll, fast=False)
768 with self.assertRaises(black.NothingChanged):
769 black.format_file_contents(just_nl, line_length=ll, fast=False)
770 same = "l = [1, 2, 3]\n"
771 with self.assertRaises(black.NothingChanged):
772 black.format_file_contents(same, line_length=ll, fast=False)
773 different = "l = [1,2,3]"
775 actual = black.format_file_contents(different, line_length=ll, fast=False)
776 self.assertEqual(expected, actual)
777 invalid = "return if you can"
778 with self.assertRaises(ValueError) as e:
779 black.format_file_contents(invalid, line_length=ll, fast=False)
780 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
782 def test_endmarker(self) -> None:
783 n = black.lib2to3_parse("\n")
784 self.assertEqual(n.type, black.syms.file_input)
785 self.assertEqual(len(n.children), 1)
786 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
788 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
789 def test_assertFormatEqual(self) -> None:
793 def out(msg: str, **kwargs: Any) -> None:
794 out_lines.append(msg)
796 def err(msg: str, **kwargs: Any) -> None:
797 err_lines.append(msg)
799 with patch("black.out", out), patch("black.err", err):
800 with self.assertRaises(AssertionError):
801 self.assertFormatEqual("l = [1, 2, 3]", "l = [1, 2, 3,]")
803 out_str = "".join(out_lines)
804 self.assertTrue("Expected tree:" in out_str)
805 self.assertTrue("Actual tree:" in out_str)
806 self.assertEqual("".join(err_lines), "")
808 def test_cache_broken_file(self) -> None:
809 mode = black.FileMode.AUTO_DETECT
810 with cache_dir() as workspace:
811 cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode)
812 with cache_file.open("w") as fobj:
813 fobj.write("this is not a pickle")
814 self.assertEqual(black.read_cache(black.DEFAULT_LINE_LENGTH, mode), {})
815 src = (workspace / "test.py").resolve()
816 with src.open("w") as fobj:
817 fobj.write("print('hello')")
818 result = CliRunner().invoke(black.main, [str(src)])
819 self.assertEqual(result.exit_code, 0)
820 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
821 self.assertIn(src, cache)
823 def test_cache_single_file_already_cached(self) -> None:
824 mode = black.FileMode.AUTO_DETECT
825 with cache_dir() as workspace:
826 src = (workspace / "test.py").resolve()
827 with src.open("w") as fobj:
828 fobj.write("print('hello')")
829 black.write_cache({}, [src], black.DEFAULT_LINE_LENGTH, mode)
830 result = CliRunner().invoke(black.main, [str(src)])
831 self.assertEqual(result.exit_code, 0)
832 with src.open("r") as fobj:
833 self.assertEqual(fobj.read(), "print('hello')")
835 @event_loop(close=False)
836 def test_cache_multiple_files(self) -> None:
837 mode = black.FileMode.AUTO_DETECT
838 with cache_dir() as workspace, patch(
839 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
841 one = (workspace / "one.py").resolve()
842 with one.open("w") as fobj:
843 fobj.write("print('hello')")
844 two = (workspace / "two.py").resolve()
845 with two.open("w") as fobj:
846 fobj.write("print('hello')")
847 black.write_cache({}, [one], black.DEFAULT_LINE_LENGTH, mode)
848 result = CliRunner().invoke(black.main, [str(workspace)])
849 self.assertEqual(result.exit_code, 0)
850 with one.open("r") as fobj:
851 self.assertEqual(fobj.read(), "print('hello')")
852 with two.open("r") as fobj:
853 self.assertEqual(fobj.read(), 'print("hello")\n')
854 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
855 self.assertIn(one, cache)
856 self.assertIn(two, cache)
858 def test_no_cache_when_writeback_diff(self) -> None:
859 mode = black.FileMode.AUTO_DETECT
860 with cache_dir() as workspace:
861 src = (workspace / "test.py").resolve()
862 with src.open("w") as fobj:
863 fobj.write("print('hello')")
864 result = CliRunner().invoke(black.main, [str(src), "--diff"])
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_no_cache_when_stdin(self) -> None:
870 mode = black.FileMode.AUTO_DETECT
872 result = CliRunner().invoke(black.main, ["-"], input="print('hello')")
873 self.assertEqual(result.exit_code, 0)
874 cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode)
875 self.assertFalse(cache_file.exists())
877 def test_read_cache_no_cachefile(self) -> None:
878 mode = black.FileMode.AUTO_DETECT
880 self.assertEqual(black.read_cache(black.DEFAULT_LINE_LENGTH, mode), {})
882 def test_write_cache_read_cache(self) -> None:
883 mode = black.FileMode.AUTO_DETECT
884 with cache_dir() as workspace:
885 src = (workspace / "test.py").resolve()
887 black.write_cache({}, [src], black.DEFAULT_LINE_LENGTH, mode)
888 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
889 self.assertIn(src, cache)
890 self.assertEqual(cache[src], black.get_cache_info(src))
892 def test_filter_cached(self) -> None:
893 with TemporaryDirectory() as workspace:
894 path = Path(workspace)
895 uncached = (path / "uncached").resolve()
896 cached = (path / "cached").resolve()
897 cached_but_changed = (path / "changed").resolve()
900 cached_but_changed.touch()
901 cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
902 todo, done = black.filter_cached(
903 cache, {uncached, cached, cached_but_changed}
905 self.assertEqual(todo, {uncached, cached_but_changed})
906 self.assertEqual(done, {cached})
908 def test_write_cache_creates_directory_if_needed(self) -> None:
909 mode = black.FileMode.AUTO_DETECT
910 with cache_dir(exists=False) as workspace:
911 self.assertFalse(workspace.exists())
912 black.write_cache({}, [], black.DEFAULT_LINE_LENGTH, mode)
913 self.assertTrue(workspace.exists())
915 @event_loop(close=False)
916 def test_failed_formatting_does_not_get_cached(self) -> None:
917 mode = black.FileMode.AUTO_DETECT
918 with cache_dir() as workspace, patch(
919 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
921 failing = (workspace / "failing.py").resolve()
922 with failing.open("w") as fobj:
923 fobj.write("not actually python")
924 clean = (workspace / "clean.py").resolve()
925 with clean.open("w") as fobj:
926 fobj.write('print("hello")\n')
927 result = CliRunner().invoke(black.main, [str(workspace)])
928 self.assertEqual(result.exit_code, 123)
929 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
930 self.assertNotIn(failing, cache)
931 self.assertIn(clean, cache)
933 def test_write_cache_write_fail(self) -> None:
934 mode = black.FileMode.AUTO_DETECT
935 with cache_dir(), patch.object(Path, "open") as mock:
936 mock.side_effect = OSError
937 black.write_cache({}, [], black.DEFAULT_LINE_LENGTH, mode)
939 @event_loop(close=False)
940 def test_check_diff_use_together(self) -> None:
942 # Files which will be reformatted.
943 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
944 result = CliRunner().invoke(black.main, [str(src1), "--diff", "--check"])
945 self.assertEqual(result.exit_code, 1, result.output)
946 # Files which will not be reformatted.
947 src2 = (THIS_DIR / "data" / "composition.py").resolve()
948 result = CliRunner().invoke(black.main, [str(src2), "--diff", "--check"])
949 self.assertEqual(result.exit_code, 0, result.output)
950 # Multi file command.
951 result = CliRunner().invoke(
952 black.main, [str(src1), str(src2), "--diff", "--check"]
954 self.assertEqual(result.exit_code, 1, result.output)
956 def test_no_files(self) -> None:
958 # Without an argument, black exits with error code 0.
959 result = CliRunner().invoke(black.main, [])
960 self.assertEqual(result.exit_code, 0)
962 def test_broken_symlink(self) -> None:
963 with cache_dir() as workspace:
964 symlink = workspace / "broken_link.py"
966 symlink.symlink_to("nonexistent.py")
968 self.skipTest(f"Can't create symlinks: {e}")
969 result = CliRunner().invoke(black.main, [str(workspace.resolve())])
970 self.assertEqual(result.exit_code, 0)
972 def test_read_cache_line_lengths(self) -> None:
973 mode = black.FileMode.AUTO_DETECT
974 with cache_dir() as workspace:
975 path = (workspace / "file.py").resolve()
977 black.write_cache({}, [path], 1, mode)
978 one = black.read_cache(1, mode)
979 self.assertIn(path, one)
980 two = black.read_cache(2, mode)
981 self.assertNotIn(path, two)
983 def test_single_file_force_pyi(self) -> None:
984 reg_mode = black.FileMode.AUTO_DETECT
985 pyi_mode = black.FileMode.PYI
986 contents, expected = read_data("force_pyi")
987 with cache_dir() as workspace:
988 path = (workspace / "file.py").resolve()
989 with open(path, "w") as fh:
991 result = CliRunner().invoke(black.main, [str(path), "--pyi"])
992 self.assertEqual(result.exit_code, 0)
993 with open(path, "r") as fh:
995 # verify cache with --pyi is separate
996 pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, pyi_mode)
997 self.assertIn(path, pyi_cache)
998 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
999 self.assertNotIn(path, normal_cache)
1000 self.assertEqual(actual, expected)
1002 @event_loop(close=False)
1003 def test_multi_file_force_pyi(self) -> None:
1004 reg_mode = black.FileMode.AUTO_DETECT
1005 pyi_mode = black.FileMode.PYI
1006 contents, expected = read_data("force_pyi")
1007 with cache_dir() as workspace:
1009 (workspace / "file1.py").resolve(),
1010 (workspace / "file2.py").resolve(),
1013 with open(path, "w") as fh:
1015 result = CliRunner().invoke(black.main, [str(p) for p in paths] + ["--pyi"])
1016 self.assertEqual(result.exit_code, 0)
1018 with open(path, "r") as fh:
1020 self.assertEqual(actual, expected)
1021 # verify cache with --pyi is separate
1022 pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, pyi_mode)
1023 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1025 self.assertIn(path, pyi_cache)
1026 self.assertNotIn(path, normal_cache)
1028 def test_pipe_force_pyi(self) -> None:
1029 source, expected = read_data("force_pyi")
1030 result = CliRunner().invoke(black.main, ["-", "-q", "--pyi"], input=source)
1031 self.assertEqual(result.exit_code, 0)
1032 actual = result.output
1033 self.assertFormatEqual(actual, expected)
1035 def test_single_file_force_py36(self) -> None:
1036 reg_mode = black.FileMode.AUTO_DETECT
1037 py36_mode = black.FileMode.PYTHON36
1038 source, expected = read_data("force_py36")
1039 with cache_dir() as workspace:
1040 path = (workspace / "file.py").resolve()
1041 with open(path, "w") as fh:
1043 result = CliRunner().invoke(black.main, [str(path), "--py36"])
1044 self.assertEqual(result.exit_code, 0)
1045 with open(path, "r") as fh:
1047 # verify cache with --py36 is separate
1048 py36_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, py36_mode)
1049 self.assertIn(path, py36_cache)
1050 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1051 self.assertNotIn(path, normal_cache)
1052 self.assertEqual(actual, expected)
1054 @event_loop(close=False)
1055 def test_multi_file_force_py36(self) -> None:
1056 reg_mode = black.FileMode.AUTO_DETECT
1057 py36_mode = black.FileMode.PYTHON36
1058 source, expected = read_data("force_py36")
1059 with cache_dir() as workspace:
1061 (workspace / "file1.py").resolve(),
1062 (workspace / "file2.py").resolve(),
1065 with open(path, "w") as fh:
1067 result = CliRunner().invoke(
1068 black.main, [str(p) for p in paths] + ["--py36"]
1070 self.assertEqual(result.exit_code, 0)
1072 with open(path, "r") as fh:
1074 self.assertEqual(actual, expected)
1075 # verify cache with --py36 is separate
1076 pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, py36_mode)
1077 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1079 self.assertIn(path, pyi_cache)
1080 self.assertNotIn(path, normal_cache)
1082 def test_pipe_force_py36(self) -> None:
1083 source, expected = read_data("force_py36")
1084 result = CliRunner().invoke(black.main, ["-", "-q", "--py36"], input=source)
1085 self.assertEqual(result.exit_code, 0)
1086 actual = result.output
1087 self.assertFormatEqual(actual, expected)
1089 def test_include_exclude(self) -> None:
1090 path = THIS_DIR / "data" / "include_exclude_tests"
1091 include = re.compile(r"\.pyi?$")
1092 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1093 report = black.Report()
1094 sources: List[Path] = []
1096 Path(path / "b/dont_exclude/a.py"),
1097 Path(path / "b/dont_exclude/a.pyi"),
1099 this_abs = THIS_DIR.resolve()
1101 black.gen_python_files_in_dir(path, this_abs, include, exclude, report)
1103 self.assertEqual(sorted(expected), sorted(sources))
1105 def test_empty_include(self) -> None:
1106 path = THIS_DIR / "data" / "include_exclude_tests"
1107 report = black.Report()
1108 empty = re.compile(r"")
1109 sources: List[Path] = []
1111 Path(path / "b/exclude/a.pie"),
1112 Path(path / "b/exclude/a.py"),
1113 Path(path / "b/exclude/a.pyi"),
1114 Path(path / "b/dont_exclude/a.pie"),
1115 Path(path / "b/dont_exclude/a.py"),
1116 Path(path / "b/dont_exclude/a.pyi"),
1117 Path(path / "b/.definitely_exclude/a.pie"),
1118 Path(path / "b/.definitely_exclude/a.py"),
1119 Path(path / "b/.definitely_exclude/a.pyi"),
1121 this_abs = THIS_DIR.resolve()
1123 black.gen_python_files_in_dir(
1124 path, this_abs, empty, re.compile(black.DEFAULT_EXCLUDES), report
1127 self.assertEqual(sorted(expected), sorted(sources))
1129 def test_empty_exclude(self) -> None:
1130 path = THIS_DIR / "data" / "include_exclude_tests"
1131 report = black.Report()
1132 empty = re.compile(r"")
1133 sources: List[Path] = []
1135 Path(path / "b/dont_exclude/a.py"),
1136 Path(path / "b/dont_exclude/a.pyi"),
1137 Path(path / "b/exclude/a.py"),
1138 Path(path / "b/exclude/a.pyi"),
1139 Path(path / "b/.definitely_exclude/a.py"),
1140 Path(path / "b/.definitely_exclude/a.pyi"),
1142 this_abs = THIS_DIR.resolve()
1144 black.gen_python_files_in_dir(
1145 path, this_abs, re.compile(black.DEFAULT_INCLUDES), empty, report
1148 self.assertEqual(sorted(expected), sorted(sources))
1150 def test_invalid_include_exclude(self) -> None:
1151 for option in ["--include", "--exclude"]:
1152 result = CliRunner().invoke(black.main, ["-", option, "**()(!!*)"])
1153 self.assertEqual(result.exit_code, 2)
1155 def test_preserves_line_endings(self) -> None:
1156 with TemporaryDirectory() as workspace:
1157 test_file = Path(workspace) / "test.py"
1158 for nl in ["\n", "\r\n"]:
1159 contents = nl.join(["def f( ):", " pass"])
1160 test_file.write_bytes(contents.encode())
1161 ff(test_file, write_back=black.WriteBack.YES)
1162 updated_contents: bytes = test_file.read_bytes()
1163 self.assertIn(nl.encode(), updated_contents) # type: ignore
1165 self.assertNotIn(b"\r\n", updated_contents) # type: ignore
1167 def test_assert_equivalent_different_asts(self) -> None:
1168 with self.assertRaises(AssertionError):
1169 black.assert_equivalent("{}", "None")
1171 def test_symlink_out_of_root_directory(self) -> None:
1175 include = re.compile(black.DEFAULT_INCLUDES)
1176 exclude = re.compile(black.DEFAULT_EXCLUDES)
1177 report = black.Report()
1178 # `child` should behave like a symlink which resolved path is clearly
1179 # outside of the `root` directory.
1180 path.iterdir.return_value = [child]
1181 child.resolve.return_value = Path("/a/b/c")
1182 child.is_symlink.return_value = True
1184 list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1185 except ValueError as ve:
1186 self.fail("`get_python_files_in_dir()` failed: {ve}")
1187 path.iterdir.assert_called_once()
1188 child.resolve.assert_called_once()
1189 child.is_symlink.assert_called_once()
1190 # `child` should behave like a strange file which resolved path is clearly
1191 # outside of the `root` directory.
1192 child.is_symlink.return_value = False
1193 with self.assertRaises(ValueError):
1194 list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1195 path.iterdir.assert_called()
1196 self.assertEqual(path.iterdir.call_count, 2)
1197 child.resolve.assert_called()
1198 self.assertEqual(child.resolve.call_count, 2)
1199 child.is_symlink.assert_called()
1200 self.assertEqual(child.is_symlink.call_count, 2)
1202 def test_shhh_click(self) -> None:
1204 from click import _unicodefun # type: ignore
1205 except ModuleNotFoundError:
1206 self.skipTest("Incompatible Click version")
1207 if not hasattr(_unicodefun, "_verify_python3_env"):
1208 self.skipTest("Incompatible Click version")
1209 # First, let's see if Click is crashing with a preferred ASCII charset.
1210 with patch("locale.getpreferredencoding") as gpe:
1211 gpe.return_value = "ASCII"
1212 with self.assertRaises(RuntimeError):
1213 _unicodefun._verify_python3_env()
1214 # Now, let's silence Click...
1216 # ...and confirm it's silent.
1217 with patch("locale.getpreferredencoding") as gpe:
1218 gpe.return_value = "ASCII"
1220 _unicodefun._verify_python3_env()
1221 except RuntimeError as re:
1222 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1225 if __name__ == "__main__":
1226 unittest.main(module="test_black")