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, List, Tuple, Iterator
14 from unittest.mock import patch
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) -> 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 with open(THIS_DIR / name, "r", encoding="utf8") as test:
41 lines = test.readlines()
44 line = line.replace(EMPTY_LINE, "")
45 if line.rstrip() == "# output":
50 if _input and not _output:
51 # If there's no output marker, treat the entire file as already pre-formatted.
53 return "".join(_input).strip() + "\n", "".join(_output).strip() + "\n"
57 def cache_dir(exists: bool = True) -> Iterator[Path]:
58 with TemporaryDirectory() as workspace:
59 cache_dir = Path(workspace)
61 cache_dir = cache_dir / "new"
62 with patch("black.CACHE_DIR", cache_dir):
67 def event_loop(close: bool) -> Iterator[None]:
68 policy = asyncio.get_event_loop_policy()
69 old_loop = policy.get_event_loop()
70 loop = policy.new_event_loop()
71 asyncio.set_event_loop(loop)
76 policy.set_event_loop(old_loop)
81 class BlackTestCase(unittest.TestCase):
84 def assertFormatEqual(self, expected: str, actual: str) -> None:
85 if actual != expected and not os.environ.get("SKIP_AST_PRINT"):
86 bdv: black.DebugVisitor[Any]
87 black.out("Expected tree:", fg="green")
89 exp_node = black.lib2to3_parse(expected)
90 bdv = black.DebugVisitor()
91 list(bdv.visit(exp_node))
92 except Exception as ve:
94 black.out("Actual tree:", fg="red")
96 exp_node = black.lib2to3_parse(actual)
97 bdv = black.DebugVisitor()
98 list(bdv.visit(exp_node))
99 except Exception as ve:
101 self.assertEqual(expected, actual)
103 @patch("black.dump_to_file", dump_to_stderr)
104 def test_empty(self) -> None:
105 source = expected = ""
107 self.assertFormatEqual(expected, actual)
108 black.assert_equivalent(source, actual)
109 black.assert_stable(source, actual, line_length=ll)
111 def test_empty_ff(self) -> None:
113 tmp_file = Path(black.dump_to_file())
115 self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES))
116 with open(tmp_file, encoding="utf8") as f:
120 self.assertFormatEqual(expected, actual)
122 @patch("black.dump_to_file", dump_to_stderr)
123 def test_self(self) -> None:
124 source, expected = read_data("test_black")
126 self.assertFormatEqual(expected, actual)
127 black.assert_equivalent(source, actual)
128 black.assert_stable(source, actual, line_length=ll)
129 self.assertFalse(ff(THIS_FILE))
131 @patch("black.dump_to_file", dump_to_stderr)
132 def test_black(self) -> None:
133 source, expected = read_data("../black")
135 self.assertFormatEqual(expected, actual)
136 black.assert_equivalent(source, actual)
137 black.assert_stable(source, actual, line_length=ll)
138 self.assertFalse(ff(THIS_DIR / ".." / "black.py"))
140 def test_piping(self) -> None:
141 source, expected = read_data("../black")
142 hold_stdin, hold_stdout = sys.stdin, sys.stdout
144 sys.stdin = TextIOWrapper(BytesIO(source.encode("utf8")), encoding="utf8")
145 sys.stdout = TextIOWrapper(BytesIO(), encoding="utf8")
146 sys.stdin.buffer.name = "<stdin>" # type: ignore
147 black.format_stdin_to_stdout(
148 line_length=ll, fast=True, write_back=black.WriteBack.YES
151 actual = sys.stdout.read()
153 sys.stdin, sys.stdout = hold_stdin, hold_stdout
154 self.assertFormatEqual(expected, actual)
155 black.assert_equivalent(source, actual)
156 black.assert_stable(source, actual, line_length=ll)
158 def test_piping_diff(self) -> None:
159 diff_header = re.compile(
160 rf"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d "
161 rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
163 source, _ = read_data("expression.py")
164 expected, _ = read_data("expression.diff")
165 hold_stdin, hold_stdout = sys.stdin, sys.stdout
167 sys.stdin = TextIOWrapper(BytesIO(source.encode("utf8")), encoding="utf8")
168 sys.stdout = TextIOWrapper(BytesIO(), encoding="utf8")
169 black.format_stdin_to_stdout(
170 line_length=ll, fast=True, write_back=black.WriteBack.DIFF
173 actual = sys.stdout.read()
174 actual = diff_header.sub("[Deterministic header]", actual)
176 sys.stdin, sys.stdout = hold_stdin, hold_stdout
177 actual = actual.rstrip() + "\n" # the diff output has a trailing space
178 self.assertEqual(expected, actual)
180 @patch("black.dump_to_file", dump_to_stderr)
181 def test_setup(self) -> None:
182 source, expected = read_data("../setup")
184 self.assertFormatEqual(expected, actual)
185 black.assert_equivalent(source, actual)
186 black.assert_stable(source, actual, line_length=ll)
187 self.assertFalse(ff(THIS_DIR / ".." / "setup.py"))
189 @patch("black.dump_to_file", dump_to_stderr)
190 def test_function(self) -> None:
191 source, expected = read_data("function")
193 self.assertFormatEqual(expected, actual)
194 black.assert_equivalent(source, actual)
195 black.assert_stable(source, actual, line_length=ll)
197 @patch("black.dump_to_file", dump_to_stderr)
198 def test_function2(self) -> None:
199 source, expected = read_data("function2")
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_expression(self) -> None:
207 source, expected = read_data("expression")
209 self.assertFormatEqual(expected, actual)
210 black.assert_equivalent(source, actual)
211 black.assert_stable(source, actual, line_length=ll)
213 def test_expression_ff(self) -> None:
214 source, expected = read_data("expression")
215 tmp_file = Path(black.dump_to_file(source))
217 self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
218 with open(tmp_file, encoding="utf8") as f:
222 self.assertFormatEqual(expected, actual)
223 with patch("black.dump_to_file", dump_to_stderr):
224 black.assert_equivalent(source, actual)
225 black.assert_stable(source, actual, line_length=ll)
227 def test_expression_diff(self) -> None:
228 source, _ = read_data("expression.py")
229 expected, _ = read_data("expression.diff")
230 tmp_file = Path(black.dump_to_file(source))
231 diff_header = re.compile(
232 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
233 rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
235 hold_stdout = sys.stdout
237 sys.stdout = TextIOWrapper(BytesIO(), encoding="utf8")
238 self.assertTrue(ff(tmp_file, write_back=black.WriteBack.DIFF))
240 actual = sys.stdout.read()
241 actual = diff_header.sub("[Deterministic header]", actual)
243 sys.stdout = hold_stdout
245 actual = actual.rstrip() + "\n" # the diff output has a trailing space
246 if expected != actual:
247 dump = black.dump_to_file(actual)
249 f"Expected diff isn't equal to the actual. If you made changes "
250 f"to expression.py and this is an anticipated difference, "
251 f"overwrite tests/expression.diff with {dump}"
253 self.assertEqual(expected, actual, msg)
255 @patch("black.dump_to_file", dump_to_stderr)
256 def test_fstring(self) -> None:
257 source, expected = read_data("fstring")
259 self.assertFormatEqual(expected, actual)
260 black.assert_equivalent(source, actual)
261 black.assert_stable(source, actual, line_length=ll)
263 @patch("black.dump_to_file", dump_to_stderr)
264 def test_string_quotes(self) -> None:
265 source, expected = read_data("string_quotes")
267 self.assertFormatEqual(expected, actual)
268 black.assert_equivalent(source, actual)
269 black.assert_stable(source, actual, line_length=ll)
270 mode = black.FileMode.NO_STRING_NORMALIZATION
271 not_normalized = fs(source, mode=mode)
272 self.assertFormatEqual(source, not_normalized)
273 black.assert_equivalent(source, not_normalized)
274 black.assert_stable(source, not_normalized, line_length=ll, mode=mode)
276 @patch("black.dump_to_file", dump_to_stderr)
277 def test_slices(self) -> None:
278 source, expected = read_data("slices")
280 self.assertFormatEqual(expected, actual)
281 black.assert_equivalent(source, actual)
282 black.assert_stable(source, actual, line_length=ll)
284 @patch("black.dump_to_file", dump_to_stderr)
285 def test_comments(self) -> None:
286 source, expected = read_data("comments")
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_comments2(self) -> None:
294 source, expected = read_data("comments2")
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_comments3(self) -> None:
302 source, expected = read_data("comments3")
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_comments4(self) -> None:
310 source, expected = read_data("comments4")
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_comments5(self) -> None:
318 source, expected = read_data("comments5")
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_cantfit(self) -> None:
326 source, expected = read_data("cantfit")
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_import_spacing(self) -> None:
334 source, expected = read_data("import_spacing")
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_composition(self) -> None:
342 source, expected = read_data("composition")
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_empty_lines(self) -> None:
350 source, expected = read_data("empty_lines")
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_string_prefixes(self) -> None:
358 source, expected = read_data("string_prefixes")
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_python2(self) -> None:
366 source, expected = read_data("python2")
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_unicode_literals(self) -> None:
374 source, expected = read_data("python2_unicode_literals")
376 self.assertFormatEqual(expected, actual)
377 black.assert_stable(source, actual, line_length=ll)
379 @patch("black.dump_to_file", dump_to_stderr)
380 def test_stub(self) -> None:
381 mode = black.FileMode.PYI
382 source, expected = read_data("stub.pyi")
383 actual = fs(source, mode=mode)
384 self.assertFormatEqual(expected, actual)
385 black.assert_stable(source, actual, line_length=ll, mode=mode)
387 @patch("black.dump_to_file", dump_to_stderr)
388 def test_fmtonoff(self) -> None:
389 source, expected = read_data("fmtonoff")
391 self.assertFormatEqual(expected, actual)
392 black.assert_equivalent(source, actual)
393 black.assert_stable(source, actual, line_length=ll)
395 @patch("black.dump_to_file", dump_to_stderr)
396 def test_remove_empty_parentheses_after_class(self) -> None:
397 source, expected = read_data("class_blank_parentheses")
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_new_line_between_class_and_code(self) -> None:
405 source, expected = read_data("class_methods_new_line")
407 self.assertFormatEqual(expected, actual)
408 black.assert_equivalent(source, actual)
409 black.assert_stable(source, actual, line_length=ll)
411 def test_report_verbose(self) -> None:
412 report = black.Report(verbose=True)
416 def out(msg: str, **kwargs: Any) -> None:
417 out_lines.append(msg)
419 def err(msg: str, **kwargs: Any) -> None:
420 err_lines.append(msg)
422 with patch("black.out", out), patch("black.err", err):
423 report.done(Path("f1"), black.Changed.NO)
424 self.assertEqual(len(out_lines), 1)
425 self.assertEqual(len(err_lines), 0)
426 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
427 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
428 self.assertEqual(report.return_code, 0)
429 report.done(Path("f2"), black.Changed.YES)
430 self.assertEqual(len(out_lines), 2)
431 self.assertEqual(len(err_lines), 0)
432 self.assertEqual(out_lines[-1], "reformatted f2")
434 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
436 report.done(Path("f3"), black.Changed.CACHED)
437 self.assertEqual(len(out_lines), 3)
438 self.assertEqual(len(err_lines), 0)
440 out_lines[-1], "f3 wasn't modified on disk since last run."
443 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
445 self.assertEqual(report.return_code, 0)
447 self.assertEqual(report.return_code, 1)
449 report.failed(Path("e1"), "boom")
450 self.assertEqual(len(out_lines), 3)
451 self.assertEqual(len(err_lines), 1)
452 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
454 unstyle(str(report)),
455 "1 file reformatted, 2 files left unchanged, "
456 "1 file failed to reformat.",
458 self.assertEqual(report.return_code, 123)
459 report.done(Path("f3"), black.Changed.YES)
460 self.assertEqual(len(out_lines), 4)
461 self.assertEqual(len(err_lines), 1)
462 self.assertEqual(out_lines[-1], "reformatted f3")
464 unstyle(str(report)),
465 "2 files reformatted, 2 files left unchanged, "
466 "1 file failed to reformat.",
468 self.assertEqual(report.return_code, 123)
469 report.failed(Path("e2"), "boom")
470 self.assertEqual(len(out_lines), 4)
471 self.assertEqual(len(err_lines), 2)
472 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
474 unstyle(str(report)),
475 "2 files reformatted, 2 files left unchanged, "
476 "2 files failed to reformat.",
478 self.assertEqual(report.return_code, 123)
479 report.path_ignored(Path("wat"), "no match")
480 self.assertEqual(len(out_lines), 5)
481 self.assertEqual(len(err_lines), 2)
482 self.assertEqual(out_lines[-1], "wat ignored: no match")
484 unstyle(str(report)),
485 "2 files reformatted, 2 files left unchanged, "
486 "2 files failed to reformat.",
488 self.assertEqual(report.return_code, 123)
489 report.done(Path("f4"), black.Changed.NO)
490 self.assertEqual(len(out_lines), 6)
491 self.assertEqual(len(err_lines), 2)
492 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
494 unstyle(str(report)),
495 "2 files reformatted, 3 files left unchanged, "
496 "2 files failed to reformat.",
498 self.assertEqual(report.return_code, 123)
501 unstyle(str(report)),
502 "2 files would be reformatted, 3 files would be left unchanged, "
503 "2 files would fail to reformat.",
506 def test_report_quiet(self) -> None:
507 report = black.Report(quiet=True)
511 def out(msg: str, **kwargs: Any) -> None:
512 out_lines.append(msg)
514 def err(msg: str, **kwargs: Any) -> None:
515 err_lines.append(msg)
517 with patch("black.out", out), patch("black.err", err):
518 report.done(Path("f1"), black.Changed.NO)
519 self.assertEqual(len(out_lines), 0)
520 self.assertEqual(len(err_lines), 0)
521 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
522 self.assertEqual(report.return_code, 0)
523 report.done(Path("f2"), black.Changed.YES)
524 self.assertEqual(len(out_lines), 0)
525 self.assertEqual(len(err_lines), 0)
527 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
529 report.done(Path("f3"), black.Changed.CACHED)
530 self.assertEqual(len(out_lines), 0)
531 self.assertEqual(len(err_lines), 0)
533 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
535 self.assertEqual(report.return_code, 0)
537 self.assertEqual(report.return_code, 1)
539 report.failed(Path("e1"), "boom")
540 self.assertEqual(len(out_lines), 0)
541 self.assertEqual(len(err_lines), 1)
542 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
544 unstyle(str(report)),
545 "1 file reformatted, 2 files left unchanged, "
546 "1 file failed to reformat.",
548 self.assertEqual(report.return_code, 123)
549 report.done(Path("f3"), black.Changed.YES)
550 self.assertEqual(len(out_lines), 0)
551 self.assertEqual(len(err_lines), 1)
553 unstyle(str(report)),
554 "2 files reformatted, 2 files left unchanged, "
555 "1 file failed to reformat.",
557 self.assertEqual(report.return_code, 123)
558 report.failed(Path("e2"), "boom")
559 self.assertEqual(len(out_lines), 0)
560 self.assertEqual(len(err_lines), 2)
561 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
563 unstyle(str(report)),
564 "2 files reformatted, 2 files left unchanged, "
565 "2 files failed to reformat.",
567 self.assertEqual(report.return_code, 123)
568 report.path_ignored(Path("wat"), "no match")
569 self.assertEqual(len(out_lines), 0)
570 self.assertEqual(len(err_lines), 2)
572 unstyle(str(report)),
573 "2 files reformatted, 2 files left unchanged, "
574 "2 files failed to reformat.",
576 self.assertEqual(report.return_code, 123)
577 report.done(Path("f4"), black.Changed.NO)
578 self.assertEqual(len(out_lines), 0)
579 self.assertEqual(len(err_lines), 2)
581 unstyle(str(report)),
582 "2 files reformatted, 3 files left unchanged, "
583 "2 files failed to reformat.",
585 self.assertEqual(report.return_code, 123)
588 unstyle(str(report)),
589 "2 files would be reformatted, 3 files would be left unchanged, "
590 "2 files would fail to reformat.",
593 def test_report_normal(self) -> None:
594 report = black.Report()
598 def out(msg: str, **kwargs: Any) -> None:
599 out_lines.append(msg)
601 def err(msg: str, **kwargs: Any) -> None:
602 err_lines.append(msg)
604 with patch("black.out", out), patch("black.err", err):
605 report.done(Path("f1"), black.Changed.NO)
606 self.assertEqual(len(out_lines), 0)
607 self.assertEqual(len(err_lines), 0)
608 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
609 self.assertEqual(report.return_code, 0)
610 report.done(Path("f2"), black.Changed.YES)
611 self.assertEqual(len(out_lines), 1)
612 self.assertEqual(len(err_lines), 0)
613 self.assertEqual(out_lines[-1], "reformatted f2")
615 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
617 report.done(Path("f3"), black.Changed.CACHED)
618 self.assertEqual(len(out_lines), 1)
619 self.assertEqual(len(err_lines), 0)
620 self.assertEqual(out_lines[-1], "reformatted f2")
622 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
624 self.assertEqual(report.return_code, 0)
626 self.assertEqual(report.return_code, 1)
628 report.failed(Path("e1"), "boom")
629 self.assertEqual(len(out_lines), 1)
630 self.assertEqual(len(err_lines), 1)
631 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
633 unstyle(str(report)),
634 "1 file reformatted, 2 files left unchanged, "
635 "1 file failed to reformat.",
637 self.assertEqual(report.return_code, 123)
638 report.done(Path("f3"), black.Changed.YES)
639 self.assertEqual(len(out_lines), 2)
640 self.assertEqual(len(err_lines), 1)
641 self.assertEqual(out_lines[-1], "reformatted f3")
643 unstyle(str(report)),
644 "2 files reformatted, 2 files left unchanged, "
645 "1 file failed to reformat.",
647 self.assertEqual(report.return_code, 123)
648 report.failed(Path("e2"), "boom")
649 self.assertEqual(len(out_lines), 2)
650 self.assertEqual(len(err_lines), 2)
651 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
653 unstyle(str(report)),
654 "2 files reformatted, 2 files left unchanged, "
655 "2 files failed to reformat.",
657 self.assertEqual(report.return_code, 123)
658 report.path_ignored(Path("wat"), "no match")
659 self.assertEqual(len(out_lines), 2)
660 self.assertEqual(len(err_lines), 2)
662 unstyle(str(report)),
663 "2 files reformatted, 2 files left unchanged, "
664 "2 files failed to reformat.",
666 self.assertEqual(report.return_code, 123)
667 report.done(Path("f4"), black.Changed.NO)
668 self.assertEqual(len(out_lines), 2)
669 self.assertEqual(len(err_lines), 2)
671 unstyle(str(report)),
672 "2 files reformatted, 3 files left unchanged, "
673 "2 files failed to reformat.",
675 self.assertEqual(report.return_code, 123)
678 unstyle(str(report)),
679 "2 files would be reformatted, 3 files would be left unchanged, "
680 "2 files would fail to reformat.",
683 def test_is_python36(self) -> None:
684 node = black.lib2to3_parse("def f(*, arg): ...\n")
685 self.assertFalse(black.is_python36(node))
686 node = black.lib2to3_parse("def f(*, arg,): ...\n")
687 self.assertTrue(black.is_python36(node))
688 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
689 self.assertTrue(black.is_python36(node))
690 source, expected = read_data("function")
691 node = black.lib2to3_parse(source)
692 self.assertTrue(black.is_python36(node))
693 node = black.lib2to3_parse(expected)
694 self.assertTrue(black.is_python36(node))
695 source, expected = read_data("expression")
696 node = black.lib2to3_parse(source)
697 self.assertFalse(black.is_python36(node))
698 node = black.lib2to3_parse(expected)
699 self.assertFalse(black.is_python36(node))
701 def test_get_future_imports(self) -> None:
702 node = black.lib2to3_parse("\n")
703 self.assertEqual(set(), black.get_future_imports(node))
704 node = black.lib2to3_parse("from __future__ import black\n")
705 self.assertEqual({"black"}, black.get_future_imports(node))
706 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
707 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
708 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
709 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
710 node = black.lib2to3_parse(
711 "from __future__ import multiple\nfrom __future__ import imports\n"
713 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
714 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
715 self.assertEqual({"black"}, black.get_future_imports(node))
716 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
717 self.assertEqual({"black"}, black.get_future_imports(node))
718 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
719 self.assertEqual(set(), black.get_future_imports(node))
720 node = black.lib2to3_parse("from some.module import black\n")
721 self.assertEqual(set(), black.get_future_imports(node))
723 def test_debug_visitor(self) -> None:
724 source, _ = read_data("debug_visitor.py")
725 expected, _ = read_data("debug_visitor.out")
729 def out(msg: str, **kwargs: Any) -> None:
730 out_lines.append(msg)
732 def err(msg: str, **kwargs: Any) -> None:
733 err_lines.append(msg)
735 with patch("black.out", out), patch("black.err", err):
736 black.DebugVisitor.show(source)
737 actual = "\n".join(out_lines) + "\n"
739 if expected != actual:
740 log_name = black.dump_to_file(*out_lines)
744 f"AST print out is different. Actual version dumped to {log_name}",
747 def test_format_file_contents(self) -> None:
749 with self.assertRaises(black.NothingChanged):
750 black.format_file_contents(empty, line_length=ll, fast=False)
752 with self.assertRaises(black.NothingChanged):
753 black.format_file_contents(just_nl, line_length=ll, fast=False)
754 same = "l = [1, 2, 3]\n"
755 with self.assertRaises(black.NothingChanged):
756 black.format_file_contents(same, line_length=ll, fast=False)
757 different = "l = [1,2,3]"
759 actual = black.format_file_contents(different, line_length=ll, fast=False)
760 self.assertEqual(expected, actual)
761 invalid = "return if you can"
762 with self.assertRaises(ValueError) as e:
763 black.format_file_contents(invalid, line_length=ll, fast=False)
764 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
766 def test_endmarker(self) -> None:
767 n = black.lib2to3_parse("\n")
768 self.assertEqual(n.type, black.syms.file_input)
769 self.assertEqual(len(n.children), 1)
770 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
772 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
773 def test_assertFormatEqual(self) -> None:
777 def out(msg: str, **kwargs: Any) -> None:
778 out_lines.append(msg)
780 def err(msg: str, **kwargs: Any) -> None:
781 err_lines.append(msg)
783 with patch("black.out", out), patch("black.err", err):
784 with self.assertRaises(AssertionError):
785 self.assertFormatEqual("l = [1, 2, 3]", "l = [1, 2, 3,]")
787 out_str = "".join(out_lines)
788 self.assertTrue("Expected tree:" in out_str)
789 self.assertTrue("Actual tree:" in out_str)
790 self.assertEqual("".join(err_lines), "")
792 def test_cache_broken_file(self) -> None:
793 mode = black.FileMode.AUTO_DETECT
794 with cache_dir() as workspace:
795 cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode)
796 with cache_file.open("w") as fobj:
797 fobj.write("this is not a pickle")
798 self.assertEqual(black.read_cache(black.DEFAULT_LINE_LENGTH, mode), {})
799 src = (workspace / "test.py").resolve()
800 with src.open("w") as fobj:
801 fobj.write("print('hello')")
802 result = CliRunner().invoke(black.main, [str(src)])
803 self.assertEqual(result.exit_code, 0)
804 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
805 self.assertIn(src, cache)
807 def test_cache_single_file_already_cached(self) -> None:
808 mode = black.FileMode.AUTO_DETECT
809 with cache_dir() as workspace:
810 src = (workspace / "test.py").resolve()
811 with src.open("w") as fobj:
812 fobj.write("print('hello')")
813 black.write_cache({}, [src], black.DEFAULT_LINE_LENGTH, mode)
814 result = CliRunner().invoke(black.main, [str(src)])
815 self.assertEqual(result.exit_code, 0)
816 with src.open("r") as fobj:
817 self.assertEqual(fobj.read(), "print('hello')")
819 @event_loop(close=False)
820 def test_cache_multiple_files(self) -> None:
821 mode = black.FileMode.AUTO_DETECT
822 with cache_dir() as workspace, patch(
823 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
825 one = (workspace / "one.py").resolve()
826 with one.open("w") as fobj:
827 fobj.write("print('hello')")
828 two = (workspace / "two.py").resolve()
829 with two.open("w") as fobj:
830 fobj.write("print('hello')")
831 black.write_cache({}, [one], black.DEFAULT_LINE_LENGTH, mode)
832 result = CliRunner().invoke(black.main, [str(workspace)])
833 self.assertEqual(result.exit_code, 0)
834 with one.open("r") as fobj:
835 self.assertEqual(fobj.read(), "print('hello')")
836 with two.open("r") as fobj:
837 self.assertEqual(fobj.read(), 'print("hello")\n')
838 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
839 self.assertIn(one, cache)
840 self.assertIn(two, cache)
842 def test_no_cache_when_writeback_diff(self) -> None:
843 mode = black.FileMode.AUTO_DETECT
844 with cache_dir() as workspace:
845 src = (workspace / "test.py").resolve()
846 with src.open("w") as fobj:
847 fobj.write("print('hello')")
848 result = CliRunner().invoke(black.main, [str(src), "--diff"])
849 self.assertEqual(result.exit_code, 0)
850 cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode)
851 self.assertFalse(cache_file.exists())
853 def test_no_cache_when_stdin(self) -> None:
854 mode = black.FileMode.AUTO_DETECT
856 result = CliRunner().invoke(black.main, ["-"], input="print('hello')")
857 self.assertEqual(result.exit_code, 0)
858 cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode)
859 self.assertFalse(cache_file.exists())
861 def test_read_cache_no_cachefile(self) -> None:
862 mode = black.FileMode.AUTO_DETECT
864 self.assertEqual(black.read_cache(black.DEFAULT_LINE_LENGTH, mode), {})
866 def test_write_cache_read_cache(self) -> None:
867 mode = black.FileMode.AUTO_DETECT
868 with cache_dir() as workspace:
869 src = (workspace / "test.py").resolve()
871 black.write_cache({}, [src], black.DEFAULT_LINE_LENGTH, mode)
872 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
873 self.assertIn(src, cache)
874 self.assertEqual(cache[src], black.get_cache_info(src))
876 def test_filter_cached(self) -> None:
877 with TemporaryDirectory() as workspace:
878 path = Path(workspace)
879 uncached = (path / "uncached").resolve()
880 cached = (path / "cached").resolve()
881 cached_but_changed = (path / "changed").resolve()
884 cached_but_changed.touch()
885 cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
886 todo, done = black.filter_cached(
887 cache, {uncached, cached, cached_but_changed}
889 self.assertEqual(todo, {uncached, cached_but_changed})
890 self.assertEqual(done, {cached})
892 def test_write_cache_creates_directory_if_needed(self) -> None:
893 mode = black.FileMode.AUTO_DETECT
894 with cache_dir(exists=False) as workspace:
895 self.assertFalse(workspace.exists())
896 black.write_cache({}, [], black.DEFAULT_LINE_LENGTH, mode)
897 self.assertTrue(workspace.exists())
899 @event_loop(close=False)
900 def test_failed_formatting_does_not_get_cached(self) -> None:
901 mode = black.FileMode.AUTO_DETECT
902 with cache_dir() as workspace, patch(
903 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
905 failing = (workspace / "failing.py").resolve()
906 with failing.open("w") as fobj:
907 fobj.write("not actually python")
908 clean = (workspace / "clean.py").resolve()
909 with clean.open("w") as fobj:
910 fobj.write('print("hello")\n')
911 result = CliRunner().invoke(black.main, [str(workspace)])
912 self.assertEqual(result.exit_code, 123)
913 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
914 self.assertNotIn(failing, cache)
915 self.assertIn(clean, cache)
917 def test_write_cache_write_fail(self) -> None:
918 mode = black.FileMode.AUTO_DETECT
919 with cache_dir(), patch.object(Path, "open") as mock:
920 mock.side_effect = OSError
921 black.write_cache({}, [], black.DEFAULT_LINE_LENGTH, mode)
923 @event_loop(close=False)
924 def test_check_diff_use_together(self) -> None:
926 # Files which will be reformatted.
927 src1 = (THIS_DIR / "string_quotes.py").resolve()
928 result = CliRunner().invoke(black.main, [str(src1), "--diff", "--check"])
929 self.assertEqual(result.exit_code, 1)
931 # Files which will not be reformatted.
932 src2 = (THIS_DIR / "composition.py").resolve()
933 result = CliRunner().invoke(black.main, [str(src2), "--diff", "--check"])
934 self.assertEqual(result.exit_code, 0)
936 # Multi file command.
937 result = CliRunner().invoke(
938 black.main, [str(src1), str(src2), "--diff", "--check"]
940 self.assertEqual(result.exit_code, 1, result.output)
942 def test_no_files(self) -> None:
944 # Without an argument, black exits with error code 0.
945 result = CliRunner().invoke(black.main, [])
946 self.assertEqual(result.exit_code, 0)
948 def test_broken_symlink(self) -> None:
949 with cache_dir() as workspace:
950 symlink = workspace / "broken_link.py"
952 symlink.symlink_to("nonexistent.py")
954 self.skipTest(f"Can't create symlinks: {e}")
955 result = CliRunner().invoke(black.main, [str(workspace.resolve())])
956 self.assertEqual(result.exit_code, 0)
958 def test_read_cache_line_lengths(self) -> None:
959 mode = black.FileMode.AUTO_DETECT
960 with cache_dir() as workspace:
961 path = (workspace / "file.py").resolve()
963 black.write_cache({}, [path], 1, mode)
964 one = black.read_cache(1, mode)
965 self.assertIn(path, one)
966 two = black.read_cache(2, mode)
967 self.assertNotIn(path, two)
969 def test_single_file_force_pyi(self) -> None:
970 reg_mode = black.FileMode.AUTO_DETECT
971 pyi_mode = black.FileMode.PYI
972 contents, expected = read_data("force_pyi")
973 with cache_dir() as workspace:
974 path = (workspace / "file.py").resolve()
975 with open(path, "w") as fh:
977 result = CliRunner().invoke(black.main, [str(path), "--pyi"])
978 self.assertEqual(result.exit_code, 0)
979 with open(path, "r") as fh:
981 # verify cache with --pyi is separate
982 pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, pyi_mode)
983 self.assertIn(path, pyi_cache)
984 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
985 self.assertNotIn(path, normal_cache)
986 self.assertEqual(actual, expected)
988 @event_loop(close=False)
989 def test_multi_file_force_pyi(self) -> None:
990 reg_mode = black.FileMode.AUTO_DETECT
991 pyi_mode = black.FileMode.PYI
992 contents, expected = read_data("force_pyi")
993 with cache_dir() as workspace:
995 (workspace / "file1.py").resolve(),
996 (workspace / "file2.py").resolve(),
999 with open(path, "w") as fh:
1001 result = CliRunner().invoke(black.main, [str(p) for p in paths] + ["--pyi"])
1002 self.assertEqual(result.exit_code, 0)
1004 with open(path, "r") as fh:
1006 self.assertEqual(actual, expected)
1007 # verify cache with --pyi is separate
1008 pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, pyi_mode)
1009 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1011 self.assertIn(path, pyi_cache)
1012 self.assertNotIn(path, normal_cache)
1014 def test_pipe_force_pyi(self) -> None:
1015 source, expected = read_data("force_pyi")
1016 result = CliRunner().invoke(black.main, ["-", "-q", "--pyi"], input=source)
1017 self.assertEqual(result.exit_code, 0)
1018 actual = result.output
1019 self.assertFormatEqual(actual, expected)
1021 def test_single_file_force_py36(self) -> None:
1022 reg_mode = black.FileMode.AUTO_DETECT
1023 py36_mode = black.FileMode.PYTHON36
1024 source, expected = read_data("force_py36")
1025 with cache_dir() as workspace:
1026 path = (workspace / "file.py").resolve()
1027 with open(path, "w") as fh:
1029 result = CliRunner().invoke(black.main, [str(path), "--py36"])
1030 self.assertEqual(result.exit_code, 0)
1031 with open(path, "r") as fh:
1033 # verify cache with --py36 is separate
1034 py36_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, py36_mode)
1035 self.assertIn(path, py36_cache)
1036 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1037 self.assertNotIn(path, normal_cache)
1038 self.assertEqual(actual, expected)
1040 @event_loop(close=False)
1041 def test_multi_file_force_py36(self) -> None:
1042 reg_mode = black.FileMode.AUTO_DETECT
1043 py36_mode = black.FileMode.PYTHON36
1044 source, expected = read_data("force_py36")
1045 with cache_dir() as workspace:
1047 (workspace / "file1.py").resolve(),
1048 (workspace / "file2.py").resolve(),
1051 with open(path, "w") as fh:
1053 result = CliRunner().invoke(
1054 black.main, [str(p) for p in paths] + ["--py36"]
1056 self.assertEqual(result.exit_code, 0)
1058 with open(path, "r") as fh:
1060 self.assertEqual(actual, expected)
1061 # verify cache with --py36 is separate
1062 pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, py36_mode)
1063 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1065 self.assertIn(path, pyi_cache)
1066 self.assertNotIn(path, normal_cache)
1068 def test_pipe_force_py36(self) -> None:
1069 source, expected = read_data("force_py36")
1070 result = CliRunner().invoke(black.main, ["-", "-q", "--py36"], input=source)
1071 self.assertEqual(result.exit_code, 0)
1072 actual = result.output
1073 self.assertFormatEqual(actual, expected)
1075 def test_include_exclude(self) -> None:
1076 path = THIS_DIR / "include_exclude_tests"
1077 include = re.compile(r"\.pyi?$")
1078 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1079 report = black.Report()
1080 sources: List[Path] = []
1082 Path(THIS_DIR / "include_exclude_tests/b/dont_exclude/a.py"),
1083 Path(THIS_DIR / "include_exclude_tests/b/dont_exclude/a.pyi"),
1085 this_abs = THIS_DIR.resolve()
1087 black.gen_python_files_in_dir(path, this_abs, include, exclude, report)
1089 self.assertEqual(sorted(expected), sorted(sources))
1091 def test_empty_include(self) -> None:
1092 path = THIS_DIR / "include_exclude_tests"
1093 report = black.Report()
1094 empty = re.compile(r"")
1095 sources: List[Path] = []
1097 Path(path / "b/exclude/a.pie"),
1098 Path(path / "b/exclude/a.py"),
1099 Path(path / "b/exclude/a.pyi"),
1100 Path(path / "b/dont_exclude/a.pie"),
1101 Path(path / "b/dont_exclude/a.py"),
1102 Path(path / "b/dont_exclude/a.pyi"),
1103 Path(path / "b/.definitely_exclude/a.pie"),
1104 Path(path / "b/.definitely_exclude/a.py"),
1105 Path(path / "b/.definitely_exclude/a.pyi"),
1107 this_abs = THIS_DIR.resolve()
1109 black.gen_python_files_in_dir(
1110 path, this_abs, empty, re.compile(black.DEFAULT_EXCLUDES), report
1113 self.assertEqual(sorted(expected), sorted(sources))
1115 def test_empty_exclude(self) -> None:
1116 path = THIS_DIR / "include_exclude_tests"
1117 report = black.Report()
1118 empty = re.compile(r"")
1119 sources: List[Path] = []
1121 Path(path / "b/dont_exclude/a.py"),
1122 Path(path / "b/dont_exclude/a.pyi"),
1123 Path(path / "b/exclude/a.py"),
1124 Path(path / "b/exclude/a.pyi"),
1125 Path(path / "b/.definitely_exclude/a.py"),
1126 Path(path / "b/.definitely_exclude/a.pyi"),
1128 this_abs = THIS_DIR.resolve()
1130 black.gen_python_files_in_dir(
1131 path, this_abs, re.compile(black.DEFAULT_INCLUDES), empty, report
1134 self.assertEqual(sorted(expected), sorted(sources))
1136 def test_invalid_include_exclude(self) -> None:
1137 for option in ["--include", "--exclude"]:
1138 result = CliRunner().invoke(black.main, ["-", option, "**()(!!*)"])
1139 self.assertEqual(result.exit_code, 2)
1141 def test_preserves_line_endings(self) -> None:
1142 with TemporaryDirectory() as workspace:
1143 test_file = Path(workspace) / "test.py"
1144 for nl in ["\n", "\r\n"]:
1145 contents = nl.join(["def f( ):", " pass"])
1146 test_file.write_bytes(contents.encode())
1147 ff(test_file, write_back=black.WriteBack.YES)
1148 updated_contents: bytes = test_file.read_bytes()
1149 self.assertIn(nl.encode(), updated_contents) # type: ignore
1151 self.assertNotIn(b"\r\n", updated_contents) # type: ignore
1154 if __name__ == "__main__":