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))
738 node = black.lib2to3_parse(
739 "from __future__ import unicode_literals as _unicode_literals"
741 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
742 node = black.lib2to3_parse(
743 "from __future__ import unicode_literals as _lol, print"
745 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
747 def test_debug_visitor(self) -> None:
748 source, _ = read_data("debug_visitor.py")
749 expected, _ = read_data("debug_visitor.out")
753 def out(msg: str, **kwargs: Any) -> None:
754 out_lines.append(msg)
756 def err(msg: str, **kwargs: Any) -> None:
757 err_lines.append(msg)
759 with patch("black.out", out), patch("black.err", err):
760 black.DebugVisitor.show(source)
761 actual = "\n".join(out_lines) + "\n"
763 if expected != actual:
764 log_name = black.dump_to_file(*out_lines)
768 f"AST print out is different. Actual version dumped to {log_name}",
771 def test_format_file_contents(self) -> None:
773 with self.assertRaises(black.NothingChanged):
774 black.format_file_contents(empty, line_length=ll, fast=False)
776 with self.assertRaises(black.NothingChanged):
777 black.format_file_contents(just_nl, line_length=ll, fast=False)
778 same = "l = [1, 2, 3]\n"
779 with self.assertRaises(black.NothingChanged):
780 black.format_file_contents(same, line_length=ll, fast=False)
781 different = "l = [1,2,3]"
783 actual = black.format_file_contents(different, line_length=ll, fast=False)
784 self.assertEqual(expected, actual)
785 invalid = "return if you can"
786 with self.assertRaises(ValueError) as e:
787 black.format_file_contents(invalid, line_length=ll, fast=False)
788 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
790 def test_endmarker(self) -> None:
791 n = black.lib2to3_parse("\n")
792 self.assertEqual(n.type, black.syms.file_input)
793 self.assertEqual(len(n.children), 1)
794 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
796 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
797 def test_assertFormatEqual(self) -> None:
801 def out(msg: str, **kwargs: Any) -> None:
802 out_lines.append(msg)
804 def err(msg: str, **kwargs: Any) -> None:
805 err_lines.append(msg)
807 with patch("black.out", out), patch("black.err", err):
808 with self.assertRaises(AssertionError):
809 self.assertFormatEqual("l = [1, 2, 3]", "l = [1, 2, 3,]")
811 out_str = "".join(out_lines)
812 self.assertTrue("Expected tree:" in out_str)
813 self.assertTrue("Actual tree:" in out_str)
814 self.assertEqual("".join(err_lines), "")
816 def test_cache_broken_file(self) -> None:
817 mode = black.FileMode.AUTO_DETECT
818 with cache_dir() as workspace:
819 cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode)
820 with cache_file.open("w") as fobj:
821 fobj.write("this is not a pickle")
822 self.assertEqual(black.read_cache(black.DEFAULT_LINE_LENGTH, mode), {})
823 src = (workspace / "test.py").resolve()
824 with src.open("w") as fobj:
825 fobj.write("print('hello')")
826 result = CliRunner().invoke(black.main, [str(src)])
827 self.assertEqual(result.exit_code, 0)
828 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
829 self.assertIn(src, cache)
831 def test_cache_single_file_already_cached(self) -> None:
832 mode = black.FileMode.AUTO_DETECT
833 with cache_dir() as workspace:
834 src = (workspace / "test.py").resolve()
835 with src.open("w") as fobj:
836 fobj.write("print('hello')")
837 black.write_cache({}, [src], black.DEFAULT_LINE_LENGTH, mode)
838 result = CliRunner().invoke(black.main, [str(src)])
839 self.assertEqual(result.exit_code, 0)
840 with src.open("r") as fobj:
841 self.assertEqual(fobj.read(), "print('hello')")
843 @event_loop(close=False)
844 def test_cache_multiple_files(self) -> None:
845 mode = black.FileMode.AUTO_DETECT
846 with cache_dir() as workspace, patch(
847 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
849 one = (workspace / "one.py").resolve()
850 with one.open("w") as fobj:
851 fobj.write("print('hello')")
852 two = (workspace / "two.py").resolve()
853 with two.open("w") as fobj:
854 fobj.write("print('hello')")
855 black.write_cache({}, [one], black.DEFAULT_LINE_LENGTH, mode)
856 result = CliRunner().invoke(black.main, [str(workspace)])
857 self.assertEqual(result.exit_code, 0)
858 with one.open("r") as fobj:
859 self.assertEqual(fobj.read(), "print('hello')")
860 with two.open("r") as fobj:
861 self.assertEqual(fobj.read(), 'print("hello")\n')
862 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
863 self.assertIn(one, cache)
864 self.assertIn(two, cache)
866 def test_no_cache_when_writeback_diff(self) -> None:
867 mode = black.FileMode.AUTO_DETECT
868 with cache_dir() as workspace:
869 src = (workspace / "test.py").resolve()
870 with src.open("w") as fobj:
871 fobj.write("print('hello')")
872 result = CliRunner().invoke(black.main, [str(src), "--diff"])
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_no_cache_when_stdin(self) -> None:
878 mode = black.FileMode.AUTO_DETECT
880 result = CliRunner().invoke(black.main, ["-"], input="print('hello')")
881 self.assertEqual(result.exit_code, 0)
882 cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode)
883 self.assertFalse(cache_file.exists())
885 def test_read_cache_no_cachefile(self) -> None:
886 mode = black.FileMode.AUTO_DETECT
888 self.assertEqual(black.read_cache(black.DEFAULT_LINE_LENGTH, mode), {})
890 def test_write_cache_read_cache(self) -> None:
891 mode = black.FileMode.AUTO_DETECT
892 with cache_dir() as workspace:
893 src = (workspace / "test.py").resolve()
895 black.write_cache({}, [src], black.DEFAULT_LINE_LENGTH, mode)
896 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
897 self.assertIn(src, cache)
898 self.assertEqual(cache[src], black.get_cache_info(src))
900 def test_filter_cached(self) -> None:
901 with TemporaryDirectory() as workspace:
902 path = Path(workspace)
903 uncached = (path / "uncached").resolve()
904 cached = (path / "cached").resolve()
905 cached_but_changed = (path / "changed").resolve()
908 cached_but_changed.touch()
909 cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
910 todo, done = black.filter_cached(
911 cache, {uncached, cached, cached_but_changed}
913 self.assertEqual(todo, {uncached, cached_but_changed})
914 self.assertEqual(done, {cached})
916 def test_write_cache_creates_directory_if_needed(self) -> None:
917 mode = black.FileMode.AUTO_DETECT
918 with cache_dir(exists=False) as workspace:
919 self.assertFalse(workspace.exists())
920 black.write_cache({}, [], black.DEFAULT_LINE_LENGTH, mode)
921 self.assertTrue(workspace.exists())
923 @event_loop(close=False)
924 def test_failed_formatting_does_not_get_cached(self) -> None:
925 mode = black.FileMode.AUTO_DETECT
926 with cache_dir() as workspace, patch(
927 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
929 failing = (workspace / "failing.py").resolve()
930 with failing.open("w") as fobj:
931 fobj.write("not actually python")
932 clean = (workspace / "clean.py").resolve()
933 with clean.open("w") as fobj:
934 fobj.write('print("hello")\n')
935 result = CliRunner().invoke(black.main, [str(workspace)])
936 self.assertEqual(result.exit_code, 123)
937 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
938 self.assertNotIn(failing, cache)
939 self.assertIn(clean, cache)
941 def test_write_cache_write_fail(self) -> None:
942 mode = black.FileMode.AUTO_DETECT
943 with cache_dir(), patch.object(Path, "open") as mock:
944 mock.side_effect = OSError
945 black.write_cache({}, [], black.DEFAULT_LINE_LENGTH, mode)
947 @event_loop(close=False)
948 def test_check_diff_use_together(self) -> None:
950 # Files which will be reformatted.
951 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
952 result = CliRunner().invoke(black.main, [str(src1), "--diff", "--check"])
953 self.assertEqual(result.exit_code, 1, result.output)
954 # Files which will not be reformatted.
955 src2 = (THIS_DIR / "data" / "composition.py").resolve()
956 result = CliRunner().invoke(black.main, [str(src2), "--diff", "--check"])
957 self.assertEqual(result.exit_code, 0, result.output)
958 # Multi file command.
959 result = CliRunner().invoke(
960 black.main, [str(src1), str(src2), "--diff", "--check"]
962 self.assertEqual(result.exit_code, 1, result.output)
964 def test_no_files(self) -> None:
966 # Without an argument, black exits with error code 0.
967 result = CliRunner().invoke(black.main, [])
968 self.assertEqual(result.exit_code, 0)
970 def test_broken_symlink(self) -> None:
971 with cache_dir() as workspace:
972 symlink = workspace / "broken_link.py"
974 symlink.symlink_to("nonexistent.py")
976 self.skipTest(f"Can't create symlinks: {e}")
977 result = CliRunner().invoke(black.main, [str(workspace.resolve())])
978 self.assertEqual(result.exit_code, 0)
980 def test_read_cache_line_lengths(self) -> None:
981 mode = black.FileMode.AUTO_DETECT
982 with cache_dir() as workspace:
983 path = (workspace / "file.py").resolve()
985 black.write_cache({}, [path], 1, mode)
986 one = black.read_cache(1, mode)
987 self.assertIn(path, one)
988 two = black.read_cache(2, mode)
989 self.assertNotIn(path, two)
991 def test_single_file_force_pyi(self) -> None:
992 reg_mode = black.FileMode.AUTO_DETECT
993 pyi_mode = black.FileMode.PYI
994 contents, expected = read_data("force_pyi")
995 with cache_dir() as workspace:
996 path = (workspace / "file.py").resolve()
997 with open(path, "w") as fh:
999 result = CliRunner().invoke(black.main, [str(path), "--pyi"])
1000 self.assertEqual(result.exit_code, 0)
1001 with open(path, "r") as fh:
1003 # verify cache with --pyi is separate
1004 pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, pyi_mode)
1005 self.assertIn(path, pyi_cache)
1006 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1007 self.assertNotIn(path, normal_cache)
1008 self.assertEqual(actual, expected)
1010 @event_loop(close=False)
1011 def test_multi_file_force_pyi(self) -> None:
1012 reg_mode = black.FileMode.AUTO_DETECT
1013 pyi_mode = black.FileMode.PYI
1014 contents, expected = read_data("force_pyi")
1015 with cache_dir() as workspace:
1017 (workspace / "file1.py").resolve(),
1018 (workspace / "file2.py").resolve(),
1021 with open(path, "w") as fh:
1023 result = CliRunner().invoke(black.main, [str(p) for p in paths] + ["--pyi"])
1024 self.assertEqual(result.exit_code, 0)
1026 with open(path, "r") as fh:
1028 self.assertEqual(actual, expected)
1029 # verify cache with --pyi is separate
1030 pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, pyi_mode)
1031 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1033 self.assertIn(path, pyi_cache)
1034 self.assertNotIn(path, normal_cache)
1036 def test_pipe_force_pyi(self) -> None:
1037 source, expected = read_data("force_pyi")
1038 result = CliRunner().invoke(black.main, ["-", "-q", "--pyi"], input=source)
1039 self.assertEqual(result.exit_code, 0)
1040 actual = result.output
1041 self.assertFormatEqual(actual, expected)
1043 def test_single_file_force_py36(self) -> None:
1044 reg_mode = black.FileMode.AUTO_DETECT
1045 py36_mode = black.FileMode.PYTHON36
1046 source, expected = read_data("force_py36")
1047 with cache_dir() as workspace:
1048 path = (workspace / "file.py").resolve()
1049 with open(path, "w") as fh:
1051 result = CliRunner().invoke(black.main, [str(path), "--py36"])
1052 self.assertEqual(result.exit_code, 0)
1053 with open(path, "r") as fh:
1055 # verify cache with --py36 is separate
1056 py36_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, py36_mode)
1057 self.assertIn(path, py36_cache)
1058 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1059 self.assertNotIn(path, normal_cache)
1060 self.assertEqual(actual, expected)
1062 @event_loop(close=False)
1063 def test_multi_file_force_py36(self) -> None:
1064 reg_mode = black.FileMode.AUTO_DETECT
1065 py36_mode = black.FileMode.PYTHON36
1066 source, expected = read_data("force_py36")
1067 with cache_dir() as workspace:
1069 (workspace / "file1.py").resolve(),
1070 (workspace / "file2.py").resolve(),
1073 with open(path, "w") as fh:
1075 result = CliRunner().invoke(
1076 black.main, [str(p) for p in paths] + ["--py36"]
1078 self.assertEqual(result.exit_code, 0)
1080 with open(path, "r") as fh:
1082 self.assertEqual(actual, expected)
1083 # verify cache with --py36 is separate
1084 pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, py36_mode)
1085 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1087 self.assertIn(path, pyi_cache)
1088 self.assertNotIn(path, normal_cache)
1090 def test_pipe_force_py36(self) -> None:
1091 source, expected = read_data("force_py36")
1092 result = CliRunner().invoke(black.main, ["-", "-q", "--py36"], input=source)
1093 self.assertEqual(result.exit_code, 0)
1094 actual = result.output
1095 self.assertFormatEqual(actual, expected)
1097 def test_include_exclude(self) -> None:
1098 path = THIS_DIR / "data" / "include_exclude_tests"
1099 include = re.compile(r"\.pyi?$")
1100 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1101 report = black.Report()
1102 sources: List[Path] = []
1104 Path(path / "b/dont_exclude/a.py"),
1105 Path(path / "b/dont_exclude/a.pyi"),
1107 this_abs = THIS_DIR.resolve()
1109 black.gen_python_files_in_dir(path, this_abs, include, exclude, report)
1111 self.assertEqual(sorted(expected), sorted(sources))
1113 def test_empty_include(self) -> None:
1114 path = THIS_DIR / "data" / "include_exclude_tests"
1115 report = black.Report()
1116 empty = re.compile(r"")
1117 sources: List[Path] = []
1119 Path(path / "b/exclude/a.pie"),
1120 Path(path / "b/exclude/a.py"),
1121 Path(path / "b/exclude/a.pyi"),
1122 Path(path / "b/dont_exclude/a.pie"),
1123 Path(path / "b/dont_exclude/a.py"),
1124 Path(path / "b/dont_exclude/a.pyi"),
1125 Path(path / "b/.definitely_exclude/a.pie"),
1126 Path(path / "b/.definitely_exclude/a.py"),
1127 Path(path / "b/.definitely_exclude/a.pyi"),
1129 this_abs = THIS_DIR.resolve()
1131 black.gen_python_files_in_dir(
1132 path, this_abs, empty, re.compile(black.DEFAULT_EXCLUDES), report
1135 self.assertEqual(sorted(expected), sorted(sources))
1137 def test_empty_exclude(self) -> None:
1138 path = THIS_DIR / "data" / "include_exclude_tests"
1139 report = black.Report()
1140 empty = re.compile(r"")
1141 sources: List[Path] = []
1143 Path(path / "b/dont_exclude/a.py"),
1144 Path(path / "b/dont_exclude/a.pyi"),
1145 Path(path / "b/exclude/a.py"),
1146 Path(path / "b/exclude/a.pyi"),
1147 Path(path / "b/.definitely_exclude/a.py"),
1148 Path(path / "b/.definitely_exclude/a.pyi"),
1150 this_abs = THIS_DIR.resolve()
1152 black.gen_python_files_in_dir(
1153 path, this_abs, re.compile(black.DEFAULT_INCLUDES), empty, report
1156 self.assertEqual(sorted(expected), sorted(sources))
1158 def test_invalid_include_exclude(self) -> None:
1159 for option in ["--include", "--exclude"]:
1160 result = CliRunner().invoke(black.main, ["-", option, "**()(!!*)"])
1161 self.assertEqual(result.exit_code, 2)
1163 def test_preserves_line_endings(self) -> None:
1164 with TemporaryDirectory() as workspace:
1165 test_file = Path(workspace) / "test.py"
1166 for nl in ["\n", "\r\n"]:
1167 contents = nl.join(["def f( ):", " pass"])
1168 test_file.write_bytes(contents.encode())
1169 ff(test_file, write_back=black.WriteBack.YES)
1170 updated_contents: bytes = test_file.read_bytes()
1171 self.assertIn(nl.encode(), updated_contents) # type: ignore
1173 self.assertNotIn(b"\r\n", updated_contents) # type: ignore
1175 def test_assert_equivalent_different_asts(self) -> None:
1176 with self.assertRaises(AssertionError):
1177 black.assert_equivalent("{}", "None")
1179 def test_symlink_out_of_root_directory(self) -> None:
1183 include = re.compile(black.DEFAULT_INCLUDES)
1184 exclude = re.compile(black.DEFAULT_EXCLUDES)
1185 report = black.Report()
1186 # `child` should behave like a symlink which resolved path is clearly
1187 # outside of the `root` directory.
1188 path.iterdir.return_value = [child]
1189 child.resolve.return_value = Path("/a/b/c")
1190 child.is_symlink.return_value = True
1192 list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1193 except ValueError as ve:
1194 self.fail("`get_python_files_in_dir()` failed: {ve}")
1195 path.iterdir.assert_called_once()
1196 child.resolve.assert_called_once()
1197 child.is_symlink.assert_called_once()
1198 # `child` should behave like a strange file which resolved path is clearly
1199 # outside of the `root` directory.
1200 child.is_symlink.return_value = False
1201 with self.assertRaises(ValueError):
1202 list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1203 path.iterdir.assert_called()
1204 self.assertEqual(path.iterdir.call_count, 2)
1205 child.resolve.assert_called()
1206 self.assertEqual(child.resolve.call_count, 2)
1207 child.is_symlink.assert_called()
1208 self.assertEqual(child.is_symlink.call_count, 2)
1210 def test_shhh_click(self) -> None:
1212 from click import _unicodefun # type: ignore
1213 except ModuleNotFoundError:
1214 self.skipTest("Incompatible Click version")
1215 if not hasattr(_unicodefun, "_verify_python3_env"):
1216 self.skipTest("Incompatible Click version")
1217 # First, let's see if Click is crashing with a preferred ASCII charset.
1218 with patch("locale.getpreferredencoding") as gpe:
1219 gpe.return_value = "ASCII"
1220 with self.assertRaises(RuntimeError):
1221 _unicodefun._verify_python3_env()
1222 # Now, let's silence Click...
1224 # ...and confirm it's silent.
1225 with patch("locale.getpreferredencoding") as gpe:
1226 gpe.return_value = "ASCII"
1228 _unicodefun._verify_python3_env()
1229 except RuntimeError as re:
1230 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1233 if __name__ == "__main__":
1234 unittest.main(module="test_black")