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 # Import other test classes
35 from .test_primer import PrimerCLITests # noqa: F401
38 ff = partial(black.format_file_in_place, mode=black.FileMode(), fast=True)
39 fs = partial(black.format_str, mode=black.FileMode())
40 THIS_FILE = Path(__file__)
41 THIS_DIR = THIS_FILE.parent
42 PROJECT_ROOT = THIS_DIR.parent
43 DETERMINISTIC_HEADER = "[Deterministic header]"
44 EMPTY_LINE = "# EMPTY LINE WITH WHITESPACE" + " (this comment will be removed)"
46 f"--target-version={version.name.lower()}" for version in black.PY36_VERSIONS
52 def dump_to_stderr(*output: str) -> str:
53 return "\n" + "\n".join(output) + "\n"
56 def read_data(name: str, data: bool = True) -> Tuple[str, str]:
57 """read_data('test_name') -> 'input', 'output'"""
58 if not name.endswith((".py", ".pyi", ".out", ".diff")):
60 _input: List[str] = []
61 _output: List[str] = []
62 base_dir = THIS_DIR / "data" if data else PROJECT_ROOT
63 with open(base_dir / name, "r", encoding="utf8") as test:
64 lines = test.readlines()
67 line = line.replace(EMPTY_LINE, "")
68 if line.rstrip() == "# output":
73 if _input and not _output:
74 # If there's no output marker, treat the entire file as already pre-formatted.
76 return "".join(_input).strip() + "\n", "".join(_output).strip() + "\n"
80 def cache_dir(exists: bool = True) -> Iterator[Path]:
81 with TemporaryDirectory() as workspace:
82 cache_dir = Path(workspace)
84 cache_dir = cache_dir / "new"
85 with patch("black.CACHE_DIR", cache_dir):
90 def event_loop() -> Iterator[None]:
91 policy = asyncio.get_event_loop_policy()
92 loop = policy.new_event_loop()
93 asyncio.set_event_loop(loop)
102 def skip_if_exception(e: str) -> Iterator[None]:
105 except Exception as exc:
106 if exc.__class__.__name__ == e:
107 unittest.skip(f"Encountered expected exception {exc}, skipping")
112 class BlackRunner(CliRunner):
113 """Modify CliRunner so that stderr is not merged with stdout.
115 This is a hack that can be removed once we depend on Click 7.x"""
117 def __init__(self) -> None:
118 self.stderrbuf = BytesIO()
119 self.stdoutbuf = BytesIO()
120 self.stdout_bytes = b""
121 self.stderr_bytes = b""
125 def isolation(self, *args: Any, **kwargs: Any) -> Generator[BinaryIO, None, None]:
126 with super().isolation(*args, **kwargs) as output:
128 hold_stderr = sys.stderr
129 sys.stderr = TextIOWrapper(self.stderrbuf, encoding=self.charset)
132 self.stdout_bytes = sys.stdout.buffer.getvalue() # type: ignore
133 self.stderr_bytes = sys.stderr.buffer.getvalue() # type: ignore
134 sys.stderr = hold_stderr
137 class BlackTestCase(unittest.TestCase):
140 def assertFormatEqual(self, expected: str, actual: str) -> None:
141 if actual != expected and not os.environ.get("SKIP_AST_PRINT"):
142 bdv: black.DebugVisitor[Any]
143 black.out("Expected tree:", fg="green")
145 exp_node = black.lib2to3_parse(expected)
146 bdv = black.DebugVisitor()
147 list(bdv.visit(exp_node))
148 except Exception as ve:
150 black.out("Actual tree:", fg="red")
152 exp_node = black.lib2to3_parse(actual)
153 bdv = black.DebugVisitor()
154 list(bdv.visit(exp_node))
155 except Exception as ve:
157 self.assertEqual(expected, actual)
160 self, args: List[str], exit_code: int = 0, ignore_config: bool = True
162 runner = BlackRunner()
164 args = ["--verbose", "--config", str(THIS_DIR / "empty.toml"), *args]
165 result = runner.invoke(black.main, args)
170 f"Failed with args: {args}\n"
171 f"stdout: {runner.stdout_bytes.decode()!r}\n"
172 f"stderr: {runner.stderr_bytes.decode()!r}\n"
173 f"exception: {result.exception}"
177 @patch("black.dump_to_file", dump_to_stderr)
178 def checkSourceFile(self, name: str) -> None:
179 path = THIS_DIR.parent / name
180 source, expected = read_data(str(path), data=False)
182 self.assertFormatEqual(expected, actual)
183 black.assert_equivalent(source, actual)
184 black.assert_stable(source, actual, black.FileMode())
185 self.assertFalse(ff(path))
187 @patch("black.dump_to_file", dump_to_stderr)
188 def test_empty(self) -> None:
189 source = expected = ""
191 self.assertFormatEqual(expected, actual)
192 black.assert_equivalent(source, actual)
193 black.assert_stable(source, actual, black.FileMode())
195 def test_empty_ff(self) -> None:
197 tmp_file = Path(black.dump_to_file())
199 self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES))
200 with open(tmp_file, encoding="utf8") as f:
204 self.assertFormatEqual(expected, actual)
206 def test_self(self) -> None:
207 self.checkSourceFile("tests/test_black.py")
209 def test_black(self) -> None:
210 self.checkSourceFile("src/black/__init__.py")
212 def test_pygram(self) -> None:
213 self.checkSourceFile("src/blib2to3/pygram.py")
215 def test_pytree(self) -> None:
216 self.checkSourceFile("src/blib2to3/pytree.py")
218 def test_conv(self) -> None:
219 self.checkSourceFile("src/blib2to3/pgen2/conv.py")
221 def test_driver(self) -> None:
222 self.checkSourceFile("src/blib2to3/pgen2/driver.py")
224 def test_grammar(self) -> None:
225 self.checkSourceFile("src/blib2to3/pgen2/grammar.py")
227 def test_literals(self) -> None:
228 self.checkSourceFile("src/blib2to3/pgen2/literals.py")
230 def test_parse(self) -> None:
231 self.checkSourceFile("src/blib2to3/pgen2/parse.py")
233 def test_pgen(self) -> None:
234 self.checkSourceFile("src/blib2to3/pgen2/pgen.py")
236 def test_tokenize(self) -> None:
237 self.checkSourceFile("src/blib2to3/pgen2/tokenize.py")
239 def test_token(self) -> None:
240 self.checkSourceFile("src/blib2to3/pgen2/token.py")
242 def test_setup(self) -> None:
243 self.checkSourceFile("setup.py")
245 def test_piping(self) -> None:
246 source, expected = read_data("src/black/__init__", data=False)
247 result = BlackRunner().invoke(
249 ["-", "--fast", f"--line-length={black.DEFAULT_LINE_LENGTH}"],
250 input=BytesIO(source.encode("utf8")),
252 self.assertEqual(result.exit_code, 0)
253 self.assertFormatEqual(expected, result.output)
254 black.assert_equivalent(source, result.output)
255 black.assert_stable(source, result.output, black.FileMode())
257 def test_piping_diff(self) -> None:
258 diff_header = re.compile(
259 r"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d\d\d\d "
262 source, _ = read_data("expression.py")
263 expected, _ = read_data("expression.diff")
264 config = THIS_DIR / "data" / "empty_pyproject.toml"
268 f"--line-length={black.DEFAULT_LINE_LENGTH}",
270 f"--config={config}",
272 result = BlackRunner().invoke(
273 black.main, args, input=BytesIO(source.encode("utf8"))
275 self.assertEqual(result.exit_code, 0)
276 actual = diff_header.sub(DETERMINISTIC_HEADER, result.output)
277 actual = actual.rstrip() + "\n" # the diff output has a trailing space
278 self.assertEqual(expected, actual)
280 def test_piping_diff_with_color(self) -> None:
281 source, _ = read_data("expression.py")
282 config = THIS_DIR / "data" / "empty_pyproject.toml"
286 f"--line-length={black.DEFAULT_LINE_LENGTH}",
289 f"--config={config}",
291 result = BlackRunner().invoke(
292 black.main, args, input=BytesIO(source.encode("utf8"))
294 actual = result.output
295 # Again, the contents are checked in a different test, so only look for colors.
296 self.assertIn("\033[1;37m", actual)
297 self.assertIn("\033[36m", actual)
298 self.assertIn("\033[32m", actual)
299 self.assertIn("\033[31m", actual)
300 self.assertIn("\033[0m", actual)
302 @patch("black.dump_to_file", dump_to_stderr)
303 def test_function(self) -> None:
304 source, expected = read_data("function")
306 self.assertFormatEqual(expected, actual)
307 black.assert_equivalent(source, actual)
308 black.assert_stable(source, actual, black.FileMode())
310 @patch("black.dump_to_file", dump_to_stderr)
311 def test_function2(self) -> None:
312 source, expected = read_data("function2")
314 self.assertFormatEqual(expected, actual)
315 black.assert_equivalent(source, actual)
316 black.assert_stable(source, actual, black.FileMode())
318 @patch("black.dump_to_file", dump_to_stderr)
319 def test_function_trailing_comma(self) -> None:
320 source, expected = read_data("function_trailing_comma")
322 self.assertFormatEqual(expected, actual)
323 black.assert_equivalent(source, actual)
324 black.assert_stable(source, actual, black.FileMode())
326 @patch("black.dump_to_file", dump_to_stderr)
327 def test_expression(self) -> None:
328 source, expected = read_data("expression")
330 self.assertFormatEqual(expected, actual)
331 black.assert_equivalent(source, actual)
332 black.assert_stable(source, actual, black.FileMode())
334 @patch("black.dump_to_file", dump_to_stderr)
335 def test_pep_572(self) -> None:
336 source, expected = read_data("pep_572")
338 self.assertFormatEqual(expected, actual)
339 black.assert_stable(source, actual, black.FileMode())
340 if sys.version_info >= (3, 8):
341 black.assert_equivalent(source, actual)
343 def test_pep_572_version_detection(self) -> None:
344 source, _ = read_data("pep_572")
345 root = black.lib2to3_parse(source)
346 features = black.get_features_used(root)
347 self.assertIn(black.Feature.ASSIGNMENT_EXPRESSIONS, features)
348 versions = black.detect_target_versions(root)
349 self.assertIn(black.TargetVersion.PY38, versions)
351 def test_expression_ff(self) -> None:
352 source, expected = read_data("expression")
353 tmp_file = Path(black.dump_to_file(source))
355 self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
356 with open(tmp_file, encoding="utf8") as f:
360 self.assertFormatEqual(expected, actual)
361 with patch("black.dump_to_file", dump_to_stderr):
362 black.assert_equivalent(source, actual)
363 black.assert_stable(source, actual, black.FileMode())
365 def test_expression_diff(self) -> None:
366 source, _ = read_data("expression.py")
367 expected, _ = read_data("expression.diff")
368 tmp_file = Path(black.dump_to_file(source))
369 diff_header = re.compile(
370 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
371 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
374 result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
375 self.assertEqual(result.exit_code, 0)
378 actual = result.output
379 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
380 actual = actual.rstrip() + "\n" # the diff output has a trailing space
381 if expected != actual:
382 dump = black.dump_to_file(actual)
384 "Expected diff isn't equal to the actual. If you made changes to"
385 " expression.py and this is an anticipated difference, overwrite"
386 f" tests/data/expression.diff with {dump}"
388 self.assertEqual(expected, actual, msg)
390 def test_expression_diff_with_color(self) -> None:
391 source, _ = read_data("expression.py")
392 expected, _ = read_data("expression.diff")
393 tmp_file = Path(black.dump_to_file(source))
395 result = BlackRunner().invoke(
396 black.main, ["--diff", "--color", str(tmp_file)]
400 actual = result.output
401 # We check the contents of the diff in `test_expression_diff`. All
402 # we need to check here is that color codes exist in the result.
403 self.assertIn("\033[1;37m", actual)
404 self.assertIn("\033[36m", actual)
405 self.assertIn("\033[32m", actual)
406 self.assertIn("\033[31m", actual)
407 self.assertIn("\033[0m", actual)
409 @patch("black.dump_to_file", dump_to_stderr)
410 def test_fstring(self) -> None:
411 source, expected = read_data("fstring")
413 self.assertFormatEqual(expected, actual)
414 black.assert_equivalent(source, actual)
415 black.assert_stable(source, actual, black.FileMode())
417 @patch("black.dump_to_file", dump_to_stderr)
418 def test_pep_570(self) -> None:
419 source, expected = read_data("pep_570")
421 self.assertFormatEqual(expected, actual)
422 black.assert_stable(source, actual, black.FileMode())
423 if sys.version_info >= (3, 8):
424 black.assert_equivalent(source, actual)
426 def test_detect_pos_only_arguments(self) -> None:
427 source, _ = read_data("pep_570")
428 root = black.lib2to3_parse(source)
429 features = black.get_features_used(root)
430 self.assertIn(black.Feature.POS_ONLY_ARGUMENTS, features)
431 versions = black.detect_target_versions(root)
432 self.assertIn(black.TargetVersion.PY38, versions)
434 @patch("black.dump_to_file", dump_to_stderr)
435 def test_string_quotes(self) -> None:
436 source, expected = read_data("string_quotes")
438 self.assertFormatEqual(expected, actual)
439 black.assert_equivalent(source, actual)
440 black.assert_stable(source, actual, black.FileMode())
441 mode = black.FileMode(string_normalization=False)
442 not_normalized = fs(source, mode=mode)
443 self.assertFormatEqual(source.replace("\\\n", ""), not_normalized)
444 black.assert_equivalent(source, not_normalized)
445 black.assert_stable(source, not_normalized, mode=mode)
447 @patch("black.dump_to_file", dump_to_stderr)
448 def test_docstring(self) -> None:
449 source, expected = read_data("docstring")
451 self.assertFormatEqual(expected, actual)
452 black.assert_equivalent(source, actual)
453 black.assert_stable(source, actual, black.FileMode())
455 def test_long_strings(self) -> None:
456 """Tests for splitting long strings."""
457 source, expected = read_data("long_strings")
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__edge_case(self) -> None:
465 """Edge-case tests for splitting long strings."""
466 source, expected = read_data("long_strings__edge_case")
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_long_strings__regression(self) -> None:
474 """Regression tests for splitting long strings."""
475 source, expected = read_data("long_strings__regression")
477 self.assertFormatEqual(expected, actual)
478 black.assert_equivalent(source, actual)
479 black.assert_stable(source, actual, black.FileMode())
481 @patch("black.dump_to_file", dump_to_stderr)
482 def test_slices(self) -> None:
483 source, expected = read_data("slices")
485 self.assertFormatEqual(expected, actual)
486 black.assert_equivalent(source, actual)
487 black.assert_stable(source, actual, black.FileMode())
489 @patch("black.dump_to_file", dump_to_stderr)
490 def test_comments(self) -> None:
491 source, expected = read_data("comments")
493 self.assertFormatEqual(expected, actual)
494 black.assert_equivalent(source, actual)
495 black.assert_stable(source, actual, black.FileMode())
497 @patch("black.dump_to_file", dump_to_stderr)
498 def test_comments2(self) -> None:
499 source, expected = read_data("comments2")
501 self.assertFormatEqual(expected, actual)
502 black.assert_equivalent(source, actual)
503 black.assert_stable(source, actual, black.FileMode())
505 @patch("black.dump_to_file", dump_to_stderr)
506 def test_comments3(self) -> None:
507 source, expected = read_data("comments3")
509 self.assertFormatEqual(expected, actual)
510 black.assert_equivalent(source, actual)
511 black.assert_stable(source, actual, black.FileMode())
513 @patch("black.dump_to_file", dump_to_stderr)
514 def test_comments4(self) -> None:
515 source, expected = read_data("comments4")
517 self.assertFormatEqual(expected, actual)
518 black.assert_equivalent(source, actual)
519 black.assert_stable(source, actual, black.FileMode())
521 @patch("black.dump_to_file", dump_to_stderr)
522 def test_comments5(self) -> None:
523 source, expected = read_data("comments5")
525 self.assertFormatEqual(expected, actual)
526 black.assert_equivalent(source, actual)
527 black.assert_stable(source, actual, black.FileMode())
529 @patch("black.dump_to_file", dump_to_stderr)
530 def test_comments6(self) -> None:
531 source, expected = read_data("comments6")
533 self.assertFormatEqual(expected, actual)
534 black.assert_equivalent(source, actual)
535 black.assert_stable(source, actual, black.FileMode())
537 @patch("black.dump_to_file", dump_to_stderr)
538 def test_comments7(self) -> None:
539 source, expected = read_data("comments7")
541 self.assertFormatEqual(expected, actual)
542 black.assert_equivalent(source, actual)
543 black.assert_stable(source, actual, black.FileMode())
545 @patch("black.dump_to_file", dump_to_stderr)
546 def test_comment_after_escaped_newline(self) -> None:
547 source, expected = read_data("comment_after_escaped_newline")
549 self.assertFormatEqual(expected, actual)
550 black.assert_equivalent(source, actual)
551 black.assert_stable(source, actual, black.FileMode())
553 @patch("black.dump_to_file", dump_to_stderr)
554 def test_cantfit(self) -> None:
555 source, expected = read_data("cantfit")
557 self.assertFormatEqual(expected, actual)
558 black.assert_equivalent(source, actual)
559 black.assert_stable(source, actual, black.FileMode())
561 @patch("black.dump_to_file", dump_to_stderr)
562 def test_import_spacing(self) -> None:
563 source, expected = read_data("import_spacing")
565 self.assertFormatEqual(expected, actual)
566 black.assert_equivalent(source, actual)
567 black.assert_stable(source, actual, black.FileMode())
569 @patch("black.dump_to_file", dump_to_stderr)
570 def test_composition(self) -> None:
571 source, expected = read_data("composition")
573 self.assertFormatEqual(expected, actual)
574 black.assert_equivalent(source, actual)
575 black.assert_stable(source, actual, black.FileMode())
577 @patch("black.dump_to_file", dump_to_stderr)
578 def test_empty_lines(self) -> None:
579 source, expected = read_data("empty_lines")
581 self.assertFormatEqual(expected, actual)
582 black.assert_equivalent(source, actual)
583 black.assert_stable(source, actual, black.FileMode())
585 @patch("black.dump_to_file", dump_to_stderr)
586 def test_remove_parens(self) -> None:
587 source, expected = read_data("remove_parens")
589 self.assertFormatEqual(expected, actual)
590 black.assert_equivalent(source, actual)
591 black.assert_stable(source, actual, black.FileMode())
593 @patch("black.dump_to_file", dump_to_stderr)
594 def test_string_prefixes(self) -> None:
595 source, expected = read_data("string_prefixes")
597 self.assertFormatEqual(expected, actual)
598 black.assert_equivalent(source, actual)
599 black.assert_stable(source, actual, black.FileMode())
601 @patch("black.dump_to_file", dump_to_stderr)
602 def test_numeric_literals(self) -> None:
603 source, expected = read_data("numeric_literals")
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_ignoring_underscores(self) -> None:
612 source, expected = read_data("numeric_literals_skip_underscores")
613 mode = black.FileMode(target_versions=black.PY36_VERSIONS)
614 actual = fs(source, mode=mode)
615 self.assertFormatEqual(expected, actual)
616 black.assert_equivalent(source, actual)
617 black.assert_stable(source, actual, mode)
619 @patch("black.dump_to_file", dump_to_stderr)
620 def test_numeric_literals_py2(self) -> None:
621 source, expected = read_data("numeric_literals_py2")
623 self.assertFormatEqual(expected, actual)
624 black.assert_stable(source, actual, black.FileMode())
626 @patch("black.dump_to_file", dump_to_stderr)
627 def test_python2(self) -> None:
628 source, expected = read_data("python2")
630 self.assertFormatEqual(expected, actual)
631 black.assert_equivalent(source, actual)
632 black.assert_stable(source, actual, black.FileMode())
634 @patch("black.dump_to_file", dump_to_stderr)
635 def test_python2_print_function(self) -> None:
636 source, expected = read_data("python2_print_function")
637 mode = black.FileMode(target_versions={TargetVersion.PY27})
638 actual = fs(source, mode=mode)
639 self.assertFormatEqual(expected, actual)
640 black.assert_equivalent(source, actual)
641 black.assert_stable(source, actual, mode)
643 @patch("black.dump_to_file", dump_to_stderr)
644 def test_python2_unicode_literals(self) -> None:
645 source, expected = read_data("python2_unicode_literals")
647 self.assertFormatEqual(expected, actual)
648 black.assert_equivalent(source, actual)
649 black.assert_stable(source, actual, black.FileMode())
651 @patch("black.dump_to_file", dump_to_stderr)
652 def test_stub(self) -> None:
653 mode = black.FileMode(is_pyi=True)
654 source, expected = read_data("stub.pyi")
655 actual = fs(source, mode=mode)
656 self.assertFormatEqual(expected, actual)
657 black.assert_stable(source, actual, mode)
659 @patch("black.dump_to_file", dump_to_stderr)
660 def test_async_as_identifier(self) -> None:
661 source_path = (THIS_DIR / "data" / "async_as_identifier.py").resolve()
662 source, expected = read_data("async_as_identifier")
664 self.assertFormatEqual(expected, actual)
665 major, minor = sys.version_info[:2]
666 if major < 3 or (major <= 3 and minor < 7):
667 black.assert_equivalent(source, actual)
668 black.assert_stable(source, actual, black.FileMode())
669 # ensure black can parse this when the target is 3.6
670 self.invokeBlack([str(source_path), "--target-version", "py36"])
671 # but not on 3.7, because async/await is no longer an identifier
672 self.invokeBlack([str(source_path), "--target-version", "py37"], exit_code=123)
674 @patch("black.dump_to_file", dump_to_stderr)
675 def test_python37(self) -> None:
676 source_path = (THIS_DIR / "data" / "python37.py").resolve()
677 source, expected = read_data("python37")
679 self.assertFormatEqual(expected, actual)
680 major, minor = sys.version_info[:2]
681 if major > 3 or (major == 3 and minor >= 7):
682 black.assert_equivalent(source, actual)
683 black.assert_stable(source, actual, black.FileMode())
684 # ensure black can parse this when the target is 3.7
685 self.invokeBlack([str(source_path), "--target-version", "py37"])
686 # but not on 3.6, because we use async as a reserved keyword
687 self.invokeBlack([str(source_path), "--target-version", "py36"], exit_code=123)
689 @patch("black.dump_to_file", dump_to_stderr)
690 def test_python38(self) -> None:
691 source, expected = read_data("python38")
693 self.assertFormatEqual(expected, actual)
694 major, minor = sys.version_info[:2]
695 if major > 3 or (major == 3 and minor >= 8):
696 black.assert_equivalent(source, actual)
697 black.assert_stable(source, actual, black.FileMode())
699 @patch("black.dump_to_file", dump_to_stderr)
700 def test_fmtonoff(self) -> None:
701 source, expected = read_data("fmtonoff")
703 self.assertFormatEqual(expected, actual)
704 black.assert_equivalent(source, actual)
705 black.assert_stable(source, actual, black.FileMode())
707 @patch("black.dump_to_file", dump_to_stderr)
708 def test_fmtonoff2(self) -> None:
709 source, expected = read_data("fmtonoff2")
711 self.assertFormatEqual(expected, actual)
712 black.assert_equivalent(source, actual)
713 black.assert_stable(source, actual, black.FileMode())
715 @patch("black.dump_to_file", dump_to_stderr)
716 def test_fmtonoff3(self) -> None:
717 source, expected = read_data("fmtonoff3")
719 self.assertFormatEqual(expected, actual)
720 black.assert_equivalent(source, actual)
721 black.assert_stable(source, actual, black.FileMode())
723 @patch("black.dump_to_file", dump_to_stderr)
724 def test_fmtonoff4(self) -> None:
725 source, expected = read_data("fmtonoff4")
727 self.assertFormatEqual(expected, actual)
728 black.assert_equivalent(source, actual)
729 black.assert_stable(source, actual, black.FileMode())
731 @patch("black.dump_to_file", dump_to_stderr)
732 def test_remove_empty_parentheses_after_class(self) -> None:
733 source, expected = read_data("class_blank_parentheses")
735 self.assertFormatEqual(expected, actual)
736 black.assert_equivalent(source, actual)
737 black.assert_stable(source, actual, black.FileMode())
739 @patch("black.dump_to_file", dump_to_stderr)
740 def test_new_line_between_class_and_code(self) -> None:
741 source, expected = read_data("class_methods_new_line")
743 self.assertFormatEqual(expected, actual)
744 black.assert_equivalent(source, actual)
745 black.assert_stable(source, actual, black.FileMode())
747 @patch("black.dump_to_file", dump_to_stderr)
748 def test_bracket_match(self) -> None:
749 source, expected = read_data("bracketmatch")
751 self.assertFormatEqual(expected, actual)
752 black.assert_equivalent(source, actual)
753 black.assert_stable(source, actual, black.FileMode())
755 @patch("black.dump_to_file", dump_to_stderr)
756 def test_tuple_assign(self) -> None:
757 source, expected = read_data("tupleassign")
759 self.assertFormatEqual(expected, actual)
760 black.assert_equivalent(source, actual)
761 black.assert_stable(source, actual, black.FileMode())
763 @patch("black.dump_to_file", dump_to_stderr)
764 def test_beginning_backslash(self) -> None:
765 source, expected = read_data("beginning_backslash")
767 self.assertFormatEqual(expected, actual)
768 black.assert_equivalent(source, actual)
769 black.assert_stable(source, actual, black.FileMode())
771 def test_tab_comment_indentation(self) -> None:
772 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
773 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
774 self.assertFormatEqual(contents_spc, fs(contents_spc))
775 self.assertFormatEqual(contents_spc, fs(contents_tab))
777 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
778 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
779 self.assertFormatEqual(contents_spc, fs(contents_spc))
780 self.assertFormatEqual(contents_spc, fs(contents_tab))
782 # mixed tabs and spaces (valid Python 2 code)
783 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t# comment\n pass\n"
784 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
785 self.assertFormatEqual(contents_spc, fs(contents_spc))
786 self.assertFormatEqual(contents_spc, fs(contents_tab))
788 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t\t# comment\n pass\n"
789 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
790 self.assertFormatEqual(contents_spc, fs(contents_spc))
791 self.assertFormatEqual(contents_spc, fs(contents_tab))
793 def test_report_verbose(self) -> None:
794 report = black.Report(verbose=True)
798 def out(msg: str, **kwargs: Any) -> None:
799 out_lines.append(msg)
801 def err(msg: str, **kwargs: Any) -> None:
802 err_lines.append(msg)
804 with patch("black.out", out), patch("black.err", err):
805 report.done(Path("f1"), black.Changed.NO)
806 self.assertEqual(len(out_lines), 1)
807 self.assertEqual(len(err_lines), 0)
808 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
809 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
810 self.assertEqual(report.return_code, 0)
811 report.done(Path("f2"), black.Changed.YES)
812 self.assertEqual(len(out_lines), 2)
813 self.assertEqual(len(err_lines), 0)
814 self.assertEqual(out_lines[-1], "reformatted f2")
816 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
818 report.done(Path("f3"), black.Changed.CACHED)
819 self.assertEqual(len(out_lines), 3)
820 self.assertEqual(len(err_lines), 0)
822 out_lines[-1], "f3 wasn't modified on disk since last run."
825 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
827 self.assertEqual(report.return_code, 0)
829 self.assertEqual(report.return_code, 1)
831 report.failed(Path("e1"), "boom")
832 self.assertEqual(len(out_lines), 3)
833 self.assertEqual(len(err_lines), 1)
834 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
836 unstyle(str(report)),
837 "1 file reformatted, 2 files left unchanged, 1 file failed to"
840 self.assertEqual(report.return_code, 123)
841 report.done(Path("f3"), black.Changed.YES)
842 self.assertEqual(len(out_lines), 4)
843 self.assertEqual(len(err_lines), 1)
844 self.assertEqual(out_lines[-1], "reformatted f3")
846 unstyle(str(report)),
847 "2 files reformatted, 2 files left unchanged, 1 file failed to"
850 self.assertEqual(report.return_code, 123)
851 report.failed(Path("e2"), "boom")
852 self.assertEqual(len(out_lines), 4)
853 self.assertEqual(len(err_lines), 2)
854 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
856 unstyle(str(report)),
857 "2 files reformatted, 2 files left unchanged, 2 files failed to"
860 self.assertEqual(report.return_code, 123)
861 report.path_ignored(Path("wat"), "no match")
862 self.assertEqual(len(out_lines), 5)
863 self.assertEqual(len(err_lines), 2)
864 self.assertEqual(out_lines[-1], "wat ignored: no match")
866 unstyle(str(report)),
867 "2 files reformatted, 2 files left unchanged, 2 files failed to"
870 self.assertEqual(report.return_code, 123)
871 report.done(Path("f4"), black.Changed.NO)
872 self.assertEqual(len(out_lines), 6)
873 self.assertEqual(len(err_lines), 2)
874 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
876 unstyle(str(report)),
877 "2 files reformatted, 3 files left unchanged, 2 files failed to"
880 self.assertEqual(report.return_code, 123)
883 unstyle(str(report)),
884 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
885 " would fail to reformat.",
890 unstyle(str(report)),
891 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
892 " would fail to reformat.",
895 def test_report_quiet(self) -> None:
896 report = black.Report(quiet=True)
900 def out(msg: str, **kwargs: Any) -> None:
901 out_lines.append(msg)
903 def err(msg: str, **kwargs: Any) -> None:
904 err_lines.append(msg)
906 with patch("black.out", out), patch("black.err", err):
907 report.done(Path("f1"), black.Changed.NO)
908 self.assertEqual(len(out_lines), 0)
909 self.assertEqual(len(err_lines), 0)
910 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
911 self.assertEqual(report.return_code, 0)
912 report.done(Path("f2"), black.Changed.YES)
913 self.assertEqual(len(out_lines), 0)
914 self.assertEqual(len(err_lines), 0)
916 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
918 report.done(Path("f3"), black.Changed.CACHED)
919 self.assertEqual(len(out_lines), 0)
920 self.assertEqual(len(err_lines), 0)
922 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
924 self.assertEqual(report.return_code, 0)
926 self.assertEqual(report.return_code, 1)
928 report.failed(Path("e1"), "boom")
929 self.assertEqual(len(out_lines), 0)
930 self.assertEqual(len(err_lines), 1)
931 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
933 unstyle(str(report)),
934 "1 file reformatted, 2 files left unchanged, 1 file failed to"
937 self.assertEqual(report.return_code, 123)
938 report.done(Path("f3"), black.Changed.YES)
939 self.assertEqual(len(out_lines), 0)
940 self.assertEqual(len(err_lines), 1)
942 unstyle(str(report)),
943 "2 files reformatted, 2 files left unchanged, 1 file failed to"
946 self.assertEqual(report.return_code, 123)
947 report.failed(Path("e2"), "boom")
948 self.assertEqual(len(out_lines), 0)
949 self.assertEqual(len(err_lines), 2)
950 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
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.path_ignored(Path("wat"), "no match")
958 self.assertEqual(len(out_lines), 0)
959 self.assertEqual(len(err_lines), 2)
961 unstyle(str(report)),
962 "2 files reformatted, 2 files left unchanged, 2 files failed to"
965 self.assertEqual(report.return_code, 123)
966 report.done(Path("f4"), black.Changed.NO)
967 self.assertEqual(len(out_lines), 0)
968 self.assertEqual(len(err_lines), 2)
970 unstyle(str(report)),
971 "2 files reformatted, 3 files left unchanged, 2 files failed to"
974 self.assertEqual(report.return_code, 123)
977 unstyle(str(report)),
978 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
979 " would fail to reformat.",
984 unstyle(str(report)),
985 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
986 " would fail to reformat.",
989 def test_report_normal(self) -> None:
990 report = black.Report()
994 def out(msg: str, **kwargs: Any) -> None:
995 out_lines.append(msg)
997 def err(msg: str, **kwargs: Any) -> None:
998 err_lines.append(msg)
1000 with patch("black.out", out), patch("black.err", err):
1001 report.done(Path("f1"), black.Changed.NO)
1002 self.assertEqual(len(out_lines), 0)
1003 self.assertEqual(len(err_lines), 0)
1004 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
1005 self.assertEqual(report.return_code, 0)
1006 report.done(Path("f2"), black.Changed.YES)
1007 self.assertEqual(len(out_lines), 1)
1008 self.assertEqual(len(err_lines), 0)
1009 self.assertEqual(out_lines[-1], "reformatted f2")
1011 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
1013 report.done(Path("f3"), black.Changed.CACHED)
1014 self.assertEqual(len(out_lines), 1)
1015 self.assertEqual(len(err_lines), 0)
1016 self.assertEqual(out_lines[-1], "reformatted f2")
1018 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
1020 self.assertEqual(report.return_code, 0)
1022 self.assertEqual(report.return_code, 1)
1023 report.check = False
1024 report.failed(Path("e1"), "boom")
1025 self.assertEqual(len(out_lines), 1)
1026 self.assertEqual(len(err_lines), 1)
1027 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
1029 unstyle(str(report)),
1030 "1 file reformatted, 2 files left unchanged, 1 file failed to"
1033 self.assertEqual(report.return_code, 123)
1034 report.done(Path("f3"), black.Changed.YES)
1035 self.assertEqual(len(out_lines), 2)
1036 self.assertEqual(len(err_lines), 1)
1037 self.assertEqual(out_lines[-1], "reformatted f3")
1039 unstyle(str(report)),
1040 "2 files reformatted, 2 files left unchanged, 1 file failed to"
1043 self.assertEqual(report.return_code, 123)
1044 report.failed(Path("e2"), "boom")
1045 self.assertEqual(len(out_lines), 2)
1046 self.assertEqual(len(err_lines), 2)
1047 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
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.path_ignored(Path("wat"), "no match")
1055 self.assertEqual(len(out_lines), 2)
1056 self.assertEqual(len(err_lines), 2)
1058 unstyle(str(report)),
1059 "2 files reformatted, 2 files left unchanged, 2 files failed to"
1062 self.assertEqual(report.return_code, 123)
1063 report.done(Path("f4"), black.Changed.NO)
1064 self.assertEqual(len(out_lines), 2)
1065 self.assertEqual(len(err_lines), 2)
1067 unstyle(str(report)),
1068 "2 files reformatted, 3 files left unchanged, 2 files failed to"
1071 self.assertEqual(report.return_code, 123)
1074 unstyle(str(report)),
1075 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1076 " would fail to reformat.",
1078 report.check = False
1081 unstyle(str(report)),
1082 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1083 " would fail to reformat.",
1086 def test_lib2to3_parse(self) -> None:
1087 with self.assertRaises(black.InvalidInput):
1088 black.lib2to3_parse("invalid syntax")
1090 straddling = "x + y"
1091 black.lib2to3_parse(straddling)
1092 black.lib2to3_parse(straddling, {TargetVersion.PY27})
1093 black.lib2to3_parse(straddling, {TargetVersion.PY36})
1094 black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
1096 py2_only = "print x"
1097 black.lib2to3_parse(py2_only)
1098 black.lib2to3_parse(py2_only, {TargetVersion.PY27})
1099 with self.assertRaises(black.InvalidInput):
1100 black.lib2to3_parse(py2_only, {TargetVersion.PY36})
1101 with self.assertRaises(black.InvalidInput):
1102 black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
1104 py3_only = "exec(x, end=y)"
1105 black.lib2to3_parse(py3_only)
1106 with self.assertRaises(black.InvalidInput):
1107 black.lib2to3_parse(py3_only, {TargetVersion.PY27})
1108 black.lib2to3_parse(py3_only, {TargetVersion.PY36})
1109 black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
1111 def test_get_features_used(self) -> None:
1112 node = black.lib2to3_parse("def f(*, arg): ...\n")
1113 self.assertEqual(black.get_features_used(node), set())
1114 node = black.lib2to3_parse("def f(*, arg,): ...\n")
1115 self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
1116 node = black.lib2to3_parse("f(*arg,)\n")
1118 black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
1120 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
1121 self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
1122 node = black.lib2to3_parse("123_456\n")
1123 self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
1124 node = black.lib2to3_parse("123456\n")
1125 self.assertEqual(black.get_features_used(node), set())
1126 source, expected = read_data("function")
1127 node = black.lib2to3_parse(source)
1128 expected_features = {
1129 Feature.TRAILING_COMMA_IN_CALL,
1130 Feature.TRAILING_COMMA_IN_DEF,
1133 self.assertEqual(black.get_features_used(node), expected_features)
1134 node = black.lib2to3_parse(expected)
1135 self.assertEqual(black.get_features_used(node), expected_features)
1136 source, expected = read_data("expression")
1137 node = black.lib2to3_parse(source)
1138 self.assertEqual(black.get_features_used(node), set())
1139 node = black.lib2to3_parse(expected)
1140 self.assertEqual(black.get_features_used(node), set())
1142 def test_get_future_imports(self) -> None:
1143 node = black.lib2to3_parse("\n")
1144 self.assertEqual(set(), black.get_future_imports(node))
1145 node = black.lib2to3_parse("from __future__ import black\n")
1146 self.assertEqual({"black"}, black.get_future_imports(node))
1147 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
1148 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1149 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
1150 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
1151 node = black.lib2to3_parse(
1152 "from __future__ import multiple\nfrom __future__ import imports\n"
1154 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1155 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
1156 self.assertEqual({"black"}, black.get_future_imports(node))
1157 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
1158 self.assertEqual({"black"}, black.get_future_imports(node))
1159 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
1160 self.assertEqual(set(), black.get_future_imports(node))
1161 node = black.lib2to3_parse("from some.module import black\n")
1162 self.assertEqual(set(), black.get_future_imports(node))
1163 node = black.lib2to3_parse(
1164 "from __future__ import unicode_literals as _unicode_literals"
1166 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
1167 node = black.lib2to3_parse(
1168 "from __future__ import unicode_literals as _lol, print"
1170 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
1172 def test_debug_visitor(self) -> None:
1173 source, _ = read_data("debug_visitor.py")
1174 expected, _ = read_data("debug_visitor.out")
1178 def out(msg: str, **kwargs: Any) -> None:
1179 out_lines.append(msg)
1181 def err(msg: str, **kwargs: Any) -> None:
1182 err_lines.append(msg)
1184 with patch("black.out", out), patch("black.err", err):
1185 black.DebugVisitor.show(source)
1186 actual = "\n".join(out_lines) + "\n"
1188 if expected != actual:
1189 log_name = black.dump_to_file(*out_lines)
1193 f"AST print out is different. Actual version dumped to {log_name}",
1196 def test_format_file_contents(self) -> None:
1198 mode = black.FileMode()
1199 with self.assertRaises(black.NothingChanged):
1200 black.format_file_contents(empty, mode=mode, fast=False)
1202 with self.assertRaises(black.NothingChanged):
1203 black.format_file_contents(just_nl, mode=mode, fast=False)
1204 same = "j = [1, 2, 3]\n"
1205 with self.assertRaises(black.NothingChanged):
1206 black.format_file_contents(same, mode=mode, fast=False)
1207 different = "j = [1,2,3]"
1209 actual = black.format_file_contents(different, mode=mode, fast=False)
1210 self.assertEqual(expected, actual)
1211 invalid = "return if you can"
1212 with self.assertRaises(black.InvalidInput) as e:
1213 black.format_file_contents(invalid, mode=mode, fast=False)
1214 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
1216 def test_endmarker(self) -> None:
1217 n = black.lib2to3_parse("\n")
1218 self.assertEqual(n.type, black.syms.file_input)
1219 self.assertEqual(len(n.children), 1)
1220 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
1222 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
1223 def test_assertFormatEqual(self) -> None:
1227 def out(msg: str, **kwargs: Any) -> None:
1228 out_lines.append(msg)
1230 def err(msg: str, **kwargs: Any) -> None:
1231 err_lines.append(msg)
1233 with patch("black.out", out), patch("black.err", err):
1234 with self.assertRaises(AssertionError):
1235 self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")
1237 out_str = "".join(out_lines)
1238 self.assertTrue("Expected tree:" in out_str)
1239 self.assertTrue("Actual tree:" in out_str)
1240 self.assertEqual("".join(err_lines), "")
1242 def test_cache_broken_file(self) -> None:
1243 mode = black.FileMode()
1244 with cache_dir() as workspace:
1245 cache_file = black.get_cache_file(mode)
1246 with cache_file.open("w") as fobj:
1247 fobj.write("this is not a pickle")
1248 self.assertEqual(black.read_cache(mode), {})
1249 src = (workspace / "test.py").resolve()
1250 with src.open("w") as fobj:
1251 fobj.write("print('hello')")
1252 self.invokeBlack([str(src)])
1253 cache = black.read_cache(mode)
1254 self.assertIn(src, cache)
1256 def test_cache_single_file_already_cached(self) -> None:
1257 mode = black.FileMode()
1258 with cache_dir() as workspace:
1259 src = (workspace / "test.py").resolve()
1260 with src.open("w") as fobj:
1261 fobj.write("print('hello')")
1262 black.write_cache({}, [src], mode)
1263 self.invokeBlack([str(src)])
1264 with src.open("r") as fobj:
1265 self.assertEqual(fobj.read(), "print('hello')")
1268 def test_cache_multiple_files(self) -> None:
1269 mode = black.FileMode()
1270 with cache_dir() as workspace, patch(
1271 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1273 one = (workspace / "one.py").resolve()
1274 with one.open("w") as fobj:
1275 fobj.write("print('hello')")
1276 two = (workspace / "two.py").resolve()
1277 with two.open("w") as fobj:
1278 fobj.write("print('hello')")
1279 black.write_cache({}, [one], mode)
1280 self.invokeBlack([str(workspace)])
1281 with one.open("r") as fobj:
1282 self.assertEqual(fobj.read(), "print('hello')")
1283 with two.open("r") as fobj:
1284 self.assertEqual(fobj.read(), 'print("hello")\n')
1285 cache = black.read_cache(mode)
1286 self.assertIn(one, cache)
1287 self.assertIn(two, cache)
1289 def test_no_cache_when_writeback_diff(self) -> None:
1290 mode = black.FileMode()
1291 with cache_dir() as workspace:
1292 src = (workspace / "test.py").resolve()
1293 with src.open("w") as fobj:
1294 fobj.write("print('hello')")
1295 self.invokeBlack([str(src), "--diff"])
1296 cache_file = black.get_cache_file(mode)
1297 self.assertFalse(cache_file.exists())
1299 def test_no_cache_when_stdin(self) -> None:
1300 mode = black.FileMode()
1302 result = CliRunner().invoke(
1303 black.main, ["-"], input=BytesIO(b"print('hello')")
1305 self.assertEqual(result.exit_code, 0)
1306 cache_file = black.get_cache_file(mode)
1307 self.assertFalse(cache_file.exists())
1309 def test_read_cache_no_cachefile(self) -> None:
1310 mode = black.FileMode()
1312 self.assertEqual(black.read_cache(mode), {})
1314 def test_write_cache_read_cache(self) -> None:
1315 mode = black.FileMode()
1316 with cache_dir() as workspace:
1317 src = (workspace / "test.py").resolve()
1319 black.write_cache({}, [src], mode)
1320 cache = black.read_cache(mode)
1321 self.assertIn(src, cache)
1322 self.assertEqual(cache[src], black.get_cache_info(src))
1324 def test_filter_cached(self) -> None:
1325 with TemporaryDirectory() as workspace:
1326 path = Path(workspace)
1327 uncached = (path / "uncached").resolve()
1328 cached = (path / "cached").resolve()
1329 cached_but_changed = (path / "changed").resolve()
1332 cached_but_changed.touch()
1333 cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
1334 todo, done = black.filter_cached(
1335 cache, {uncached, cached, cached_but_changed}
1337 self.assertEqual(todo, {uncached, cached_but_changed})
1338 self.assertEqual(done, {cached})
1340 def test_write_cache_creates_directory_if_needed(self) -> None:
1341 mode = black.FileMode()
1342 with cache_dir(exists=False) as workspace:
1343 self.assertFalse(workspace.exists())
1344 black.write_cache({}, [], mode)
1345 self.assertTrue(workspace.exists())
1348 def test_failed_formatting_does_not_get_cached(self) -> None:
1349 mode = black.FileMode()
1350 with cache_dir() as workspace, patch(
1351 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1353 failing = (workspace / "failing.py").resolve()
1354 with failing.open("w") as fobj:
1355 fobj.write("not actually python")
1356 clean = (workspace / "clean.py").resolve()
1357 with clean.open("w") as fobj:
1358 fobj.write('print("hello")\n')
1359 self.invokeBlack([str(workspace)], exit_code=123)
1360 cache = black.read_cache(mode)
1361 self.assertNotIn(failing, cache)
1362 self.assertIn(clean, cache)
1364 def test_write_cache_write_fail(self) -> None:
1365 mode = black.FileMode()
1366 with cache_dir(), patch.object(Path, "open") as mock:
1367 mock.side_effect = OSError
1368 black.write_cache({}, [], mode)
1371 @patch("black.ProcessPoolExecutor", MagicMock(side_effect=OSError))
1372 def test_works_in_mono_process_only_environment(self) -> None:
1373 with cache_dir() as workspace:
1375 (workspace / "one.py").resolve(),
1376 (workspace / "two.py").resolve(),
1378 f.write_text('print("hello")\n')
1379 self.invokeBlack([str(workspace)])
1382 def test_check_diff_use_together(self) -> None:
1384 # Files which will be reformatted.
1385 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1386 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1387 # Files which will not be reformatted.
1388 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1389 self.invokeBlack([str(src2), "--diff", "--check"])
1390 # Multi file command.
1391 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1393 def test_no_files(self) -> None:
1395 # Without an argument, black exits with error code 0.
1396 self.invokeBlack([])
1398 def test_broken_symlink(self) -> None:
1399 with cache_dir() as workspace:
1400 symlink = workspace / "broken_link.py"
1402 symlink.symlink_to("nonexistent.py")
1403 except OSError as e:
1404 self.skipTest(f"Can't create symlinks: {e}")
1405 self.invokeBlack([str(workspace.resolve())])
1407 def test_read_cache_line_lengths(self) -> None:
1408 mode = black.FileMode()
1409 short_mode = black.FileMode(line_length=1)
1410 with cache_dir() as workspace:
1411 path = (workspace / "file.py").resolve()
1413 black.write_cache({}, [path], mode)
1414 one = black.read_cache(mode)
1415 self.assertIn(path, one)
1416 two = black.read_cache(short_mode)
1417 self.assertNotIn(path, two)
1419 def test_tricky_unicode_symbols(self) -> None:
1420 source, expected = read_data("tricky_unicode_symbols")
1422 self.assertFormatEqual(expected, actual)
1423 black.assert_equivalent(source, actual)
1424 black.assert_stable(source, actual, black.FileMode())
1426 def test_single_file_force_pyi(self) -> None:
1427 reg_mode = black.FileMode()
1428 pyi_mode = black.FileMode(is_pyi=True)
1429 contents, expected = read_data("force_pyi")
1430 with cache_dir() as workspace:
1431 path = (workspace / "file.py").resolve()
1432 with open(path, "w") as fh:
1434 self.invokeBlack([str(path), "--pyi"])
1435 with open(path, "r") as fh:
1437 # verify cache with --pyi is separate
1438 pyi_cache = black.read_cache(pyi_mode)
1439 self.assertIn(path, pyi_cache)
1440 normal_cache = black.read_cache(reg_mode)
1441 self.assertNotIn(path, normal_cache)
1442 self.assertEqual(actual, expected)
1445 def test_multi_file_force_pyi(self) -> None:
1446 reg_mode = black.FileMode()
1447 pyi_mode = black.FileMode(is_pyi=True)
1448 contents, expected = read_data("force_pyi")
1449 with cache_dir() as workspace:
1451 (workspace / "file1.py").resolve(),
1452 (workspace / "file2.py").resolve(),
1455 with open(path, "w") as fh:
1457 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1459 with open(path, "r") as fh:
1461 self.assertEqual(actual, expected)
1462 # verify cache with --pyi is separate
1463 pyi_cache = black.read_cache(pyi_mode)
1464 normal_cache = black.read_cache(reg_mode)
1466 self.assertIn(path, pyi_cache)
1467 self.assertNotIn(path, normal_cache)
1469 def test_pipe_force_pyi(self) -> None:
1470 source, expected = read_data("force_pyi")
1471 result = CliRunner().invoke(
1472 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1474 self.assertEqual(result.exit_code, 0)
1475 actual = result.output
1476 self.assertFormatEqual(actual, expected)
1478 def test_single_file_force_py36(self) -> None:
1479 reg_mode = black.FileMode()
1480 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1481 source, expected = read_data("force_py36")
1482 with cache_dir() as workspace:
1483 path = (workspace / "file.py").resolve()
1484 with open(path, "w") as fh:
1486 self.invokeBlack([str(path), *PY36_ARGS])
1487 with open(path, "r") as fh:
1489 # verify cache with --target-version is separate
1490 py36_cache = black.read_cache(py36_mode)
1491 self.assertIn(path, py36_cache)
1492 normal_cache = black.read_cache(reg_mode)
1493 self.assertNotIn(path, normal_cache)
1494 self.assertEqual(actual, expected)
1497 def test_multi_file_force_py36(self) -> None:
1498 reg_mode = black.FileMode()
1499 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1500 source, expected = read_data("force_py36")
1501 with cache_dir() as workspace:
1503 (workspace / "file1.py").resolve(),
1504 (workspace / "file2.py").resolve(),
1507 with open(path, "w") as fh:
1509 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1511 with open(path, "r") as fh:
1513 self.assertEqual(actual, expected)
1514 # verify cache with --target-version is separate
1515 pyi_cache = black.read_cache(py36_mode)
1516 normal_cache = black.read_cache(reg_mode)
1518 self.assertIn(path, pyi_cache)
1519 self.assertNotIn(path, normal_cache)
1521 def test_collections(self) -> None:
1522 source, expected = read_data("collections")
1524 self.assertFormatEqual(expected, actual)
1525 black.assert_equivalent(source, actual)
1526 black.assert_stable(source, actual, black.FileMode())
1528 def test_pipe_force_py36(self) -> None:
1529 source, expected = read_data("force_py36")
1530 result = CliRunner().invoke(
1532 ["-", "-q", "--target-version=py36"],
1533 input=BytesIO(source.encode("utf8")),
1535 self.assertEqual(result.exit_code, 0)
1536 actual = result.output
1537 self.assertFormatEqual(actual, expected)
1539 def test_include_exclude(self) -> None:
1540 path = THIS_DIR / "data" / "include_exclude_tests"
1541 include = re.compile(r"\.pyi?$")
1542 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1543 report = black.Report()
1544 gitignore = PathSpec.from_lines("gitwildmatch", [])
1545 sources: List[Path] = []
1547 Path(path / "b/dont_exclude/a.py"),
1548 Path(path / "b/dont_exclude/a.pyi"),
1550 this_abs = THIS_DIR.resolve()
1552 black.gen_python_files(
1553 path.iterdir(), this_abs, include, [exclude], report, gitignore
1556 self.assertEqual(sorted(expected), sorted(sources))
1558 def test_gitignore_exclude(self) -> None:
1559 path = THIS_DIR / "data" / "include_exclude_tests"
1560 include = re.compile(r"\.pyi?$")
1561 exclude = re.compile(r"")
1562 report = black.Report()
1563 gitignore = PathSpec.from_lines(
1564 "gitwildmatch", ["exclude/", ".definitely_exclude"]
1566 sources: List[Path] = []
1568 Path(path / "b/dont_exclude/a.py"),
1569 Path(path / "b/dont_exclude/a.pyi"),
1571 this_abs = THIS_DIR.resolve()
1573 black.gen_python_files(
1574 path.iterdir(), this_abs, include, [exclude], report, gitignore
1577 self.assertEqual(sorted(expected), sorted(sources))
1579 def test_empty_include(self) -> None:
1580 path = THIS_DIR / "data" / "include_exclude_tests"
1581 report = black.Report()
1582 gitignore = PathSpec.from_lines("gitwildmatch", [])
1583 empty = re.compile(r"")
1584 sources: List[Path] = []
1586 Path(path / "b/exclude/a.pie"),
1587 Path(path / "b/exclude/a.py"),
1588 Path(path / "b/exclude/a.pyi"),
1589 Path(path / "b/dont_exclude/a.pie"),
1590 Path(path / "b/dont_exclude/a.py"),
1591 Path(path / "b/dont_exclude/a.pyi"),
1592 Path(path / "b/.definitely_exclude/a.pie"),
1593 Path(path / "b/.definitely_exclude/a.py"),
1594 Path(path / "b/.definitely_exclude/a.pyi"),
1596 this_abs = THIS_DIR.resolve()
1598 black.gen_python_files(
1602 [re.compile(black.DEFAULT_EXCLUDES)],
1607 self.assertEqual(sorted(expected), sorted(sources))
1609 def test_empty_exclude(self) -> None:
1610 path = THIS_DIR / "data" / "include_exclude_tests"
1611 report = black.Report()
1612 gitignore = PathSpec.from_lines("gitwildmatch", [])
1613 empty = re.compile(r"")
1614 sources: List[Path] = []
1616 Path(path / "b/dont_exclude/a.py"),
1617 Path(path / "b/dont_exclude/a.pyi"),
1618 Path(path / "b/exclude/a.py"),
1619 Path(path / "b/exclude/a.pyi"),
1620 Path(path / "b/.definitely_exclude/a.py"),
1621 Path(path / "b/.definitely_exclude/a.pyi"),
1623 this_abs = THIS_DIR.resolve()
1625 black.gen_python_files(
1628 re.compile(black.DEFAULT_INCLUDES),
1634 self.assertEqual(sorted(expected), sorted(sources))
1636 def test_invalid_include_exclude(self) -> None:
1637 for option in ["--include", "--exclude"]:
1638 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1640 def test_preserves_line_endings(self) -> None:
1641 with TemporaryDirectory() as workspace:
1642 test_file = Path(workspace) / "test.py"
1643 for nl in ["\n", "\r\n"]:
1644 contents = nl.join(["def f( ):", " pass"])
1645 test_file.write_bytes(contents.encode())
1646 ff(test_file, write_back=black.WriteBack.YES)
1647 updated_contents: bytes = test_file.read_bytes()
1648 self.assertIn(nl.encode(), updated_contents)
1650 self.assertNotIn(b"\r\n", updated_contents)
1652 def test_preserves_line_endings_via_stdin(self) -> None:
1653 for nl in ["\n", "\r\n"]:
1654 contents = nl.join(["def f( ):", " pass"])
1655 runner = BlackRunner()
1656 result = runner.invoke(
1657 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1659 self.assertEqual(result.exit_code, 0)
1660 output = runner.stdout_bytes
1661 self.assertIn(nl.encode("utf8"), output)
1663 self.assertNotIn(b"\r\n", output)
1665 def test_assert_equivalent_different_asts(self) -> None:
1666 with self.assertRaises(AssertionError):
1667 black.assert_equivalent("{}", "None")
1669 def test_symlink_out_of_root_directory(self) -> None:
1671 root = THIS_DIR.resolve()
1673 include = re.compile(black.DEFAULT_INCLUDES)
1674 exclude = re.compile(black.DEFAULT_EXCLUDES)
1675 report = black.Report()
1676 gitignore = PathSpec.from_lines("gitwildmatch", [])
1677 # `child` should behave like a symlink which resolved path is clearly
1678 # outside of the `root` directory.
1679 path.iterdir.return_value = [child]
1680 child.resolve.return_value = Path("/a/b/c")
1681 child.as_posix.return_value = "/a/b/c"
1682 child.is_symlink.return_value = True
1685 black.gen_python_files(
1686 path.iterdir(), root, include, exclude, report, gitignore
1689 except ValueError as ve:
1690 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1691 path.iterdir.assert_called_once()
1692 child.resolve.assert_called_once()
1693 child.is_symlink.assert_called_once()
1694 # `child` should behave like a strange file which resolved path is clearly
1695 # outside of the `root` directory.
1696 child.is_symlink.return_value = False
1697 with self.assertRaises(ValueError):
1699 black.gen_python_files(
1700 path.iterdir(), root, include, exclude, report, gitignore
1703 path.iterdir.assert_called()
1704 self.assertEqual(path.iterdir.call_count, 2)
1705 child.resolve.assert_called()
1706 self.assertEqual(child.resolve.call_count, 2)
1707 child.is_symlink.assert_called()
1708 self.assertEqual(child.is_symlink.call_count, 2)
1710 def test_shhh_click(self) -> None:
1712 from click import _unicodefun # type: ignore
1713 except ModuleNotFoundError:
1714 self.skipTest("Incompatible Click version")
1715 if not hasattr(_unicodefun, "_verify_python3_env"):
1716 self.skipTest("Incompatible Click version")
1717 # First, let's see if Click is crashing with a preferred ASCII charset.
1718 with patch("locale.getpreferredencoding") as gpe:
1719 gpe.return_value = "ASCII"
1720 with self.assertRaises(RuntimeError):
1721 _unicodefun._verify_python3_env()
1722 # Now, let's silence Click...
1724 # ...and confirm it's silent.
1725 with patch("locale.getpreferredencoding") as gpe:
1726 gpe.return_value = "ASCII"
1728 _unicodefun._verify_python3_env()
1729 except RuntimeError as re:
1730 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1732 def test_root_logger_not_used_directly(self) -> None:
1733 def fail(*args: Any, **kwargs: Any) -> None:
1734 self.fail("Record created with root logger")
1736 with patch.multiple(
1747 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1748 def test_blackd_main(self) -> None:
1749 with patch("blackd.web.run_app"):
1750 result = CliRunner().invoke(blackd.main, [])
1751 if result.exception is not None:
1752 raise result.exception
1753 self.assertEqual(result.exit_code, 0)
1755 def test_invalid_config_return_code(self) -> None:
1756 tmp_file = Path(black.dump_to_file())
1758 tmp_config = Path(black.dump_to_file())
1760 args = ["--config", str(tmp_config), str(tmp_file)]
1761 self.invokeBlack(args, exit_code=2, ignore_config=False)
1766 class BlackDTestCase(AioHTTPTestCase):
1767 async def get_application(self) -> web.Application:
1768 return blackd.make_app()
1770 # TODO: remove these decorators once the below is released
1771 # https://github.com/aio-libs/aiohttp/pull/3727
1772 @skip_if_exception("ClientOSError")
1773 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1775 async def test_blackd_request_needs_formatting(self) -> None:
1776 response = await self.client.post("/", data=b"print('hello world')")
1777 self.assertEqual(response.status, 200)
1778 self.assertEqual(response.charset, "utf8")
1779 self.assertEqual(await response.read(), b'print("hello world")\n')
1781 @skip_if_exception("ClientOSError")
1782 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1784 async def test_blackd_request_no_change(self) -> None:
1785 response = await self.client.post("/", data=b'print("hello world")\n')
1786 self.assertEqual(response.status, 204)
1787 self.assertEqual(await response.read(), b"")
1789 @skip_if_exception("ClientOSError")
1790 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1792 async def test_blackd_request_syntax_error(self) -> None:
1793 response = await self.client.post("/", data=b"what even ( is")
1794 self.assertEqual(response.status, 400)
1795 content = await response.text()
1797 content.startswith("Cannot parse"),
1798 msg=f"Expected error to start with 'Cannot parse', got {repr(content)}",
1801 @skip_if_exception("ClientOSError")
1802 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1804 async def test_blackd_unsupported_version(self) -> None:
1805 response = await self.client.post(
1806 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "2"}
1808 self.assertEqual(response.status, 501)
1810 @skip_if_exception("ClientOSError")
1811 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1813 async def test_blackd_supported_version(self) -> None:
1814 response = await self.client.post(
1815 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "1"}
1817 self.assertEqual(response.status, 200)
1819 @skip_if_exception("ClientOSError")
1820 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1822 async def test_blackd_invalid_python_variant(self) -> None:
1823 async def check(header_value: str, expected_status: int = 400) -> None:
1824 response = await self.client.post(
1825 "/", data=b"what", headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1827 self.assertEqual(response.status, expected_status)
1830 await check("ruby3.5")
1831 await check("pyi3.6")
1832 await check("py1.5")
1834 await check("py2.8")
1836 await check("pypy3.0")
1837 await check("jython3.4")
1839 @skip_if_exception("ClientOSError")
1840 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1842 async def test_blackd_pyi(self) -> None:
1843 source, expected = read_data("stub.pyi")
1844 response = await self.client.post(
1845 "/", data=source, headers={blackd.PYTHON_VARIANT_HEADER: "pyi"}
1847 self.assertEqual(response.status, 200)
1848 self.assertEqual(await response.text(), expected)
1850 @skip_if_exception("ClientOSError")
1851 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1853 async def test_blackd_diff(self) -> None:
1854 diff_header = re.compile(
1855 r"(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"
1858 source, _ = read_data("blackd_diff.py")
1859 expected, _ = read_data("blackd_diff.diff")
1861 response = await self.client.post(
1862 "/", data=source, headers={blackd.DIFF_HEADER: "true"}
1864 self.assertEqual(response.status, 200)
1866 actual = await response.text()
1867 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
1868 self.assertEqual(actual, expected)
1870 @skip_if_exception("ClientOSError")
1871 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1873 async def test_blackd_python_variant(self) -> None:
1876 " and_has_a_bunch_of,\n"
1877 " very_long_arguments_too,\n"
1878 " and_lots_of_them_as_well_lol,\n"
1879 " **and_very_long_keyword_arguments\n"
1884 async def check(header_value: str, expected_status: int) -> None:
1885 response = await self.client.post(
1886 "/", data=code, headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1889 response.status, expected_status, msg=await response.text()
1892 await check("3.6", 200)
1893 await check("py3.6", 200)
1894 await check("3.6,3.7", 200)
1895 await check("3.6,py3.7", 200)
1896 await check("py36,py37", 200)
1897 await check("36", 200)
1898 await check("3.6.4", 200)
1900 await check("2", 204)
1901 await check("2.7", 204)
1902 await check("py2.7", 204)
1903 await check("3.4", 204)
1904 await check("py3.4", 204)
1905 await check("py34,py36", 204)
1906 await check("34", 204)
1908 @skip_if_exception("ClientOSError")
1909 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1911 async def test_blackd_line_length(self) -> None:
1912 response = await self.client.post(
1913 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "7"}
1915 self.assertEqual(response.status, 200)
1917 @skip_if_exception("ClientOSError")
1918 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1920 async def test_blackd_invalid_line_length(self) -> None:
1921 response = await self.client.post(
1922 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "NaN"}
1924 self.assertEqual(response.status, 400)
1926 @skip_if_exception("ClientOSError")
1927 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1929 async def test_blackd_response_black_version_header(self) -> None:
1930 response = await self.client.post("/")
1931 self.assertIsNotNone(response.headers.get(blackd.BLACK_VERSION_HEADER))
1934 if __name__ == "__main__":
1935 unittest.main(module="test_black")