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