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.
4 from concurrent.futures import ThreadPoolExecutor
5 from contextlib import contextmanager
6 from functools import partial
7 from io import BytesIO, TextIOWrapper
9 from pathlib import Path
12 from tempfile import TemporaryDirectory
13 from typing import Any, BinaryIO, Generator, List, Tuple, Iterator, TypeVar
15 from unittest.mock import patch, MagicMock
17 from click import unstyle
18 from click.testing import CliRunner
21 from black import Feature, TargetVersion
25 from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
26 from aiohttp import web
28 has_blackd_deps = False
30 has_blackd_deps = True
32 from pathspec import PathSpec
34 ff = partial(black.format_file_in_place, mode=black.FileMode(), fast=True)
35 fs = partial(black.format_str, mode=black.FileMode())
36 THIS_FILE = Path(__file__)
37 THIS_DIR = THIS_FILE.parent
38 DETERMINISTIC_HEADER = "[Deterministic header]"
39 EMPTY_LINE = "# EMPTY LINE WITH WHITESPACE" + " (this comment will be removed)"
41 f"--target-version={version.name.lower()}" for version in black.PY36_VERSIONS
47 def dump_to_stderr(*output: str) -> str:
48 return "\n" + "\n".join(output) + "\n"
51 def read_data(name: str, data: bool = True) -> Tuple[str, str]:
52 """read_data('test_name') -> 'input', 'output'"""
53 if not name.endswith((".py", ".pyi", ".out", ".diff")):
55 _input: List[str] = []
56 _output: List[str] = []
57 base_dir = THIS_DIR / "data" if data else THIS_DIR
58 with open(base_dir / name, "r", encoding="utf8") as test:
59 lines = test.readlines()
62 line = line.replace(EMPTY_LINE, "")
63 if line.rstrip() == "# output":
68 if _input and not _output:
69 # If there's no output marker, treat the entire file as already pre-formatted.
71 return "".join(_input).strip() + "\n", "".join(_output).strip() + "\n"
75 def cache_dir(exists: bool = True) -> Iterator[Path]:
76 with TemporaryDirectory() as workspace:
77 cache_dir = Path(workspace)
79 cache_dir = cache_dir / "new"
80 with patch("black.CACHE_DIR", cache_dir):
85 def event_loop(close: bool) -> Iterator[None]:
86 policy = asyncio.get_event_loop_policy()
87 loop = policy.new_event_loop()
88 asyncio.set_event_loop(loop)
98 def skip_if_exception(e: str) -> Iterator[None]:
101 except Exception as exc:
102 if exc.__class__.__name__ == e:
103 unittest.skip(f"Encountered expected exception {exc}, skipping")
108 class BlackRunner(CliRunner):
109 """Modify CliRunner so that stderr is not merged with stdout.
111 This is a hack that can be removed once we depend on Click 7.x"""
113 def __init__(self) -> None:
114 self.stderrbuf = BytesIO()
115 self.stdoutbuf = BytesIO()
116 self.stdout_bytes = b""
117 self.stderr_bytes = b""
121 def isolation(self, *args: Any, **kwargs: Any) -> Generator[BinaryIO, None, None]:
122 with super().isolation(*args, **kwargs) as output:
124 hold_stderr = sys.stderr
125 sys.stderr = TextIOWrapper(self.stderrbuf, encoding=self.charset)
128 self.stdout_bytes = sys.stdout.buffer.getvalue() # type: ignore
129 self.stderr_bytes = sys.stderr.buffer.getvalue() # type: ignore
130 sys.stderr = hold_stderr
133 class BlackTestCase(unittest.TestCase):
136 def assertFormatEqual(self, expected: str, actual: str) -> None:
137 if actual != expected and not os.environ.get("SKIP_AST_PRINT"):
138 bdv: black.DebugVisitor[Any]
139 black.out("Expected tree:", fg="green")
141 exp_node = black.lib2to3_parse(expected)
142 bdv = black.DebugVisitor()
143 list(bdv.visit(exp_node))
144 except Exception as ve:
146 black.out("Actual tree:", fg="red")
148 exp_node = black.lib2to3_parse(actual)
149 bdv = black.DebugVisitor()
150 list(bdv.visit(exp_node))
151 except Exception as ve:
153 self.assertEqual(expected, actual)
156 self, args: List[str], exit_code: int = 0, ignore_config: bool = True
158 runner = BlackRunner()
160 args = ["--verbose", "--config", str(THIS_DIR / "empty.toml"), *args]
161 result = runner.invoke(black.main, args)
165 msg=f"Failed with args: {args}. Stderr: {runner.stderr_bytes.decode()!r}",
168 @patch("black.dump_to_file", dump_to_stderr)
169 def checkSourceFile(self, name: str) -> None:
170 path = THIS_DIR.parent / name
171 source, expected = read_data(str(path), data=False)
173 self.assertFormatEqual(expected, actual)
174 black.assert_equivalent(source, actual)
175 black.assert_stable(source, actual, black.FileMode())
176 self.assertFalse(ff(path))
178 @patch("black.dump_to_file", dump_to_stderr)
179 def test_empty(self) -> None:
180 source = expected = ""
182 self.assertFormatEqual(expected, actual)
183 black.assert_equivalent(source, actual)
184 black.assert_stable(source, actual, black.FileMode())
186 def test_empty_ff(self) -> None:
188 tmp_file = Path(black.dump_to_file())
190 self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES))
191 with open(tmp_file, encoding="utf8") as f:
195 self.assertFormatEqual(expected, actual)
197 def test_self(self) -> None:
198 self.checkSourceFile("tests/test_black.py")
200 def test_black(self) -> None:
201 self.checkSourceFile("black.py")
203 def test_pygram(self) -> None:
204 self.checkSourceFile("blib2to3/pygram.py")
206 def test_pytree(self) -> None:
207 self.checkSourceFile("blib2to3/pytree.py")
209 def test_conv(self) -> None:
210 self.checkSourceFile("blib2to3/pgen2/conv.py")
212 def test_driver(self) -> None:
213 self.checkSourceFile("blib2to3/pgen2/driver.py")
215 def test_grammar(self) -> None:
216 self.checkSourceFile("blib2to3/pgen2/grammar.py")
218 def test_literals(self) -> None:
219 self.checkSourceFile("blib2to3/pgen2/literals.py")
221 def test_parse(self) -> None:
222 self.checkSourceFile("blib2to3/pgen2/parse.py")
224 def test_pgen(self) -> None:
225 self.checkSourceFile("blib2to3/pgen2/pgen.py")
227 def test_tokenize(self) -> None:
228 self.checkSourceFile("blib2to3/pgen2/tokenize.py")
230 def test_token(self) -> None:
231 self.checkSourceFile("blib2to3/pgen2/token.py")
233 def test_setup(self) -> None:
234 self.checkSourceFile("setup.py")
236 def test_piping(self) -> None:
237 source, expected = read_data("../black", data=False)
238 result = BlackRunner().invoke(
240 ["-", "--fast", f"--line-length={black.DEFAULT_LINE_LENGTH}"],
241 input=BytesIO(source.encode("utf8")),
243 self.assertEqual(result.exit_code, 0)
244 self.assertFormatEqual(expected, result.output)
245 black.assert_equivalent(source, result.output)
246 black.assert_stable(source, result.output, black.FileMode())
248 def test_piping_diff(self) -> None:
249 diff_header = re.compile(
250 rf"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d\d\d\d "
253 source, _ = read_data("expression.py")
254 expected, _ = read_data("expression.diff")
255 config = THIS_DIR / "data" / "empty_pyproject.toml"
259 f"--line-length={black.DEFAULT_LINE_LENGTH}",
261 f"--config={config}",
263 result = BlackRunner().invoke(
264 black.main, args, input=BytesIO(source.encode("utf8"))
266 self.assertEqual(result.exit_code, 0)
267 actual = diff_header.sub(DETERMINISTIC_HEADER, result.output)
268 actual = actual.rstrip() + "\n" # the diff output has a trailing space
269 self.assertEqual(expected, actual)
271 def test_piping_diff_with_color(self) -> None:
272 source, _ = read_data("expression.py")
273 config = THIS_DIR / "data" / "empty_pyproject.toml"
277 f"--line-length={black.DEFAULT_LINE_LENGTH}",
280 f"--config={config}",
282 result = BlackRunner().invoke(
283 black.main, args, input=BytesIO(source.encode("utf8"))
285 actual = result.output
286 # Again, the contents are checked in a different test, so only look for colors.
287 self.assertIn("\033[1;37m", actual)
288 self.assertIn("\033[36m", actual)
289 self.assertIn("\033[32m", actual)
290 self.assertIn("\033[31m", actual)
291 self.assertIn("\033[0m", actual)
293 @patch("black.dump_to_file", dump_to_stderr)
294 def test_function(self) -> None:
295 source, expected = read_data("function")
297 self.assertFormatEqual(expected, actual)
298 black.assert_equivalent(source, actual)
299 black.assert_stable(source, actual, black.FileMode())
301 @patch("black.dump_to_file", dump_to_stderr)
302 def test_function2(self) -> None:
303 source, expected = read_data("function2")
305 self.assertFormatEqual(expected, actual)
306 black.assert_equivalent(source, actual)
307 black.assert_stable(source, actual, black.FileMode())
309 @patch("black.dump_to_file", dump_to_stderr)
310 def test_function_trailing_comma(self) -> None:
311 source, expected = read_data("function_trailing_comma")
313 self.assertFormatEqual(expected, actual)
314 black.assert_equivalent(source, actual)
315 black.assert_stable(source, actual, black.FileMode())
317 @patch("black.dump_to_file", dump_to_stderr)
318 def test_expression(self) -> None:
319 source, expected = read_data("expression")
321 self.assertFormatEqual(expected, actual)
322 black.assert_equivalent(source, actual)
323 black.assert_stable(source, actual, black.FileMode())
325 @patch("black.dump_to_file", dump_to_stderr)
326 def test_pep_572(self) -> None:
327 source, expected = read_data("pep_572")
329 self.assertFormatEqual(expected, actual)
330 black.assert_stable(source, actual, black.FileMode())
331 if sys.version_info >= (3, 8):
332 black.assert_equivalent(source, actual)
334 def test_pep_572_version_detection(self) -> None:
335 source, _ = read_data("pep_572")
336 root = black.lib2to3_parse(source)
337 features = black.get_features_used(root)
338 self.assertIn(black.Feature.ASSIGNMENT_EXPRESSIONS, features)
339 versions = black.detect_target_versions(root)
340 self.assertIn(black.TargetVersion.PY38, versions)
342 def test_expression_ff(self) -> None:
343 source, expected = read_data("expression")
344 tmp_file = Path(black.dump_to_file(source))
346 self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
347 with open(tmp_file, encoding="utf8") as f:
351 self.assertFormatEqual(expected, actual)
352 with patch("black.dump_to_file", dump_to_stderr):
353 black.assert_equivalent(source, actual)
354 black.assert_stable(source, actual, black.FileMode())
356 def test_expression_diff(self) -> None:
357 source, _ = read_data("expression.py")
358 expected, _ = read_data("expression.diff")
359 tmp_file = Path(black.dump_to_file(source))
360 diff_header = re.compile(
361 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
362 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
365 result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
366 self.assertEqual(result.exit_code, 0)
369 actual = result.output
370 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
371 actual = actual.rstrip() + "\n" # the diff output has a trailing space
372 if expected != actual:
373 dump = black.dump_to_file(actual)
375 "Expected diff isn't equal to the actual. If you made changes to"
376 " expression.py and this is an anticipated difference, overwrite"
377 f" tests/data/expression.diff with {dump}"
379 self.assertEqual(expected, actual, msg)
381 def test_expression_diff_with_color(self) -> None:
382 source, _ = read_data("expression.py")
383 expected, _ = read_data("expression.diff")
384 tmp_file = Path(black.dump_to_file(source))
386 result = BlackRunner().invoke(
387 black.main, ["--diff", "--color", str(tmp_file)]
391 actual = result.output
392 # We check the contents of the diff in `test_expression_diff`. All
393 # we need to check here is that color codes exist in the result.
394 self.assertIn("\033[1;37m", actual)
395 self.assertIn("\033[36m", actual)
396 self.assertIn("\033[32m", actual)
397 self.assertIn("\033[31m", actual)
398 self.assertIn("\033[0m", actual)
400 @patch("black.dump_to_file", dump_to_stderr)
401 def test_fstring(self) -> None:
402 source, expected = read_data("fstring")
404 self.assertFormatEqual(expected, actual)
405 black.assert_equivalent(source, actual)
406 black.assert_stable(source, actual, black.FileMode())
408 @patch("black.dump_to_file", dump_to_stderr)
409 def test_pep_570(self) -> None:
410 source, expected = read_data("pep_570")
412 self.assertFormatEqual(expected, actual)
413 black.assert_stable(source, actual, black.FileMode())
414 if sys.version_info >= (3, 8):
415 black.assert_equivalent(source, actual)
417 def test_detect_pos_only_arguments(self) -> None:
418 source, _ = read_data("pep_570")
419 root = black.lib2to3_parse(source)
420 features = black.get_features_used(root)
421 self.assertIn(black.Feature.POS_ONLY_ARGUMENTS, features)
422 versions = black.detect_target_versions(root)
423 self.assertIn(black.TargetVersion.PY38, versions)
425 @patch("black.dump_to_file", dump_to_stderr)
426 def test_string_quotes(self) -> None:
427 source, expected = read_data("string_quotes")
429 self.assertFormatEqual(expected, actual)
430 black.assert_equivalent(source, actual)
431 black.assert_stable(source, actual, black.FileMode())
432 mode = black.FileMode(string_normalization=False)
433 not_normalized = fs(source, mode=mode)
434 self.assertFormatEqual(source.replace("\\\n", ""), not_normalized)
435 black.assert_equivalent(source, not_normalized)
436 black.assert_stable(source, not_normalized, mode=mode)
438 @patch("black.dump_to_file", dump_to_stderr)
439 def test_docstring(self) -> None:
440 source, expected = read_data("docstring")
442 self.assertFormatEqual(expected, actual)
443 black.assert_equivalent(source, actual)
444 black.assert_stable(source, actual, black.FileMode())
446 def test_long_strings(self) -> None:
447 """Tests for splitting long strings."""
448 source, expected = read_data("long_strings")
450 self.assertFormatEqual(expected, actual)
451 black.assert_equivalent(source, actual)
452 black.assert_stable(source, actual, black.FileMode())
454 @patch("black.dump_to_file", dump_to_stderr)
455 def test_long_strings__edge_case(self) -> None:
456 """Edge-case tests for splitting long strings."""
457 source, expected = read_data("long_strings__edge_case")
459 self.assertFormatEqual(expected, actual)
460 black.assert_equivalent(source, actual)
461 black.assert_stable(source, actual, black.FileMode())
463 @patch("black.dump_to_file", dump_to_stderr)
464 def test_long_strings__regression(self) -> None:
465 """Regression tests for splitting long strings."""
466 source, expected = read_data("long_strings__regression")
468 self.assertFormatEqual(expected, actual)
469 black.assert_equivalent(source, actual)
470 black.assert_stable(source, actual, black.FileMode())
472 @patch("black.dump_to_file", dump_to_stderr)
473 def test_slices(self) -> None:
474 source, expected = read_data("slices")
476 self.assertFormatEqual(expected, actual)
477 black.assert_equivalent(source, actual)
478 black.assert_stable(source, actual, black.FileMode())
480 @patch("black.dump_to_file", dump_to_stderr)
481 def test_comments(self) -> None:
482 source, expected = read_data("comments")
484 self.assertFormatEqual(expected, actual)
485 black.assert_equivalent(source, actual)
486 black.assert_stable(source, actual, black.FileMode())
488 @patch("black.dump_to_file", dump_to_stderr)
489 def test_comments2(self) -> None:
490 source, expected = read_data("comments2")
492 self.assertFormatEqual(expected, actual)
493 black.assert_equivalent(source, actual)
494 black.assert_stable(source, actual, black.FileMode())
496 @patch("black.dump_to_file", dump_to_stderr)
497 def test_comments3(self) -> None:
498 source, expected = read_data("comments3")
500 self.assertFormatEqual(expected, actual)
501 black.assert_equivalent(source, actual)
502 black.assert_stable(source, actual, black.FileMode())
504 @patch("black.dump_to_file", dump_to_stderr)
505 def test_comments4(self) -> None:
506 source, expected = read_data("comments4")
508 self.assertFormatEqual(expected, actual)
509 black.assert_equivalent(source, actual)
510 black.assert_stable(source, actual, black.FileMode())
512 @patch("black.dump_to_file", dump_to_stderr)
513 def test_comments5(self) -> None:
514 source, expected = read_data("comments5")
516 self.assertFormatEqual(expected, actual)
517 black.assert_equivalent(source, actual)
518 black.assert_stable(source, actual, black.FileMode())
520 @patch("black.dump_to_file", dump_to_stderr)
521 def test_comments6(self) -> None:
522 source, expected = read_data("comments6")
524 self.assertFormatEqual(expected, actual)
525 black.assert_equivalent(source, actual)
526 black.assert_stable(source, actual, black.FileMode())
528 @patch("black.dump_to_file", dump_to_stderr)
529 def test_comments7(self) -> None:
530 source, expected = read_data("comments7")
532 self.assertFormatEqual(expected, actual)
533 black.assert_equivalent(source, actual)
534 black.assert_stable(source, actual, black.FileMode())
536 @patch("black.dump_to_file", dump_to_stderr)
537 def test_comment_after_escaped_newline(self) -> None:
538 source, expected = read_data("comment_after_escaped_newline")
540 self.assertFormatEqual(expected, actual)
541 black.assert_equivalent(source, actual)
542 black.assert_stable(source, actual, black.FileMode())
544 @patch("black.dump_to_file", dump_to_stderr)
545 def test_cantfit(self) -> None:
546 source, expected = read_data("cantfit")
548 self.assertFormatEqual(expected, actual)
549 black.assert_equivalent(source, actual)
550 black.assert_stable(source, actual, black.FileMode())
552 @patch("black.dump_to_file", dump_to_stderr)
553 def test_import_spacing(self) -> None:
554 source, expected = read_data("import_spacing")
556 self.assertFormatEqual(expected, actual)
557 black.assert_equivalent(source, actual)
558 black.assert_stable(source, actual, black.FileMode())
560 @patch("black.dump_to_file", dump_to_stderr)
561 def test_composition(self) -> None:
562 source, expected = read_data("composition")
564 self.assertFormatEqual(expected, actual)
565 black.assert_equivalent(source, actual)
566 black.assert_stable(source, actual, black.FileMode())
568 @patch("black.dump_to_file", dump_to_stderr)
569 def test_empty_lines(self) -> None:
570 source, expected = read_data("empty_lines")
572 self.assertFormatEqual(expected, actual)
573 black.assert_equivalent(source, actual)
574 black.assert_stable(source, actual, black.FileMode())
576 @patch("black.dump_to_file", dump_to_stderr)
577 def test_remove_parens(self) -> None:
578 source, expected = read_data("remove_parens")
580 self.assertFormatEqual(expected, actual)
581 black.assert_equivalent(source, actual)
582 black.assert_stable(source, actual, black.FileMode())
584 @patch("black.dump_to_file", dump_to_stderr)
585 def test_string_prefixes(self) -> None:
586 source, expected = read_data("string_prefixes")
588 self.assertFormatEqual(expected, actual)
589 black.assert_equivalent(source, actual)
590 black.assert_stable(source, actual, black.FileMode())
592 @patch("black.dump_to_file", dump_to_stderr)
593 def test_numeric_literals(self) -> None:
594 source, expected = read_data("numeric_literals")
595 mode = black.FileMode(target_versions=black.PY36_VERSIONS)
596 actual = fs(source, mode=mode)
597 self.assertFormatEqual(expected, actual)
598 black.assert_equivalent(source, actual)
599 black.assert_stable(source, actual, mode)
601 @patch("black.dump_to_file", dump_to_stderr)
602 def test_numeric_literals_ignoring_underscores(self) -> None:
603 source, expected = read_data("numeric_literals_skip_underscores")
604 mode = black.FileMode(target_versions=black.PY36_VERSIONS)
605 actual = fs(source, mode=mode)
606 self.assertFormatEqual(expected, actual)
607 black.assert_equivalent(source, actual)
608 black.assert_stable(source, actual, mode)
610 @patch("black.dump_to_file", dump_to_stderr)
611 def test_numeric_literals_py2(self) -> None:
612 source, expected = read_data("numeric_literals_py2")
614 self.assertFormatEqual(expected, actual)
615 black.assert_stable(source, actual, black.FileMode())
617 @patch("black.dump_to_file", dump_to_stderr)
618 def test_python2(self) -> None:
619 source, expected = read_data("python2")
621 self.assertFormatEqual(expected, actual)
622 black.assert_equivalent(source, actual)
623 black.assert_stable(source, actual, black.FileMode())
625 @patch("black.dump_to_file", dump_to_stderr)
626 def test_python2_print_function(self) -> None:
627 source, expected = read_data("python2_print_function")
628 mode = black.FileMode(target_versions={TargetVersion.PY27})
629 actual = fs(source, mode=mode)
630 self.assertFormatEqual(expected, actual)
631 black.assert_equivalent(source, actual)
632 black.assert_stable(source, actual, mode)
634 @patch("black.dump_to_file", dump_to_stderr)
635 def test_python2_unicode_literals(self) -> None:
636 source, expected = read_data("python2_unicode_literals")
638 self.assertFormatEqual(expected, actual)
639 black.assert_equivalent(source, actual)
640 black.assert_stable(source, actual, black.FileMode())
642 @patch("black.dump_to_file", dump_to_stderr)
643 def test_stub(self) -> None:
644 mode = black.FileMode(is_pyi=True)
645 source, expected = read_data("stub.pyi")
646 actual = fs(source, mode=mode)
647 self.assertFormatEqual(expected, actual)
648 black.assert_stable(source, actual, mode)
650 @patch("black.dump_to_file", dump_to_stderr)
651 def test_async_as_identifier(self) -> None:
652 source_path = (THIS_DIR / "data" / "async_as_identifier.py").resolve()
653 source, expected = read_data("async_as_identifier")
655 self.assertFormatEqual(expected, actual)
656 major, minor = sys.version_info[:2]
657 if major < 3 or (major <= 3 and minor < 7):
658 black.assert_equivalent(source, actual)
659 black.assert_stable(source, actual, black.FileMode())
660 # ensure black can parse this when the target is 3.6
661 self.invokeBlack([str(source_path), "--target-version", "py36"])
662 # but not on 3.7, because async/await is no longer an identifier
663 self.invokeBlack([str(source_path), "--target-version", "py37"], exit_code=123)
665 @patch("black.dump_to_file", dump_to_stderr)
666 def test_python37(self) -> None:
667 source_path = (THIS_DIR / "data" / "python37.py").resolve()
668 source, expected = read_data("python37")
670 self.assertFormatEqual(expected, actual)
671 major, minor = sys.version_info[:2]
672 if major > 3 or (major == 3 and minor >= 7):
673 black.assert_equivalent(source, actual)
674 black.assert_stable(source, actual, black.FileMode())
675 # ensure black can parse this when the target is 3.7
676 self.invokeBlack([str(source_path), "--target-version", "py37"])
677 # but not on 3.6, because we use async as a reserved keyword
678 self.invokeBlack([str(source_path), "--target-version", "py36"], exit_code=123)
680 @patch("black.dump_to_file", dump_to_stderr)
681 def test_python38(self) -> None:
682 source, expected = read_data("python38")
684 self.assertFormatEqual(expected, actual)
685 major, minor = sys.version_info[:2]
686 if major > 3 or (major == 3 and minor >= 8):
687 black.assert_equivalent(source, actual)
688 black.assert_stable(source, actual, black.FileMode())
690 @patch("black.dump_to_file", dump_to_stderr)
691 def test_fmtonoff(self) -> None:
692 source, expected = read_data("fmtonoff")
694 self.assertFormatEqual(expected, actual)
695 black.assert_equivalent(source, actual)
696 black.assert_stable(source, actual, black.FileMode())
698 @patch("black.dump_to_file", dump_to_stderr)
699 def test_fmtonoff2(self) -> None:
700 source, expected = read_data("fmtonoff2")
702 self.assertFormatEqual(expected, actual)
703 black.assert_equivalent(source, actual)
704 black.assert_stable(source, actual, black.FileMode())
706 @patch("black.dump_to_file", dump_to_stderr)
707 def test_fmtonoff3(self) -> None:
708 source, expected = read_data("fmtonoff3")
710 self.assertFormatEqual(expected, actual)
711 black.assert_equivalent(source, actual)
712 black.assert_stable(source, actual, black.FileMode())
714 @patch("black.dump_to_file", dump_to_stderr)
715 def test_fmtonoff4(self) -> None:
716 source, expected = read_data("fmtonoff4")
718 self.assertFormatEqual(expected, actual)
719 black.assert_equivalent(source, actual)
720 black.assert_stable(source, actual, black.FileMode())
722 @patch("black.dump_to_file", dump_to_stderr)
723 def test_remove_empty_parentheses_after_class(self) -> None:
724 source, expected = read_data("class_blank_parentheses")
726 self.assertFormatEqual(expected, actual)
727 black.assert_equivalent(source, actual)
728 black.assert_stable(source, actual, black.FileMode())
730 @patch("black.dump_to_file", dump_to_stderr)
731 def test_new_line_between_class_and_code(self) -> None:
732 source, expected = read_data("class_methods_new_line")
734 self.assertFormatEqual(expected, actual)
735 black.assert_equivalent(source, actual)
736 black.assert_stable(source, actual, black.FileMode())
738 @patch("black.dump_to_file", dump_to_stderr)
739 def test_bracket_match(self) -> None:
740 source, expected = read_data("bracketmatch")
742 self.assertFormatEqual(expected, actual)
743 black.assert_equivalent(source, actual)
744 black.assert_stable(source, actual, black.FileMode())
746 @patch("black.dump_to_file", dump_to_stderr)
747 def test_tuple_assign(self) -> None:
748 source, expected = read_data("tupleassign")
750 self.assertFormatEqual(expected, actual)
751 black.assert_equivalent(source, actual)
752 black.assert_stable(source, actual, black.FileMode())
754 @patch("black.dump_to_file", dump_to_stderr)
755 def test_beginning_backslash(self) -> None:
756 source, expected = read_data("beginning_backslash")
758 self.assertFormatEqual(expected, actual)
759 black.assert_equivalent(source, actual)
760 black.assert_stable(source, actual, black.FileMode())
762 def test_tab_comment_indentation(self) -> None:
763 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
764 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
765 self.assertFormatEqual(contents_spc, fs(contents_spc))
766 self.assertFormatEqual(contents_spc, fs(contents_tab))
768 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
769 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
770 self.assertFormatEqual(contents_spc, fs(contents_spc))
771 self.assertFormatEqual(contents_spc, fs(contents_tab))
773 # mixed tabs and spaces (valid Python 2 code)
774 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t# comment\n pass\n"
775 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
776 self.assertFormatEqual(contents_spc, fs(contents_spc))
777 self.assertFormatEqual(contents_spc, fs(contents_tab))
779 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t\t# comment\n pass\n"
780 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
781 self.assertFormatEqual(contents_spc, fs(contents_spc))
782 self.assertFormatEqual(contents_spc, fs(contents_tab))
784 def test_report_verbose(self) -> None:
785 report = black.Report(verbose=True)
789 def out(msg: str, **kwargs: Any) -> None:
790 out_lines.append(msg)
792 def err(msg: str, **kwargs: Any) -> None:
793 err_lines.append(msg)
795 with patch("black.out", out), patch("black.err", err):
796 report.done(Path("f1"), black.Changed.NO)
797 self.assertEqual(len(out_lines), 1)
798 self.assertEqual(len(err_lines), 0)
799 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
800 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
801 self.assertEqual(report.return_code, 0)
802 report.done(Path("f2"), black.Changed.YES)
803 self.assertEqual(len(out_lines), 2)
804 self.assertEqual(len(err_lines), 0)
805 self.assertEqual(out_lines[-1], "reformatted f2")
807 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
809 report.done(Path("f3"), black.Changed.CACHED)
810 self.assertEqual(len(out_lines), 3)
811 self.assertEqual(len(err_lines), 0)
813 out_lines[-1], "f3 wasn't modified on disk since last run."
816 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
818 self.assertEqual(report.return_code, 0)
820 self.assertEqual(report.return_code, 1)
822 report.failed(Path("e1"), "boom")
823 self.assertEqual(len(out_lines), 3)
824 self.assertEqual(len(err_lines), 1)
825 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
827 unstyle(str(report)),
828 "1 file reformatted, 2 files left unchanged, 1 file failed to"
831 self.assertEqual(report.return_code, 123)
832 report.done(Path("f3"), black.Changed.YES)
833 self.assertEqual(len(out_lines), 4)
834 self.assertEqual(len(err_lines), 1)
835 self.assertEqual(out_lines[-1], "reformatted f3")
837 unstyle(str(report)),
838 "2 files reformatted, 2 files left unchanged, 1 file failed to"
841 self.assertEqual(report.return_code, 123)
842 report.failed(Path("e2"), "boom")
843 self.assertEqual(len(out_lines), 4)
844 self.assertEqual(len(err_lines), 2)
845 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
847 unstyle(str(report)),
848 "2 files reformatted, 2 files left unchanged, 2 files failed to"
851 self.assertEqual(report.return_code, 123)
852 report.path_ignored(Path("wat"), "no match")
853 self.assertEqual(len(out_lines), 5)
854 self.assertEqual(len(err_lines), 2)
855 self.assertEqual(out_lines[-1], "wat ignored: no match")
857 unstyle(str(report)),
858 "2 files reformatted, 2 files left unchanged, 2 files failed to"
861 self.assertEqual(report.return_code, 123)
862 report.done(Path("f4"), black.Changed.NO)
863 self.assertEqual(len(out_lines), 6)
864 self.assertEqual(len(err_lines), 2)
865 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
867 unstyle(str(report)),
868 "2 files reformatted, 3 files left unchanged, 2 files failed to"
871 self.assertEqual(report.return_code, 123)
874 unstyle(str(report)),
875 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
876 " would fail to reformat.",
881 unstyle(str(report)),
882 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
883 " would fail to reformat.",
886 def test_report_quiet(self) -> None:
887 report = black.Report(quiet=True)
891 def out(msg: str, **kwargs: Any) -> None:
892 out_lines.append(msg)
894 def err(msg: str, **kwargs: Any) -> None:
895 err_lines.append(msg)
897 with patch("black.out", out), patch("black.err", err):
898 report.done(Path("f1"), black.Changed.NO)
899 self.assertEqual(len(out_lines), 0)
900 self.assertEqual(len(err_lines), 0)
901 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
902 self.assertEqual(report.return_code, 0)
903 report.done(Path("f2"), black.Changed.YES)
904 self.assertEqual(len(out_lines), 0)
905 self.assertEqual(len(err_lines), 0)
907 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
909 report.done(Path("f3"), black.Changed.CACHED)
910 self.assertEqual(len(out_lines), 0)
911 self.assertEqual(len(err_lines), 0)
913 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
915 self.assertEqual(report.return_code, 0)
917 self.assertEqual(report.return_code, 1)
919 report.failed(Path("e1"), "boom")
920 self.assertEqual(len(out_lines), 0)
921 self.assertEqual(len(err_lines), 1)
922 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
924 unstyle(str(report)),
925 "1 file reformatted, 2 files left unchanged, 1 file failed to"
928 self.assertEqual(report.return_code, 123)
929 report.done(Path("f3"), black.Changed.YES)
930 self.assertEqual(len(out_lines), 0)
931 self.assertEqual(len(err_lines), 1)
933 unstyle(str(report)),
934 "2 files reformatted, 2 files left unchanged, 1 file failed to"
937 self.assertEqual(report.return_code, 123)
938 report.failed(Path("e2"), "boom")
939 self.assertEqual(len(out_lines), 0)
940 self.assertEqual(len(err_lines), 2)
941 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
943 unstyle(str(report)),
944 "2 files reformatted, 2 files left unchanged, 2 files failed to"
947 self.assertEqual(report.return_code, 123)
948 report.path_ignored(Path("wat"), "no match")
949 self.assertEqual(len(out_lines), 0)
950 self.assertEqual(len(err_lines), 2)
952 unstyle(str(report)),
953 "2 files reformatted, 2 files left unchanged, 2 files failed to"
956 self.assertEqual(report.return_code, 123)
957 report.done(Path("f4"), black.Changed.NO)
958 self.assertEqual(len(out_lines), 0)
959 self.assertEqual(len(err_lines), 2)
961 unstyle(str(report)),
962 "2 files reformatted, 3 files left unchanged, 2 files failed to"
965 self.assertEqual(report.return_code, 123)
968 unstyle(str(report)),
969 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
970 " would fail to reformat.",
975 unstyle(str(report)),
976 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
977 " would fail to reformat.",
980 def test_report_normal(self) -> None:
981 report = black.Report()
985 def out(msg: str, **kwargs: Any) -> None:
986 out_lines.append(msg)
988 def err(msg: str, **kwargs: Any) -> None:
989 err_lines.append(msg)
991 with patch("black.out", out), patch("black.err", err):
992 report.done(Path("f1"), black.Changed.NO)
993 self.assertEqual(len(out_lines), 0)
994 self.assertEqual(len(err_lines), 0)
995 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
996 self.assertEqual(report.return_code, 0)
997 report.done(Path("f2"), black.Changed.YES)
998 self.assertEqual(len(out_lines), 1)
999 self.assertEqual(len(err_lines), 0)
1000 self.assertEqual(out_lines[-1], "reformatted f2")
1002 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
1004 report.done(Path("f3"), black.Changed.CACHED)
1005 self.assertEqual(len(out_lines), 1)
1006 self.assertEqual(len(err_lines), 0)
1007 self.assertEqual(out_lines[-1], "reformatted f2")
1009 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
1011 self.assertEqual(report.return_code, 0)
1013 self.assertEqual(report.return_code, 1)
1014 report.check = False
1015 report.failed(Path("e1"), "boom")
1016 self.assertEqual(len(out_lines), 1)
1017 self.assertEqual(len(err_lines), 1)
1018 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
1020 unstyle(str(report)),
1021 "1 file reformatted, 2 files left unchanged, 1 file failed to"
1024 self.assertEqual(report.return_code, 123)
1025 report.done(Path("f3"), black.Changed.YES)
1026 self.assertEqual(len(out_lines), 2)
1027 self.assertEqual(len(err_lines), 1)
1028 self.assertEqual(out_lines[-1], "reformatted f3")
1030 unstyle(str(report)),
1031 "2 files reformatted, 2 files left unchanged, 1 file failed to"
1034 self.assertEqual(report.return_code, 123)
1035 report.failed(Path("e2"), "boom")
1036 self.assertEqual(len(out_lines), 2)
1037 self.assertEqual(len(err_lines), 2)
1038 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
1040 unstyle(str(report)),
1041 "2 files reformatted, 2 files left unchanged, 2 files failed to"
1044 self.assertEqual(report.return_code, 123)
1045 report.path_ignored(Path("wat"), "no match")
1046 self.assertEqual(len(out_lines), 2)
1047 self.assertEqual(len(err_lines), 2)
1049 unstyle(str(report)),
1050 "2 files reformatted, 2 files left unchanged, 2 files failed to"
1053 self.assertEqual(report.return_code, 123)
1054 report.done(Path("f4"), black.Changed.NO)
1055 self.assertEqual(len(out_lines), 2)
1056 self.assertEqual(len(err_lines), 2)
1058 unstyle(str(report)),
1059 "2 files reformatted, 3 files left unchanged, 2 files failed to"
1062 self.assertEqual(report.return_code, 123)
1065 unstyle(str(report)),
1066 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1067 " would fail to reformat.",
1069 report.check = False
1072 unstyle(str(report)),
1073 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1074 " would fail to reformat.",
1077 def test_lib2to3_parse(self) -> None:
1078 with self.assertRaises(black.InvalidInput):
1079 black.lib2to3_parse("invalid syntax")
1081 straddling = "x + y"
1082 black.lib2to3_parse(straddling)
1083 black.lib2to3_parse(straddling, {TargetVersion.PY27})
1084 black.lib2to3_parse(straddling, {TargetVersion.PY36})
1085 black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
1087 py2_only = "print x"
1088 black.lib2to3_parse(py2_only)
1089 black.lib2to3_parse(py2_only, {TargetVersion.PY27})
1090 with self.assertRaises(black.InvalidInput):
1091 black.lib2to3_parse(py2_only, {TargetVersion.PY36})
1092 with self.assertRaises(black.InvalidInput):
1093 black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
1095 py3_only = "exec(x, end=y)"
1096 black.lib2to3_parse(py3_only)
1097 with self.assertRaises(black.InvalidInput):
1098 black.lib2to3_parse(py3_only, {TargetVersion.PY27})
1099 black.lib2to3_parse(py3_only, {TargetVersion.PY36})
1100 black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
1102 def test_get_features_used(self) -> None:
1103 node = black.lib2to3_parse("def f(*, arg): ...\n")
1104 self.assertEqual(black.get_features_used(node), set())
1105 node = black.lib2to3_parse("def f(*, arg,): ...\n")
1106 self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
1107 node = black.lib2to3_parse("f(*arg,)\n")
1109 black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
1111 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
1112 self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
1113 node = black.lib2to3_parse("123_456\n")
1114 self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
1115 node = black.lib2to3_parse("123456\n")
1116 self.assertEqual(black.get_features_used(node), set())
1117 source, expected = read_data("function")
1118 node = black.lib2to3_parse(source)
1119 expected_features = {
1120 Feature.TRAILING_COMMA_IN_CALL,
1121 Feature.TRAILING_COMMA_IN_DEF,
1124 self.assertEqual(black.get_features_used(node), expected_features)
1125 node = black.lib2to3_parse(expected)
1126 self.assertEqual(black.get_features_used(node), expected_features)
1127 source, expected = read_data("expression")
1128 node = black.lib2to3_parse(source)
1129 self.assertEqual(black.get_features_used(node), set())
1130 node = black.lib2to3_parse(expected)
1131 self.assertEqual(black.get_features_used(node), set())
1133 def test_get_future_imports(self) -> None:
1134 node = black.lib2to3_parse("\n")
1135 self.assertEqual(set(), black.get_future_imports(node))
1136 node = black.lib2to3_parse("from __future__ import black\n")
1137 self.assertEqual({"black"}, black.get_future_imports(node))
1138 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
1139 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1140 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
1141 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
1142 node = black.lib2to3_parse(
1143 "from __future__ import multiple\nfrom __future__ import imports\n"
1145 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1146 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
1147 self.assertEqual({"black"}, black.get_future_imports(node))
1148 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
1149 self.assertEqual({"black"}, black.get_future_imports(node))
1150 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
1151 self.assertEqual(set(), black.get_future_imports(node))
1152 node = black.lib2to3_parse("from some.module import black\n")
1153 self.assertEqual(set(), black.get_future_imports(node))
1154 node = black.lib2to3_parse(
1155 "from __future__ import unicode_literals as _unicode_literals"
1157 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
1158 node = black.lib2to3_parse(
1159 "from __future__ import unicode_literals as _lol, print"
1161 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
1163 def test_debug_visitor(self) -> None:
1164 source, _ = read_data("debug_visitor.py")
1165 expected, _ = read_data("debug_visitor.out")
1169 def out(msg: str, **kwargs: Any) -> None:
1170 out_lines.append(msg)
1172 def err(msg: str, **kwargs: Any) -> None:
1173 err_lines.append(msg)
1175 with patch("black.out", out), patch("black.err", err):
1176 black.DebugVisitor.show(source)
1177 actual = "\n".join(out_lines) + "\n"
1179 if expected != actual:
1180 log_name = black.dump_to_file(*out_lines)
1184 f"AST print out is different. Actual version dumped to {log_name}",
1187 def test_format_file_contents(self) -> None:
1189 mode = black.FileMode()
1190 with self.assertRaises(black.NothingChanged):
1191 black.format_file_contents(empty, mode=mode, fast=False)
1193 with self.assertRaises(black.NothingChanged):
1194 black.format_file_contents(just_nl, mode=mode, fast=False)
1195 same = "j = [1, 2, 3]\n"
1196 with self.assertRaises(black.NothingChanged):
1197 black.format_file_contents(same, mode=mode, fast=False)
1198 different = "j = [1,2,3]"
1200 actual = black.format_file_contents(different, mode=mode, fast=False)
1201 self.assertEqual(expected, actual)
1202 invalid = "return if you can"
1203 with self.assertRaises(black.InvalidInput) as e:
1204 black.format_file_contents(invalid, mode=mode, fast=False)
1205 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
1207 def test_endmarker(self) -> None:
1208 n = black.lib2to3_parse("\n")
1209 self.assertEqual(n.type, black.syms.file_input)
1210 self.assertEqual(len(n.children), 1)
1211 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
1213 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
1214 def test_assertFormatEqual(self) -> None:
1218 def out(msg: str, **kwargs: Any) -> None:
1219 out_lines.append(msg)
1221 def err(msg: str, **kwargs: Any) -> None:
1222 err_lines.append(msg)
1224 with patch("black.out", out), patch("black.err", err):
1225 with self.assertRaises(AssertionError):
1226 self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")
1228 out_str = "".join(out_lines)
1229 self.assertTrue("Expected tree:" in out_str)
1230 self.assertTrue("Actual tree:" in out_str)
1231 self.assertEqual("".join(err_lines), "")
1233 def test_cache_broken_file(self) -> None:
1234 mode = black.FileMode()
1235 with cache_dir() as workspace:
1236 cache_file = black.get_cache_file(mode)
1237 with cache_file.open("w") as fobj:
1238 fobj.write("this is not a pickle")
1239 self.assertEqual(black.read_cache(mode), {})
1240 src = (workspace / "test.py").resolve()
1241 with src.open("w") as fobj:
1242 fobj.write("print('hello')")
1243 self.invokeBlack([str(src)])
1244 cache = black.read_cache(mode)
1245 self.assertIn(src, cache)
1247 def test_cache_single_file_already_cached(self) -> None:
1248 mode = black.FileMode()
1249 with cache_dir() as workspace:
1250 src = (workspace / "test.py").resolve()
1251 with src.open("w") as fobj:
1252 fobj.write("print('hello')")
1253 black.write_cache({}, [src], mode)
1254 self.invokeBlack([str(src)])
1255 with src.open("r") as fobj:
1256 self.assertEqual(fobj.read(), "print('hello')")
1258 @event_loop(close=False)
1259 def test_cache_multiple_files(self) -> None:
1260 mode = black.FileMode()
1261 with cache_dir() as workspace, patch(
1262 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1264 one = (workspace / "one.py").resolve()
1265 with one.open("w") as fobj:
1266 fobj.write("print('hello')")
1267 two = (workspace / "two.py").resolve()
1268 with two.open("w") as fobj:
1269 fobj.write("print('hello')")
1270 black.write_cache({}, [one], mode)
1271 self.invokeBlack([str(workspace)])
1272 with one.open("r") as fobj:
1273 self.assertEqual(fobj.read(), "print('hello')")
1274 with two.open("r") as fobj:
1275 self.assertEqual(fobj.read(), 'print("hello")\n')
1276 cache = black.read_cache(mode)
1277 self.assertIn(one, cache)
1278 self.assertIn(two, cache)
1280 def test_no_cache_when_writeback_diff(self) -> None:
1281 mode = black.FileMode()
1282 with cache_dir() as workspace:
1283 src = (workspace / "test.py").resolve()
1284 with src.open("w") as fobj:
1285 fobj.write("print('hello')")
1286 self.invokeBlack([str(src), "--diff"])
1287 cache_file = black.get_cache_file(mode)
1288 self.assertFalse(cache_file.exists())
1290 def test_no_cache_when_stdin(self) -> None:
1291 mode = black.FileMode()
1293 result = CliRunner().invoke(
1294 black.main, ["-"], input=BytesIO(b"print('hello')")
1296 self.assertEqual(result.exit_code, 0)
1297 cache_file = black.get_cache_file(mode)
1298 self.assertFalse(cache_file.exists())
1300 def test_read_cache_no_cachefile(self) -> None:
1301 mode = black.FileMode()
1303 self.assertEqual(black.read_cache(mode), {})
1305 def test_write_cache_read_cache(self) -> None:
1306 mode = black.FileMode()
1307 with cache_dir() as workspace:
1308 src = (workspace / "test.py").resolve()
1310 black.write_cache({}, [src], mode)
1311 cache = black.read_cache(mode)
1312 self.assertIn(src, cache)
1313 self.assertEqual(cache[src], black.get_cache_info(src))
1315 def test_filter_cached(self) -> None:
1316 with TemporaryDirectory() as workspace:
1317 path = Path(workspace)
1318 uncached = (path / "uncached").resolve()
1319 cached = (path / "cached").resolve()
1320 cached_but_changed = (path / "changed").resolve()
1323 cached_but_changed.touch()
1324 cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
1325 todo, done = black.filter_cached(
1326 cache, {uncached, cached, cached_but_changed}
1328 self.assertEqual(todo, {uncached, cached_but_changed})
1329 self.assertEqual(done, {cached})
1331 def test_write_cache_creates_directory_if_needed(self) -> None:
1332 mode = black.FileMode()
1333 with cache_dir(exists=False) as workspace:
1334 self.assertFalse(workspace.exists())
1335 black.write_cache({}, [], mode)
1336 self.assertTrue(workspace.exists())
1338 @event_loop(close=False)
1339 def test_failed_formatting_does_not_get_cached(self) -> None:
1340 mode = black.FileMode()
1341 with cache_dir() as workspace, patch(
1342 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1344 failing = (workspace / "failing.py").resolve()
1345 with failing.open("w") as fobj:
1346 fobj.write("not actually python")
1347 clean = (workspace / "clean.py").resolve()
1348 with clean.open("w") as fobj:
1349 fobj.write('print("hello")\n')
1350 self.invokeBlack([str(workspace)], exit_code=123)
1351 cache = black.read_cache(mode)
1352 self.assertNotIn(failing, cache)
1353 self.assertIn(clean, cache)
1355 def test_write_cache_write_fail(self) -> None:
1356 mode = black.FileMode()
1357 with cache_dir(), patch.object(Path, "open") as mock:
1358 mock.side_effect = OSError
1359 black.write_cache({}, [], mode)
1361 @patch("black.ProcessPoolExecutor", autospec=True)
1362 def test_works_in_mono_process_only_environment(self, executor: MagicMock) -> None:
1363 self.skipTest("this test fails when run with the rest of the suite")
1364 executor.side_effect = OSError()
1365 with cache_dir() as workspace:
1367 (workspace / "one.py").resolve(),
1368 (workspace / "two.py").resolve(),
1370 f.write_text("print('hello')")
1371 self.invokeBlack([str(workspace)])
1373 @event_loop(close=False)
1374 def test_check_diff_use_together(self) -> None:
1376 # Files which will be reformatted.
1377 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1378 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1379 # Files which will not be reformatted.
1380 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1381 self.invokeBlack([str(src2), "--diff", "--check"])
1382 # Multi file command.
1383 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1385 def test_no_files(self) -> None:
1387 # Without an argument, black exits with error code 0.
1388 self.invokeBlack([])
1390 def test_broken_symlink(self) -> None:
1391 with cache_dir() as workspace:
1392 symlink = workspace / "broken_link.py"
1394 symlink.symlink_to("nonexistent.py")
1395 except OSError as e:
1396 self.skipTest(f"Can't create symlinks: {e}")
1397 self.invokeBlack([str(workspace.resolve())])
1399 def test_read_cache_line_lengths(self) -> None:
1400 mode = black.FileMode()
1401 short_mode = black.FileMode(line_length=1)
1402 with cache_dir() as workspace:
1403 path = (workspace / "file.py").resolve()
1405 black.write_cache({}, [path], mode)
1406 one = black.read_cache(mode)
1407 self.assertIn(path, one)
1408 two = black.read_cache(short_mode)
1409 self.assertNotIn(path, two)
1411 def test_tricky_unicode_symbols(self) -> None:
1412 source, expected = read_data("tricky_unicode_symbols")
1414 self.assertFormatEqual(expected, actual)
1415 black.assert_equivalent(source, actual)
1416 black.assert_stable(source, actual, black.FileMode())
1418 def test_single_file_force_pyi(self) -> None:
1419 reg_mode = black.FileMode()
1420 pyi_mode = black.FileMode(is_pyi=True)
1421 contents, expected = read_data("force_pyi")
1422 with cache_dir() as workspace:
1423 path = (workspace / "file.py").resolve()
1424 with open(path, "w") as fh:
1426 self.invokeBlack([str(path), "--pyi"])
1427 with open(path, "r") as fh:
1429 # verify cache with --pyi is separate
1430 pyi_cache = black.read_cache(pyi_mode)
1431 self.assertIn(path, pyi_cache)
1432 normal_cache = black.read_cache(reg_mode)
1433 self.assertNotIn(path, normal_cache)
1434 self.assertEqual(actual, expected)
1436 @event_loop(close=False)
1437 def test_multi_file_force_pyi(self) -> None:
1438 reg_mode = black.FileMode()
1439 pyi_mode = black.FileMode(is_pyi=True)
1440 contents, expected = read_data("force_pyi")
1441 with cache_dir() as workspace:
1443 (workspace / "file1.py").resolve(),
1444 (workspace / "file2.py").resolve(),
1447 with open(path, "w") as fh:
1449 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1451 with open(path, "r") as fh:
1453 self.assertEqual(actual, expected)
1454 # verify cache with --pyi is separate
1455 pyi_cache = black.read_cache(pyi_mode)
1456 normal_cache = black.read_cache(reg_mode)
1458 self.assertIn(path, pyi_cache)
1459 self.assertNotIn(path, normal_cache)
1461 def test_pipe_force_pyi(self) -> None:
1462 source, expected = read_data("force_pyi")
1463 result = CliRunner().invoke(
1464 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1466 self.assertEqual(result.exit_code, 0)
1467 actual = result.output
1468 self.assertFormatEqual(actual, expected)
1470 def test_single_file_force_py36(self) -> None:
1471 reg_mode = black.FileMode()
1472 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1473 source, expected = read_data("force_py36")
1474 with cache_dir() as workspace:
1475 path = (workspace / "file.py").resolve()
1476 with open(path, "w") as fh:
1478 self.invokeBlack([str(path), *PY36_ARGS])
1479 with open(path, "r") as fh:
1481 # verify cache with --target-version is separate
1482 py36_cache = black.read_cache(py36_mode)
1483 self.assertIn(path, py36_cache)
1484 normal_cache = black.read_cache(reg_mode)
1485 self.assertNotIn(path, normal_cache)
1486 self.assertEqual(actual, expected)
1488 @event_loop(close=False)
1489 def test_multi_file_force_py36(self) -> None:
1490 reg_mode = black.FileMode()
1491 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1492 source, expected = read_data("force_py36")
1493 with cache_dir() as workspace:
1495 (workspace / "file1.py").resolve(),
1496 (workspace / "file2.py").resolve(),
1499 with open(path, "w") as fh:
1501 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1503 with open(path, "r") as fh:
1505 self.assertEqual(actual, expected)
1506 # verify cache with --target-version is separate
1507 pyi_cache = black.read_cache(py36_mode)
1508 normal_cache = black.read_cache(reg_mode)
1510 self.assertIn(path, pyi_cache)
1511 self.assertNotIn(path, normal_cache)
1513 def test_collections(self) -> None:
1514 source, expected = read_data("collections")
1516 self.assertFormatEqual(expected, actual)
1517 black.assert_equivalent(source, actual)
1518 black.assert_stable(source, actual, black.FileMode())
1520 def test_pipe_force_py36(self) -> None:
1521 source, expected = read_data("force_py36")
1522 result = CliRunner().invoke(
1524 ["-", "-q", "--target-version=py36"],
1525 input=BytesIO(source.encode("utf8")),
1527 self.assertEqual(result.exit_code, 0)
1528 actual = result.output
1529 self.assertFormatEqual(actual, expected)
1531 def test_include_exclude(self) -> None:
1532 path = THIS_DIR / "data" / "include_exclude_tests"
1533 include = re.compile(r"\.pyi?$")
1534 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1535 report = black.Report()
1536 gitignore = PathSpec.from_lines("gitwildmatch", [])
1537 sources: List[Path] = []
1539 Path(path / "b/dont_exclude/a.py"),
1540 Path(path / "b/dont_exclude/a.pyi"),
1542 this_abs = THIS_DIR.resolve()
1544 black.gen_python_files(
1545 path.iterdir(), this_abs, include, [exclude], report, gitignore
1548 self.assertEqual(sorted(expected), sorted(sources))
1550 def test_gitignore_exclude(self) -> None:
1551 path = THIS_DIR / "data" / "include_exclude_tests"
1552 include = re.compile(r"\.pyi?$")
1553 exclude = re.compile(r"")
1554 report = black.Report()
1555 gitignore = PathSpec.from_lines(
1556 "gitwildmatch", ["exclude/", ".definitely_exclude"]
1558 sources: List[Path] = []
1560 Path(path / "b/dont_exclude/a.py"),
1561 Path(path / "b/dont_exclude/a.pyi"),
1563 this_abs = THIS_DIR.resolve()
1565 black.gen_python_files(
1566 path.iterdir(), this_abs, include, [exclude], report, gitignore
1569 self.assertEqual(sorted(expected), sorted(sources))
1571 def test_empty_include(self) -> None:
1572 path = THIS_DIR / "data" / "include_exclude_tests"
1573 report = black.Report()
1574 gitignore = PathSpec.from_lines("gitwildmatch", [])
1575 empty = re.compile(r"")
1576 sources: List[Path] = []
1578 Path(path / "b/exclude/a.pie"),
1579 Path(path / "b/exclude/a.py"),
1580 Path(path / "b/exclude/a.pyi"),
1581 Path(path / "b/dont_exclude/a.pie"),
1582 Path(path / "b/dont_exclude/a.py"),
1583 Path(path / "b/dont_exclude/a.pyi"),
1584 Path(path / "b/.definitely_exclude/a.pie"),
1585 Path(path / "b/.definitely_exclude/a.py"),
1586 Path(path / "b/.definitely_exclude/a.pyi"),
1588 this_abs = THIS_DIR.resolve()
1590 black.gen_python_files(
1594 [re.compile(black.DEFAULT_EXCLUDES)],
1599 self.assertEqual(sorted(expected), sorted(sources))
1601 def test_empty_exclude(self) -> None:
1602 path = THIS_DIR / "data" / "include_exclude_tests"
1603 report = black.Report()
1604 gitignore = PathSpec.from_lines("gitwildmatch", [])
1605 empty = re.compile(r"")
1606 sources: List[Path] = []
1608 Path(path / "b/dont_exclude/a.py"),
1609 Path(path / "b/dont_exclude/a.pyi"),
1610 Path(path / "b/exclude/a.py"),
1611 Path(path / "b/exclude/a.pyi"),
1612 Path(path / "b/.definitely_exclude/a.py"),
1613 Path(path / "b/.definitely_exclude/a.pyi"),
1615 this_abs = THIS_DIR.resolve()
1617 black.gen_python_files(
1620 re.compile(black.DEFAULT_INCLUDES),
1626 self.assertEqual(sorted(expected), sorted(sources))
1628 def test_invalid_include_exclude(self) -> None:
1629 for option in ["--include", "--exclude"]:
1630 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1632 def test_preserves_line_endings(self) -> None:
1633 with TemporaryDirectory() as workspace:
1634 test_file = Path(workspace) / "test.py"
1635 for nl in ["\n", "\r\n"]:
1636 contents = nl.join(["def f( ):", " pass"])
1637 test_file.write_bytes(contents.encode())
1638 ff(test_file, write_back=black.WriteBack.YES)
1639 updated_contents: bytes = test_file.read_bytes()
1640 self.assertIn(nl.encode(), updated_contents)
1642 self.assertNotIn(b"\r\n", updated_contents)
1644 def test_preserves_line_endings_via_stdin(self) -> None:
1645 for nl in ["\n", "\r\n"]:
1646 contents = nl.join(["def f( ):", " pass"])
1647 runner = BlackRunner()
1648 result = runner.invoke(
1649 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1651 self.assertEqual(result.exit_code, 0)
1652 output = runner.stdout_bytes
1653 self.assertIn(nl.encode("utf8"), output)
1655 self.assertNotIn(b"\r\n", output)
1657 def test_assert_equivalent_different_asts(self) -> None:
1658 with self.assertRaises(AssertionError):
1659 black.assert_equivalent("{}", "None")
1661 def test_symlink_out_of_root_directory(self) -> None:
1665 include = re.compile(black.DEFAULT_INCLUDES)
1666 exclude = re.compile(black.DEFAULT_EXCLUDES)
1667 report = black.Report()
1668 gitignore = PathSpec.from_lines("gitwildmatch", [])
1669 # `child` should behave like a symlink which resolved path is clearly
1670 # outside of the `root` directory.
1671 path.iterdir.return_value = [child]
1672 child.resolve.return_value = Path("/a/b/c")
1673 child.as_posix.return_value = "/a/b/c"
1674 child.is_symlink.return_value = True
1677 black.gen_python_files(
1678 path.iterdir(), root, include, exclude, report, gitignore
1681 except ValueError as ve:
1682 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1683 path.iterdir.assert_called_once()
1684 child.resolve.assert_called_once()
1685 child.is_symlink.assert_called_once()
1686 # `child` should behave like a strange file which resolved path is clearly
1687 # outside of the `root` directory.
1688 child.is_symlink.return_value = False
1689 with self.assertRaises(ValueError):
1691 black.gen_python_files(
1692 path.iterdir(), root, include, exclude, report, gitignore
1695 path.iterdir.assert_called()
1696 self.assertEqual(path.iterdir.call_count, 2)
1697 child.resolve.assert_called()
1698 self.assertEqual(child.resolve.call_count, 2)
1699 child.is_symlink.assert_called()
1700 self.assertEqual(child.is_symlink.call_count, 2)
1702 def test_shhh_click(self) -> None:
1704 from click import _unicodefun # type: ignore
1705 except ModuleNotFoundError:
1706 self.skipTest("Incompatible Click version")
1707 if not hasattr(_unicodefun, "_verify_python3_env"):
1708 self.skipTest("Incompatible Click version")
1709 # First, let's see if Click is crashing with a preferred ASCII charset.
1710 with patch("locale.getpreferredencoding") as gpe:
1711 gpe.return_value = "ASCII"
1712 with self.assertRaises(RuntimeError):
1713 _unicodefun._verify_python3_env()
1714 # Now, let's silence Click...
1716 # ...and confirm it's silent.
1717 with patch("locale.getpreferredencoding") as gpe:
1718 gpe.return_value = "ASCII"
1720 _unicodefun._verify_python3_env()
1721 except RuntimeError as re:
1722 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1724 def test_root_logger_not_used_directly(self) -> None:
1725 def fail(*args: Any, **kwargs: Any) -> None:
1726 self.fail("Record created with root logger")
1728 with patch.multiple(
1739 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1740 def test_blackd_main(self) -> None:
1741 with patch("blackd.web.run_app"):
1742 result = CliRunner().invoke(blackd.main, [])
1743 if result.exception is not None:
1744 raise result.exception
1745 self.assertEqual(result.exit_code, 0)
1747 def test_invalid_config_return_code(self) -> None:
1748 tmp_file = Path(black.dump_to_file())
1750 tmp_config = Path(black.dump_to_file())
1752 args = ["--config", str(tmp_config), str(tmp_file)]
1753 self.invokeBlack(args, exit_code=2, ignore_config=False)
1758 class BlackDTestCase(AioHTTPTestCase):
1759 async def get_application(self) -> web.Application:
1760 return blackd.make_app()
1762 # TODO: remove these decorators once the below is released
1763 # https://github.com/aio-libs/aiohttp/pull/3727
1764 @skip_if_exception("ClientOSError")
1765 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1767 async def test_blackd_request_needs_formatting(self) -> None:
1768 response = await self.client.post("/", data=b"print('hello world')")
1769 self.assertEqual(response.status, 200)
1770 self.assertEqual(response.charset, "utf8")
1771 self.assertEqual(await response.read(), b'print("hello world")\n')
1773 @skip_if_exception("ClientOSError")
1774 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1776 async def test_blackd_request_no_change(self) -> None:
1777 response = await self.client.post("/", data=b'print("hello world")\n')
1778 self.assertEqual(response.status, 204)
1779 self.assertEqual(await response.read(), b"")
1781 @skip_if_exception("ClientOSError")
1782 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1784 async def test_blackd_request_syntax_error(self) -> None:
1785 response = await self.client.post("/", data=b"what even ( is")
1786 self.assertEqual(response.status, 400)
1787 content = await response.text()
1789 content.startswith("Cannot parse"),
1790 msg=f"Expected error to start with 'Cannot parse', got {repr(content)}",
1793 @skip_if_exception("ClientOSError")
1794 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1796 async def test_blackd_unsupported_version(self) -> None:
1797 response = await self.client.post(
1798 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "2"}
1800 self.assertEqual(response.status, 501)
1802 @skip_if_exception("ClientOSError")
1803 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1805 async def test_blackd_supported_version(self) -> None:
1806 response = await self.client.post(
1807 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "1"}
1809 self.assertEqual(response.status, 200)
1811 @skip_if_exception("ClientOSError")
1812 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1814 async def test_blackd_invalid_python_variant(self) -> None:
1815 async def check(header_value: str, expected_status: int = 400) -> None:
1816 response = await self.client.post(
1817 "/", data=b"what", headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1819 self.assertEqual(response.status, expected_status)
1822 await check("ruby3.5")
1823 await check("pyi3.6")
1824 await check("py1.5")
1826 await check("py2.8")
1828 await check("pypy3.0")
1829 await check("jython3.4")
1831 @skip_if_exception("ClientOSError")
1832 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1834 async def test_blackd_pyi(self) -> None:
1835 source, expected = read_data("stub.pyi")
1836 response = await self.client.post(
1837 "/", data=source, headers={blackd.PYTHON_VARIANT_HEADER: "pyi"}
1839 self.assertEqual(response.status, 200)
1840 self.assertEqual(await response.text(), expected)
1842 @skip_if_exception("ClientOSError")
1843 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1845 async def test_blackd_diff(self) -> None:
1846 diff_header = re.compile(
1847 rf"(In|Out)\t\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
1850 source, _ = read_data("blackd_diff.py")
1851 expected, _ = read_data("blackd_diff.diff")
1853 response = await self.client.post(
1854 "/", data=source, headers={blackd.DIFF_HEADER: "true"}
1856 self.assertEqual(response.status, 200)
1858 actual = await response.text()
1859 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
1860 self.assertEqual(actual, expected)
1862 @skip_if_exception("ClientOSError")
1863 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1865 async def test_blackd_python_variant(self) -> None:
1868 " and_has_a_bunch_of,\n"
1869 " very_long_arguments_too,\n"
1870 " and_lots_of_them_as_well_lol,\n"
1871 " **and_very_long_keyword_arguments\n"
1876 async def check(header_value: str, expected_status: int) -> None:
1877 response = await self.client.post(
1878 "/", data=code, headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1881 response.status, expected_status, msg=await response.text()
1884 await check("3.6", 200)
1885 await check("py3.6", 200)
1886 await check("3.6,3.7", 200)
1887 await check("3.6,py3.7", 200)
1888 await check("py36,py37", 200)
1889 await check("36", 200)
1890 await check("3.6.4", 200)
1892 await check("2", 204)
1893 await check("2.7", 204)
1894 await check("py2.7", 204)
1895 await check("3.4", 204)
1896 await check("py3.4", 204)
1897 await check("py34,py36", 204)
1898 await check("34", 204)
1900 @skip_if_exception("ClientOSError")
1901 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1903 async def test_blackd_line_length(self) -> None:
1904 response = await self.client.post(
1905 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "7"}
1907 self.assertEqual(response.status, 200)
1909 @skip_if_exception("ClientOSError")
1910 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1912 async def test_blackd_invalid_line_length(self) -> None:
1913 response = await self.client.post(
1914 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "NaN"}
1916 self.assertEqual(response.status, 400)
1918 @skip_if_exception("ClientOSError")
1919 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1921 async def test_blackd_response_black_version_header(self) -> None:
1922 response = await self.client.post("/")
1923 self.assertIsNotNone(response.headers.get(blackd.BLACK_VERSION_HEADER))
1926 if __name__ == "__main__":
1927 unittest.main(module="test_black")