All patches and comments are welcome. Please squash your changes to logical
commits before using git-format-patch and git-send-email to
patches@git.madduck.net.
If you'd read over the Git project's submission guidelines and adhered to them,
I'd be especially grateful.
3 from concurrent.futures import ThreadPoolExecutor
4 from contextlib import contextmanager
5 from functools import partial
6 from io import BytesIO, TextIOWrapper
8 from pathlib import Path
11 from tempfile import TemporaryDirectory
12 from typing import Any, BinaryIO, Generator, List, Tuple, Iterator
14 from unittest.mock import patch, MagicMock
16 from click import unstyle
17 from click.testing import CliRunner
23 ff = partial(black.format_file_in_place, line_length=ll, fast=True)
24 fs = partial(black.format_str, line_length=ll)
25 THIS_FILE = Path(__file__)
26 THIS_DIR = THIS_FILE.parent
27 EMPTY_LINE = "# EMPTY LINE WITH WHITESPACE" + " (this comment will be removed)"
30 def dump_to_stderr(*output: str) -> str:
31 return "\n" + "\n".join(output) + "\n"
34 def read_data(name: str, data: bool = True) -> Tuple[str, str]:
35 """read_data('test_name') -> 'input', 'output'"""
36 if not name.endswith((".py", ".pyi", ".out", ".diff")):
38 _input: List[str] = []
39 _output: List[str] = []
40 base_dir = THIS_DIR / "data" if data else THIS_DIR
41 with open(base_dir / name, "r", encoding="utf8") as test:
42 lines = test.readlines()
45 line = line.replace(EMPTY_LINE, "")
46 if line.rstrip() == "# output":
51 if _input and not _output:
52 # If there's no output marker, treat the entire file as already pre-formatted.
54 return "".join(_input).strip() + "\n", "".join(_output).strip() + "\n"
58 def cache_dir(exists: bool = True) -> Iterator[Path]:
59 with TemporaryDirectory() as workspace:
60 cache_dir = Path(workspace)
62 cache_dir = cache_dir / "new"
63 with patch("black.CACHE_DIR", cache_dir):
68 def event_loop(close: bool) -> Iterator[None]:
69 policy = asyncio.get_event_loop_policy()
70 old_loop = policy.get_event_loop()
71 loop = policy.new_event_loop()
72 asyncio.set_event_loop(loop)
77 policy.set_event_loop(old_loop)
82 class BlackRunner(CliRunner):
83 """Modify CliRunner so that stderr is not merged with stdout.
85 This is a hack that can be removed once we depend on Click 7.x"""
87 def __init__(self, stderrbuf: BinaryIO) -> None:
88 self.stderrbuf = stderrbuf
92 def isolation(self, *args: Any, **kwargs: Any) -> Generator[BinaryIO, None, None]:
93 with super().isolation(*args, **kwargs) as output:
95 hold_stderr = sys.stderr
96 sys.stderr = TextIOWrapper(self.stderrbuf, encoding=self.charset)
99 sys.stderr = hold_stderr
102 class BlackTestCase(unittest.TestCase):
105 def assertFormatEqual(self, expected: str, actual: str) -> None:
106 if actual != expected and not os.environ.get("SKIP_AST_PRINT"):
107 bdv: black.DebugVisitor[Any]
108 black.out("Expected tree:", fg="green")
110 exp_node = black.lib2to3_parse(expected)
111 bdv = black.DebugVisitor()
112 list(bdv.visit(exp_node))
113 except Exception as ve:
115 black.out("Actual tree:", fg="red")
117 exp_node = black.lib2to3_parse(actual)
118 bdv = black.DebugVisitor()
119 list(bdv.visit(exp_node))
120 except Exception as ve:
122 self.assertEqual(expected, actual)
124 @patch("black.dump_to_file", dump_to_stderr)
125 def test_empty(self) -> None:
126 source = expected = ""
128 self.assertFormatEqual(expected, actual)
129 black.assert_equivalent(source, actual)
130 black.assert_stable(source, actual, line_length=ll)
132 def test_empty_ff(self) -> None:
134 tmp_file = Path(black.dump_to_file())
136 self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES))
137 with open(tmp_file, encoding="utf8") as f:
141 self.assertFormatEqual(expected, actual)
143 @patch("black.dump_to_file", dump_to_stderr)
144 def test_self(self) -> None:
145 source, expected = read_data("test_black", data=False)
147 self.assertFormatEqual(expected, actual)
148 black.assert_equivalent(source, actual)
149 black.assert_stable(source, actual, line_length=ll)
150 self.assertFalse(ff(THIS_FILE))
152 @patch("black.dump_to_file", dump_to_stderr)
153 def test_black(self) -> None:
154 source, expected = read_data("../black", data=False)
156 self.assertFormatEqual(expected, actual)
157 black.assert_equivalent(source, actual)
158 black.assert_stable(source, actual, line_length=ll)
159 self.assertFalse(ff(THIS_DIR / ".." / "black.py"))
161 def test_piping(self) -> None:
162 source, expected = read_data("../black", data=False)
163 stderrbuf = BytesIO()
164 result = BlackRunner(stderrbuf).invoke(
165 black.main, ["-", "--fast", f"--line-length={ll}"], input=source
167 self.assertEqual(result.exit_code, 0)
168 self.assertFormatEqual(expected, result.output)
169 black.assert_equivalent(source, result.output)
170 black.assert_stable(source, result.output, line_length=ll)
172 def test_piping_diff(self) -> None:
173 diff_header = re.compile(
174 rf"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d "
175 rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
177 source, _ = read_data("expression.py")
178 expected, _ = read_data("expression.diff")
179 config = THIS_DIR / "data" / "empty_pyproject.toml"
180 stderrbuf = BytesIO()
181 args = ["-", "--fast", f"--line-length={ll}", "--diff", f"--config={config}"]
182 result = BlackRunner(stderrbuf).invoke(black.main, args, input=source)
183 self.assertEqual(result.exit_code, 0)
184 actual = diff_header.sub("[Deterministic header]", result.output)
185 actual = actual.rstrip() + "\n" # the diff output has a trailing space
186 self.assertEqual(expected, actual)
188 @patch("black.dump_to_file", dump_to_stderr)
189 def test_setup(self) -> None:
190 source, expected = read_data("../setup", data=False)
192 self.assertFormatEqual(expected, actual)
193 black.assert_equivalent(source, actual)
194 black.assert_stable(source, actual, line_length=ll)
195 self.assertFalse(ff(THIS_DIR / ".." / "setup.py"))
197 @patch("black.dump_to_file", dump_to_stderr)
198 def test_function(self) -> None:
199 source, expected = read_data("function")
201 self.assertFormatEqual(expected, actual)
202 black.assert_equivalent(source, actual)
203 black.assert_stable(source, actual, line_length=ll)
205 @patch("black.dump_to_file", dump_to_stderr)
206 def test_function2(self) -> None:
207 source, expected = read_data("function2")
209 self.assertFormatEqual(expected, actual)
210 black.assert_equivalent(source, actual)
211 black.assert_stable(source, actual, line_length=ll)
213 @patch("black.dump_to_file", dump_to_stderr)
214 def test_expression(self) -> None:
215 source, expected = read_data("expression")
217 self.assertFormatEqual(expected, actual)
218 black.assert_equivalent(source, actual)
219 black.assert_stable(source, actual, line_length=ll)
221 def test_expression_ff(self) -> None:
222 source, expected = read_data("expression")
223 tmp_file = Path(black.dump_to_file(source))
225 self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
226 with open(tmp_file, encoding="utf8") as f:
230 self.assertFormatEqual(expected, actual)
231 with patch("black.dump_to_file", dump_to_stderr):
232 black.assert_equivalent(source, actual)
233 black.assert_stable(source, actual, line_length=ll)
235 def test_expression_diff(self) -> None:
236 source, _ = read_data("expression.py")
237 expected, _ = read_data("expression.diff")
238 tmp_file = Path(black.dump_to_file(source))
239 diff_header = re.compile(
240 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
241 rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
243 stderrbuf = BytesIO()
245 result = BlackRunner(stderrbuf).invoke(
246 black.main, ["--diff", str(tmp_file)]
248 self.assertEqual(result.exit_code, 0)
251 actual = result.output
252 actual = diff_header.sub("[Deterministic header]", actual)
253 actual = actual.rstrip() + "\n" # the diff output has a trailing space
254 if expected != actual:
255 dump = black.dump_to_file(actual)
257 f"Expected diff isn't equal to the actual. If you made changes "
258 f"to expression.py and this is an anticipated difference, "
259 f"overwrite tests/expression.diff with {dump}"
261 self.assertEqual(expected, actual, msg)
263 @patch("black.dump_to_file", dump_to_stderr)
264 def test_fstring(self) -> None:
265 source, expected = read_data("fstring")
267 self.assertFormatEqual(expected, actual)
268 black.assert_equivalent(source, actual)
269 black.assert_stable(source, actual, line_length=ll)
271 @patch("black.dump_to_file", dump_to_stderr)
272 def test_string_quotes(self) -> None:
273 source, expected = read_data("string_quotes")
275 self.assertFormatEqual(expected, actual)
276 black.assert_equivalent(source, actual)
277 black.assert_stable(source, actual, line_length=ll)
278 mode = black.FileMode.NO_STRING_NORMALIZATION
279 not_normalized = fs(source, mode=mode)
280 self.assertFormatEqual(source, not_normalized)
281 black.assert_equivalent(source, not_normalized)
282 black.assert_stable(source, not_normalized, line_length=ll, mode=mode)
284 @patch("black.dump_to_file", dump_to_stderr)
285 def test_slices(self) -> None:
286 source, expected = read_data("slices")
288 self.assertFormatEqual(expected, actual)
289 black.assert_equivalent(source, actual)
290 black.assert_stable(source, actual, line_length=ll)
292 @patch("black.dump_to_file", dump_to_stderr)
293 def test_comments(self) -> None:
294 source, expected = read_data("comments")
296 self.assertFormatEqual(expected, actual)
297 black.assert_equivalent(source, actual)
298 black.assert_stable(source, actual, line_length=ll)
300 @patch("black.dump_to_file", dump_to_stderr)
301 def test_comments2(self) -> None:
302 source, expected = read_data("comments2")
304 self.assertFormatEqual(expected, actual)
305 black.assert_equivalent(source, actual)
306 black.assert_stable(source, actual, line_length=ll)
308 @patch("black.dump_to_file", dump_to_stderr)
309 def test_comments3(self) -> None:
310 source, expected = read_data("comments3")
312 self.assertFormatEqual(expected, actual)
313 black.assert_equivalent(source, actual)
314 black.assert_stable(source, actual, line_length=ll)
316 @patch("black.dump_to_file", dump_to_stderr)
317 def test_comments4(self) -> None:
318 source, expected = read_data("comments4")
320 self.assertFormatEqual(expected, actual)
321 black.assert_equivalent(source, actual)
322 black.assert_stable(source, actual, line_length=ll)
324 @patch("black.dump_to_file", dump_to_stderr)
325 def test_comments5(self) -> None:
326 source, expected = read_data("comments5")
328 self.assertFormatEqual(expected, actual)
329 black.assert_equivalent(source, actual)
330 black.assert_stable(source, actual, line_length=ll)
332 @patch("black.dump_to_file", dump_to_stderr)
333 def test_cantfit(self) -> None:
334 source, expected = read_data("cantfit")
336 self.assertFormatEqual(expected, actual)
337 black.assert_equivalent(source, actual)
338 black.assert_stable(source, actual, line_length=ll)
340 @patch("black.dump_to_file", dump_to_stderr)
341 def test_import_spacing(self) -> None:
342 source, expected = read_data("import_spacing")
344 self.assertFormatEqual(expected, actual)
345 black.assert_equivalent(source, actual)
346 black.assert_stable(source, actual, line_length=ll)
348 @patch("black.dump_to_file", dump_to_stderr)
349 def test_composition(self) -> None:
350 source, expected = read_data("composition")
352 self.assertFormatEqual(expected, actual)
353 black.assert_equivalent(source, actual)
354 black.assert_stable(source, actual, line_length=ll)
356 @patch("black.dump_to_file", dump_to_stderr)
357 def test_empty_lines(self) -> None:
358 source, expected = read_data("empty_lines")
360 self.assertFormatEqual(expected, actual)
361 black.assert_equivalent(source, actual)
362 black.assert_stable(source, actual, line_length=ll)
364 @patch("black.dump_to_file", dump_to_stderr)
365 def test_string_prefixes(self) -> None:
366 source, expected = read_data("string_prefixes")
368 self.assertFormatEqual(expected, actual)
369 black.assert_equivalent(source, actual)
370 black.assert_stable(source, actual, line_length=ll)
372 @patch("black.dump_to_file", dump_to_stderr)
373 def test_python2(self) -> None:
374 source, expected = read_data("python2")
376 self.assertFormatEqual(expected, actual)
377 # black.assert_equivalent(source, actual)
378 black.assert_stable(source, actual, line_length=ll)
380 @patch("black.dump_to_file", dump_to_stderr)
381 def test_python2_unicode_literals(self) -> None:
382 source, expected = read_data("python2_unicode_literals")
384 self.assertFormatEqual(expected, actual)
385 black.assert_stable(source, actual, line_length=ll)
387 @patch("black.dump_to_file", dump_to_stderr)
388 def test_stub(self) -> None:
389 mode = black.FileMode.PYI
390 source, expected = read_data("stub.pyi")
391 actual = fs(source, mode=mode)
392 self.assertFormatEqual(expected, actual)
393 black.assert_stable(source, actual, line_length=ll, mode=mode)
395 @patch("black.dump_to_file", dump_to_stderr)
396 def test_fmtonoff(self) -> None:
397 source, expected = read_data("fmtonoff")
399 self.assertFormatEqual(expected, actual)
400 black.assert_equivalent(source, actual)
401 black.assert_stable(source, actual, line_length=ll)
403 @patch("black.dump_to_file", dump_to_stderr)
404 def test_fmtonoff2(self) -> None:
405 source, expected = read_data("fmtonoff2")
407 self.assertFormatEqual(expected, actual)
408 black.assert_equivalent(source, actual)
409 black.assert_stable(source, actual, line_length=ll)
411 @patch("black.dump_to_file", dump_to_stderr)
412 def test_remove_empty_parentheses_after_class(self) -> None:
413 source, expected = read_data("class_blank_parentheses")
415 self.assertFormatEqual(expected, actual)
416 black.assert_equivalent(source, actual)
417 black.assert_stable(source, actual, line_length=ll)
419 @patch("black.dump_to_file", dump_to_stderr)
420 def test_new_line_between_class_and_code(self) -> None:
421 source, expected = read_data("class_methods_new_line")
423 self.assertFormatEqual(expected, actual)
424 black.assert_equivalent(source, actual)
425 black.assert_stable(source, actual, line_length=ll)
427 def test_report_verbose(self) -> None:
428 report = black.Report(verbose=True)
432 def out(msg: str, **kwargs: Any) -> None:
433 out_lines.append(msg)
435 def err(msg: str, **kwargs: Any) -> None:
436 err_lines.append(msg)
438 with patch("black.out", out), patch("black.err", err):
439 report.done(Path("f1"), black.Changed.NO)
440 self.assertEqual(len(out_lines), 1)
441 self.assertEqual(len(err_lines), 0)
442 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
443 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
444 self.assertEqual(report.return_code, 0)
445 report.done(Path("f2"), black.Changed.YES)
446 self.assertEqual(len(out_lines), 2)
447 self.assertEqual(len(err_lines), 0)
448 self.assertEqual(out_lines[-1], "reformatted f2")
450 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
452 report.done(Path("f3"), black.Changed.CACHED)
453 self.assertEqual(len(out_lines), 3)
454 self.assertEqual(len(err_lines), 0)
456 out_lines[-1], "f3 wasn't modified on disk since last run."
459 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
461 self.assertEqual(report.return_code, 0)
463 self.assertEqual(report.return_code, 1)
465 report.failed(Path("e1"), "boom")
466 self.assertEqual(len(out_lines), 3)
467 self.assertEqual(len(err_lines), 1)
468 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
470 unstyle(str(report)),
471 "1 file reformatted, 2 files left unchanged, "
472 "1 file failed to reformat.",
474 self.assertEqual(report.return_code, 123)
475 report.done(Path("f3"), black.Changed.YES)
476 self.assertEqual(len(out_lines), 4)
477 self.assertEqual(len(err_lines), 1)
478 self.assertEqual(out_lines[-1], "reformatted f3")
480 unstyle(str(report)),
481 "2 files reformatted, 2 files left unchanged, "
482 "1 file failed to reformat.",
484 self.assertEqual(report.return_code, 123)
485 report.failed(Path("e2"), "boom")
486 self.assertEqual(len(out_lines), 4)
487 self.assertEqual(len(err_lines), 2)
488 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
490 unstyle(str(report)),
491 "2 files reformatted, 2 files left unchanged, "
492 "2 files failed to reformat.",
494 self.assertEqual(report.return_code, 123)
495 report.path_ignored(Path("wat"), "no match")
496 self.assertEqual(len(out_lines), 5)
497 self.assertEqual(len(err_lines), 2)
498 self.assertEqual(out_lines[-1], "wat ignored: no match")
500 unstyle(str(report)),
501 "2 files reformatted, 2 files left unchanged, "
502 "2 files failed to reformat.",
504 self.assertEqual(report.return_code, 123)
505 report.done(Path("f4"), black.Changed.NO)
506 self.assertEqual(len(out_lines), 6)
507 self.assertEqual(len(err_lines), 2)
508 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
510 unstyle(str(report)),
511 "2 files reformatted, 3 files left unchanged, "
512 "2 files failed to reformat.",
514 self.assertEqual(report.return_code, 123)
517 unstyle(str(report)),
518 "2 files would be reformatted, 3 files would be left unchanged, "
519 "2 files would fail to reformat.",
522 def test_report_quiet(self) -> None:
523 report = black.Report(quiet=True)
527 def out(msg: str, **kwargs: Any) -> None:
528 out_lines.append(msg)
530 def err(msg: str, **kwargs: Any) -> None:
531 err_lines.append(msg)
533 with patch("black.out", out), patch("black.err", err):
534 report.done(Path("f1"), black.Changed.NO)
535 self.assertEqual(len(out_lines), 0)
536 self.assertEqual(len(err_lines), 0)
537 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
538 self.assertEqual(report.return_code, 0)
539 report.done(Path("f2"), black.Changed.YES)
540 self.assertEqual(len(out_lines), 0)
541 self.assertEqual(len(err_lines), 0)
543 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
545 report.done(Path("f3"), black.Changed.CACHED)
546 self.assertEqual(len(out_lines), 0)
547 self.assertEqual(len(err_lines), 0)
549 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
551 self.assertEqual(report.return_code, 0)
553 self.assertEqual(report.return_code, 1)
555 report.failed(Path("e1"), "boom")
556 self.assertEqual(len(out_lines), 0)
557 self.assertEqual(len(err_lines), 1)
558 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
560 unstyle(str(report)),
561 "1 file reformatted, 2 files left unchanged, "
562 "1 file failed to reformat.",
564 self.assertEqual(report.return_code, 123)
565 report.done(Path("f3"), black.Changed.YES)
566 self.assertEqual(len(out_lines), 0)
567 self.assertEqual(len(err_lines), 1)
569 unstyle(str(report)),
570 "2 files reformatted, 2 files left unchanged, "
571 "1 file failed to reformat.",
573 self.assertEqual(report.return_code, 123)
574 report.failed(Path("e2"), "boom")
575 self.assertEqual(len(out_lines), 0)
576 self.assertEqual(len(err_lines), 2)
577 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
579 unstyle(str(report)),
580 "2 files reformatted, 2 files left unchanged, "
581 "2 files failed to reformat.",
583 self.assertEqual(report.return_code, 123)
584 report.path_ignored(Path("wat"), "no match")
585 self.assertEqual(len(out_lines), 0)
586 self.assertEqual(len(err_lines), 2)
588 unstyle(str(report)),
589 "2 files reformatted, 2 files left unchanged, "
590 "2 files failed to reformat.",
592 self.assertEqual(report.return_code, 123)
593 report.done(Path("f4"), black.Changed.NO)
594 self.assertEqual(len(out_lines), 0)
595 self.assertEqual(len(err_lines), 2)
597 unstyle(str(report)),
598 "2 files reformatted, 3 files left unchanged, "
599 "2 files failed to reformat.",
601 self.assertEqual(report.return_code, 123)
604 unstyle(str(report)),
605 "2 files would be reformatted, 3 files would be left unchanged, "
606 "2 files would fail to reformat.",
609 def test_report_normal(self) -> None:
610 report = black.Report()
614 def out(msg: str, **kwargs: Any) -> None:
615 out_lines.append(msg)
617 def err(msg: str, **kwargs: Any) -> None:
618 err_lines.append(msg)
620 with patch("black.out", out), patch("black.err", err):
621 report.done(Path("f1"), black.Changed.NO)
622 self.assertEqual(len(out_lines), 0)
623 self.assertEqual(len(err_lines), 0)
624 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
625 self.assertEqual(report.return_code, 0)
626 report.done(Path("f2"), black.Changed.YES)
627 self.assertEqual(len(out_lines), 1)
628 self.assertEqual(len(err_lines), 0)
629 self.assertEqual(out_lines[-1], "reformatted f2")
631 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
633 report.done(Path("f3"), black.Changed.CACHED)
634 self.assertEqual(len(out_lines), 1)
635 self.assertEqual(len(err_lines), 0)
636 self.assertEqual(out_lines[-1], "reformatted f2")
638 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
640 self.assertEqual(report.return_code, 0)
642 self.assertEqual(report.return_code, 1)
644 report.failed(Path("e1"), "boom")
645 self.assertEqual(len(out_lines), 1)
646 self.assertEqual(len(err_lines), 1)
647 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
649 unstyle(str(report)),
650 "1 file reformatted, 2 files left unchanged, "
651 "1 file failed to reformat.",
653 self.assertEqual(report.return_code, 123)
654 report.done(Path("f3"), black.Changed.YES)
655 self.assertEqual(len(out_lines), 2)
656 self.assertEqual(len(err_lines), 1)
657 self.assertEqual(out_lines[-1], "reformatted f3")
659 unstyle(str(report)),
660 "2 files reformatted, 2 files left unchanged, "
661 "1 file failed to reformat.",
663 self.assertEqual(report.return_code, 123)
664 report.failed(Path("e2"), "boom")
665 self.assertEqual(len(out_lines), 2)
666 self.assertEqual(len(err_lines), 2)
667 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
669 unstyle(str(report)),
670 "2 files reformatted, 2 files left unchanged, "
671 "2 files failed to reformat.",
673 self.assertEqual(report.return_code, 123)
674 report.path_ignored(Path("wat"), "no match")
675 self.assertEqual(len(out_lines), 2)
676 self.assertEqual(len(err_lines), 2)
678 unstyle(str(report)),
679 "2 files reformatted, 2 files left unchanged, "
680 "2 files failed to reformat.",
682 self.assertEqual(report.return_code, 123)
683 report.done(Path("f4"), black.Changed.NO)
684 self.assertEqual(len(out_lines), 2)
685 self.assertEqual(len(err_lines), 2)
687 unstyle(str(report)),
688 "2 files reformatted, 3 files left unchanged, "
689 "2 files failed to reformat.",
691 self.assertEqual(report.return_code, 123)
694 unstyle(str(report)),
695 "2 files would be reformatted, 3 files would be left unchanged, "
696 "2 files would fail to reformat.",
699 def test_is_python36(self) -> None:
700 node = black.lib2to3_parse("def f(*, arg): ...\n")
701 self.assertFalse(black.is_python36(node))
702 node = black.lib2to3_parse("def f(*, arg,): ...\n")
703 self.assertTrue(black.is_python36(node))
704 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
705 self.assertTrue(black.is_python36(node))
706 source, expected = read_data("function")
707 node = black.lib2to3_parse(source)
708 self.assertTrue(black.is_python36(node))
709 node = black.lib2to3_parse(expected)
710 self.assertTrue(black.is_python36(node))
711 source, expected = read_data("expression")
712 node = black.lib2to3_parse(source)
713 self.assertFalse(black.is_python36(node))
714 node = black.lib2to3_parse(expected)
715 self.assertFalse(black.is_python36(node))
717 def test_get_future_imports(self) -> None:
718 node = black.lib2to3_parse("\n")
719 self.assertEqual(set(), black.get_future_imports(node))
720 node = black.lib2to3_parse("from __future__ import black\n")
721 self.assertEqual({"black"}, black.get_future_imports(node))
722 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
723 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
724 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
725 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
726 node = black.lib2to3_parse(
727 "from __future__ import multiple\nfrom __future__ import imports\n"
729 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
730 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
731 self.assertEqual({"black"}, black.get_future_imports(node))
732 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
733 self.assertEqual({"black"}, black.get_future_imports(node))
734 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
735 self.assertEqual(set(), black.get_future_imports(node))
736 node = black.lib2to3_parse("from some.module import black\n")
737 self.assertEqual(set(), black.get_future_imports(node))
739 def test_debug_visitor(self) -> None:
740 source, _ = read_data("debug_visitor.py")
741 expected, _ = read_data("debug_visitor.out")
745 def out(msg: str, **kwargs: Any) -> None:
746 out_lines.append(msg)
748 def err(msg: str, **kwargs: Any) -> None:
749 err_lines.append(msg)
751 with patch("black.out", out), patch("black.err", err):
752 black.DebugVisitor.show(source)
753 actual = "\n".join(out_lines) + "\n"
755 if expected != actual:
756 log_name = black.dump_to_file(*out_lines)
760 f"AST print out is different. Actual version dumped to {log_name}",
763 def test_format_file_contents(self) -> None:
765 with self.assertRaises(black.NothingChanged):
766 black.format_file_contents(empty, line_length=ll, fast=False)
768 with self.assertRaises(black.NothingChanged):
769 black.format_file_contents(just_nl, line_length=ll, fast=False)
770 same = "l = [1, 2, 3]\n"
771 with self.assertRaises(black.NothingChanged):
772 black.format_file_contents(same, line_length=ll, fast=False)
773 different = "l = [1,2,3]"
775 actual = black.format_file_contents(different, line_length=ll, fast=False)
776 self.assertEqual(expected, actual)
777 invalid = "return if you can"
778 with self.assertRaises(ValueError) as e:
779 black.format_file_contents(invalid, line_length=ll, fast=False)
780 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
782 def test_endmarker(self) -> None:
783 n = black.lib2to3_parse("\n")
784 self.assertEqual(n.type, black.syms.file_input)
785 self.assertEqual(len(n.children), 1)
786 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
788 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
789 def test_assertFormatEqual(self) -> None:
793 def out(msg: str, **kwargs: Any) -> None:
794 out_lines.append(msg)
796 def err(msg: str, **kwargs: Any) -> None:
797 err_lines.append(msg)
799 with patch("black.out", out), patch("black.err", err):
800 with self.assertRaises(AssertionError):
801 self.assertFormatEqual("l = [1, 2, 3]", "l = [1, 2, 3,]")
803 out_str = "".join(out_lines)
804 self.assertTrue("Expected tree:" in out_str)
805 self.assertTrue("Actual tree:" in out_str)
806 self.assertEqual("".join(err_lines), "")
808 def test_cache_broken_file(self) -> None:
809 mode = black.FileMode.AUTO_DETECT
810 with cache_dir() as workspace:
811 cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode)
812 with cache_file.open("w") as fobj:
813 fobj.write("this is not a pickle")
814 self.assertEqual(black.read_cache(black.DEFAULT_LINE_LENGTH, mode), {})
815 src = (workspace / "test.py").resolve()
816 with src.open("w") as fobj:
817 fobj.write("print('hello')")
818 result = CliRunner().invoke(black.main, [str(src)])
819 self.assertEqual(result.exit_code, 0)
820 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
821 self.assertIn(src, cache)
823 def test_cache_single_file_already_cached(self) -> None:
824 mode = black.FileMode.AUTO_DETECT
825 with cache_dir() as workspace:
826 src = (workspace / "test.py").resolve()
827 with src.open("w") as fobj:
828 fobj.write("print('hello')")
829 black.write_cache({}, [src], black.DEFAULT_LINE_LENGTH, mode)
830 result = CliRunner().invoke(black.main, [str(src)])
831 self.assertEqual(result.exit_code, 0)
832 with src.open("r") as fobj:
833 self.assertEqual(fobj.read(), "print('hello')")
835 @event_loop(close=False)
836 def test_cache_multiple_files(self) -> None:
837 mode = black.FileMode.AUTO_DETECT
838 with cache_dir() as workspace, patch(
839 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
841 one = (workspace / "one.py").resolve()
842 with one.open("w") as fobj:
843 fobj.write("print('hello')")
844 two = (workspace / "two.py").resolve()
845 with two.open("w") as fobj:
846 fobj.write("print('hello')")
847 black.write_cache({}, [one], black.DEFAULT_LINE_LENGTH, mode)
848 result = CliRunner().invoke(black.main, [str(workspace)])
849 self.assertEqual(result.exit_code, 0)
850 with one.open("r") as fobj:
851 self.assertEqual(fobj.read(), "print('hello')")
852 with two.open("r") as fobj:
853 self.assertEqual(fobj.read(), 'print("hello")\n')
854 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
855 self.assertIn(one, cache)
856 self.assertIn(two, cache)
858 def test_no_cache_when_writeback_diff(self) -> None:
859 mode = black.FileMode.AUTO_DETECT
860 with cache_dir() as workspace:
861 src = (workspace / "test.py").resolve()
862 with src.open("w") as fobj:
863 fobj.write("print('hello')")
864 result = CliRunner().invoke(black.main, [str(src), "--diff"])
865 self.assertEqual(result.exit_code, 0)
866 cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode)
867 self.assertFalse(cache_file.exists())
869 def test_no_cache_when_stdin(self) -> None:
870 mode = black.FileMode.AUTO_DETECT
872 result = CliRunner().invoke(black.main, ["-"], input="print('hello')")
873 self.assertEqual(result.exit_code, 0)
874 cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode)
875 self.assertFalse(cache_file.exists())
877 def test_read_cache_no_cachefile(self) -> None:
878 mode = black.FileMode.AUTO_DETECT
880 self.assertEqual(black.read_cache(black.DEFAULT_LINE_LENGTH, mode), {})
882 def test_write_cache_read_cache(self) -> None:
883 mode = black.FileMode.AUTO_DETECT
884 with cache_dir() as workspace:
885 src = (workspace / "test.py").resolve()
887 black.write_cache({}, [src], black.DEFAULT_LINE_LENGTH, mode)
888 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
889 self.assertIn(src, cache)
890 self.assertEqual(cache[src], black.get_cache_info(src))
892 def test_filter_cached(self) -> None:
893 with TemporaryDirectory() as workspace:
894 path = Path(workspace)
895 uncached = (path / "uncached").resolve()
896 cached = (path / "cached").resolve()
897 cached_but_changed = (path / "changed").resolve()
900 cached_but_changed.touch()
901 cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
902 todo, done = black.filter_cached(
903 cache, {uncached, cached, cached_but_changed}
905 self.assertEqual(todo, {uncached, cached_but_changed})
906 self.assertEqual(done, {cached})
908 def test_write_cache_creates_directory_if_needed(self) -> None:
909 mode = black.FileMode.AUTO_DETECT
910 with cache_dir(exists=False) as workspace:
911 self.assertFalse(workspace.exists())
912 black.write_cache({}, [], black.DEFAULT_LINE_LENGTH, mode)
913 self.assertTrue(workspace.exists())
915 @event_loop(close=False)
916 def test_failed_formatting_does_not_get_cached(self) -> None:
917 mode = black.FileMode.AUTO_DETECT
918 with cache_dir() as workspace, patch(
919 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
921 failing = (workspace / "failing.py").resolve()
922 with failing.open("w") as fobj:
923 fobj.write("not actually python")
924 clean = (workspace / "clean.py").resolve()
925 with clean.open("w") as fobj:
926 fobj.write('print("hello")\n')
927 result = CliRunner().invoke(black.main, [str(workspace)])
928 self.assertEqual(result.exit_code, 123)
929 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
930 self.assertNotIn(failing, cache)
931 self.assertIn(clean, cache)
933 def test_write_cache_write_fail(self) -> None:
934 mode = black.FileMode.AUTO_DETECT
935 with cache_dir(), patch.object(Path, "open") as mock:
936 mock.side_effect = OSError
937 black.write_cache({}, [], black.DEFAULT_LINE_LENGTH, mode)
939 @event_loop(close=False)
940 def test_check_diff_use_together(self) -> None:
942 # Files which will be reformatted.
943 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
944 result = CliRunner().invoke(black.main, [str(src1), "--diff", "--check"])
945 self.assertEqual(result.exit_code, 1, result.output)
947 # Files which will not be reformatted.
948 src2 = (THIS_DIR / "data" / "composition.py").resolve()
949 result = CliRunner().invoke(black.main, [str(src2), "--diff", "--check"])
950 self.assertEqual(result.exit_code, 0, result.output)
952 # Multi file command.
953 result = CliRunner().invoke(
954 black.main, [str(src1), str(src2), "--diff", "--check"]
956 self.assertEqual(result.exit_code, 1, result.output)
958 def test_no_files(self) -> None:
960 # Without an argument, black exits with error code 0.
961 result = CliRunner().invoke(black.main, [])
962 self.assertEqual(result.exit_code, 0)
964 def test_broken_symlink(self) -> None:
965 with cache_dir() as workspace:
966 symlink = workspace / "broken_link.py"
968 symlink.symlink_to("nonexistent.py")
970 self.skipTest(f"Can't create symlinks: {e}")
971 result = CliRunner().invoke(black.main, [str(workspace.resolve())])
972 self.assertEqual(result.exit_code, 0)
974 def test_read_cache_line_lengths(self) -> None:
975 mode = black.FileMode.AUTO_DETECT
976 with cache_dir() as workspace:
977 path = (workspace / "file.py").resolve()
979 black.write_cache({}, [path], 1, mode)
980 one = black.read_cache(1, mode)
981 self.assertIn(path, one)
982 two = black.read_cache(2, mode)
983 self.assertNotIn(path, two)
985 def test_single_file_force_pyi(self) -> None:
986 reg_mode = black.FileMode.AUTO_DETECT
987 pyi_mode = black.FileMode.PYI
988 contents, expected = read_data("force_pyi")
989 with cache_dir() as workspace:
990 path = (workspace / "file.py").resolve()
991 with open(path, "w") as fh:
993 result = CliRunner().invoke(black.main, [str(path), "--pyi"])
994 self.assertEqual(result.exit_code, 0)
995 with open(path, "r") as fh:
997 # verify cache with --pyi is separate
998 pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, pyi_mode)
999 self.assertIn(path, pyi_cache)
1000 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1001 self.assertNotIn(path, normal_cache)
1002 self.assertEqual(actual, expected)
1004 @event_loop(close=False)
1005 def test_multi_file_force_pyi(self) -> None:
1006 reg_mode = black.FileMode.AUTO_DETECT
1007 pyi_mode = black.FileMode.PYI
1008 contents, expected = read_data("force_pyi")
1009 with cache_dir() as workspace:
1011 (workspace / "file1.py").resolve(),
1012 (workspace / "file2.py").resolve(),
1015 with open(path, "w") as fh:
1017 result = CliRunner().invoke(black.main, [str(p) for p in paths] + ["--pyi"])
1018 self.assertEqual(result.exit_code, 0)
1020 with open(path, "r") as fh:
1022 self.assertEqual(actual, expected)
1023 # verify cache with --pyi is separate
1024 pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, pyi_mode)
1025 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1027 self.assertIn(path, pyi_cache)
1028 self.assertNotIn(path, normal_cache)
1030 def test_pipe_force_pyi(self) -> None:
1031 source, expected = read_data("force_pyi")
1032 result = CliRunner().invoke(black.main, ["-", "-q", "--pyi"], input=source)
1033 self.assertEqual(result.exit_code, 0)
1034 actual = result.output
1035 self.assertFormatEqual(actual, expected)
1037 def test_single_file_force_py36(self) -> None:
1038 reg_mode = black.FileMode.AUTO_DETECT
1039 py36_mode = black.FileMode.PYTHON36
1040 source, expected = read_data("force_py36")
1041 with cache_dir() as workspace:
1042 path = (workspace / "file.py").resolve()
1043 with open(path, "w") as fh:
1045 result = CliRunner().invoke(black.main, [str(path), "--py36"])
1046 self.assertEqual(result.exit_code, 0)
1047 with open(path, "r") as fh:
1049 # verify cache with --py36 is separate
1050 py36_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, py36_mode)
1051 self.assertIn(path, py36_cache)
1052 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1053 self.assertNotIn(path, normal_cache)
1054 self.assertEqual(actual, expected)
1056 @event_loop(close=False)
1057 def test_multi_file_force_py36(self) -> None:
1058 reg_mode = black.FileMode.AUTO_DETECT
1059 py36_mode = black.FileMode.PYTHON36
1060 source, expected = read_data("force_py36")
1061 with cache_dir() as workspace:
1063 (workspace / "file1.py").resolve(),
1064 (workspace / "file2.py").resolve(),
1067 with open(path, "w") as fh:
1069 result = CliRunner().invoke(
1070 black.main, [str(p) for p in paths] + ["--py36"]
1072 self.assertEqual(result.exit_code, 0)
1074 with open(path, "r") as fh:
1076 self.assertEqual(actual, expected)
1077 # verify cache with --py36 is separate
1078 pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, py36_mode)
1079 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1081 self.assertIn(path, pyi_cache)
1082 self.assertNotIn(path, normal_cache)
1084 def test_pipe_force_py36(self) -> None:
1085 source, expected = read_data("force_py36")
1086 result = CliRunner().invoke(black.main, ["-", "-q", "--py36"], input=source)
1087 self.assertEqual(result.exit_code, 0)
1088 actual = result.output
1089 self.assertFormatEqual(actual, expected)
1091 def test_include_exclude(self) -> None:
1092 path = THIS_DIR / "data" / "include_exclude_tests"
1093 include = re.compile(r"\.pyi?$")
1094 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1095 report = black.Report()
1096 sources: List[Path] = []
1098 Path(path / "b/dont_exclude/a.py"),
1099 Path(path / "b/dont_exclude/a.pyi"),
1101 this_abs = THIS_DIR.resolve()
1103 black.gen_python_files_in_dir(path, this_abs, include, exclude, report)
1105 self.assertEqual(sorted(expected), sorted(sources))
1107 def test_empty_include(self) -> None:
1108 path = THIS_DIR / "data" / "include_exclude_tests"
1109 report = black.Report()
1110 empty = re.compile(r"")
1111 sources: List[Path] = []
1113 Path(path / "b/exclude/a.pie"),
1114 Path(path / "b/exclude/a.py"),
1115 Path(path / "b/exclude/a.pyi"),
1116 Path(path / "b/dont_exclude/a.pie"),
1117 Path(path / "b/dont_exclude/a.py"),
1118 Path(path / "b/dont_exclude/a.pyi"),
1119 Path(path / "b/.definitely_exclude/a.pie"),
1120 Path(path / "b/.definitely_exclude/a.py"),
1121 Path(path / "b/.definitely_exclude/a.pyi"),
1123 this_abs = THIS_DIR.resolve()
1125 black.gen_python_files_in_dir(
1126 path, this_abs, empty, re.compile(black.DEFAULT_EXCLUDES), report
1129 self.assertEqual(sorted(expected), sorted(sources))
1131 def test_empty_exclude(self) -> None:
1132 path = THIS_DIR / "data" / "include_exclude_tests"
1133 report = black.Report()
1134 empty = re.compile(r"")
1135 sources: List[Path] = []
1137 Path(path / "b/dont_exclude/a.py"),
1138 Path(path / "b/dont_exclude/a.pyi"),
1139 Path(path / "b/exclude/a.py"),
1140 Path(path / "b/exclude/a.pyi"),
1141 Path(path / "b/.definitely_exclude/a.py"),
1142 Path(path / "b/.definitely_exclude/a.pyi"),
1144 this_abs = THIS_DIR.resolve()
1146 black.gen_python_files_in_dir(
1147 path, this_abs, re.compile(black.DEFAULT_INCLUDES), empty, report
1150 self.assertEqual(sorted(expected), sorted(sources))
1152 def test_invalid_include_exclude(self) -> None:
1153 for option in ["--include", "--exclude"]:
1154 result = CliRunner().invoke(black.main, ["-", option, "**()(!!*)"])
1155 self.assertEqual(result.exit_code, 2)
1157 def test_preserves_line_endings(self) -> None:
1158 with TemporaryDirectory() as workspace:
1159 test_file = Path(workspace) / "test.py"
1160 for nl in ["\n", "\r\n"]:
1161 contents = nl.join(["def f( ):", " pass"])
1162 test_file.write_bytes(contents.encode())
1163 ff(test_file, write_back=black.WriteBack.YES)
1164 updated_contents: bytes = test_file.read_bytes()
1165 self.assertIn(nl.encode(), updated_contents) # type: ignore
1167 self.assertNotIn(b"\r\n", updated_contents) # type: ignore
1169 def test_assert_equivalent_different_asts(self) -> None:
1170 with self.assertRaises(AssertionError):
1171 black.assert_equivalent("{}", "None")
1173 def test_symlink_out_of_root_directory(self) -> None:
1178 include = re.compile(black.DEFAULT_INCLUDES)
1179 exclude = re.compile(black.DEFAULT_EXCLUDES)
1180 report = black.Report()
1182 # set the behavior of mock arguments
1183 # child should behave like a symlink which resolved path is clearly
1184 # outside of the root directory
1185 path.iterdir.return_value = [child]
1186 child.resolve.return_value = Path("/a/b/c")
1187 child.is_symlink.return_value = True
1190 # it should not raise any error
1191 list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1193 # check the call of the methods of the mock objects
1194 path.iterdir.assert_called_once()
1195 child.resolve.assert_called_once()
1196 child.is_symlink.assert_called_once()
1198 # set the behavior of mock arguments
1199 # child should behave like a strange file which resolved path is clearly
1200 # outside of the root directory
1201 child.is_symlink.return_value = False
1204 # it should raise a ValueError
1205 with self.assertRaises(ValueError):
1206 list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1208 # check the call of the methods of the mock objects
1209 path.iterdir.assert_called()
1210 self.assertEqual(path.iterdir.call_count, 2)
1211 child.resolve.assert_called()
1212 self.assertEqual(child.resolve.call_count, 2)
1213 child.is_symlink.assert_called()
1214 self.assertEqual(child.is_symlink.call_count, 2)
1216 def test_shhh_click(self) -> None:
1218 from click import _unicodefun # type: ignore
1219 except ModuleNotFoundError:
1220 self.skipTest("Incompatible Click version")
1221 if not hasattr(_unicodefun, "_verify_python3_env"):
1222 self.skipTest("Incompatible Click version")
1223 # First, let's see if Click is crashing with a preferred ASCII charset.
1224 with patch("locale.getpreferredencoding") as gpe:
1225 gpe.return_value = "ASCII"
1226 with self.assertRaises(RuntimeError):
1227 _unicodefun._verify_python3_env()
1228 # Now, let's silence Click...
1230 # ...and confirm it's silent.
1231 with patch("locale.getpreferredencoding") as gpe:
1232 gpe.return_value = "ASCII"
1234 _unicodefun._verify_python3_env()
1235 except RuntimeError as re:
1236 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1239 if __name__ == "__main__":
1240 unittest.main(module="test_black")