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_python2(self) -> None:
378 source, expected = read_data("python2")
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_python2_unicode_literals(self) -> None:
386 source, expected = read_data("python2_unicode_literals")
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_stub(self) -> None:
393 mode = black.FileMode.PYI
394 source, expected = read_data("stub.pyi")
395 actual = fs(source, mode=mode)
396 self.assertFormatEqual(expected, actual)
397 black.assert_stable(source, actual, line_length=ll, mode=mode)
399 @patch("black.dump_to_file", dump_to_stderr)
400 def test_fmtonoff(self) -> None:
401 source, expected = read_data("fmtonoff")
403 self.assertFormatEqual(expected, actual)
404 black.assert_equivalent(source, actual)
405 black.assert_stable(source, actual, line_length=ll)
407 @patch("black.dump_to_file", dump_to_stderr)
408 def test_fmtonoff2(self) -> None:
409 source, expected = read_data("fmtonoff2")
411 self.assertFormatEqual(expected, actual)
412 black.assert_equivalent(source, actual)
413 black.assert_stable(source, actual, line_length=ll)
415 @patch("black.dump_to_file", dump_to_stderr)
416 def test_remove_empty_parentheses_after_class(self) -> None:
417 source, expected = read_data("class_blank_parentheses")
419 self.assertFormatEqual(expected, actual)
420 black.assert_equivalent(source, actual)
421 black.assert_stable(source, actual, line_length=ll)
423 @patch("black.dump_to_file", dump_to_stderr)
424 def test_new_line_between_class_and_code(self) -> None:
425 source, expected = read_data("class_methods_new_line")
427 self.assertFormatEqual(expected, actual)
428 black.assert_equivalent(source, actual)
429 black.assert_stable(source, actual, line_length=ll)
431 def test_report_verbose(self) -> None:
432 report = black.Report(verbose=True)
436 def out(msg: str, **kwargs: Any) -> None:
437 out_lines.append(msg)
439 def err(msg: str, **kwargs: Any) -> None:
440 err_lines.append(msg)
442 with patch("black.out", out), patch("black.err", err):
443 report.done(Path("f1"), black.Changed.NO)
444 self.assertEqual(len(out_lines), 1)
445 self.assertEqual(len(err_lines), 0)
446 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
447 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
448 self.assertEqual(report.return_code, 0)
449 report.done(Path("f2"), black.Changed.YES)
450 self.assertEqual(len(out_lines), 2)
451 self.assertEqual(len(err_lines), 0)
452 self.assertEqual(out_lines[-1], "reformatted f2")
454 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
456 report.done(Path("f3"), black.Changed.CACHED)
457 self.assertEqual(len(out_lines), 3)
458 self.assertEqual(len(err_lines), 0)
460 out_lines[-1], "f3 wasn't modified on disk since last run."
463 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
465 self.assertEqual(report.return_code, 0)
467 self.assertEqual(report.return_code, 1)
469 report.failed(Path("e1"), "boom")
470 self.assertEqual(len(out_lines), 3)
471 self.assertEqual(len(err_lines), 1)
472 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
474 unstyle(str(report)),
475 "1 file reformatted, 2 files left unchanged, "
476 "1 file failed to reformat.",
478 self.assertEqual(report.return_code, 123)
479 report.done(Path("f3"), black.Changed.YES)
480 self.assertEqual(len(out_lines), 4)
481 self.assertEqual(len(err_lines), 1)
482 self.assertEqual(out_lines[-1], "reformatted f3")
484 unstyle(str(report)),
485 "2 files reformatted, 2 files left unchanged, "
486 "1 file failed to reformat.",
488 self.assertEqual(report.return_code, 123)
489 report.failed(Path("e2"), "boom")
490 self.assertEqual(len(out_lines), 4)
491 self.assertEqual(len(err_lines), 2)
492 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
494 unstyle(str(report)),
495 "2 files reformatted, 2 files left unchanged, "
496 "2 files failed to reformat.",
498 self.assertEqual(report.return_code, 123)
499 report.path_ignored(Path("wat"), "no match")
500 self.assertEqual(len(out_lines), 5)
501 self.assertEqual(len(err_lines), 2)
502 self.assertEqual(out_lines[-1], "wat ignored: no match")
504 unstyle(str(report)),
505 "2 files reformatted, 2 files left unchanged, "
506 "2 files failed to reformat.",
508 self.assertEqual(report.return_code, 123)
509 report.done(Path("f4"), black.Changed.NO)
510 self.assertEqual(len(out_lines), 6)
511 self.assertEqual(len(err_lines), 2)
512 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
514 unstyle(str(report)),
515 "2 files reformatted, 3 files left unchanged, "
516 "2 files failed to reformat.",
518 self.assertEqual(report.return_code, 123)
521 unstyle(str(report)),
522 "2 files would be reformatted, 3 files would be left unchanged, "
523 "2 files would fail to reformat.",
526 def test_report_quiet(self) -> None:
527 report = black.Report(quiet=True)
531 def out(msg: str, **kwargs: Any) -> None:
532 out_lines.append(msg)
534 def err(msg: str, **kwargs: Any) -> None:
535 err_lines.append(msg)
537 with patch("black.out", out), patch("black.err", err):
538 report.done(Path("f1"), black.Changed.NO)
539 self.assertEqual(len(out_lines), 0)
540 self.assertEqual(len(err_lines), 0)
541 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
542 self.assertEqual(report.return_code, 0)
543 report.done(Path("f2"), black.Changed.YES)
544 self.assertEqual(len(out_lines), 0)
545 self.assertEqual(len(err_lines), 0)
547 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
549 report.done(Path("f3"), black.Changed.CACHED)
550 self.assertEqual(len(out_lines), 0)
551 self.assertEqual(len(err_lines), 0)
553 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
555 self.assertEqual(report.return_code, 0)
557 self.assertEqual(report.return_code, 1)
559 report.failed(Path("e1"), "boom")
560 self.assertEqual(len(out_lines), 0)
561 self.assertEqual(len(err_lines), 1)
562 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
564 unstyle(str(report)),
565 "1 file reformatted, 2 files left unchanged, "
566 "1 file failed to reformat.",
568 self.assertEqual(report.return_code, 123)
569 report.done(Path("f3"), black.Changed.YES)
570 self.assertEqual(len(out_lines), 0)
571 self.assertEqual(len(err_lines), 1)
573 unstyle(str(report)),
574 "2 files reformatted, 2 files left unchanged, "
575 "1 file failed to reformat.",
577 self.assertEqual(report.return_code, 123)
578 report.failed(Path("e2"), "boom")
579 self.assertEqual(len(out_lines), 0)
580 self.assertEqual(len(err_lines), 2)
581 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
583 unstyle(str(report)),
584 "2 files reformatted, 2 files left unchanged, "
585 "2 files failed to reformat.",
587 self.assertEqual(report.return_code, 123)
588 report.path_ignored(Path("wat"), "no match")
589 self.assertEqual(len(out_lines), 0)
590 self.assertEqual(len(err_lines), 2)
592 unstyle(str(report)),
593 "2 files reformatted, 2 files left unchanged, "
594 "2 files failed to reformat.",
596 self.assertEqual(report.return_code, 123)
597 report.done(Path("f4"), black.Changed.NO)
598 self.assertEqual(len(out_lines), 0)
599 self.assertEqual(len(err_lines), 2)
601 unstyle(str(report)),
602 "2 files reformatted, 3 files left unchanged, "
603 "2 files failed to reformat.",
605 self.assertEqual(report.return_code, 123)
608 unstyle(str(report)),
609 "2 files would be reformatted, 3 files would be left unchanged, "
610 "2 files would fail to reformat.",
613 def test_report_normal(self) -> None:
614 report = black.Report()
618 def out(msg: str, **kwargs: Any) -> None:
619 out_lines.append(msg)
621 def err(msg: str, **kwargs: Any) -> None:
622 err_lines.append(msg)
624 with patch("black.out", out), patch("black.err", err):
625 report.done(Path("f1"), black.Changed.NO)
626 self.assertEqual(len(out_lines), 0)
627 self.assertEqual(len(err_lines), 0)
628 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
629 self.assertEqual(report.return_code, 0)
630 report.done(Path("f2"), black.Changed.YES)
631 self.assertEqual(len(out_lines), 1)
632 self.assertEqual(len(err_lines), 0)
633 self.assertEqual(out_lines[-1], "reformatted f2")
635 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
637 report.done(Path("f3"), black.Changed.CACHED)
638 self.assertEqual(len(out_lines), 1)
639 self.assertEqual(len(err_lines), 0)
640 self.assertEqual(out_lines[-1], "reformatted f2")
642 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
644 self.assertEqual(report.return_code, 0)
646 self.assertEqual(report.return_code, 1)
648 report.failed(Path("e1"), "boom")
649 self.assertEqual(len(out_lines), 1)
650 self.assertEqual(len(err_lines), 1)
651 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
653 unstyle(str(report)),
654 "1 file reformatted, 2 files left unchanged, "
655 "1 file failed to reformat.",
657 self.assertEqual(report.return_code, 123)
658 report.done(Path("f3"), black.Changed.YES)
659 self.assertEqual(len(out_lines), 2)
660 self.assertEqual(len(err_lines), 1)
661 self.assertEqual(out_lines[-1], "reformatted f3")
663 unstyle(str(report)),
664 "2 files reformatted, 2 files left unchanged, "
665 "1 file failed to reformat.",
667 self.assertEqual(report.return_code, 123)
668 report.failed(Path("e2"), "boom")
669 self.assertEqual(len(out_lines), 2)
670 self.assertEqual(len(err_lines), 2)
671 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
673 unstyle(str(report)),
674 "2 files reformatted, 2 files left unchanged, "
675 "2 files failed to reformat.",
677 self.assertEqual(report.return_code, 123)
678 report.path_ignored(Path("wat"), "no match")
679 self.assertEqual(len(out_lines), 2)
680 self.assertEqual(len(err_lines), 2)
682 unstyle(str(report)),
683 "2 files reformatted, 2 files left unchanged, "
684 "2 files failed to reformat.",
686 self.assertEqual(report.return_code, 123)
687 report.done(Path("f4"), black.Changed.NO)
688 self.assertEqual(len(out_lines), 2)
689 self.assertEqual(len(err_lines), 2)
691 unstyle(str(report)),
692 "2 files reformatted, 3 files left unchanged, "
693 "2 files failed to reformat.",
695 self.assertEqual(report.return_code, 123)
698 unstyle(str(report)),
699 "2 files would be reformatted, 3 files would be left unchanged, "
700 "2 files would fail to reformat.",
703 def test_is_python36(self) -> None:
704 node = black.lib2to3_parse("def f(*, arg): ...\n")
705 self.assertFalse(black.is_python36(node))
706 node = black.lib2to3_parse("def f(*, arg,): ...\n")
707 self.assertTrue(black.is_python36(node))
708 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
709 self.assertTrue(black.is_python36(node))
710 source, expected = read_data("function")
711 node = black.lib2to3_parse(source)
712 self.assertTrue(black.is_python36(node))
713 node = black.lib2to3_parse(expected)
714 self.assertTrue(black.is_python36(node))
715 source, expected = read_data("expression")
716 node = black.lib2to3_parse(source)
717 self.assertFalse(black.is_python36(node))
718 node = black.lib2to3_parse(expected)
719 self.assertFalse(black.is_python36(node))
721 def test_get_future_imports(self) -> None:
722 node = black.lib2to3_parse("\n")
723 self.assertEqual(set(), black.get_future_imports(node))
724 node = black.lib2to3_parse("from __future__ import black\n")
725 self.assertEqual({"black"}, black.get_future_imports(node))
726 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
727 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
728 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
729 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
730 node = black.lib2to3_parse(
731 "from __future__ import multiple\nfrom __future__ import imports\n"
733 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
734 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
735 self.assertEqual({"black"}, black.get_future_imports(node))
736 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
737 self.assertEqual({"black"}, black.get_future_imports(node))
738 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
739 self.assertEqual(set(), black.get_future_imports(node))
740 node = black.lib2to3_parse("from some.module import black\n")
741 self.assertEqual(set(), black.get_future_imports(node))
742 node = black.lib2to3_parse(
743 "from __future__ import unicode_literals as _unicode_literals"
745 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
746 node = black.lib2to3_parse(
747 "from __future__ import unicode_literals as _lol, print"
749 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
751 def test_debug_visitor(self) -> None:
752 source, _ = read_data("debug_visitor.py")
753 expected, _ = read_data("debug_visitor.out")
757 def out(msg: str, **kwargs: Any) -> None:
758 out_lines.append(msg)
760 def err(msg: str, **kwargs: Any) -> None:
761 err_lines.append(msg)
763 with patch("black.out", out), patch("black.err", err):
764 black.DebugVisitor.show(source)
765 actual = "\n".join(out_lines) + "\n"
767 if expected != actual:
768 log_name = black.dump_to_file(*out_lines)
772 f"AST print out is different. Actual version dumped to {log_name}",
775 def test_format_file_contents(self) -> None:
777 with self.assertRaises(black.NothingChanged):
778 black.format_file_contents(empty, line_length=ll, fast=False)
780 with self.assertRaises(black.NothingChanged):
781 black.format_file_contents(just_nl, line_length=ll, fast=False)
782 same = "l = [1, 2, 3]\n"
783 with self.assertRaises(black.NothingChanged):
784 black.format_file_contents(same, line_length=ll, fast=False)
785 different = "l = [1,2,3]"
787 actual = black.format_file_contents(different, line_length=ll, fast=False)
788 self.assertEqual(expected, actual)
789 invalid = "return if you can"
790 with self.assertRaises(ValueError) as e:
791 black.format_file_contents(invalid, line_length=ll, fast=False)
792 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
794 def test_endmarker(self) -> None:
795 n = black.lib2to3_parse("\n")
796 self.assertEqual(n.type, black.syms.file_input)
797 self.assertEqual(len(n.children), 1)
798 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
800 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
801 def test_assertFormatEqual(self) -> None:
805 def out(msg: str, **kwargs: Any) -> None:
806 out_lines.append(msg)
808 def err(msg: str, **kwargs: Any) -> None:
809 err_lines.append(msg)
811 with patch("black.out", out), patch("black.err", err):
812 with self.assertRaises(AssertionError):
813 self.assertFormatEqual("l = [1, 2, 3]", "l = [1, 2, 3,]")
815 out_str = "".join(out_lines)
816 self.assertTrue("Expected tree:" in out_str)
817 self.assertTrue("Actual tree:" in out_str)
818 self.assertEqual("".join(err_lines), "")
820 def test_cache_broken_file(self) -> None:
821 mode = black.FileMode.AUTO_DETECT
822 with cache_dir() as workspace:
823 cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode)
824 with cache_file.open("w") as fobj:
825 fobj.write("this is not a pickle")
826 self.assertEqual(black.read_cache(black.DEFAULT_LINE_LENGTH, mode), {})
827 src = (workspace / "test.py").resolve()
828 with src.open("w") as fobj:
829 fobj.write("print('hello')")
830 result = CliRunner().invoke(black.main, [str(src)])
831 self.assertEqual(result.exit_code, 0)
832 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
833 self.assertIn(src, cache)
835 def test_cache_single_file_already_cached(self) -> None:
836 mode = black.FileMode.AUTO_DETECT
837 with cache_dir() as workspace:
838 src = (workspace / "test.py").resolve()
839 with src.open("w") as fobj:
840 fobj.write("print('hello')")
841 black.write_cache({}, [src], black.DEFAULT_LINE_LENGTH, mode)
842 result = CliRunner().invoke(black.main, [str(src)])
843 self.assertEqual(result.exit_code, 0)
844 with src.open("r") as fobj:
845 self.assertEqual(fobj.read(), "print('hello')")
847 @event_loop(close=False)
848 def test_cache_multiple_files(self) -> None:
849 mode = black.FileMode.AUTO_DETECT
850 with cache_dir() as workspace, patch(
851 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
853 one = (workspace / "one.py").resolve()
854 with one.open("w") as fobj:
855 fobj.write("print('hello')")
856 two = (workspace / "two.py").resolve()
857 with two.open("w") as fobj:
858 fobj.write("print('hello')")
859 black.write_cache({}, [one], black.DEFAULT_LINE_LENGTH, mode)
860 result = CliRunner().invoke(black.main, [str(workspace)])
861 self.assertEqual(result.exit_code, 0)
862 with one.open("r") as fobj:
863 self.assertEqual(fobj.read(), "print('hello')")
864 with two.open("r") as fobj:
865 self.assertEqual(fobj.read(), 'print("hello")\n')
866 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
867 self.assertIn(one, cache)
868 self.assertIn(two, cache)
870 def test_no_cache_when_writeback_diff(self) -> None:
871 mode = black.FileMode.AUTO_DETECT
872 with cache_dir() as workspace:
873 src = (workspace / "test.py").resolve()
874 with src.open("w") as fobj:
875 fobj.write("print('hello')")
876 result = CliRunner().invoke(black.main, [str(src), "--diff"])
877 self.assertEqual(result.exit_code, 0)
878 cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode)
879 self.assertFalse(cache_file.exists())
881 def test_no_cache_when_stdin(self) -> None:
882 mode = black.FileMode.AUTO_DETECT
884 result = CliRunner().invoke(
885 black.main, ["-"], input=BytesIO(b"print('hello')")
887 self.assertEqual(result.exit_code, 0)
888 cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode)
889 self.assertFalse(cache_file.exists())
891 def test_read_cache_no_cachefile(self) -> None:
892 mode = black.FileMode.AUTO_DETECT
894 self.assertEqual(black.read_cache(black.DEFAULT_LINE_LENGTH, mode), {})
896 def test_write_cache_read_cache(self) -> None:
897 mode = black.FileMode.AUTO_DETECT
898 with cache_dir() as workspace:
899 src = (workspace / "test.py").resolve()
901 black.write_cache({}, [src], black.DEFAULT_LINE_LENGTH, mode)
902 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
903 self.assertIn(src, cache)
904 self.assertEqual(cache[src], black.get_cache_info(src))
906 def test_filter_cached(self) -> None:
907 with TemporaryDirectory() as workspace:
908 path = Path(workspace)
909 uncached = (path / "uncached").resolve()
910 cached = (path / "cached").resolve()
911 cached_but_changed = (path / "changed").resolve()
914 cached_but_changed.touch()
915 cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
916 todo, done = black.filter_cached(
917 cache, {uncached, cached, cached_but_changed}
919 self.assertEqual(todo, {uncached, cached_but_changed})
920 self.assertEqual(done, {cached})
922 def test_write_cache_creates_directory_if_needed(self) -> None:
923 mode = black.FileMode.AUTO_DETECT
924 with cache_dir(exists=False) as workspace:
925 self.assertFalse(workspace.exists())
926 black.write_cache({}, [], black.DEFAULT_LINE_LENGTH, mode)
927 self.assertTrue(workspace.exists())
929 @event_loop(close=False)
930 def test_failed_formatting_does_not_get_cached(self) -> None:
931 mode = black.FileMode.AUTO_DETECT
932 with cache_dir() as workspace, patch(
933 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
935 failing = (workspace / "failing.py").resolve()
936 with failing.open("w") as fobj:
937 fobj.write("not actually python")
938 clean = (workspace / "clean.py").resolve()
939 with clean.open("w") as fobj:
940 fobj.write('print("hello")\n')
941 result = CliRunner().invoke(black.main, [str(workspace)])
942 self.assertEqual(result.exit_code, 123)
943 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
944 self.assertNotIn(failing, cache)
945 self.assertIn(clean, cache)
947 def test_write_cache_write_fail(self) -> None:
948 mode = black.FileMode.AUTO_DETECT
949 with cache_dir(), patch.object(Path, "open") as mock:
950 mock.side_effect = OSError
951 black.write_cache({}, [], black.DEFAULT_LINE_LENGTH, mode)
953 @event_loop(close=False)
954 def test_check_diff_use_together(self) -> None:
956 # Files which will be reformatted.
957 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
958 result = CliRunner().invoke(black.main, [str(src1), "--diff", "--check"])
959 self.assertEqual(result.exit_code, 1, result.output)
960 # Files which will not be reformatted.
961 src2 = (THIS_DIR / "data" / "composition.py").resolve()
962 result = CliRunner().invoke(black.main, [str(src2), "--diff", "--check"])
963 self.assertEqual(result.exit_code, 0, result.output)
964 # Multi file command.
965 result = CliRunner().invoke(
966 black.main, [str(src1), str(src2), "--diff", "--check"]
968 self.assertEqual(result.exit_code, 1, result.output)
970 def test_no_files(self) -> None:
972 # Without an argument, black exits with error code 0.
973 result = CliRunner().invoke(black.main, [])
974 self.assertEqual(result.exit_code, 0)
976 def test_broken_symlink(self) -> None:
977 with cache_dir() as workspace:
978 symlink = workspace / "broken_link.py"
980 symlink.symlink_to("nonexistent.py")
982 self.skipTest(f"Can't create symlinks: {e}")
983 result = CliRunner().invoke(black.main, [str(workspace.resolve())])
984 self.assertEqual(result.exit_code, 0)
986 def test_read_cache_line_lengths(self) -> None:
987 mode = black.FileMode.AUTO_DETECT
988 with cache_dir() as workspace:
989 path = (workspace / "file.py").resolve()
991 black.write_cache({}, [path], 1, mode)
992 one = black.read_cache(1, mode)
993 self.assertIn(path, one)
994 two = black.read_cache(2, mode)
995 self.assertNotIn(path, two)
997 def test_single_file_force_pyi(self) -> None:
998 reg_mode = black.FileMode.AUTO_DETECT
999 pyi_mode = black.FileMode.PYI
1000 contents, expected = read_data("force_pyi")
1001 with cache_dir() as workspace:
1002 path = (workspace / "file.py").resolve()
1003 with open(path, "w") as fh:
1005 result = CliRunner().invoke(black.main, [str(path), "--pyi"])
1006 self.assertEqual(result.exit_code, 0)
1007 with open(path, "r") as fh:
1009 # verify cache with --pyi is separate
1010 pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, pyi_mode)
1011 self.assertIn(path, pyi_cache)
1012 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1013 self.assertNotIn(path, normal_cache)
1014 self.assertEqual(actual, expected)
1016 @event_loop(close=False)
1017 def test_multi_file_force_pyi(self) -> None:
1018 reg_mode = black.FileMode.AUTO_DETECT
1019 pyi_mode = black.FileMode.PYI
1020 contents, expected = read_data("force_pyi")
1021 with cache_dir() as workspace:
1023 (workspace / "file1.py").resolve(),
1024 (workspace / "file2.py").resolve(),
1027 with open(path, "w") as fh:
1029 result = CliRunner().invoke(black.main, [str(p) for p in paths] + ["--pyi"])
1030 self.assertEqual(result.exit_code, 0)
1032 with open(path, "r") as fh:
1034 self.assertEqual(actual, expected)
1035 # verify cache with --pyi is separate
1036 pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, pyi_mode)
1037 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1039 self.assertIn(path, pyi_cache)
1040 self.assertNotIn(path, normal_cache)
1042 def test_pipe_force_pyi(self) -> None:
1043 source, expected = read_data("force_pyi")
1044 result = CliRunner().invoke(
1045 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1047 self.assertEqual(result.exit_code, 0)
1048 actual = result.output
1049 self.assertFormatEqual(actual, expected)
1051 def test_single_file_force_py36(self) -> None:
1052 reg_mode = black.FileMode.AUTO_DETECT
1053 py36_mode = black.FileMode.PYTHON36
1054 source, expected = read_data("force_py36")
1055 with cache_dir() as workspace:
1056 path = (workspace / "file.py").resolve()
1057 with open(path, "w") as fh:
1059 result = CliRunner().invoke(black.main, [str(path), "--py36"])
1060 self.assertEqual(result.exit_code, 0)
1061 with open(path, "r") as fh:
1063 # verify cache with --py36 is separate
1064 py36_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, py36_mode)
1065 self.assertIn(path, py36_cache)
1066 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1067 self.assertNotIn(path, normal_cache)
1068 self.assertEqual(actual, expected)
1070 @event_loop(close=False)
1071 def test_multi_file_force_py36(self) -> None:
1072 reg_mode = black.FileMode.AUTO_DETECT
1073 py36_mode = black.FileMode.PYTHON36
1074 source, expected = read_data("force_py36")
1075 with cache_dir() as workspace:
1077 (workspace / "file1.py").resolve(),
1078 (workspace / "file2.py").resolve(),
1081 with open(path, "w") as fh:
1083 result = CliRunner().invoke(
1084 black.main, [str(p) for p in paths] + ["--py36"]
1086 self.assertEqual(result.exit_code, 0)
1088 with open(path, "r") as fh:
1090 self.assertEqual(actual, expected)
1091 # verify cache with --py36 is separate
1092 pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, py36_mode)
1093 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1095 self.assertIn(path, pyi_cache)
1096 self.assertNotIn(path, normal_cache)
1098 def test_pipe_force_py36(self) -> None:
1099 source, expected = read_data("force_py36")
1100 result = CliRunner().invoke(
1101 black.main, ["-", "-q", "--py36"], input=BytesIO(source.encode("utf8"))
1103 self.assertEqual(result.exit_code, 0)
1104 actual = result.output
1105 self.assertFormatEqual(actual, expected)
1107 def test_include_exclude(self) -> None:
1108 path = THIS_DIR / "data" / "include_exclude_tests"
1109 include = re.compile(r"\.pyi?$")
1110 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1111 report = black.Report()
1112 sources: List[Path] = []
1114 Path(path / "b/dont_exclude/a.py"),
1115 Path(path / "b/dont_exclude/a.pyi"),
1117 this_abs = THIS_DIR.resolve()
1119 black.gen_python_files_in_dir(path, this_abs, include, exclude, report)
1121 self.assertEqual(sorted(expected), sorted(sources))
1123 def test_empty_include(self) -> None:
1124 path = THIS_DIR / "data" / "include_exclude_tests"
1125 report = black.Report()
1126 empty = re.compile(r"")
1127 sources: List[Path] = []
1129 Path(path / "b/exclude/a.pie"),
1130 Path(path / "b/exclude/a.py"),
1131 Path(path / "b/exclude/a.pyi"),
1132 Path(path / "b/dont_exclude/a.pie"),
1133 Path(path / "b/dont_exclude/a.py"),
1134 Path(path / "b/dont_exclude/a.pyi"),
1135 Path(path / "b/.definitely_exclude/a.pie"),
1136 Path(path / "b/.definitely_exclude/a.py"),
1137 Path(path / "b/.definitely_exclude/a.pyi"),
1139 this_abs = THIS_DIR.resolve()
1141 black.gen_python_files_in_dir(
1142 path, this_abs, empty, re.compile(black.DEFAULT_EXCLUDES), report
1145 self.assertEqual(sorted(expected), sorted(sources))
1147 def test_empty_exclude(self) -> None:
1148 path = THIS_DIR / "data" / "include_exclude_tests"
1149 report = black.Report()
1150 empty = re.compile(r"")
1151 sources: List[Path] = []
1153 Path(path / "b/dont_exclude/a.py"),
1154 Path(path / "b/dont_exclude/a.pyi"),
1155 Path(path / "b/exclude/a.py"),
1156 Path(path / "b/exclude/a.pyi"),
1157 Path(path / "b/.definitely_exclude/a.py"),
1158 Path(path / "b/.definitely_exclude/a.pyi"),
1160 this_abs = THIS_DIR.resolve()
1162 black.gen_python_files_in_dir(
1163 path, this_abs, re.compile(black.DEFAULT_INCLUDES), empty, report
1166 self.assertEqual(sorted(expected), sorted(sources))
1168 def test_invalid_include_exclude(self) -> None:
1169 for option in ["--include", "--exclude"]:
1170 result = CliRunner().invoke(black.main, ["-", option, "**()(!!*)"])
1171 self.assertEqual(result.exit_code, 2)
1173 def test_preserves_line_endings(self) -> None:
1174 with TemporaryDirectory() as workspace:
1175 test_file = Path(workspace) / "test.py"
1176 for nl in ["\n", "\r\n"]:
1177 contents = nl.join(["def f( ):", " pass"])
1178 test_file.write_bytes(contents.encode())
1179 ff(test_file, write_back=black.WriteBack.YES)
1180 updated_contents: bytes = test_file.read_bytes()
1181 self.assertIn(nl.encode(), updated_contents)
1183 self.assertNotIn(b"\r\n", updated_contents)
1185 def test_assert_equivalent_different_asts(self) -> None:
1186 with self.assertRaises(AssertionError):
1187 black.assert_equivalent("{}", "None")
1189 def test_symlink_out_of_root_directory(self) -> None:
1193 include = re.compile(black.DEFAULT_INCLUDES)
1194 exclude = re.compile(black.DEFAULT_EXCLUDES)
1195 report = black.Report()
1196 # `child` should behave like a symlink which resolved path is clearly
1197 # outside of the `root` directory.
1198 path.iterdir.return_value = [child]
1199 child.resolve.return_value = Path("/a/b/c")
1200 child.is_symlink.return_value = True
1202 list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1203 except ValueError as ve:
1204 self.fail("`get_python_files_in_dir()` failed: {ve}")
1205 path.iterdir.assert_called_once()
1206 child.resolve.assert_called_once()
1207 child.is_symlink.assert_called_once()
1208 # `child` should behave like a strange file which resolved path is clearly
1209 # outside of the `root` directory.
1210 child.is_symlink.return_value = False
1211 with self.assertRaises(ValueError):
1212 list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1213 path.iterdir.assert_called()
1214 self.assertEqual(path.iterdir.call_count, 2)
1215 child.resolve.assert_called()
1216 self.assertEqual(child.resolve.call_count, 2)
1217 child.is_symlink.assert_called()
1218 self.assertEqual(child.is_symlink.call_count, 2)
1220 def test_shhh_click(self) -> None:
1222 from click import _unicodefun # type: ignore
1223 except ModuleNotFoundError:
1224 self.skipTest("Incompatible Click version")
1225 if not hasattr(_unicodefun, "_verify_python3_env"):
1226 self.skipTest("Incompatible Click version")
1227 # First, let's see if Click is crashing with a preferred ASCII charset.
1228 with patch("locale.getpreferredencoding") as gpe:
1229 gpe.return_value = "ASCII"
1230 with self.assertRaises(RuntimeError):
1231 _unicodefun._verify_python3_env()
1232 # Now, let's silence Click...
1234 # ...and confirm it's silent.
1235 with patch("locale.getpreferredencoding") as gpe:
1236 gpe.return_value = "ASCII"
1238 _unicodefun._verify_python3_env()
1239 except RuntimeError as re:
1240 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1243 if __name__ == "__main__":
1244 unittest.main(module="test_black")