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_fmtonoff(self) -> None:
416 source, expected = read_data("fmtonoff")
418 self.assertFormatEqual(expected, actual)
419 black.assert_equivalent(source, actual)
420 black.assert_stable(source, actual, line_length=ll)
422 @patch("black.dump_to_file", dump_to_stderr)
423 def test_fmtonoff2(self) -> None:
424 source, expected = read_data("fmtonoff2")
426 self.assertFormatEqual(expected, actual)
427 black.assert_equivalent(source, actual)
428 black.assert_stable(source, actual, line_length=ll)
430 @patch("black.dump_to_file", dump_to_stderr)
431 def test_remove_empty_parentheses_after_class(self) -> None:
432 source, expected = read_data("class_blank_parentheses")
434 self.assertFormatEqual(expected, actual)
435 black.assert_equivalent(source, actual)
436 black.assert_stable(source, actual, line_length=ll)
438 @patch("black.dump_to_file", dump_to_stderr)
439 def test_new_line_between_class_and_code(self) -> None:
440 source, expected = read_data("class_methods_new_line")
442 self.assertFormatEqual(expected, actual)
443 black.assert_equivalent(source, actual)
444 black.assert_stable(source, actual, line_length=ll)
446 def test_report_verbose(self) -> None:
447 report = black.Report(verbose=True)
451 def out(msg: str, **kwargs: Any) -> None:
452 out_lines.append(msg)
454 def err(msg: str, **kwargs: Any) -> None:
455 err_lines.append(msg)
457 with patch("black.out", out), patch("black.err", err):
458 report.done(Path("f1"), black.Changed.NO)
459 self.assertEqual(len(out_lines), 1)
460 self.assertEqual(len(err_lines), 0)
461 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
462 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
463 self.assertEqual(report.return_code, 0)
464 report.done(Path("f2"), black.Changed.YES)
465 self.assertEqual(len(out_lines), 2)
466 self.assertEqual(len(err_lines), 0)
467 self.assertEqual(out_lines[-1], "reformatted f2")
469 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
471 report.done(Path("f3"), black.Changed.CACHED)
472 self.assertEqual(len(out_lines), 3)
473 self.assertEqual(len(err_lines), 0)
475 out_lines[-1], "f3 wasn't modified on disk since last run."
478 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
480 self.assertEqual(report.return_code, 0)
482 self.assertEqual(report.return_code, 1)
484 report.failed(Path("e1"), "boom")
485 self.assertEqual(len(out_lines), 3)
486 self.assertEqual(len(err_lines), 1)
487 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
489 unstyle(str(report)),
490 "1 file reformatted, 2 files left unchanged, "
491 "1 file failed to reformat.",
493 self.assertEqual(report.return_code, 123)
494 report.done(Path("f3"), black.Changed.YES)
495 self.assertEqual(len(out_lines), 4)
496 self.assertEqual(len(err_lines), 1)
497 self.assertEqual(out_lines[-1], "reformatted f3")
499 unstyle(str(report)),
500 "2 files reformatted, 2 files left unchanged, "
501 "1 file failed to reformat.",
503 self.assertEqual(report.return_code, 123)
504 report.failed(Path("e2"), "boom")
505 self.assertEqual(len(out_lines), 4)
506 self.assertEqual(len(err_lines), 2)
507 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
509 unstyle(str(report)),
510 "2 files reformatted, 2 files left unchanged, "
511 "2 files failed to reformat.",
513 self.assertEqual(report.return_code, 123)
514 report.path_ignored(Path("wat"), "no match")
515 self.assertEqual(len(out_lines), 5)
516 self.assertEqual(len(err_lines), 2)
517 self.assertEqual(out_lines[-1], "wat ignored: no match")
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.done(Path("f4"), black.Changed.NO)
525 self.assertEqual(len(out_lines), 6)
526 self.assertEqual(len(err_lines), 2)
527 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
529 unstyle(str(report)),
530 "2 files reformatted, 3 files left unchanged, "
531 "2 files failed to reformat.",
533 self.assertEqual(report.return_code, 123)
536 unstyle(str(report)),
537 "2 files would be reformatted, 3 files would be left unchanged, "
538 "2 files would fail to reformat.",
541 def test_report_quiet(self) -> None:
542 report = black.Report(quiet=True)
546 def out(msg: str, **kwargs: Any) -> None:
547 out_lines.append(msg)
549 def err(msg: str, **kwargs: Any) -> None:
550 err_lines.append(msg)
552 with patch("black.out", out), patch("black.err", err):
553 report.done(Path("f1"), black.Changed.NO)
554 self.assertEqual(len(out_lines), 0)
555 self.assertEqual(len(err_lines), 0)
556 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
557 self.assertEqual(report.return_code, 0)
558 report.done(Path("f2"), black.Changed.YES)
559 self.assertEqual(len(out_lines), 0)
560 self.assertEqual(len(err_lines), 0)
562 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
564 report.done(Path("f3"), black.Changed.CACHED)
565 self.assertEqual(len(out_lines), 0)
566 self.assertEqual(len(err_lines), 0)
568 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
570 self.assertEqual(report.return_code, 0)
572 self.assertEqual(report.return_code, 1)
574 report.failed(Path("e1"), "boom")
575 self.assertEqual(len(out_lines), 0)
576 self.assertEqual(len(err_lines), 1)
577 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
579 unstyle(str(report)),
580 "1 file reformatted, 2 files left unchanged, "
581 "1 file failed to reformat.",
583 self.assertEqual(report.return_code, 123)
584 report.done(Path("f3"), black.Changed.YES)
585 self.assertEqual(len(out_lines), 0)
586 self.assertEqual(len(err_lines), 1)
588 unstyle(str(report)),
589 "2 files reformatted, 2 files left unchanged, "
590 "1 file failed to reformat.",
592 self.assertEqual(report.return_code, 123)
593 report.failed(Path("e2"), "boom")
594 self.assertEqual(len(out_lines), 0)
595 self.assertEqual(len(err_lines), 2)
596 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
598 unstyle(str(report)),
599 "2 files reformatted, 2 files left unchanged, "
600 "2 files failed to reformat.",
602 self.assertEqual(report.return_code, 123)
603 report.path_ignored(Path("wat"), "no match")
604 self.assertEqual(len(out_lines), 0)
605 self.assertEqual(len(err_lines), 2)
607 unstyle(str(report)),
608 "2 files reformatted, 2 files left unchanged, "
609 "2 files failed to reformat.",
611 self.assertEqual(report.return_code, 123)
612 report.done(Path("f4"), black.Changed.NO)
613 self.assertEqual(len(out_lines), 0)
614 self.assertEqual(len(err_lines), 2)
616 unstyle(str(report)),
617 "2 files reformatted, 3 files left unchanged, "
618 "2 files failed to reformat.",
620 self.assertEqual(report.return_code, 123)
623 unstyle(str(report)),
624 "2 files would be reformatted, 3 files would be left unchanged, "
625 "2 files would fail to reformat.",
628 def test_report_normal(self) -> None:
629 report = black.Report()
633 def out(msg: str, **kwargs: Any) -> None:
634 out_lines.append(msg)
636 def err(msg: str, **kwargs: Any) -> None:
637 err_lines.append(msg)
639 with patch("black.out", out), patch("black.err", err):
640 report.done(Path("f1"), black.Changed.NO)
641 self.assertEqual(len(out_lines), 0)
642 self.assertEqual(len(err_lines), 0)
643 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
644 self.assertEqual(report.return_code, 0)
645 report.done(Path("f2"), black.Changed.YES)
646 self.assertEqual(len(out_lines), 1)
647 self.assertEqual(len(err_lines), 0)
648 self.assertEqual(out_lines[-1], "reformatted f2")
650 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
652 report.done(Path("f3"), black.Changed.CACHED)
653 self.assertEqual(len(out_lines), 1)
654 self.assertEqual(len(err_lines), 0)
655 self.assertEqual(out_lines[-1], "reformatted f2")
657 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
659 self.assertEqual(report.return_code, 0)
661 self.assertEqual(report.return_code, 1)
663 report.failed(Path("e1"), "boom")
664 self.assertEqual(len(out_lines), 1)
665 self.assertEqual(len(err_lines), 1)
666 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
668 unstyle(str(report)),
669 "1 file reformatted, 2 files left unchanged, "
670 "1 file failed to reformat.",
672 self.assertEqual(report.return_code, 123)
673 report.done(Path("f3"), black.Changed.YES)
674 self.assertEqual(len(out_lines), 2)
675 self.assertEqual(len(err_lines), 1)
676 self.assertEqual(out_lines[-1], "reformatted f3")
678 unstyle(str(report)),
679 "2 files reformatted, 2 files left unchanged, "
680 "1 file failed to reformat.",
682 self.assertEqual(report.return_code, 123)
683 report.failed(Path("e2"), "boom")
684 self.assertEqual(len(out_lines), 2)
685 self.assertEqual(len(err_lines), 2)
686 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
688 unstyle(str(report)),
689 "2 files reformatted, 2 files left unchanged, "
690 "2 files failed to reformat.",
692 self.assertEqual(report.return_code, 123)
693 report.path_ignored(Path("wat"), "no match")
694 self.assertEqual(len(out_lines), 2)
695 self.assertEqual(len(err_lines), 2)
697 unstyle(str(report)),
698 "2 files reformatted, 2 files left unchanged, "
699 "2 files failed to reformat.",
701 self.assertEqual(report.return_code, 123)
702 report.done(Path("f4"), black.Changed.NO)
703 self.assertEqual(len(out_lines), 2)
704 self.assertEqual(len(err_lines), 2)
706 unstyle(str(report)),
707 "2 files reformatted, 3 files left unchanged, "
708 "2 files failed to reformat.",
710 self.assertEqual(report.return_code, 123)
713 unstyle(str(report)),
714 "2 files would be reformatted, 3 files would be left unchanged, "
715 "2 files would fail to reformat.",
718 def test_is_python36(self) -> None:
719 node = black.lib2to3_parse("def f(*, arg): ...\n")
720 self.assertFalse(black.is_python36(node))
721 node = black.lib2to3_parse("def f(*, arg,): ...\n")
722 self.assertTrue(black.is_python36(node))
723 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
724 self.assertTrue(black.is_python36(node))
725 node = black.lib2to3_parse("123_456\n")
726 self.assertTrue(black.is_python36(node))
727 node = black.lib2to3_parse("123456\n")
728 self.assertFalse(black.is_python36(node))
729 source, expected = read_data("function")
730 node = black.lib2to3_parse(source)
731 self.assertTrue(black.is_python36(node))
732 node = black.lib2to3_parse(expected)
733 self.assertTrue(black.is_python36(node))
734 source, expected = read_data("expression")
735 node = black.lib2to3_parse(source)
736 self.assertFalse(black.is_python36(node))
737 node = black.lib2to3_parse(expected)
738 self.assertFalse(black.is_python36(node))
740 def test_get_future_imports(self) -> None:
741 node = black.lib2to3_parse("\n")
742 self.assertEqual(set(), black.get_future_imports(node))
743 node = black.lib2to3_parse("from __future__ import black\n")
744 self.assertEqual({"black"}, black.get_future_imports(node))
745 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
746 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
747 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
748 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
749 node = black.lib2to3_parse(
750 "from __future__ import multiple\nfrom __future__ import imports\n"
752 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
753 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
754 self.assertEqual({"black"}, black.get_future_imports(node))
755 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
756 self.assertEqual({"black"}, black.get_future_imports(node))
757 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
758 self.assertEqual(set(), black.get_future_imports(node))
759 node = black.lib2to3_parse("from some.module import black\n")
760 self.assertEqual(set(), black.get_future_imports(node))
761 node = black.lib2to3_parse(
762 "from __future__ import unicode_literals as _unicode_literals"
764 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
765 node = black.lib2to3_parse(
766 "from __future__ import unicode_literals as _lol, print"
768 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
770 def test_debug_visitor(self) -> None:
771 source, _ = read_data("debug_visitor.py")
772 expected, _ = read_data("debug_visitor.out")
776 def out(msg: str, **kwargs: Any) -> None:
777 out_lines.append(msg)
779 def err(msg: str, **kwargs: Any) -> None:
780 err_lines.append(msg)
782 with patch("black.out", out), patch("black.err", err):
783 black.DebugVisitor.show(source)
784 actual = "\n".join(out_lines) + "\n"
786 if expected != actual:
787 log_name = black.dump_to_file(*out_lines)
791 f"AST print out is different. Actual version dumped to {log_name}",
794 def test_format_file_contents(self) -> None:
796 with self.assertRaises(black.NothingChanged):
797 black.format_file_contents(empty, line_length=ll, fast=False)
799 with self.assertRaises(black.NothingChanged):
800 black.format_file_contents(just_nl, line_length=ll, fast=False)
801 same = "l = [1, 2, 3]\n"
802 with self.assertRaises(black.NothingChanged):
803 black.format_file_contents(same, line_length=ll, fast=False)
804 different = "l = [1,2,3]"
806 actual = black.format_file_contents(different, line_length=ll, fast=False)
807 self.assertEqual(expected, actual)
808 invalid = "return if you can"
809 with self.assertRaises(ValueError) as e:
810 black.format_file_contents(invalid, line_length=ll, fast=False)
811 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
813 def test_endmarker(self) -> None:
814 n = black.lib2to3_parse("\n")
815 self.assertEqual(n.type, black.syms.file_input)
816 self.assertEqual(len(n.children), 1)
817 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
819 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
820 def test_assertFormatEqual(self) -> None:
824 def out(msg: str, **kwargs: Any) -> None:
825 out_lines.append(msg)
827 def err(msg: str, **kwargs: Any) -> None:
828 err_lines.append(msg)
830 with patch("black.out", out), patch("black.err", err):
831 with self.assertRaises(AssertionError):
832 self.assertFormatEqual("l = [1, 2, 3]", "l = [1, 2, 3,]")
834 out_str = "".join(out_lines)
835 self.assertTrue("Expected tree:" in out_str)
836 self.assertTrue("Actual tree:" in out_str)
837 self.assertEqual("".join(err_lines), "")
839 def test_cache_broken_file(self) -> None:
840 mode = black.FileMode.AUTO_DETECT
841 with cache_dir() as workspace:
842 cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode)
843 with cache_file.open("w") as fobj:
844 fobj.write("this is not a pickle")
845 self.assertEqual(black.read_cache(black.DEFAULT_LINE_LENGTH, mode), {})
846 src = (workspace / "test.py").resolve()
847 with src.open("w") as fobj:
848 fobj.write("print('hello')")
849 result = CliRunner().invoke(black.main, [str(src)])
850 self.assertEqual(result.exit_code, 0)
851 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
852 self.assertIn(src, cache)
854 def test_cache_single_file_already_cached(self) -> None:
855 mode = black.FileMode.AUTO_DETECT
856 with cache_dir() as workspace:
857 src = (workspace / "test.py").resolve()
858 with src.open("w") as fobj:
859 fobj.write("print('hello')")
860 black.write_cache({}, [src], black.DEFAULT_LINE_LENGTH, mode)
861 result = CliRunner().invoke(black.main, [str(src)])
862 self.assertEqual(result.exit_code, 0)
863 with src.open("r") as fobj:
864 self.assertEqual(fobj.read(), "print('hello')")
866 @event_loop(close=False)
867 def test_cache_multiple_files(self) -> None:
868 mode = black.FileMode.AUTO_DETECT
869 with cache_dir() as workspace, patch(
870 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
872 one = (workspace / "one.py").resolve()
873 with one.open("w") as fobj:
874 fobj.write("print('hello')")
875 two = (workspace / "two.py").resolve()
876 with two.open("w") as fobj:
877 fobj.write("print('hello')")
878 black.write_cache({}, [one], black.DEFAULT_LINE_LENGTH, mode)
879 result = CliRunner().invoke(black.main, [str(workspace)])
880 self.assertEqual(result.exit_code, 0)
881 with one.open("r") as fobj:
882 self.assertEqual(fobj.read(), "print('hello')")
883 with two.open("r") as fobj:
884 self.assertEqual(fobj.read(), 'print("hello")\n')
885 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
886 self.assertIn(one, cache)
887 self.assertIn(two, cache)
889 def test_no_cache_when_writeback_diff(self) -> None:
890 mode = black.FileMode.AUTO_DETECT
891 with cache_dir() as workspace:
892 src = (workspace / "test.py").resolve()
893 with src.open("w") as fobj:
894 fobj.write("print('hello')")
895 result = CliRunner().invoke(black.main, [str(src), "--diff"])
896 self.assertEqual(result.exit_code, 0)
897 cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode)
898 self.assertFalse(cache_file.exists())
900 def test_no_cache_when_stdin(self) -> None:
901 mode = black.FileMode.AUTO_DETECT
903 result = CliRunner().invoke(
904 black.main, ["-"], input=BytesIO(b"print('hello')")
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_read_cache_no_cachefile(self) -> None:
911 mode = black.FileMode.AUTO_DETECT
913 self.assertEqual(black.read_cache(black.DEFAULT_LINE_LENGTH, mode), {})
915 def test_write_cache_read_cache(self) -> None:
916 mode = black.FileMode.AUTO_DETECT
917 with cache_dir() as workspace:
918 src = (workspace / "test.py").resolve()
920 black.write_cache({}, [src], black.DEFAULT_LINE_LENGTH, mode)
921 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
922 self.assertIn(src, cache)
923 self.assertEqual(cache[src], black.get_cache_info(src))
925 def test_filter_cached(self) -> None:
926 with TemporaryDirectory() as workspace:
927 path = Path(workspace)
928 uncached = (path / "uncached").resolve()
929 cached = (path / "cached").resolve()
930 cached_but_changed = (path / "changed").resolve()
933 cached_but_changed.touch()
934 cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
935 todo, done = black.filter_cached(
936 cache, {uncached, cached, cached_but_changed}
938 self.assertEqual(todo, {uncached, cached_but_changed})
939 self.assertEqual(done, {cached})
941 def test_write_cache_creates_directory_if_needed(self) -> None:
942 mode = black.FileMode.AUTO_DETECT
943 with cache_dir(exists=False) as workspace:
944 self.assertFalse(workspace.exists())
945 black.write_cache({}, [], black.DEFAULT_LINE_LENGTH, mode)
946 self.assertTrue(workspace.exists())
948 @event_loop(close=False)
949 def test_failed_formatting_does_not_get_cached(self) -> None:
950 mode = black.FileMode.AUTO_DETECT
951 with cache_dir() as workspace, patch(
952 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
954 failing = (workspace / "failing.py").resolve()
955 with failing.open("w") as fobj:
956 fobj.write("not actually python")
957 clean = (workspace / "clean.py").resolve()
958 with clean.open("w") as fobj:
959 fobj.write('print("hello")\n')
960 result = CliRunner().invoke(black.main, [str(workspace)])
961 self.assertEqual(result.exit_code, 123)
962 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
963 self.assertNotIn(failing, cache)
964 self.assertIn(clean, cache)
966 def test_write_cache_write_fail(self) -> None:
967 mode = black.FileMode.AUTO_DETECT
968 with cache_dir(), patch.object(Path, "open") as mock:
969 mock.side_effect = OSError
970 black.write_cache({}, [], black.DEFAULT_LINE_LENGTH, mode)
972 @event_loop(close=False)
973 def test_check_diff_use_together(self) -> None:
975 # Files which will be reformatted.
976 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
977 result = CliRunner().invoke(black.main, [str(src1), "--diff", "--check"])
978 self.assertEqual(result.exit_code, 1, result.output)
979 # Files which will not be reformatted.
980 src2 = (THIS_DIR / "data" / "composition.py").resolve()
981 result = CliRunner().invoke(black.main, [str(src2), "--diff", "--check"])
982 self.assertEqual(result.exit_code, 0, result.output)
983 # Multi file command.
984 result = CliRunner().invoke(
985 black.main, [str(src1), str(src2), "--diff", "--check"]
987 self.assertEqual(result.exit_code, 1, result.output)
989 def test_no_files(self) -> None:
991 # Without an argument, black exits with error code 0.
992 result = CliRunner().invoke(black.main, [])
993 self.assertEqual(result.exit_code, 0)
995 def test_broken_symlink(self) -> None:
996 with cache_dir() as workspace:
997 symlink = workspace / "broken_link.py"
999 symlink.symlink_to("nonexistent.py")
1000 except OSError as e:
1001 self.skipTest(f"Can't create symlinks: {e}")
1002 result = CliRunner().invoke(black.main, [str(workspace.resolve())])
1003 self.assertEqual(result.exit_code, 0)
1005 def test_read_cache_line_lengths(self) -> None:
1006 mode = black.FileMode.AUTO_DETECT
1007 with cache_dir() as workspace:
1008 path = (workspace / "file.py").resolve()
1010 black.write_cache({}, [path], 1, mode)
1011 one = black.read_cache(1, mode)
1012 self.assertIn(path, one)
1013 two = black.read_cache(2, mode)
1014 self.assertNotIn(path, two)
1016 def test_single_file_force_pyi(self) -> None:
1017 reg_mode = black.FileMode.AUTO_DETECT
1018 pyi_mode = black.FileMode.PYI
1019 contents, expected = read_data("force_pyi")
1020 with cache_dir() as workspace:
1021 path = (workspace / "file.py").resolve()
1022 with open(path, "w") as fh:
1024 result = CliRunner().invoke(black.main, [str(path), "--pyi"])
1025 self.assertEqual(result.exit_code, 0)
1026 with open(path, "r") as fh:
1028 # verify cache with --pyi is separate
1029 pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, pyi_mode)
1030 self.assertIn(path, pyi_cache)
1031 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1032 self.assertNotIn(path, normal_cache)
1033 self.assertEqual(actual, expected)
1035 @event_loop(close=False)
1036 def test_multi_file_force_pyi(self) -> None:
1037 reg_mode = black.FileMode.AUTO_DETECT
1038 pyi_mode = black.FileMode.PYI
1039 contents, expected = read_data("force_pyi")
1040 with cache_dir() as workspace:
1042 (workspace / "file1.py").resolve(),
1043 (workspace / "file2.py").resolve(),
1046 with open(path, "w") as fh:
1048 result = CliRunner().invoke(black.main, [str(p) for p in paths] + ["--pyi"])
1049 self.assertEqual(result.exit_code, 0)
1051 with open(path, "r") as fh:
1053 self.assertEqual(actual, expected)
1054 # verify cache with --pyi is separate
1055 pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, pyi_mode)
1056 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1058 self.assertIn(path, pyi_cache)
1059 self.assertNotIn(path, normal_cache)
1061 def test_pipe_force_pyi(self) -> None:
1062 source, expected = read_data("force_pyi")
1063 result = CliRunner().invoke(
1064 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1066 self.assertEqual(result.exit_code, 0)
1067 actual = result.output
1068 self.assertFormatEqual(actual, expected)
1070 def test_single_file_force_py36(self) -> None:
1071 reg_mode = black.FileMode.AUTO_DETECT
1072 py36_mode = black.FileMode.PYTHON36
1073 source, expected = read_data("force_py36")
1074 with cache_dir() as workspace:
1075 path = (workspace / "file.py").resolve()
1076 with open(path, "w") as fh:
1078 result = CliRunner().invoke(black.main, [str(path), "--py36"])
1079 self.assertEqual(result.exit_code, 0)
1080 with open(path, "r") as fh:
1082 # verify cache with --py36 is separate
1083 py36_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, py36_mode)
1084 self.assertIn(path, py36_cache)
1085 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1086 self.assertNotIn(path, normal_cache)
1087 self.assertEqual(actual, expected)
1089 @event_loop(close=False)
1090 def test_multi_file_force_py36(self) -> None:
1091 reg_mode = black.FileMode.AUTO_DETECT
1092 py36_mode = black.FileMode.PYTHON36
1093 source, expected = read_data("force_py36")
1094 with cache_dir() as workspace:
1096 (workspace / "file1.py").resolve(),
1097 (workspace / "file2.py").resolve(),
1100 with open(path, "w") as fh:
1102 result = CliRunner().invoke(
1103 black.main, [str(p) for p in paths] + ["--py36"]
1105 self.assertEqual(result.exit_code, 0)
1107 with open(path, "r") as fh:
1109 self.assertEqual(actual, expected)
1110 # verify cache with --py36 is separate
1111 pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, py36_mode)
1112 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1114 self.assertIn(path, pyi_cache)
1115 self.assertNotIn(path, normal_cache)
1117 def test_pipe_force_py36(self) -> None:
1118 source, expected = read_data("force_py36")
1119 result = CliRunner().invoke(
1120 black.main, ["-", "-q", "--py36"], input=BytesIO(source.encode("utf8"))
1122 self.assertEqual(result.exit_code, 0)
1123 actual = result.output
1124 self.assertFormatEqual(actual, expected)
1126 def test_include_exclude(self) -> None:
1127 path = THIS_DIR / "data" / "include_exclude_tests"
1128 include = re.compile(r"\.pyi?$")
1129 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1130 report = black.Report()
1131 sources: List[Path] = []
1133 Path(path / "b/dont_exclude/a.py"),
1134 Path(path / "b/dont_exclude/a.pyi"),
1136 this_abs = THIS_DIR.resolve()
1138 black.gen_python_files_in_dir(path, this_abs, include, exclude, report)
1140 self.assertEqual(sorted(expected), sorted(sources))
1142 def test_empty_include(self) -> None:
1143 path = THIS_DIR / "data" / "include_exclude_tests"
1144 report = black.Report()
1145 empty = re.compile(r"")
1146 sources: List[Path] = []
1148 Path(path / "b/exclude/a.pie"),
1149 Path(path / "b/exclude/a.py"),
1150 Path(path / "b/exclude/a.pyi"),
1151 Path(path / "b/dont_exclude/a.pie"),
1152 Path(path / "b/dont_exclude/a.py"),
1153 Path(path / "b/dont_exclude/a.pyi"),
1154 Path(path / "b/.definitely_exclude/a.pie"),
1155 Path(path / "b/.definitely_exclude/a.py"),
1156 Path(path / "b/.definitely_exclude/a.pyi"),
1158 this_abs = THIS_DIR.resolve()
1160 black.gen_python_files_in_dir(
1161 path, this_abs, empty, re.compile(black.DEFAULT_EXCLUDES), report
1164 self.assertEqual(sorted(expected), sorted(sources))
1166 def test_empty_exclude(self) -> None:
1167 path = THIS_DIR / "data" / "include_exclude_tests"
1168 report = black.Report()
1169 empty = re.compile(r"")
1170 sources: List[Path] = []
1172 Path(path / "b/dont_exclude/a.py"),
1173 Path(path / "b/dont_exclude/a.pyi"),
1174 Path(path / "b/exclude/a.py"),
1175 Path(path / "b/exclude/a.pyi"),
1176 Path(path / "b/.definitely_exclude/a.py"),
1177 Path(path / "b/.definitely_exclude/a.pyi"),
1179 this_abs = THIS_DIR.resolve()
1181 black.gen_python_files_in_dir(
1182 path, this_abs, re.compile(black.DEFAULT_INCLUDES), empty, report
1185 self.assertEqual(sorted(expected), sorted(sources))
1187 def test_invalid_include_exclude(self) -> None:
1188 for option in ["--include", "--exclude"]:
1189 result = CliRunner().invoke(black.main, ["-", option, "**()(!!*)"])
1190 self.assertEqual(result.exit_code, 2)
1192 def test_preserves_line_endings(self) -> None:
1193 with TemporaryDirectory() as workspace:
1194 test_file = Path(workspace) / "test.py"
1195 for nl in ["\n", "\r\n"]:
1196 contents = nl.join(["def f( ):", " pass"])
1197 test_file.write_bytes(contents.encode())
1198 ff(test_file, write_back=black.WriteBack.YES)
1199 updated_contents: bytes = test_file.read_bytes()
1200 self.assertIn(nl.encode(), updated_contents)
1202 self.assertNotIn(b"\r\n", updated_contents)
1204 def test_assert_equivalent_different_asts(self) -> None:
1205 with self.assertRaises(AssertionError):
1206 black.assert_equivalent("{}", "None")
1208 def test_symlink_out_of_root_directory(self) -> None:
1212 include = re.compile(black.DEFAULT_INCLUDES)
1213 exclude = re.compile(black.DEFAULT_EXCLUDES)
1214 report = black.Report()
1215 # `child` should behave like a symlink which resolved path is clearly
1216 # outside of the `root` directory.
1217 path.iterdir.return_value = [child]
1218 child.resolve.return_value = Path("/a/b/c")
1219 child.is_symlink.return_value = True
1221 list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1222 except ValueError as ve:
1223 self.fail("`get_python_files_in_dir()` failed: {ve}")
1224 path.iterdir.assert_called_once()
1225 child.resolve.assert_called_once()
1226 child.is_symlink.assert_called_once()
1227 # `child` should behave like a strange file which resolved path is clearly
1228 # outside of the `root` directory.
1229 child.is_symlink.return_value = False
1230 with self.assertRaises(ValueError):
1231 list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1232 path.iterdir.assert_called()
1233 self.assertEqual(path.iterdir.call_count, 2)
1234 child.resolve.assert_called()
1235 self.assertEqual(child.resolve.call_count, 2)
1236 child.is_symlink.assert_called()
1237 self.assertEqual(child.is_symlink.call_count, 2)
1239 def test_shhh_click(self) -> None:
1241 from click import _unicodefun # type: ignore
1242 except ModuleNotFoundError:
1243 self.skipTest("Incompatible Click version")
1244 if not hasattr(_unicodefun, "_verify_python3_env"):
1245 self.skipTest("Incompatible Click version")
1246 # First, let's see if Click is crashing with a preferred ASCII charset.
1247 with patch("locale.getpreferredencoding") as gpe:
1248 gpe.return_value = "ASCII"
1249 with self.assertRaises(RuntimeError):
1250 _unicodefun._verify_python3_env()
1251 # Now, let's silence Click...
1253 # ...and confirm it's silent.
1254 with patch("locale.getpreferredencoding") as gpe:
1255 gpe.return_value = "ASCII"
1257 _unicodefun._verify_python3_env()
1258 except RuntimeError as re:
1259 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1262 if __name__ == "__main__":
1263 unittest.main(module="test_black")