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 PROJECT_ROOT = THIS_DIR.parent
39 DETERMINISTIC_HEADER = "[Deterministic header]"
40 EMPTY_LINE = "# EMPTY LINE WITH WHITESPACE" + " (this comment will be removed)"
42 f"--target-version={version.name.lower()}" for version in black.PY36_VERSIONS
48 def dump_to_stderr(*output: str) -> str:
49 return "\n" + "\n".join(output) + "\n"
52 def read_data(name: str, data: bool = True) -> Tuple[str, str]:
53 """read_data('test_name') -> 'input', 'output'"""
54 if not name.endswith((".py", ".pyi", ".out", ".diff")):
56 _input: List[str] = []
57 _output: List[str] = []
58 base_dir = THIS_DIR / "data" if data else PROJECT_ROOT
59 with open(base_dir / name, "r", encoding="utf8") as test:
60 lines = test.readlines()
63 line = line.replace(EMPTY_LINE, "")
64 if line.rstrip() == "# output":
69 if _input and not _output:
70 # If there's no output marker, treat the entire file as already pre-formatted.
72 return "".join(_input).strip() + "\n", "".join(_output).strip() + "\n"
76 def cache_dir(exists: bool = True) -> Iterator[Path]:
77 with TemporaryDirectory() as workspace:
78 cache_dir = Path(workspace)
80 cache_dir = cache_dir / "new"
81 with patch("black.CACHE_DIR", cache_dir):
86 def event_loop() -> Iterator[None]:
87 policy = asyncio.get_event_loop_policy()
88 loop = policy.new_event_loop()
89 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)
166 f"Failed with args: {args}\n"
167 f"stdout: {runner.stdout_bytes.decode()!r}\n"
168 f"stderr: {runner.stderr_bytes.decode()!r}\n"
169 f"exception: {result.exception}"
173 @patch("black.dump_to_file", dump_to_stderr)
174 def checkSourceFile(self, name: str) -> None:
175 path = THIS_DIR.parent / name
176 source, expected = read_data(str(path), data=False)
178 self.assertFormatEqual(expected, actual)
179 black.assert_equivalent(source, actual)
180 black.assert_stable(source, actual, black.FileMode())
181 self.assertFalse(ff(path))
183 @patch("black.dump_to_file", dump_to_stderr)
184 def test_empty(self) -> None:
185 source = expected = ""
187 self.assertFormatEqual(expected, actual)
188 black.assert_equivalent(source, actual)
189 black.assert_stable(source, actual, black.FileMode())
191 def test_empty_ff(self) -> None:
193 tmp_file = Path(black.dump_to_file())
195 self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES))
196 with open(tmp_file, encoding="utf8") as f:
200 self.assertFormatEqual(expected, actual)
202 def test_self(self) -> None:
203 self.checkSourceFile("tests/test_black.py")
205 def test_black(self) -> None:
206 self.checkSourceFile("src/black/__init__.py")
208 def test_pygram(self) -> None:
209 self.checkSourceFile("src/blib2to3/pygram.py")
211 def test_pytree(self) -> None:
212 self.checkSourceFile("src/blib2to3/pytree.py")
214 def test_conv(self) -> None:
215 self.checkSourceFile("src/blib2to3/pgen2/conv.py")
217 def test_driver(self) -> None:
218 self.checkSourceFile("src/blib2to3/pgen2/driver.py")
220 def test_grammar(self) -> None:
221 self.checkSourceFile("src/blib2to3/pgen2/grammar.py")
223 def test_literals(self) -> None:
224 self.checkSourceFile("src/blib2to3/pgen2/literals.py")
226 def test_parse(self) -> None:
227 self.checkSourceFile("src/blib2to3/pgen2/parse.py")
229 def test_pgen(self) -> None:
230 self.checkSourceFile("src/blib2to3/pgen2/pgen.py")
232 def test_tokenize(self) -> None:
233 self.checkSourceFile("src/blib2to3/pgen2/tokenize.py")
235 def test_token(self) -> None:
236 self.checkSourceFile("src/blib2to3/pgen2/token.py")
238 def test_setup(self) -> None:
239 self.checkSourceFile("setup.py")
241 def test_piping(self) -> None:
242 source, expected = read_data("src/black/__init__", data=False)
243 result = BlackRunner().invoke(
245 ["-", "--fast", f"--line-length={black.DEFAULT_LINE_LENGTH}"],
246 input=BytesIO(source.encode("utf8")),
248 self.assertEqual(result.exit_code, 0)
249 self.assertFormatEqual(expected, result.output)
250 black.assert_equivalent(source, result.output)
251 black.assert_stable(source, result.output, black.FileMode())
253 def test_piping_diff(self) -> None:
254 diff_header = re.compile(
255 rf"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d\d\d\d "
258 source, _ = read_data("expression.py")
259 expected, _ = read_data("expression.diff")
260 config = THIS_DIR / "data" / "empty_pyproject.toml"
264 f"--line-length={black.DEFAULT_LINE_LENGTH}",
266 f"--config={config}",
268 result = BlackRunner().invoke(
269 black.main, args, input=BytesIO(source.encode("utf8"))
271 self.assertEqual(result.exit_code, 0)
272 actual = diff_header.sub(DETERMINISTIC_HEADER, result.output)
273 actual = actual.rstrip() + "\n" # the diff output has a trailing space
274 self.assertEqual(expected, actual)
276 def test_piping_diff_with_color(self) -> None:
277 source, _ = read_data("expression.py")
278 config = THIS_DIR / "data" / "empty_pyproject.toml"
282 f"--line-length={black.DEFAULT_LINE_LENGTH}",
285 f"--config={config}",
287 result = BlackRunner().invoke(
288 black.main, args, input=BytesIO(source.encode("utf8"))
290 actual = result.output
291 # Again, the contents are checked in a different test, so only look for colors.
292 self.assertIn("\033[1;37m", actual)
293 self.assertIn("\033[36m", actual)
294 self.assertIn("\033[32m", actual)
295 self.assertIn("\033[31m", actual)
296 self.assertIn("\033[0m", actual)
298 @patch("black.dump_to_file", dump_to_stderr)
299 def test_function(self) -> None:
300 source, expected = read_data("function")
302 self.assertFormatEqual(expected, actual)
303 black.assert_equivalent(source, actual)
304 black.assert_stable(source, actual, black.FileMode())
306 @patch("black.dump_to_file", dump_to_stderr)
307 def test_function2(self) -> None:
308 source, expected = read_data("function2")
310 self.assertFormatEqual(expected, actual)
311 black.assert_equivalent(source, actual)
312 black.assert_stable(source, actual, black.FileMode())
314 @patch("black.dump_to_file", dump_to_stderr)
315 def test_function_trailing_comma(self) -> None:
316 source, expected = read_data("function_trailing_comma")
318 self.assertFormatEqual(expected, actual)
319 black.assert_equivalent(source, actual)
320 black.assert_stable(source, actual, black.FileMode())
322 @patch("black.dump_to_file", dump_to_stderr)
323 def test_expression(self) -> None:
324 source, expected = read_data("expression")
326 self.assertFormatEqual(expected, actual)
327 black.assert_equivalent(source, actual)
328 black.assert_stable(source, actual, black.FileMode())
330 @patch("black.dump_to_file", dump_to_stderr)
331 def test_pep_572(self) -> None:
332 source, expected = read_data("pep_572")
334 self.assertFormatEqual(expected, actual)
335 black.assert_stable(source, actual, black.FileMode())
336 if sys.version_info >= (3, 8):
337 black.assert_equivalent(source, actual)
339 def test_pep_572_version_detection(self) -> None:
340 source, _ = read_data("pep_572")
341 root = black.lib2to3_parse(source)
342 features = black.get_features_used(root)
343 self.assertIn(black.Feature.ASSIGNMENT_EXPRESSIONS, features)
344 versions = black.detect_target_versions(root)
345 self.assertIn(black.TargetVersion.PY38, versions)
347 def test_expression_ff(self) -> None:
348 source, expected = read_data("expression")
349 tmp_file = Path(black.dump_to_file(source))
351 self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
352 with open(tmp_file, encoding="utf8") as f:
356 self.assertFormatEqual(expected, actual)
357 with patch("black.dump_to_file", dump_to_stderr):
358 black.assert_equivalent(source, actual)
359 black.assert_stable(source, actual, black.FileMode())
361 def test_expression_diff(self) -> None:
362 source, _ = read_data("expression.py")
363 expected, _ = read_data("expression.diff")
364 tmp_file = Path(black.dump_to_file(source))
365 diff_header = re.compile(
366 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
367 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
370 result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
371 self.assertEqual(result.exit_code, 0)
374 actual = result.output
375 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
376 actual = actual.rstrip() + "\n" # the diff output has a trailing space
377 if expected != actual:
378 dump = black.dump_to_file(actual)
380 "Expected diff isn't equal to the actual. If you made changes to"
381 " expression.py and this is an anticipated difference, overwrite"
382 f" tests/data/expression.diff with {dump}"
384 self.assertEqual(expected, actual, msg)
386 def test_expression_diff_with_color(self) -> None:
387 source, _ = read_data("expression.py")
388 expected, _ = read_data("expression.diff")
389 tmp_file = Path(black.dump_to_file(source))
391 result = BlackRunner().invoke(
392 black.main, ["--diff", "--color", str(tmp_file)]
396 actual = result.output
397 # We check the contents of the diff in `test_expression_diff`. All
398 # we need to check here is that color codes exist in the result.
399 self.assertIn("\033[1;37m", actual)
400 self.assertIn("\033[36m", actual)
401 self.assertIn("\033[32m", actual)
402 self.assertIn("\033[31m", actual)
403 self.assertIn("\033[0m", actual)
405 @patch("black.dump_to_file", dump_to_stderr)
406 def test_fstring(self) -> None:
407 source, expected = read_data("fstring")
409 self.assertFormatEqual(expected, actual)
410 black.assert_equivalent(source, actual)
411 black.assert_stable(source, actual, black.FileMode())
413 @patch("black.dump_to_file", dump_to_stderr)
414 def test_pep_570(self) -> None:
415 source, expected = read_data("pep_570")
417 self.assertFormatEqual(expected, actual)
418 black.assert_stable(source, actual, black.FileMode())
419 if sys.version_info >= (3, 8):
420 black.assert_equivalent(source, actual)
422 def test_detect_pos_only_arguments(self) -> None:
423 source, _ = read_data("pep_570")
424 root = black.lib2to3_parse(source)
425 features = black.get_features_used(root)
426 self.assertIn(black.Feature.POS_ONLY_ARGUMENTS, features)
427 versions = black.detect_target_versions(root)
428 self.assertIn(black.TargetVersion.PY38, versions)
430 @patch("black.dump_to_file", dump_to_stderr)
431 def test_string_quotes(self) -> None:
432 source, expected = read_data("string_quotes")
434 self.assertFormatEqual(expected, actual)
435 black.assert_equivalent(source, actual)
436 black.assert_stable(source, actual, black.FileMode())
437 mode = black.FileMode(string_normalization=False)
438 not_normalized = fs(source, mode=mode)
439 self.assertFormatEqual(source.replace("\\\n", ""), not_normalized)
440 black.assert_equivalent(source, not_normalized)
441 black.assert_stable(source, not_normalized, mode=mode)
443 @patch("black.dump_to_file", dump_to_stderr)
444 def test_docstring(self) -> None:
445 source, expected = read_data("docstring")
447 self.assertFormatEqual(expected, actual)
448 black.assert_equivalent(source, actual)
449 black.assert_stable(source, actual, black.FileMode())
451 def test_long_strings(self) -> None:
452 """Tests for splitting long strings."""
453 source, expected = read_data("long_strings")
455 self.assertFormatEqual(expected, actual)
456 black.assert_equivalent(source, actual)
457 black.assert_stable(source, actual, black.FileMode())
459 @patch("black.dump_to_file", dump_to_stderr)
460 def test_long_strings__edge_case(self) -> None:
461 """Edge-case tests for splitting long strings."""
462 source, expected = read_data("long_strings__edge_case")
464 self.assertFormatEqual(expected, actual)
465 black.assert_equivalent(source, actual)
466 black.assert_stable(source, actual, black.FileMode())
468 @patch("black.dump_to_file", dump_to_stderr)
469 def test_long_strings__regression(self) -> None:
470 """Regression tests for splitting long strings."""
471 source, expected = read_data("long_strings__regression")
473 self.assertFormatEqual(expected, actual)
474 black.assert_equivalent(source, actual)
475 black.assert_stable(source, actual, black.FileMode())
477 @patch("black.dump_to_file", dump_to_stderr)
478 def test_slices(self) -> None:
479 source, expected = read_data("slices")
481 self.assertFormatEqual(expected, actual)
482 black.assert_equivalent(source, actual)
483 black.assert_stable(source, actual, black.FileMode())
485 @patch("black.dump_to_file", dump_to_stderr)
486 def test_comments(self) -> None:
487 source, expected = read_data("comments")
489 self.assertFormatEqual(expected, actual)
490 black.assert_equivalent(source, actual)
491 black.assert_stable(source, actual, black.FileMode())
493 @patch("black.dump_to_file", dump_to_stderr)
494 def test_comments2(self) -> None:
495 source, expected = read_data("comments2")
497 self.assertFormatEqual(expected, actual)
498 black.assert_equivalent(source, actual)
499 black.assert_stable(source, actual, black.FileMode())
501 @patch("black.dump_to_file", dump_to_stderr)
502 def test_comments3(self) -> None:
503 source, expected = read_data("comments3")
505 self.assertFormatEqual(expected, actual)
506 black.assert_equivalent(source, actual)
507 black.assert_stable(source, actual, black.FileMode())
509 @patch("black.dump_to_file", dump_to_stderr)
510 def test_comments4(self) -> None:
511 source, expected = read_data("comments4")
513 self.assertFormatEqual(expected, actual)
514 black.assert_equivalent(source, actual)
515 black.assert_stable(source, actual, black.FileMode())
517 @patch("black.dump_to_file", dump_to_stderr)
518 def test_comments5(self) -> None:
519 source, expected = read_data("comments5")
521 self.assertFormatEqual(expected, actual)
522 black.assert_equivalent(source, actual)
523 black.assert_stable(source, actual, black.FileMode())
525 @patch("black.dump_to_file", dump_to_stderr)
526 def test_comments6(self) -> None:
527 source, expected = read_data("comments6")
529 self.assertFormatEqual(expected, actual)
530 black.assert_equivalent(source, actual)
531 black.assert_stable(source, actual, black.FileMode())
533 @patch("black.dump_to_file", dump_to_stderr)
534 def test_comments7(self) -> None:
535 source, expected = read_data("comments7")
537 self.assertFormatEqual(expected, actual)
538 black.assert_equivalent(source, actual)
539 black.assert_stable(source, actual, black.FileMode())
541 @patch("black.dump_to_file", dump_to_stderr)
542 def test_comment_after_escaped_newline(self) -> None:
543 source, expected = read_data("comment_after_escaped_newline")
545 self.assertFormatEqual(expected, actual)
546 black.assert_equivalent(source, actual)
547 black.assert_stable(source, actual, black.FileMode())
549 @patch("black.dump_to_file", dump_to_stderr)
550 def test_cantfit(self) -> None:
551 source, expected = read_data("cantfit")
553 self.assertFormatEqual(expected, actual)
554 black.assert_equivalent(source, actual)
555 black.assert_stable(source, actual, black.FileMode())
557 @patch("black.dump_to_file", dump_to_stderr)
558 def test_import_spacing(self) -> None:
559 source, expected = read_data("import_spacing")
561 self.assertFormatEqual(expected, actual)
562 black.assert_equivalent(source, actual)
563 black.assert_stable(source, actual, black.FileMode())
565 @patch("black.dump_to_file", dump_to_stderr)
566 def test_composition(self) -> None:
567 source, expected = read_data("composition")
569 self.assertFormatEqual(expected, actual)
570 black.assert_equivalent(source, actual)
571 black.assert_stable(source, actual, black.FileMode())
573 @patch("black.dump_to_file", dump_to_stderr)
574 def test_empty_lines(self) -> None:
575 source, expected = read_data("empty_lines")
577 self.assertFormatEqual(expected, actual)
578 black.assert_equivalent(source, actual)
579 black.assert_stable(source, actual, black.FileMode())
581 @patch("black.dump_to_file", dump_to_stderr)
582 def test_remove_parens(self) -> None:
583 source, expected = read_data("remove_parens")
585 self.assertFormatEqual(expected, actual)
586 black.assert_equivalent(source, actual)
587 black.assert_stable(source, actual, black.FileMode())
589 @patch("black.dump_to_file", dump_to_stderr)
590 def test_string_prefixes(self) -> None:
591 source, expected = read_data("string_prefixes")
593 self.assertFormatEqual(expected, actual)
594 black.assert_equivalent(source, actual)
595 black.assert_stable(source, actual, black.FileMode())
597 @patch("black.dump_to_file", dump_to_stderr)
598 def test_numeric_literals(self) -> None:
599 source, expected = read_data("numeric_literals")
600 mode = black.FileMode(target_versions=black.PY36_VERSIONS)
601 actual = fs(source, mode=mode)
602 self.assertFormatEqual(expected, actual)
603 black.assert_equivalent(source, actual)
604 black.assert_stable(source, actual, mode)
606 @patch("black.dump_to_file", dump_to_stderr)
607 def test_numeric_literals_ignoring_underscores(self) -> None:
608 source, expected = read_data("numeric_literals_skip_underscores")
609 mode = black.FileMode(target_versions=black.PY36_VERSIONS)
610 actual = fs(source, mode=mode)
611 self.assertFormatEqual(expected, actual)
612 black.assert_equivalent(source, actual)
613 black.assert_stable(source, actual, mode)
615 @patch("black.dump_to_file", dump_to_stderr)
616 def test_numeric_literals_py2(self) -> None:
617 source, expected = read_data("numeric_literals_py2")
619 self.assertFormatEqual(expected, actual)
620 black.assert_stable(source, actual, black.FileMode())
622 @patch("black.dump_to_file", dump_to_stderr)
623 def test_python2(self) -> None:
624 source, expected = read_data("python2")
626 self.assertFormatEqual(expected, actual)
627 black.assert_equivalent(source, actual)
628 black.assert_stable(source, actual, black.FileMode())
630 @patch("black.dump_to_file", dump_to_stderr)
631 def test_python2_print_function(self) -> None:
632 source, expected = read_data("python2_print_function")
633 mode = black.FileMode(target_versions={TargetVersion.PY27})
634 actual = fs(source, mode=mode)
635 self.assertFormatEqual(expected, actual)
636 black.assert_equivalent(source, actual)
637 black.assert_stable(source, actual, mode)
639 @patch("black.dump_to_file", dump_to_stderr)
640 def test_python2_unicode_literals(self) -> None:
641 source, expected = read_data("python2_unicode_literals")
643 self.assertFormatEqual(expected, actual)
644 black.assert_equivalent(source, actual)
645 black.assert_stable(source, actual, black.FileMode())
647 @patch("black.dump_to_file", dump_to_stderr)
648 def test_stub(self) -> None:
649 mode = black.FileMode(is_pyi=True)
650 source, expected = read_data("stub.pyi")
651 actual = fs(source, mode=mode)
652 self.assertFormatEqual(expected, actual)
653 black.assert_stable(source, actual, mode)
655 @patch("black.dump_to_file", dump_to_stderr)
656 def test_async_as_identifier(self) -> None:
657 source_path = (THIS_DIR / "data" / "async_as_identifier.py").resolve()
658 source, expected = read_data("async_as_identifier")
660 self.assertFormatEqual(expected, actual)
661 major, minor = sys.version_info[:2]
662 if major < 3 or (major <= 3 and minor < 7):
663 black.assert_equivalent(source, actual)
664 black.assert_stable(source, actual, black.FileMode())
665 # ensure black can parse this when the target is 3.6
666 self.invokeBlack([str(source_path), "--target-version", "py36"])
667 # but not on 3.7, because async/await is no longer an identifier
668 self.invokeBlack([str(source_path), "--target-version", "py37"], exit_code=123)
670 @patch("black.dump_to_file", dump_to_stderr)
671 def test_python37(self) -> None:
672 source_path = (THIS_DIR / "data" / "python37.py").resolve()
673 source, expected = read_data("python37")
675 self.assertFormatEqual(expected, actual)
676 major, minor = sys.version_info[:2]
677 if major > 3 or (major == 3 and minor >= 7):
678 black.assert_equivalent(source, actual)
679 black.assert_stable(source, actual, black.FileMode())
680 # ensure black can parse this when the target is 3.7
681 self.invokeBlack([str(source_path), "--target-version", "py37"])
682 # but not on 3.6, because we use async as a reserved keyword
683 self.invokeBlack([str(source_path), "--target-version", "py36"], exit_code=123)
685 @patch("black.dump_to_file", dump_to_stderr)
686 def test_python38(self) -> None:
687 source, expected = read_data("python38")
689 self.assertFormatEqual(expected, actual)
690 major, minor = sys.version_info[:2]
691 if major > 3 or (major == 3 and minor >= 8):
692 black.assert_equivalent(source, actual)
693 black.assert_stable(source, actual, black.FileMode())
695 @patch("black.dump_to_file", dump_to_stderr)
696 def test_fmtonoff(self) -> None:
697 source, expected = read_data("fmtonoff")
699 self.assertFormatEqual(expected, actual)
700 black.assert_equivalent(source, actual)
701 black.assert_stable(source, actual, black.FileMode())
703 @patch("black.dump_to_file", dump_to_stderr)
704 def test_fmtonoff2(self) -> None:
705 source, expected = read_data("fmtonoff2")
707 self.assertFormatEqual(expected, actual)
708 black.assert_equivalent(source, actual)
709 black.assert_stable(source, actual, black.FileMode())
711 @patch("black.dump_to_file", dump_to_stderr)
712 def test_fmtonoff3(self) -> None:
713 source, expected = read_data("fmtonoff3")
715 self.assertFormatEqual(expected, actual)
716 black.assert_equivalent(source, actual)
717 black.assert_stable(source, actual, black.FileMode())
719 @patch("black.dump_to_file", dump_to_stderr)
720 def test_fmtonoff4(self) -> None:
721 source, expected = read_data("fmtonoff4")
723 self.assertFormatEqual(expected, actual)
724 black.assert_equivalent(source, actual)
725 black.assert_stable(source, actual, black.FileMode())
727 @patch("black.dump_to_file", dump_to_stderr)
728 def test_remove_empty_parentheses_after_class(self) -> None:
729 source, expected = read_data("class_blank_parentheses")
731 self.assertFormatEqual(expected, actual)
732 black.assert_equivalent(source, actual)
733 black.assert_stable(source, actual, black.FileMode())
735 @patch("black.dump_to_file", dump_to_stderr)
736 def test_new_line_between_class_and_code(self) -> None:
737 source, expected = read_data("class_methods_new_line")
739 self.assertFormatEqual(expected, actual)
740 black.assert_equivalent(source, actual)
741 black.assert_stable(source, actual, black.FileMode())
743 @patch("black.dump_to_file", dump_to_stderr)
744 def test_bracket_match(self) -> None:
745 source, expected = read_data("bracketmatch")
747 self.assertFormatEqual(expected, actual)
748 black.assert_equivalent(source, actual)
749 black.assert_stable(source, actual, black.FileMode())
751 @patch("black.dump_to_file", dump_to_stderr)
752 def test_tuple_assign(self) -> None:
753 source, expected = read_data("tupleassign")
755 self.assertFormatEqual(expected, actual)
756 black.assert_equivalent(source, actual)
757 black.assert_stable(source, actual, black.FileMode())
759 @patch("black.dump_to_file", dump_to_stderr)
760 def test_beginning_backslash(self) -> None:
761 source, expected = read_data("beginning_backslash")
763 self.assertFormatEqual(expected, actual)
764 black.assert_equivalent(source, actual)
765 black.assert_stable(source, actual, black.FileMode())
767 def test_tab_comment_indentation(self) -> None:
768 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\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 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
774 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
775 self.assertFormatEqual(contents_spc, fs(contents_spc))
776 self.assertFormatEqual(contents_spc, fs(contents_tab))
778 # mixed tabs and spaces (valid Python 2 code)
779 contents_tab = "if 1:\n if 2:\n\t\tpass\n\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 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t\t# comment\n pass\n"
785 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
786 self.assertFormatEqual(contents_spc, fs(contents_spc))
787 self.assertFormatEqual(contents_spc, fs(contents_tab))
789 def test_report_verbose(self) -> None:
790 report = black.Report(verbose=True)
794 def out(msg: str, **kwargs: Any) -> None:
795 out_lines.append(msg)
797 def err(msg: str, **kwargs: Any) -> None:
798 err_lines.append(msg)
800 with patch("black.out", out), patch("black.err", err):
801 report.done(Path("f1"), black.Changed.NO)
802 self.assertEqual(len(out_lines), 1)
803 self.assertEqual(len(err_lines), 0)
804 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
805 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
806 self.assertEqual(report.return_code, 0)
807 report.done(Path("f2"), black.Changed.YES)
808 self.assertEqual(len(out_lines), 2)
809 self.assertEqual(len(err_lines), 0)
810 self.assertEqual(out_lines[-1], "reformatted f2")
812 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
814 report.done(Path("f3"), black.Changed.CACHED)
815 self.assertEqual(len(out_lines), 3)
816 self.assertEqual(len(err_lines), 0)
818 out_lines[-1], "f3 wasn't modified on disk since last run."
821 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
823 self.assertEqual(report.return_code, 0)
825 self.assertEqual(report.return_code, 1)
827 report.failed(Path("e1"), "boom")
828 self.assertEqual(len(out_lines), 3)
829 self.assertEqual(len(err_lines), 1)
830 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
832 unstyle(str(report)),
833 "1 file reformatted, 2 files left unchanged, 1 file failed to"
836 self.assertEqual(report.return_code, 123)
837 report.done(Path("f3"), black.Changed.YES)
838 self.assertEqual(len(out_lines), 4)
839 self.assertEqual(len(err_lines), 1)
840 self.assertEqual(out_lines[-1], "reformatted f3")
842 unstyle(str(report)),
843 "2 files reformatted, 2 files left unchanged, 1 file failed to"
846 self.assertEqual(report.return_code, 123)
847 report.failed(Path("e2"), "boom")
848 self.assertEqual(len(out_lines), 4)
849 self.assertEqual(len(err_lines), 2)
850 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
852 unstyle(str(report)),
853 "2 files reformatted, 2 files left unchanged, 2 files failed to"
856 self.assertEqual(report.return_code, 123)
857 report.path_ignored(Path("wat"), "no match")
858 self.assertEqual(len(out_lines), 5)
859 self.assertEqual(len(err_lines), 2)
860 self.assertEqual(out_lines[-1], "wat ignored: no match")
862 unstyle(str(report)),
863 "2 files reformatted, 2 files left unchanged, 2 files failed to"
866 self.assertEqual(report.return_code, 123)
867 report.done(Path("f4"), black.Changed.NO)
868 self.assertEqual(len(out_lines), 6)
869 self.assertEqual(len(err_lines), 2)
870 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
872 unstyle(str(report)),
873 "2 files reformatted, 3 files left unchanged, 2 files failed to"
876 self.assertEqual(report.return_code, 123)
879 unstyle(str(report)),
880 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
881 " would fail to reformat.",
886 unstyle(str(report)),
887 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
888 " would fail to reformat.",
891 def test_report_quiet(self) -> None:
892 report = black.Report(quiet=True)
896 def out(msg: str, **kwargs: Any) -> None:
897 out_lines.append(msg)
899 def err(msg: str, **kwargs: Any) -> None:
900 err_lines.append(msg)
902 with patch("black.out", out), patch("black.err", err):
903 report.done(Path("f1"), black.Changed.NO)
904 self.assertEqual(len(out_lines), 0)
905 self.assertEqual(len(err_lines), 0)
906 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
907 self.assertEqual(report.return_code, 0)
908 report.done(Path("f2"), black.Changed.YES)
909 self.assertEqual(len(out_lines), 0)
910 self.assertEqual(len(err_lines), 0)
912 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
914 report.done(Path("f3"), black.Changed.CACHED)
915 self.assertEqual(len(out_lines), 0)
916 self.assertEqual(len(err_lines), 0)
918 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
920 self.assertEqual(report.return_code, 0)
922 self.assertEqual(report.return_code, 1)
924 report.failed(Path("e1"), "boom")
925 self.assertEqual(len(out_lines), 0)
926 self.assertEqual(len(err_lines), 1)
927 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
929 unstyle(str(report)),
930 "1 file reformatted, 2 files left unchanged, 1 file failed to"
933 self.assertEqual(report.return_code, 123)
934 report.done(Path("f3"), black.Changed.YES)
935 self.assertEqual(len(out_lines), 0)
936 self.assertEqual(len(err_lines), 1)
938 unstyle(str(report)),
939 "2 files reformatted, 2 files left unchanged, 1 file failed to"
942 self.assertEqual(report.return_code, 123)
943 report.failed(Path("e2"), "boom")
944 self.assertEqual(len(out_lines), 0)
945 self.assertEqual(len(err_lines), 2)
946 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
948 unstyle(str(report)),
949 "2 files reformatted, 2 files left unchanged, 2 files failed to"
952 self.assertEqual(report.return_code, 123)
953 report.path_ignored(Path("wat"), "no match")
954 self.assertEqual(len(out_lines), 0)
955 self.assertEqual(len(err_lines), 2)
957 unstyle(str(report)),
958 "2 files reformatted, 2 files left unchanged, 2 files failed to"
961 self.assertEqual(report.return_code, 123)
962 report.done(Path("f4"), black.Changed.NO)
963 self.assertEqual(len(out_lines), 0)
964 self.assertEqual(len(err_lines), 2)
966 unstyle(str(report)),
967 "2 files reformatted, 3 files left unchanged, 2 files failed to"
970 self.assertEqual(report.return_code, 123)
973 unstyle(str(report)),
974 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
975 " would fail to reformat.",
980 unstyle(str(report)),
981 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
982 " would fail to reformat.",
985 def test_report_normal(self) -> None:
986 report = black.Report()
990 def out(msg: str, **kwargs: Any) -> None:
991 out_lines.append(msg)
993 def err(msg: str, **kwargs: Any) -> None:
994 err_lines.append(msg)
996 with patch("black.out", out), patch("black.err", err):
997 report.done(Path("f1"), black.Changed.NO)
998 self.assertEqual(len(out_lines), 0)
999 self.assertEqual(len(err_lines), 0)
1000 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
1001 self.assertEqual(report.return_code, 0)
1002 report.done(Path("f2"), black.Changed.YES)
1003 self.assertEqual(len(out_lines), 1)
1004 self.assertEqual(len(err_lines), 0)
1005 self.assertEqual(out_lines[-1], "reformatted f2")
1007 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
1009 report.done(Path("f3"), black.Changed.CACHED)
1010 self.assertEqual(len(out_lines), 1)
1011 self.assertEqual(len(err_lines), 0)
1012 self.assertEqual(out_lines[-1], "reformatted f2")
1014 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
1016 self.assertEqual(report.return_code, 0)
1018 self.assertEqual(report.return_code, 1)
1019 report.check = False
1020 report.failed(Path("e1"), "boom")
1021 self.assertEqual(len(out_lines), 1)
1022 self.assertEqual(len(err_lines), 1)
1023 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
1025 unstyle(str(report)),
1026 "1 file reformatted, 2 files left unchanged, 1 file failed to"
1029 self.assertEqual(report.return_code, 123)
1030 report.done(Path("f3"), black.Changed.YES)
1031 self.assertEqual(len(out_lines), 2)
1032 self.assertEqual(len(err_lines), 1)
1033 self.assertEqual(out_lines[-1], "reformatted f3")
1035 unstyle(str(report)),
1036 "2 files reformatted, 2 files left unchanged, 1 file failed to"
1039 self.assertEqual(report.return_code, 123)
1040 report.failed(Path("e2"), "boom")
1041 self.assertEqual(len(out_lines), 2)
1042 self.assertEqual(len(err_lines), 2)
1043 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
1045 unstyle(str(report)),
1046 "2 files reformatted, 2 files left unchanged, 2 files failed to"
1049 self.assertEqual(report.return_code, 123)
1050 report.path_ignored(Path("wat"), "no match")
1051 self.assertEqual(len(out_lines), 2)
1052 self.assertEqual(len(err_lines), 2)
1054 unstyle(str(report)),
1055 "2 files reformatted, 2 files left unchanged, 2 files failed to"
1058 self.assertEqual(report.return_code, 123)
1059 report.done(Path("f4"), black.Changed.NO)
1060 self.assertEqual(len(out_lines), 2)
1061 self.assertEqual(len(err_lines), 2)
1063 unstyle(str(report)),
1064 "2 files reformatted, 3 files left unchanged, 2 files failed to"
1067 self.assertEqual(report.return_code, 123)
1070 unstyle(str(report)),
1071 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1072 " would fail to reformat.",
1074 report.check = False
1077 unstyle(str(report)),
1078 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1079 " would fail to reformat.",
1082 def test_lib2to3_parse(self) -> None:
1083 with self.assertRaises(black.InvalidInput):
1084 black.lib2to3_parse("invalid syntax")
1086 straddling = "x + y"
1087 black.lib2to3_parse(straddling)
1088 black.lib2to3_parse(straddling, {TargetVersion.PY27})
1089 black.lib2to3_parse(straddling, {TargetVersion.PY36})
1090 black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
1092 py2_only = "print x"
1093 black.lib2to3_parse(py2_only)
1094 black.lib2to3_parse(py2_only, {TargetVersion.PY27})
1095 with self.assertRaises(black.InvalidInput):
1096 black.lib2to3_parse(py2_only, {TargetVersion.PY36})
1097 with self.assertRaises(black.InvalidInput):
1098 black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
1100 py3_only = "exec(x, end=y)"
1101 black.lib2to3_parse(py3_only)
1102 with self.assertRaises(black.InvalidInput):
1103 black.lib2to3_parse(py3_only, {TargetVersion.PY27})
1104 black.lib2to3_parse(py3_only, {TargetVersion.PY36})
1105 black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
1107 def test_get_features_used(self) -> None:
1108 node = black.lib2to3_parse("def f(*, arg): ...\n")
1109 self.assertEqual(black.get_features_used(node), set())
1110 node = black.lib2to3_parse("def f(*, arg,): ...\n")
1111 self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
1112 node = black.lib2to3_parse("f(*arg,)\n")
1114 black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
1116 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
1117 self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
1118 node = black.lib2to3_parse("123_456\n")
1119 self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
1120 node = black.lib2to3_parse("123456\n")
1121 self.assertEqual(black.get_features_used(node), set())
1122 source, expected = read_data("function")
1123 node = black.lib2to3_parse(source)
1124 expected_features = {
1125 Feature.TRAILING_COMMA_IN_CALL,
1126 Feature.TRAILING_COMMA_IN_DEF,
1129 self.assertEqual(black.get_features_used(node), expected_features)
1130 node = black.lib2to3_parse(expected)
1131 self.assertEqual(black.get_features_used(node), expected_features)
1132 source, expected = read_data("expression")
1133 node = black.lib2to3_parse(source)
1134 self.assertEqual(black.get_features_used(node), set())
1135 node = black.lib2to3_parse(expected)
1136 self.assertEqual(black.get_features_used(node), set())
1138 def test_get_future_imports(self) -> None:
1139 node = black.lib2to3_parse("\n")
1140 self.assertEqual(set(), black.get_future_imports(node))
1141 node = black.lib2to3_parse("from __future__ import black\n")
1142 self.assertEqual({"black"}, black.get_future_imports(node))
1143 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
1144 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1145 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
1146 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
1147 node = black.lib2to3_parse(
1148 "from __future__ import multiple\nfrom __future__ import imports\n"
1150 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1151 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
1152 self.assertEqual({"black"}, black.get_future_imports(node))
1153 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
1154 self.assertEqual({"black"}, black.get_future_imports(node))
1155 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
1156 self.assertEqual(set(), black.get_future_imports(node))
1157 node = black.lib2to3_parse("from some.module import black\n")
1158 self.assertEqual(set(), black.get_future_imports(node))
1159 node = black.lib2to3_parse(
1160 "from __future__ import unicode_literals as _unicode_literals"
1162 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
1163 node = black.lib2to3_parse(
1164 "from __future__ import unicode_literals as _lol, print"
1166 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
1168 def test_debug_visitor(self) -> None:
1169 source, _ = read_data("debug_visitor.py")
1170 expected, _ = read_data("debug_visitor.out")
1174 def out(msg: str, **kwargs: Any) -> None:
1175 out_lines.append(msg)
1177 def err(msg: str, **kwargs: Any) -> None:
1178 err_lines.append(msg)
1180 with patch("black.out", out), patch("black.err", err):
1181 black.DebugVisitor.show(source)
1182 actual = "\n".join(out_lines) + "\n"
1184 if expected != actual:
1185 log_name = black.dump_to_file(*out_lines)
1189 f"AST print out is different. Actual version dumped to {log_name}",
1192 def test_format_file_contents(self) -> None:
1194 mode = black.FileMode()
1195 with self.assertRaises(black.NothingChanged):
1196 black.format_file_contents(empty, mode=mode, fast=False)
1198 with self.assertRaises(black.NothingChanged):
1199 black.format_file_contents(just_nl, mode=mode, fast=False)
1200 same = "j = [1, 2, 3]\n"
1201 with self.assertRaises(black.NothingChanged):
1202 black.format_file_contents(same, mode=mode, fast=False)
1203 different = "j = [1,2,3]"
1205 actual = black.format_file_contents(different, mode=mode, fast=False)
1206 self.assertEqual(expected, actual)
1207 invalid = "return if you can"
1208 with self.assertRaises(black.InvalidInput) as e:
1209 black.format_file_contents(invalid, mode=mode, fast=False)
1210 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
1212 def test_endmarker(self) -> None:
1213 n = black.lib2to3_parse("\n")
1214 self.assertEqual(n.type, black.syms.file_input)
1215 self.assertEqual(len(n.children), 1)
1216 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
1218 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
1219 def test_assertFormatEqual(self) -> None:
1223 def out(msg: str, **kwargs: Any) -> None:
1224 out_lines.append(msg)
1226 def err(msg: str, **kwargs: Any) -> None:
1227 err_lines.append(msg)
1229 with patch("black.out", out), patch("black.err", err):
1230 with self.assertRaises(AssertionError):
1231 self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")
1233 out_str = "".join(out_lines)
1234 self.assertTrue("Expected tree:" in out_str)
1235 self.assertTrue("Actual tree:" in out_str)
1236 self.assertEqual("".join(err_lines), "")
1238 def test_cache_broken_file(self) -> None:
1239 mode = black.FileMode()
1240 with cache_dir() as workspace:
1241 cache_file = black.get_cache_file(mode)
1242 with cache_file.open("w") as fobj:
1243 fobj.write("this is not a pickle")
1244 self.assertEqual(black.read_cache(mode), {})
1245 src = (workspace / "test.py").resolve()
1246 with src.open("w") as fobj:
1247 fobj.write("print('hello')")
1248 self.invokeBlack([str(src)])
1249 cache = black.read_cache(mode)
1250 self.assertIn(src, cache)
1252 def test_cache_single_file_already_cached(self) -> None:
1253 mode = black.FileMode()
1254 with cache_dir() as workspace:
1255 src = (workspace / "test.py").resolve()
1256 with src.open("w") as fobj:
1257 fobj.write("print('hello')")
1258 black.write_cache({}, [src], mode)
1259 self.invokeBlack([str(src)])
1260 with src.open("r") as fobj:
1261 self.assertEqual(fobj.read(), "print('hello')")
1264 def test_cache_multiple_files(self) -> None:
1265 mode = black.FileMode()
1266 with cache_dir() as workspace, patch(
1267 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1269 one = (workspace / "one.py").resolve()
1270 with one.open("w") as fobj:
1271 fobj.write("print('hello')")
1272 two = (workspace / "two.py").resolve()
1273 with two.open("w") as fobj:
1274 fobj.write("print('hello')")
1275 black.write_cache({}, [one], mode)
1276 self.invokeBlack([str(workspace)])
1277 with one.open("r") as fobj:
1278 self.assertEqual(fobj.read(), "print('hello')")
1279 with two.open("r") as fobj:
1280 self.assertEqual(fobj.read(), 'print("hello")\n')
1281 cache = black.read_cache(mode)
1282 self.assertIn(one, cache)
1283 self.assertIn(two, cache)
1285 def test_no_cache_when_writeback_diff(self) -> None:
1286 mode = black.FileMode()
1287 with cache_dir() as workspace:
1288 src = (workspace / "test.py").resolve()
1289 with src.open("w") as fobj:
1290 fobj.write("print('hello')")
1291 self.invokeBlack([str(src), "--diff"])
1292 cache_file = black.get_cache_file(mode)
1293 self.assertFalse(cache_file.exists())
1295 def test_no_cache_when_stdin(self) -> None:
1296 mode = black.FileMode()
1298 result = CliRunner().invoke(
1299 black.main, ["-"], input=BytesIO(b"print('hello')")
1301 self.assertEqual(result.exit_code, 0)
1302 cache_file = black.get_cache_file(mode)
1303 self.assertFalse(cache_file.exists())
1305 def test_read_cache_no_cachefile(self) -> None:
1306 mode = black.FileMode()
1308 self.assertEqual(black.read_cache(mode), {})
1310 def test_write_cache_read_cache(self) -> None:
1311 mode = black.FileMode()
1312 with cache_dir() as workspace:
1313 src = (workspace / "test.py").resolve()
1315 black.write_cache({}, [src], mode)
1316 cache = black.read_cache(mode)
1317 self.assertIn(src, cache)
1318 self.assertEqual(cache[src], black.get_cache_info(src))
1320 def test_filter_cached(self) -> None:
1321 with TemporaryDirectory() as workspace:
1322 path = Path(workspace)
1323 uncached = (path / "uncached").resolve()
1324 cached = (path / "cached").resolve()
1325 cached_but_changed = (path / "changed").resolve()
1328 cached_but_changed.touch()
1329 cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
1330 todo, done = black.filter_cached(
1331 cache, {uncached, cached, cached_but_changed}
1333 self.assertEqual(todo, {uncached, cached_but_changed})
1334 self.assertEqual(done, {cached})
1336 def test_write_cache_creates_directory_if_needed(self) -> None:
1337 mode = black.FileMode()
1338 with cache_dir(exists=False) as workspace:
1339 self.assertFalse(workspace.exists())
1340 black.write_cache({}, [], mode)
1341 self.assertTrue(workspace.exists())
1344 def test_failed_formatting_does_not_get_cached(self) -> None:
1345 mode = black.FileMode()
1346 with cache_dir() as workspace, patch(
1347 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1349 failing = (workspace / "failing.py").resolve()
1350 with failing.open("w") as fobj:
1351 fobj.write("not actually python")
1352 clean = (workspace / "clean.py").resolve()
1353 with clean.open("w") as fobj:
1354 fobj.write('print("hello")\n')
1355 self.invokeBlack([str(workspace)], exit_code=123)
1356 cache = black.read_cache(mode)
1357 self.assertNotIn(failing, cache)
1358 self.assertIn(clean, cache)
1360 def test_write_cache_write_fail(self) -> None:
1361 mode = black.FileMode()
1362 with cache_dir(), patch.object(Path, "open") as mock:
1363 mock.side_effect = OSError
1364 black.write_cache({}, [], mode)
1367 @patch("black.ProcessPoolExecutor", MagicMock(side_effect=OSError))
1368 def test_works_in_mono_process_only_environment(self) -> None:
1369 with cache_dir() as workspace:
1371 (workspace / "one.py").resolve(),
1372 (workspace / "two.py").resolve(),
1374 f.write_text('print("hello")\n')
1375 self.invokeBlack([str(workspace)])
1378 def test_check_diff_use_together(self) -> None:
1380 # Files which will be reformatted.
1381 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1382 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1383 # Files which will not be reformatted.
1384 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1385 self.invokeBlack([str(src2), "--diff", "--check"])
1386 # Multi file command.
1387 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1389 def test_no_files(self) -> None:
1391 # Without an argument, black exits with error code 0.
1392 self.invokeBlack([])
1394 def test_broken_symlink(self) -> None:
1395 with cache_dir() as workspace:
1396 symlink = workspace / "broken_link.py"
1398 symlink.symlink_to("nonexistent.py")
1399 except OSError as e:
1400 self.skipTest(f"Can't create symlinks: {e}")
1401 self.invokeBlack([str(workspace.resolve())])
1403 def test_read_cache_line_lengths(self) -> None:
1404 mode = black.FileMode()
1405 short_mode = black.FileMode(line_length=1)
1406 with cache_dir() as workspace:
1407 path = (workspace / "file.py").resolve()
1409 black.write_cache({}, [path], mode)
1410 one = black.read_cache(mode)
1411 self.assertIn(path, one)
1412 two = black.read_cache(short_mode)
1413 self.assertNotIn(path, two)
1415 def test_tricky_unicode_symbols(self) -> None:
1416 source, expected = read_data("tricky_unicode_symbols")
1418 self.assertFormatEqual(expected, actual)
1419 black.assert_equivalent(source, actual)
1420 black.assert_stable(source, actual, black.FileMode())
1422 def test_single_file_force_pyi(self) -> None:
1423 reg_mode = black.FileMode()
1424 pyi_mode = black.FileMode(is_pyi=True)
1425 contents, expected = read_data("force_pyi")
1426 with cache_dir() as workspace:
1427 path = (workspace / "file.py").resolve()
1428 with open(path, "w") as fh:
1430 self.invokeBlack([str(path), "--pyi"])
1431 with open(path, "r") as fh:
1433 # verify cache with --pyi is separate
1434 pyi_cache = black.read_cache(pyi_mode)
1435 self.assertIn(path, pyi_cache)
1436 normal_cache = black.read_cache(reg_mode)
1437 self.assertNotIn(path, normal_cache)
1438 self.assertEqual(actual, expected)
1441 def test_multi_file_force_pyi(self) -> None:
1442 reg_mode = black.FileMode()
1443 pyi_mode = black.FileMode(is_pyi=True)
1444 contents, expected = read_data("force_pyi")
1445 with cache_dir() as workspace:
1447 (workspace / "file1.py").resolve(),
1448 (workspace / "file2.py").resolve(),
1451 with open(path, "w") as fh:
1453 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1455 with open(path, "r") as fh:
1457 self.assertEqual(actual, expected)
1458 # verify cache with --pyi is separate
1459 pyi_cache = black.read_cache(pyi_mode)
1460 normal_cache = black.read_cache(reg_mode)
1462 self.assertIn(path, pyi_cache)
1463 self.assertNotIn(path, normal_cache)
1465 def test_pipe_force_pyi(self) -> None:
1466 source, expected = read_data("force_pyi")
1467 result = CliRunner().invoke(
1468 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1470 self.assertEqual(result.exit_code, 0)
1471 actual = result.output
1472 self.assertFormatEqual(actual, expected)
1474 def test_single_file_force_py36(self) -> None:
1475 reg_mode = black.FileMode()
1476 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1477 source, expected = read_data("force_py36")
1478 with cache_dir() as workspace:
1479 path = (workspace / "file.py").resolve()
1480 with open(path, "w") as fh:
1482 self.invokeBlack([str(path), *PY36_ARGS])
1483 with open(path, "r") as fh:
1485 # verify cache with --target-version is separate
1486 py36_cache = black.read_cache(py36_mode)
1487 self.assertIn(path, py36_cache)
1488 normal_cache = black.read_cache(reg_mode)
1489 self.assertNotIn(path, normal_cache)
1490 self.assertEqual(actual, expected)
1493 def test_multi_file_force_py36(self) -> None:
1494 reg_mode = black.FileMode()
1495 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1496 source, expected = read_data("force_py36")
1497 with cache_dir() as workspace:
1499 (workspace / "file1.py").resolve(),
1500 (workspace / "file2.py").resolve(),
1503 with open(path, "w") as fh:
1505 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1507 with open(path, "r") as fh:
1509 self.assertEqual(actual, expected)
1510 # verify cache with --target-version is separate
1511 pyi_cache = black.read_cache(py36_mode)
1512 normal_cache = black.read_cache(reg_mode)
1514 self.assertIn(path, pyi_cache)
1515 self.assertNotIn(path, normal_cache)
1517 def test_collections(self) -> None:
1518 source, expected = read_data("collections")
1520 self.assertFormatEqual(expected, actual)
1521 black.assert_equivalent(source, actual)
1522 black.assert_stable(source, actual, black.FileMode())
1524 def test_pipe_force_py36(self) -> None:
1525 source, expected = read_data("force_py36")
1526 result = CliRunner().invoke(
1528 ["-", "-q", "--target-version=py36"],
1529 input=BytesIO(source.encode("utf8")),
1531 self.assertEqual(result.exit_code, 0)
1532 actual = result.output
1533 self.assertFormatEqual(actual, expected)
1535 def test_include_exclude(self) -> None:
1536 path = THIS_DIR / "data" / "include_exclude_tests"
1537 include = re.compile(r"\.pyi?$")
1538 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1539 report = black.Report()
1540 gitignore = PathSpec.from_lines("gitwildmatch", [])
1541 sources: List[Path] = []
1543 Path(path / "b/dont_exclude/a.py"),
1544 Path(path / "b/dont_exclude/a.pyi"),
1546 this_abs = THIS_DIR.resolve()
1548 black.gen_python_files(
1549 path.iterdir(), this_abs, include, [exclude], report, gitignore
1552 self.assertEqual(sorted(expected), sorted(sources))
1554 def test_gitignore_exclude(self) -> None:
1555 path = THIS_DIR / "data" / "include_exclude_tests"
1556 include = re.compile(r"\.pyi?$")
1557 exclude = re.compile(r"")
1558 report = black.Report()
1559 gitignore = PathSpec.from_lines(
1560 "gitwildmatch", ["exclude/", ".definitely_exclude"]
1562 sources: List[Path] = []
1564 Path(path / "b/dont_exclude/a.py"),
1565 Path(path / "b/dont_exclude/a.pyi"),
1567 this_abs = THIS_DIR.resolve()
1569 black.gen_python_files(
1570 path.iterdir(), this_abs, include, [exclude], report, gitignore
1573 self.assertEqual(sorted(expected), sorted(sources))
1575 def test_empty_include(self) -> None:
1576 path = THIS_DIR / "data" / "include_exclude_tests"
1577 report = black.Report()
1578 gitignore = PathSpec.from_lines("gitwildmatch", [])
1579 empty = re.compile(r"")
1580 sources: List[Path] = []
1582 Path(path / "b/exclude/a.pie"),
1583 Path(path / "b/exclude/a.py"),
1584 Path(path / "b/exclude/a.pyi"),
1585 Path(path / "b/dont_exclude/a.pie"),
1586 Path(path / "b/dont_exclude/a.py"),
1587 Path(path / "b/dont_exclude/a.pyi"),
1588 Path(path / "b/.definitely_exclude/a.pie"),
1589 Path(path / "b/.definitely_exclude/a.py"),
1590 Path(path / "b/.definitely_exclude/a.pyi"),
1592 this_abs = THIS_DIR.resolve()
1594 black.gen_python_files(
1598 [re.compile(black.DEFAULT_EXCLUDES)],
1603 self.assertEqual(sorted(expected), sorted(sources))
1605 def test_empty_exclude(self) -> None:
1606 path = THIS_DIR / "data" / "include_exclude_tests"
1607 report = black.Report()
1608 gitignore = PathSpec.from_lines("gitwildmatch", [])
1609 empty = re.compile(r"")
1610 sources: List[Path] = []
1612 Path(path / "b/dont_exclude/a.py"),
1613 Path(path / "b/dont_exclude/a.pyi"),
1614 Path(path / "b/exclude/a.py"),
1615 Path(path / "b/exclude/a.pyi"),
1616 Path(path / "b/.definitely_exclude/a.py"),
1617 Path(path / "b/.definitely_exclude/a.pyi"),
1619 this_abs = THIS_DIR.resolve()
1621 black.gen_python_files(
1624 re.compile(black.DEFAULT_INCLUDES),
1630 self.assertEqual(sorted(expected), sorted(sources))
1632 def test_invalid_include_exclude(self) -> None:
1633 for option in ["--include", "--exclude"]:
1634 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1636 def test_preserves_line_endings(self) -> None:
1637 with TemporaryDirectory() as workspace:
1638 test_file = Path(workspace) / "test.py"
1639 for nl in ["\n", "\r\n"]:
1640 contents = nl.join(["def f( ):", " pass"])
1641 test_file.write_bytes(contents.encode())
1642 ff(test_file, write_back=black.WriteBack.YES)
1643 updated_contents: bytes = test_file.read_bytes()
1644 self.assertIn(nl.encode(), updated_contents)
1646 self.assertNotIn(b"\r\n", updated_contents)
1648 def test_preserves_line_endings_via_stdin(self) -> None:
1649 for nl in ["\n", "\r\n"]:
1650 contents = nl.join(["def f( ):", " pass"])
1651 runner = BlackRunner()
1652 result = runner.invoke(
1653 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1655 self.assertEqual(result.exit_code, 0)
1656 output = runner.stdout_bytes
1657 self.assertIn(nl.encode("utf8"), output)
1659 self.assertNotIn(b"\r\n", output)
1661 def test_assert_equivalent_different_asts(self) -> None:
1662 with self.assertRaises(AssertionError):
1663 black.assert_equivalent("{}", "None")
1665 def test_symlink_out_of_root_directory(self) -> None:
1667 root = THIS_DIR.resolve()
1669 include = re.compile(black.DEFAULT_INCLUDES)
1670 exclude = re.compile(black.DEFAULT_EXCLUDES)
1671 report = black.Report()
1672 gitignore = PathSpec.from_lines("gitwildmatch", [])
1673 # `child` should behave like a symlink which resolved path is clearly
1674 # outside of the `root` directory.
1675 path.iterdir.return_value = [child]
1676 child.resolve.return_value = Path("/a/b/c")
1677 child.as_posix.return_value = "/a/b/c"
1678 child.is_symlink.return_value = True
1681 black.gen_python_files(
1682 path.iterdir(), root, include, exclude, report, gitignore
1685 except ValueError as ve:
1686 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1687 path.iterdir.assert_called_once()
1688 child.resolve.assert_called_once()
1689 child.is_symlink.assert_called_once()
1690 # `child` should behave like a strange file which resolved path is clearly
1691 # outside of the `root` directory.
1692 child.is_symlink.return_value = False
1693 with self.assertRaises(ValueError):
1695 black.gen_python_files(
1696 path.iterdir(), root, include, exclude, report, gitignore
1699 path.iterdir.assert_called()
1700 self.assertEqual(path.iterdir.call_count, 2)
1701 child.resolve.assert_called()
1702 self.assertEqual(child.resolve.call_count, 2)
1703 child.is_symlink.assert_called()
1704 self.assertEqual(child.is_symlink.call_count, 2)
1706 def test_shhh_click(self) -> None:
1708 from click import _unicodefun # type: ignore
1709 except ModuleNotFoundError:
1710 self.skipTest("Incompatible Click version")
1711 if not hasattr(_unicodefun, "_verify_python3_env"):
1712 self.skipTest("Incompatible Click version")
1713 # First, let's see if Click is crashing with a preferred ASCII charset.
1714 with patch("locale.getpreferredencoding") as gpe:
1715 gpe.return_value = "ASCII"
1716 with self.assertRaises(RuntimeError):
1717 _unicodefun._verify_python3_env()
1718 # Now, let's silence Click...
1720 # ...and confirm it's silent.
1721 with patch("locale.getpreferredencoding") as gpe:
1722 gpe.return_value = "ASCII"
1724 _unicodefun._verify_python3_env()
1725 except RuntimeError as re:
1726 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1728 def test_root_logger_not_used_directly(self) -> None:
1729 def fail(*args: Any, **kwargs: Any) -> None:
1730 self.fail("Record created with root logger")
1732 with patch.multiple(
1743 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1744 def test_blackd_main(self) -> None:
1745 with patch("blackd.web.run_app"):
1746 result = CliRunner().invoke(blackd.main, [])
1747 if result.exception is not None:
1748 raise result.exception
1749 self.assertEqual(result.exit_code, 0)
1751 def test_invalid_config_return_code(self) -> None:
1752 tmp_file = Path(black.dump_to_file())
1754 tmp_config = Path(black.dump_to_file())
1756 args = ["--config", str(tmp_config), str(tmp_file)]
1757 self.invokeBlack(args, exit_code=2, ignore_config=False)
1762 class BlackDTestCase(AioHTTPTestCase):
1763 async def get_application(self) -> web.Application:
1764 return blackd.make_app()
1766 # TODO: remove these decorators once the below is released
1767 # https://github.com/aio-libs/aiohttp/pull/3727
1768 @skip_if_exception("ClientOSError")
1769 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1771 async def test_blackd_request_needs_formatting(self) -> None:
1772 response = await self.client.post("/", data=b"print('hello world')")
1773 self.assertEqual(response.status, 200)
1774 self.assertEqual(response.charset, "utf8")
1775 self.assertEqual(await response.read(), b'print("hello world")\n')
1777 @skip_if_exception("ClientOSError")
1778 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1780 async def test_blackd_request_no_change(self) -> None:
1781 response = await self.client.post("/", data=b'print("hello world")\n')
1782 self.assertEqual(response.status, 204)
1783 self.assertEqual(await response.read(), b"")
1785 @skip_if_exception("ClientOSError")
1786 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1788 async def test_blackd_request_syntax_error(self) -> None:
1789 response = await self.client.post("/", data=b"what even ( is")
1790 self.assertEqual(response.status, 400)
1791 content = await response.text()
1793 content.startswith("Cannot parse"),
1794 msg=f"Expected error to start with 'Cannot parse', got {repr(content)}",
1797 @skip_if_exception("ClientOSError")
1798 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1800 async def test_blackd_unsupported_version(self) -> None:
1801 response = await self.client.post(
1802 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "2"}
1804 self.assertEqual(response.status, 501)
1806 @skip_if_exception("ClientOSError")
1807 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1809 async def test_blackd_supported_version(self) -> None:
1810 response = await self.client.post(
1811 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "1"}
1813 self.assertEqual(response.status, 200)
1815 @skip_if_exception("ClientOSError")
1816 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1818 async def test_blackd_invalid_python_variant(self) -> None:
1819 async def check(header_value: str, expected_status: int = 400) -> None:
1820 response = await self.client.post(
1821 "/", data=b"what", headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1823 self.assertEqual(response.status, expected_status)
1826 await check("ruby3.5")
1827 await check("pyi3.6")
1828 await check("py1.5")
1830 await check("py2.8")
1832 await check("pypy3.0")
1833 await check("jython3.4")
1835 @skip_if_exception("ClientOSError")
1836 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1838 async def test_blackd_pyi(self) -> None:
1839 source, expected = read_data("stub.pyi")
1840 response = await self.client.post(
1841 "/", data=source, headers={blackd.PYTHON_VARIANT_HEADER: "pyi"}
1843 self.assertEqual(response.status, 200)
1844 self.assertEqual(await response.text(), expected)
1846 @skip_if_exception("ClientOSError")
1847 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1849 async def test_blackd_diff(self) -> None:
1850 diff_header = re.compile(
1851 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"
1854 source, _ = read_data("blackd_diff.py")
1855 expected, _ = read_data("blackd_diff.diff")
1857 response = await self.client.post(
1858 "/", data=source, headers={blackd.DIFF_HEADER: "true"}
1860 self.assertEqual(response.status, 200)
1862 actual = await response.text()
1863 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
1864 self.assertEqual(actual, expected)
1866 @skip_if_exception("ClientOSError")
1867 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1869 async def test_blackd_python_variant(self) -> None:
1872 " and_has_a_bunch_of,\n"
1873 " very_long_arguments_too,\n"
1874 " and_lots_of_them_as_well_lol,\n"
1875 " **and_very_long_keyword_arguments\n"
1880 async def check(header_value: str, expected_status: int) -> None:
1881 response = await self.client.post(
1882 "/", data=code, headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1885 response.status, expected_status, msg=await response.text()
1888 await check("3.6", 200)
1889 await check("py3.6", 200)
1890 await check("3.6,3.7", 200)
1891 await check("3.6,py3.7", 200)
1892 await check("py36,py37", 200)
1893 await check("36", 200)
1894 await check("3.6.4", 200)
1896 await check("2", 204)
1897 await check("2.7", 204)
1898 await check("py2.7", 204)
1899 await check("3.4", 204)
1900 await check("py3.4", 204)
1901 await check("py34,py36", 204)
1902 await check("34", 204)
1904 @skip_if_exception("ClientOSError")
1905 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1907 async def test_blackd_line_length(self) -> None:
1908 response = await self.client.post(
1909 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "7"}
1911 self.assertEqual(response.status, 200)
1913 @skip_if_exception("ClientOSError")
1914 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1916 async def test_blackd_invalid_line_length(self) -> None:
1917 response = await self.client.post(
1918 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "NaN"}
1920 self.assertEqual(response.status, 400)
1922 @skip_if_exception("ClientOSError")
1923 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1925 async def test_blackd_response_black_version_header(self) -> None:
1926 response = await self.client.post("/")
1927 self.assertIsNotNone(response.headers.get(blackd.BLACK_VERSION_HEADER))
1930 if __name__ == "__main__":
1931 unittest.main(module="test_black")