All patches and comments are welcome. Please squash your changes to logical
commits before using git-format-patch and git-send-email to
patches@git.madduck.net.
If you'd read over the Git project's submission guidelines and adhered to them,
I'd be especially grateful.
4 from concurrent.futures import ThreadPoolExecutor
5 from contextlib import contextmanager
6 from functools import partial
7 from io import BytesIO, TextIOWrapper
9 from pathlib import Path
12 from tempfile import TemporaryDirectory
13 from typing import Any, BinaryIO, Generator, List, Tuple, Iterator, TypeVar
15 from unittest.mock import patch, MagicMock
17 from click import unstyle
18 from click.testing import CliRunner
21 from black import Feature, TargetVersion
25 from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
26 from aiohttp import web
28 has_blackd_deps = False
30 has_blackd_deps = True
32 from pathspec import PathSpec
34 ff = partial(black.format_file_in_place, mode=black.FileMode(), fast=True)
35 fs = partial(black.format_str, mode=black.FileMode())
36 THIS_FILE = Path(__file__)
37 THIS_DIR = THIS_FILE.parent
38 DETERMINISTIC_HEADER = "[Deterministic header]"
39 EMPTY_LINE = "# EMPTY LINE WITH WHITESPACE" + " (this comment will be removed)"
41 f"--target-version={version.name.lower()}" for version in black.PY36_VERSIONS
47 def dump_to_stderr(*output: str) -> str:
48 return "\n" + "\n".join(output) + "\n"
51 def read_data(name: str, data: bool = True) -> Tuple[str, str]:
52 """read_data('test_name') -> 'input', 'output'"""
53 if not name.endswith((".py", ".pyi", ".out", ".diff")):
55 _input: List[str] = []
56 _output: List[str] = []
57 base_dir = THIS_DIR / "data" if data else THIS_DIR
58 with open(base_dir / name, "r", encoding="utf8") as test:
59 lines = test.readlines()
62 line = line.replace(EMPTY_LINE, "")
63 if line.rstrip() == "# output":
68 if _input and not _output:
69 # If there's no output marker, treat the entire file as already pre-formatted.
71 return "".join(_input).strip() + "\n", "".join(_output).strip() + "\n"
75 def cache_dir(exists: bool = True) -> Iterator[Path]:
76 with TemporaryDirectory() as workspace:
77 cache_dir = Path(workspace)
79 cache_dir = cache_dir / "new"
80 with patch("black.CACHE_DIR", cache_dir):
85 def event_loop(close: bool) -> Iterator[None]:
86 policy = asyncio.get_event_loop_policy()
87 loop = policy.new_event_loop()
88 asyncio.set_event_loop(loop)
98 def skip_if_exception(e: str) -> Iterator[None]:
101 except Exception as exc:
102 if exc.__class__.__name__ == e:
103 unittest.skip(f"Encountered expected exception {exc}, skipping")
108 class BlackRunner(CliRunner):
109 """Modify CliRunner so that stderr is not merged with stdout.
111 This is a hack that can be removed once we depend on Click 7.x"""
113 def __init__(self) -> None:
114 self.stderrbuf = BytesIO()
115 self.stdoutbuf = BytesIO()
116 self.stdout_bytes = b""
117 self.stderr_bytes = b""
121 def isolation(self, *args: Any, **kwargs: Any) -> Generator[BinaryIO, None, None]:
122 with super().isolation(*args, **kwargs) as output:
124 hold_stderr = sys.stderr
125 sys.stderr = TextIOWrapper(self.stderrbuf, encoding=self.charset)
128 self.stdout_bytes = sys.stdout.buffer.getvalue() # type: ignore
129 self.stderr_bytes = sys.stderr.buffer.getvalue() # type: ignore
130 sys.stderr = hold_stderr
133 class BlackTestCase(unittest.TestCase):
136 def assertFormatEqual(self, expected: str, actual: str) -> None:
137 if actual != expected and not os.environ.get("SKIP_AST_PRINT"):
138 bdv: black.DebugVisitor[Any]
139 black.out("Expected tree:", fg="green")
141 exp_node = black.lib2to3_parse(expected)
142 bdv = black.DebugVisitor()
143 list(bdv.visit(exp_node))
144 except Exception as ve:
146 black.out("Actual tree:", fg="red")
148 exp_node = black.lib2to3_parse(actual)
149 bdv = black.DebugVisitor()
150 list(bdv.visit(exp_node))
151 except Exception as ve:
153 self.assertEqual(expected, actual)
156 self, args: List[str], exit_code: int = 0, ignore_config: bool = True
158 runner = BlackRunner()
160 args = ["--config", str(THIS_DIR / "empty.toml"), *args]
161 result = runner.invoke(black.main, args)
162 self.assertEqual(result.exit_code, exit_code, msg=runner.stderr_bytes.decode())
164 @patch("black.dump_to_file", dump_to_stderr)
165 def checkSourceFile(self, name: str) -> None:
166 path = THIS_DIR.parent / name
167 source, expected = read_data(str(path), data=False)
169 self.assertFormatEqual(expected, actual)
170 black.assert_equivalent(source, actual)
171 black.assert_stable(source, actual, black.FileMode())
172 self.assertFalse(ff(path))
174 @patch("black.dump_to_file", dump_to_stderr)
175 def test_empty(self) -> None:
176 source = expected = ""
178 self.assertFormatEqual(expected, actual)
179 black.assert_equivalent(source, actual)
180 black.assert_stable(source, actual, black.FileMode())
182 def test_empty_ff(self) -> None:
184 tmp_file = Path(black.dump_to_file())
186 self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES))
187 with open(tmp_file, encoding="utf8") as f:
191 self.assertFormatEqual(expected, actual)
193 def test_self(self) -> None:
194 self.checkSourceFile("tests/test_black.py")
196 def test_black(self) -> None:
197 self.checkSourceFile("black.py")
199 def test_pygram(self) -> None:
200 self.checkSourceFile("blib2to3/pygram.py")
202 def test_pytree(self) -> None:
203 self.checkSourceFile("blib2to3/pytree.py")
205 def test_conv(self) -> None:
206 self.checkSourceFile("blib2to3/pgen2/conv.py")
208 def test_driver(self) -> None:
209 self.checkSourceFile("blib2to3/pgen2/driver.py")
211 def test_grammar(self) -> None:
212 self.checkSourceFile("blib2to3/pgen2/grammar.py")
214 def test_literals(self) -> None:
215 self.checkSourceFile("blib2to3/pgen2/literals.py")
217 def test_parse(self) -> None:
218 self.checkSourceFile("blib2to3/pgen2/parse.py")
220 def test_pgen(self) -> None:
221 self.checkSourceFile("blib2to3/pgen2/pgen.py")
223 def test_tokenize(self) -> None:
224 self.checkSourceFile("blib2to3/pgen2/tokenize.py")
226 def test_token(self) -> None:
227 self.checkSourceFile("blib2to3/pgen2/token.py")
229 def test_setup(self) -> None:
230 self.checkSourceFile("setup.py")
232 def test_piping(self) -> None:
233 source, expected = read_data("../black", data=False)
234 result = BlackRunner().invoke(
236 ["-", "--fast", f"--line-length={black.DEFAULT_LINE_LENGTH}"],
237 input=BytesIO(source.encode("utf8")),
239 self.assertEqual(result.exit_code, 0)
240 self.assertFormatEqual(expected, result.output)
241 black.assert_equivalent(source, result.output)
242 black.assert_stable(source, result.output, black.FileMode())
244 def test_piping_diff(self) -> None:
245 diff_header = re.compile(
246 rf"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d\d\d\d "
249 source, _ = read_data("expression.py")
250 expected, _ = read_data("expression.diff")
251 config = THIS_DIR / "data" / "empty_pyproject.toml"
255 f"--line-length={black.DEFAULT_LINE_LENGTH}",
257 f"--config={config}",
259 result = BlackRunner().invoke(
260 black.main, args, input=BytesIO(source.encode("utf8"))
262 self.assertEqual(result.exit_code, 0)
263 actual = diff_header.sub(DETERMINISTIC_HEADER, result.output)
264 actual = actual.rstrip() + "\n" # the diff output has a trailing space
265 self.assertEqual(expected, actual)
267 def test_piping_diff_with_color(self) -> None:
268 source, _ = read_data("expression.py")
269 config = THIS_DIR / "data" / "empty_pyproject.toml"
273 f"--line-length={black.DEFAULT_LINE_LENGTH}",
276 f"--config={config}",
278 result = BlackRunner().invoke(
279 black.main, args, input=BytesIO(source.encode("utf8"))
281 actual = result.output
282 # Again, the contents are checked in a different test, so only look for colors.
283 self.assertIn("\033[1;37m", actual)
284 self.assertIn("\033[36m", actual)
285 self.assertIn("\033[32m", actual)
286 self.assertIn("\033[31m", actual)
287 self.assertIn("\033[0m", actual)
289 @patch("black.dump_to_file", dump_to_stderr)
290 def test_function(self) -> None:
291 source, expected = read_data("function")
293 self.assertFormatEqual(expected, actual)
294 black.assert_equivalent(source, actual)
295 black.assert_stable(source, actual, black.FileMode())
297 @patch("black.dump_to_file", dump_to_stderr)
298 def test_function2(self) -> None:
299 source, expected = read_data("function2")
301 self.assertFormatEqual(expected, actual)
302 black.assert_equivalent(source, actual)
303 black.assert_stable(source, actual, black.FileMode())
305 @patch("black.dump_to_file", dump_to_stderr)
306 def test_function_trailing_comma(self) -> None:
307 source, expected = read_data("function_trailing_comma")
309 self.assertFormatEqual(expected, actual)
310 black.assert_equivalent(source, actual)
311 black.assert_stable(source, actual, black.FileMode())
313 @patch("black.dump_to_file", dump_to_stderr)
314 def test_expression(self) -> None:
315 source, expected = read_data("expression")
317 self.assertFormatEqual(expected, actual)
318 black.assert_equivalent(source, actual)
319 black.assert_stable(source, actual, black.FileMode())
321 @patch("black.dump_to_file", dump_to_stderr)
322 def test_pep_572(self) -> None:
323 source, expected = read_data("pep_572")
325 self.assertFormatEqual(expected, actual)
326 black.assert_stable(source, actual, black.FileMode())
327 if sys.version_info >= (3, 8):
328 black.assert_equivalent(source, actual)
330 def test_pep_572_version_detection(self) -> None:
331 source, _ = read_data("pep_572")
332 root = black.lib2to3_parse(source)
333 features = black.get_features_used(root)
334 self.assertIn(black.Feature.ASSIGNMENT_EXPRESSIONS, features)
335 versions = black.detect_target_versions(root)
336 self.assertIn(black.TargetVersion.PY38, versions)
338 def test_expression_ff(self) -> None:
339 source, expected = read_data("expression")
340 tmp_file = Path(black.dump_to_file(source))
342 self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
343 with open(tmp_file, encoding="utf8") as f:
347 self.assertFormatEqual(expected, actual)
348 with patch("black.dump_to_file", dump_to_stderr):
349 black.assert_equivalent(source, actual)
350 black.assert_stable(source, actual, black.FileMode())
352 def test_expression_diff(self) -> None:
353 source, _ = read_data("expression.py")
354 expected, _ = read_data("expression.diff")
355 tmp_file = Path(black.dump_to_file(source))
356 diff_header = re.compile(
357 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
358 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
361 result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
362 self.assertEqual(result.exit_code, 0)
365 actual = result.output
366 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
367 actual = actual.rstrip() + "\n" # the diff output has a trailing space
368 if expected != actual:
369 dump = black.dump_to_file(actual)
371 "Expected diff isn't equal to the actual. If you made changes to"
372 " expression.py and this is an anticipated difference, overwrite"
373 f" tests/data/expression.diff with {dump}"
375 self.assertEqual(expected, actual, msg)
377 def test_expression_diff_with_color(self) -> None:
378 source, _ = read_data("expression.py")
379 expected, _ = read_data("expression.diff")
380 tmp_file = Path(black.dump_to_file(source))
382 result = BlackRunner().invoke(
383 black.main, ["--diff", "--color", str(tmp_file)]
387 actual = result.output
388 # We check the contents of the diff in `test_expression_diff`. All
389 # we need to check here is that color codes exist in the result.
390 self.assertIn("\033[1;37m", actual)
391 self.assertIn("\033[36m", actual)
392 self.assertIn("\033[32m", actual)
393 self.assertIn("\033[31m", actual)
394 self.assertIn("\033[0m", actual)
396 @patch("black.dump_to_file", dump_to_stderr)
397 def test_fstring(self) -> None:
398 source, expected = read_data("fstring")
400 self.assertFormatEqual(expected, actual)
401 black.assert_equivalent(source, actual)
402 black.assert_stable(source, actual, black.FileMode())
404 @patch("black.dump_to_file", dump_to_stderr)
405 def test_pep_570(self) -> None:
406 source, expected = read_data("pep_570")
408 self.assertFormatEqual(expected, actual)
409 black.assert_stable(source, actual, black.FileMode())
410 if sys.version_info >= (3, 8):
411 black.assert_equivalent(source, actual)
413 def test_detect_pos_only_arguments(self) -> None:
414 source, _ = read_data("pep_570")
415 root = black.lib2to3_parse(source)
416 features = black.get_features_used(root)
417 self.assertIn(black.Feature.POS_ONLY_ARGUMENTS, features)
418 versions = black.detect_target_versions(root)
419 self.assertIn(black.TargetVersion.PY38, versions)
421 @patch("black.dump_to_file", dump_to_stderr)
422 def test_string_quotes(self) -> None:
423 source, expected = read_data("string_quotes")
425 self.assertFormatEqual(expected, actual)
426 black.assert_equivalent(source, actual)
427 black.assert_stable(source, actual, black.FileMode())
428 mode = black.FileMode(string_normalization=False)
429 not_normalized = fs(source, mode=mode)
430 self.assertFormatEqual(source.replace("\\\n", ""), not_normalized)
431 black.assert_equivalent(source, not_normalized)
432 black.assert_stable(source, not_normalized, mode=mode)
434 @patch("black.dump_to_file", dump_to_stderr)
435 def test_docstring(self) -> None:
436 source, expected = read_data("docstring")
438 self.assertFormatEqual(expected, actual)
439 black.assert_equivalent(source, actual)
440 black.assert_stable(source, actual, black.FileMode())
442 def test_long_strings(self) -> None:
443 """Tests for splitting long strings."""
444 source, expected = read_data("long_strings")
446 self.assertFormatEqual(expected, actual)
447 black.assert_equivalent(source, actual)
448 black.assert_stable(source, actual, black.FileMode())
450 @patch("black.dump_to_file", dump_to_stderr)
451 def test_long_strings__edge_case(self) -> None:
452 """Edge-case tests for splitting long strings."""
453 source, expected = read_data("long_strings__edge_case")
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__regression(self) -> None:
461 """Regression tests for splitting long strings."""
462 source, expected = read_data("long_strings__regression")
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_slices(self) -> None:
470 source, expected = read_data("slices")
472 self.assertFormatEqual(expected, actual)
473 black.assert_equivalent(source, actual)
474 black.assert_stable(source, actual, black.FileMode())
476 @patch("black.dump_to_file", dump_to_stderr)
477 def test_comments(self) -> None:
478 source, expected = read_data("comments")
480 self.assertFormatEqual(expected, actual)
481 black.assert_equivalent(source, actual)
482 black.assert_stable(source, actual, black.FileMode())
484 @patch("black.dump_to_file", dump_to_stderr)
485 def test_comments2(self) -> None:
486 source, expected = read_data("comments2")
488 self.assertFormatEqual(expected, actual)
489 black.assert_equivalent(source, actual)
490 black.assert_stable(source, actual, black.FileMode())
492 @patch("black.dump_to_file", dump_to_stderr)
493 def test_comments3(self) -> None:
494 source, expected = read_data("comments3")
496 self.assertFormatEqual(expected, actual)
497 black.assert_equivalent(source, actual)
498 black.assert_stable(source, actual, black.FileMode())
500 @patch("black.dump_to_file", dump_to_stderr)
501 def test_comments4(self) -> None:
502 source, expected = read_data("comments4")
504 self.assertFormatEqual(expected, actual)
505 black.assert_equivalent(source, actual)
506 black.assert_stable(source, actual, black.FileMode())
508 @patch("black.dump_to_file", dump_to_stderr)
509 def test_comments5(self) -> None:
510 source, expected = read_data("comments5")
512 self.assertFormatEqual(expected, actual)
513 black.assert_equivalent(source, actual)
514 black.assert_stable(source, actual, black.FileMode())
516 @patch("black.dump_to_file", dump_to_stderr)
517 def test_comments6(self) -> None:
518 source, expected = read_data("comments6")
520 self.assertFormatEqual(expected, actual)
521 black.assert_equivalent(source, actual)
522 black.assert_stable(source, actual, black.FileMode())
524 @patch("black.dump_to_file", dump_to_stderr)
525 def test_comments7(self) -> None:
526 source, expected = read_data("comments7")
528 self.assertFormatEqual(expected, actual)
529 black.assert_equivalent(source, actual)
530 black.assert_stable(source, actual, black.FileMode())
532 @patch("black.dump_to_file", dump_to_stderr)
533 def test_comment_after_escaped_newline(self) -> None:
534 source, expected = read_data("comment_after_escaped_newline")
536 self.assertFormatEqual(expected, actual)
537 black.assert_equivalent(source, actual)
538 black.assert_stable(source, actual, black.FileMode())
540 @patch("black.dump_to_file", dump_to_stderr)
541 def test_cantfit(self) -> None:
542 source, expected = read_data("cantfit")
544 self.assertFormatEqual(expected, actual)
545 black.assert_equivalent(source, actual)
546 black.assert_stable(source, actual, black.FileMode())
548 @patch("black.dump_to_file", dump_to_stderr)
549 def test_import_spacing(self) -> None:
550 source, expected = read_data("import_spacing")
552 self.assertFormatEqual(expected, actual)
553 black.assert_equivalent(source, actual)
554 black.assert_stable(source, actual, black.FileMode())
556 @patch("black.dump_to_file", dump_to_stderr)
557 def test_composition(self) -> None:
558 source, expected = read_data("composition")
560 self.assertFormatEqual(expected, actual)
561 black.assert_equivalent(source, actual)
562 black.assert_stable(source, actual, black.FileMode())
564 @patch("black.dump_to_file", dump_to_stderr)
565 def test_empty_lines(self) -> None:
566 source, expected = read_data("empty_lines")
568 self.assertFormatEqual(expected, actual)
569 black.assert_equivalent(source, actual)
570 black.assert_stable(source, actual, black.FileMode())
572 @patch("black.dump_to_file", dump_to_stderr)
573 def test_remove_parens(self) -> None:
574 source, expected = read_data("remove_parens")
576 self.assertFormatEqual(expected, actual)
577 black.assert_equivalent(source, actual)
578 black.assert_stable(source, actual, black.FileMode())
580 @patch("black.dump_to_file", dump_to_stderr)
581 def test_string_prefixes(self) -> None:
582 source, expected = read_data("string_prefixes")
584 self.assertFormatEqual(expected, actual)
585 black.assert_equivalent(source, actual)
586 black.assert_stable(source, actual, black.FileMode())
588 @patch("black.dump_to_file", dump_to_stderr)
589 def test_numeric_literals(self) -> None:
590 source, expected = read_data("numeric_literals")
591 mode = black.FileMode(target_versions=black.PY36_VERSIONS)
592 actual = fs(source, mode=mode)
593 self.assertFormatEqual(expected, actual)
594 black.assert_equivalent(source, actual)
595 black.assert_stable(source, actual, mode)
597 @patch("black.dump_to_file", dump_to_stderr)
598 def test_numeric_literals_ignoring_underscores(self) -> None:
599 source, expected = read_data("numeric_literals_skip_underscores")
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_py2(self) -> None:
608 source, expected = read_data("numeric_literals_py2")
610 self.assertFormatEqual(expected, actual)
611 black.assert_stable(source, actual, black.FileMode())
613 @patch("black.dump_to_file", dump_to_stderr)
614 def test_python2(self) -> None:
615 source, expected = read_data("python2")
617 self.assertFormatEqual(expected, actual)
618 black.assert_equivalent(source, actual)
619 black.assert_stable(source, actual, black.FileMode())
621 @patch("black.dump_to_file", dump_to_stderr)
622 def test_python2_print_function(self) -> None:
623 source, expected = read_data("python2_print_function")
624 mode = black.FileMode(target_versions={TargetVersion.PY27})
625 actual = fs(source, mode=mode)
626 self.assertFormatEqual(expected, actual)
627 black.assert_equivalent(source, actual)
628 black.assert_stable(source, actual, mode)
630 @patch("black.dump_to_file", dump_to_stderr)
631 def test_python2_unicode_literals(self) -> None:
632 source, expected = read_data("python2_unicode_literals")
634 self.assertFormatEqual(expected, actual)
635 black.assert_equivalent(source, actual)
636 black.assert_stable(source, actual, black.FileMode())
638 @patch("black.dump_to_file", dump_to_stderr)
639 def test_stub(self) -> None:
640 mode = black.FileMode(is_pyi=True)
641 source, expected = read_data("stub.pyi")
642 actual = fs(source, mode=mode)
643 self.assertFormatEqual(expected, actual)
644 black.assert_stable(source, actual, mode)
646 @patch("black.dump_to_file", dump_to_stderr)
647 def test_async_as_identifier(self) -> None:
648 source_path = (THIS_DIR / "data" / "async_as_identifier.py").resolve()
649 source, expected = read_data("async_as_identifier")
651 self.assertFormatEqual(expected, actual)
652 major, minor = sys.version_info[:2]
653 if major < 3 or (major <= 3 and minor < 7):
654 black.assert_equivalent(source, actual)
655 black.assert_stable(source, actual, black.FileMode())
656 # ensure black can parse this when the target is 3.6
657 self.invokeBlack([str(source_path), "--target-version", "py36"])
658 # but not on 3.7, because async/await is no longer an identifier
659 self.invokeBlack([str(source_path), "--target-version", "py37"], exit_code=123)
661 @patch("black.dump_to_file", dump_to_stderr)
662 def test_python37(self) -> None:
663 source_path = (THIS_DIR / "data" / "python37.py").resolve()
664 source, expected = read_data("python37")
666 self.assertFormatEqual(expected, actual)
667 major, minor = sys.version_info[:2]
668 if major > 3 or (major == 3 and minor >= 7):
669 black.assert_equivalent(source, actual)
670 black.assert_stable(source, actual, black.FileMode())
671 # ensure black can parse this when the target is 3.7
672 self.invokeBlack([str(source_path), "--target-version", "py37"])
673 # but not on 3.6, because we use async as a reserved keyword
674 self.invokeBlack([str(source_path), "--target-version", "py36"], exit_code=123)
676 @patch("black.dump_to_file", dump_to_stderr)
677 def test_python38(self) -> None:
678 source, expected = read_data("python38")
680 self.assertFormatEqual(expected, actual)
681 major, minor = sys.version_info[:2]
682 if major > 3 or (major == 3 and minor >= 8):
683 black.assert_equivalent(source, actual)
684 black.assert_stable(source, actual, black.FileMode())
686 @patch("black.dump_to_file", dump_to_stderr)
687 def test_fmtonoff(self) -> None:
688 source, expected = read_data("fmtonoff")
690 self.assertFormatEqual(expected, actual)
691 black.assert_equivalent(source, actual)
692 black.assert_stable(source, actual, black.FileMode())
694 @patch("black.dump_to_file", dump_to_stderr)
695 def test_fmtonoff2(self) -> None:
696 source, expected = read_data("fmtonoff2")
698 self.assertFormatEqual(expected, actual)
699 black.assert_equivalent(source, actual)
700 black.assert_stable(source, actual, black.FileMode())
702 @patch("black.dump_to_file", dump_to_stderr)
703 def test_fmtonoff3(self) -> None:
704 source, expected = read_data("fmtonoff3")
706 self.assertFormatEqual(expected, actual)
707 black.assert_equivalent(source, actual)
708 black.assert_stable(source, actual, black.FileMode())
710 @patch("black.dump_to_file", dump_to_stderr)
711 def test_fmtonoff4(self) -> None:
712 source, expected = read_data("fmtonoff4")
714 self.assertFormatEqual(expected, actual)
715 black.assert_equivalent(source, actual)
716 black.assert_stable(source, actual, black.FileMode())
718 @patch("black.dump_to_file", dump_to_stderr)
719 def test_remove_empty_parentheses_after_class(self) -> None:
720 source, expected = read_data("class_blank_parentheses")
722 self.assertFormatEqual(expected, actual)
723 black.assert_equivalent(source, actual)
724 black.assert_stable(source, actual, black.FileMode())
726 @patch("black.dump_to_file", dump_to_stderr)
727 def test_new_line_between_class_and_code(self) -> None:
728 source, expected = read_data("class_methods_new_line")
730 self.assertFormatEqual(expected, actual)
731 black.assert_equivalent(source, actual)
732 black.assert_stable(source, actual, black.FileMode())
734 @patch("black.dump_to_file", dump_to_stderr)
735 def test_bracket_match(self) -> None:
736 source, expected = read_data("bracketmatch")
738 self.assertFormatEqual(expected, actual)
739 black.assert_equivalent(source, actual)
740 black.assert_stable(source, actual, black.FileMode())
742 @patch("black.dump_to_file", dump_to_stderr)
743 def test_tuple_assign(self) -> None:
744 source, expected = read_data("tupleassign")
746 self.assertFormatEqual(expected, actual)
747 black.assert_equivalent(source, actual)
748 black.assert_stable(source, actual, black.FileMode())
750 @patch("black.dump_to_file", dump_to_stderr)
751 def test_beginning_backslash(self) -> None:
752 source, expected = read_data("beginning_backslash")
754 self.assertFormatEqual(expected, actual)
755 black.assert_equivalent(source, actual)
756 black.assert_stable(source, actual, black.FileMode())
758 def test_tab_comment_indentation(self) -> None:
759 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
760 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
761 self.assertFormatEqual(contents_spc, fs(contents_spc))
762 self.assertFormatEqual(contents_spc, fs(contents_tab))
764 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
765 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
766 self.assertFormatEqual(contents_spc, fs(contents_spc))
767 self.assertFormatEqual(contents_spc, fs(contents_tab))
769 # mixed tabs and spaces (valid Python 2 code)
770 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t# comment\n pass\n"
771 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
772 self.assertFormatEqual(contents_spc, fs(contents_spc))
773 self.assertFormatEqual(contents_spc, fs(contents_tab))
775 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t\t# comment\n pass\n"
776 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
777 self.assertFormatEqual(contents_spc, fs(contents_spc))
778 self.assertFormatEqual(contents_spc, fs(contents_tab))
780 def test_report_verbose(self) -> None:
781 report = black.Report(verbose=True)
785 def out(msg: str, **kwargs: Any) -> None:
786 out_lines.append(msg)
788 def err(msg: str, **kwargs: Any) -> None:
789 err_lines.append(msg)
791 with patch("black.out", out), patch("black.err", err):
792 report.done(Path("f1"), black.Changed.NO)
793 self.assertEqual(len(out_lines), 1)
794 self.assertEqual(len(err_lines), 0)
795 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
796 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
797 self.assertEqual(report.return_code, 0)
798 report.done(Path("f2"), black.Changed.YES)
799 self.assertEqual(len(out_lines), 2)
800 self.assertEqual(len(err_lines), 0)
801 self.assertEqual(out_lines[-1], "reformatted f2")
803 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
805 report.done(Path("f3"), black.Changed.CACHED)
806 self.assertEqual(len(out_lines), 3)
807 self.assertEqual(len(err_lines), 0)
809 out_lines[-1], "f3 wasn't modified on disk since last run."
812 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
814 self.assertEqual(report.return_code, 0)
816 self.assertEqual(report.return_code, 1)
818 report.failed(Path("e1"), "boom")
819 self.assertEqual(len(out_lines), 3)
820 self.assertEqual(len(err_lines), 1)
821 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
823 unstyle(str(report)),
824 "1 file reformatted, 2 files left unchanged, 1 file failed to"
827 self.assertEqual(report.return_code, 123)
828 report.done(Path("f3"), black.Changed.YES)
829 self.assertEqual(len(out_lines), 4)
830 self.assertEqual(len(err_lines), 1)
831 self.assertEqual(out_lines[-1], "reformatted f3")
833 unstyle(str(report)),
834 "2 files reformatted, 2 files left unchanged, 1 file failed to"
837 self.assertEqual(report.return_code, 123)
838 report.failed(Path("e2"), "boom")
839 self.assertEqual(len(out_lines), 4)
840 self.assertEqual(len(err_lines), 2)
841 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
843 unstyle(str(report)),
844 "2 files reformatted, 2 files left unchanged, 2 files failed to"
847 self.assertEqual(report.return_code, 123)
848 report.path_ignored(Path("wat"), "no match")
849 self.assertEqual(len(out_lines), 5)
850 self.assertEqual(len(err_lines), 2)
851 self.assertEqual(out_lines[-1], "wat ignored: no match")
853 unstyle(str(report)),
854 "2 files reformatted, 2 files left unchanged, 2 files failed to"
857 self.assertEqual(report.return_code, 123)
858 report.done(Path("f4"), black.Changed.NO)
859 self.assertEqual(len(out_lines), 6)
860 self.assertEqual(len(err_lines), 2)
861 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
863 unstyle(str(report)),
864 "2 files reformatted, 3 files left unchanged, 2 files failed to"
867 self.assertEqual(report.return_code, 123)
870 unstyle(str(report)),
871 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
872 " would fail to reformat.",
877 unstyle(str(report)),
878 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
879 " would fail to reformat.",
882 def test_report_quiet(self) -> None:
883 report = black.Report(quiet=True)
887 def out(msg: str, **kwargs: Any) -> None:
888 out_lines.append(msg)
890 def err(msg: str, **kwargs: Any) -> None:
891 err_lines.append(msg)
893 with patch("black.out", out), patch("black.err", err):
894 report.done(Path("f1"), black.Changed.NO)
895 self.assertEqual(len(out_lines), 0)
896 self.assertEqual(len(err_lines), 0)
897 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
898 self.assertEqual(report.return_code, 0)
899 report.done(Path("f2"), black.Changed.YES)
900 self.assertEqual(len(out_lines), 0)
901 self.assertEqual(len(err_lines), 0)
903 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
905 report.done(Path("f3"), black.Changed.CACHED)
906 self.assertEqual(len(out_lines), 0)
907 self.assertEqual(len(err_lines), 0)
909 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
911 self.assertEqual(report.return_code, 0)
913 self.assertEqual(report.return_code, 1)
915 report.failed(Path("e1"), "boom")
916 self.assertEqual(len(out_lines), 0)
917 self.assertEqual(len(err_lines), 1)
918 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
920 unstyle(str(report)),
921 "1 file reformatted, 2 files left unchanged, 1 file failed to"
924 self.assertEqual(report.return_code, 123)
925 report.done(Path("f3"), black.Changed.YES)
926 self.assertEqual(len(out_lines), 0)
927 self.assertEqual(len(err_lines), 1)
929 unstyle(str(report)),
930 "2 files reformatted, 2 files left unchanged, 1 file failed to"
933 self.assertEqual(report.return_code, 123)
934 report.failed(Path("e2"), "boom")
935 self.assertEqual(len(out_lines), 0)
936 self.assertEqual(len(err_lines), 2)
937 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
939 unstyle(str(report)),
940 "2 files reformatted, 2 files left unchanged, 2 files failed to"
943 self.assertEqual(report.return_code, 123)
944 report.path_ignored(Path("wat"), "no match")
945 self.assertEqual(len(out_lines), 0)
946 self.assertEqual(len(err_lines), 2)
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.done(Path("f4"), black.Changed.NO)
954 self.assertEqual(len(out_lines), 0)
955 self.assertEqual(len(err_lines), 2)
957 unstyle(str(report)),
958 "2 files reformatted, 3 files left unchanged, 2 files failed to"
961 self.assertEqual(report.return_code, 123)
964 unstyle(str(report)),
965 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
966 " would fail to reformat.",
971 unstyle(str(report)),
972 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
973 " would fail to reformat.",
976 def test_report_normal(self) -> None:
977 report = black.Report()
981 def out(msg: str, **kwargs: Any) -> None:
982 out_lines.append(msg)
984 def err(msg: str, **kwargs: Any) -> None:
985 err_lines.append(msg)
987 with patch("black.out", out), patch("black.err", err):
988 report.done(Path("f1"), black.Changed.NO)
989 self.assertEqual(len(out_lines), 0)
990 self.assertEqual(len(err_lines), 0)
991 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
992 self.assertEqual(report.return_code, 0)
993 report.done(Path("f2"), black.Changed.YES)
994 self.assertEqual(len(out_lines), 1)
995 self.assertEqual(len(err_lines), 0)
996 self.assertEqual(out_lines[-1], "reformatted f2")
998 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
1000 report.done(Path("f3"), black.Changed.CACHED)
1001 self.assertEqual(len(out_lines), 1)
1002 self.assertEqual(len(err_lines), 0)
1003 self.assertEqual(out_lines[-1], "reformatted f2")
1005 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
1007 self.assertEqual(report.return_code, 0)
1009 self.assertEqual(report.return_code, 1)
1010 report.check = False
1011 report.failed(Path("e1"), "boom")
1012 self.assertEqual(len(out_lines), 1)
1013 self.assertEqual(len(err_lines), 1)
1014 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
1016 unstyle(str(report)),
1017 "1 file reformatted, 2 files left unchanged, 1 file failed to"
1020 self.assertEqual(report.return_code, 123)
1021 report.done(Path("f3"), black.Changed.YES)
1022 self.assertEqual(len(out_lines), 2)
1023 self.assertEqual(len(err_lines), 1)
1024 self.assertEqual(out_lines[-1], "reformatted f3")
1026 unstyle(str(report)),
1027 "2 files reformatted, 2 files left unchanged, 1 file failed to"
1030 self.assertEqual(report.return_code, 123)
1031 report.failed(Path("e2"), "boom")
1032 self.assertEqual(len(out_lines), 2)
1033 self.assertEqual(len(err_lines), 2)
1034 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
1036 unstyle(str(report)),
1037 "2 files reformatted, 2 files left unchanged, 2 files failed to"
1040 self.assertEqual(report.return_code, 123)
1041 report.path_ignored(Path("wat"), "no match")
1042 self.assertEqual(len(out_lines), 2)
1043 self.assertEqual(len(err_lines), 2)
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.done(Path("f4"), black.Changed.NO)
1051 self.assertEqual(len(out_lines), 2)
1052 self.assertEqual(len(err_lines), 2)
1054 unstyle(str(report)),
1055 "2 files reformatted, 3 files left unchanged, 2 files failed to"
1058 self.assertEqual(report.return_code, 123)
1061 unstyle(str(report)),
1062 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1063 " would fail to reformat.",
1065 report.check = False
1068 unstyle(str(report)),
1069 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1070 " would fail to reformat.",
1073 def test_lib2to3_parse(self) -> None:
1074 with self.assertRaises(black.InvalidInput):
1075 black.lib2to3_parse("invalid syntax")
1077 straddling = "x + y"
1078 black.lib2to3_parse(straddling)
1079 black.lib2to3_parse(straddling, {TargetVersion.PY27})
1080 black.lib2to3_parse(straddling, {TargetVersion.PY36})
1081 black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
1083 py2_only = "print x"
1084 black.lib2to3_parse(py2_only)
1085 black.lib2to3_parse(py2_only, {TargetVersion.PY27})
1086 with self.assertRaises(black.InvalidInput):
1087 black.lib2to3_parse(py2_only, {TargetVersion.PY36})
1088 with self.assertRaises(black.InvalidInput):
1089 black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
1091 py3_only = "exec(x, end=y)"
1092 black.lib2to3_parse(py3_only)
1093 with self.assertRaises(black.InvalidInput):
1094 black.lib2to3_parse(py3_only, {TargetVersion.PY27})
1095 black.lib2to3_parse(py3_only, {TargetVersion.PY36})
1096 black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
1098 def test_get_features_used(self) -> None:
1099 node = black.lib2to3_parse("def f(*, arg): ...\n")
1100 self.assertEqual(black.get_features_used(node), set())
1101 node = black.lib2to3_parse("def f(*, arg,): ...\n")
1102 self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
1103 node = black.lib2to3_parse("f(*arg,)\n")
1105 black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
1107 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
1108 self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
1109 node = black.lib2to3_parse("123_456\n")
1110 self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
1111 node = black.lib2to3_parse("123456\n")
1112 self.assertEqual(black.get_features_used(node), set())
1113 source, expected = read_data("function")
1114 node = black.lib2to3_parse(source)
1115 expected_features = {
1116 Feature.TRAILING_COMMA_IN_CALL,
1117 Feature.TRAILING_COMMA_IN_DEF,
1120 self.assertEqual(black.get_features_used(node), expected_features)
1121 node = black.lib2to3_parse(expected)
1122 self.assertEqual(black.get_features_used(node), expected_features)
1123 source, expected = read_data("expression")
1124 node = black.lib2to3_parse(source)
1125 self.assertEqual(black.get_features_used(node), set())
1126 node = black.lib2to3_parse(expected)
1127 self.assertEqual(black.get_features_used(node), set())
1129 def test_get_future_imports(self) -> None:
1130 node = black.lib2to3_parse("\n")
1131 self.assertEqual(set(), black.get_future_imports(node))
1132 node = black.lib2to3_parse("from __future__ import black\n")
1133 self.assertEqual({"black"}, black.get_future_imports(node))
1134 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
1135 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1136 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
1137 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
1138 node = black.lib2to3_parse(
1139 "from __future__ import multiple\nfrom __future__ import imports\n"
1141 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1142 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
1143 self.assertEqual({"black"}, black.get_future_imports(node))
1144 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
1145 self.assertEqual({"black"}, black.get_future_imports(node))
1146 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
1147 self.assertEqual(set(), black.get_future_imports(node))
1148 node = black.lib2to3_parse("from some.module import black\n")
1149 self.assertEqual(set(), black.get_future_imports(node))
1150 node = black.lib2to3_parse(
1151 "from __future__ import unicode_literals as _unicode_literals"
1153 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
1154 node = black.lib2to3_parse(
1155 "from __future__ import unicode_literals as _lol, print"
1157 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
1159 def test_debug_visitor(self) -> None:
1160 source, _ = read_data("debug_visitor.py")
1161 expected, _ = read_data("debug_visitor.out")
1165 def out(msg: str, **kwargs: Any) -> None:
1166 out_lines.append(msg)
1168 def err(msg: str, **kwargs: Any) -> None:
1169 err_lines.append(msg)
1171 with patch("black.out", out), patch("black.err", err):
1172 black.DebugVisitor.show(source)
1173 actual = "\n".join(out_lines) + "\n"
1175 if expected != actual:
1176 log_name = black.dump_to_file(*out_lines)
1180 f"AST print out is different. Actual version dumped to {log_name}",
1183 def test_format_file_contents(self) -> None:
1185 mode = black.FileMode()
1186 with self.assertRaises(black.NothingChanged):
1187 black.format_file_contents(empty, mode=mode, fast=False)
1189 with self.assertRaises(black.NothingChanged):
1190 black.format_file_contents(just_nl, mode=mode, fast=False)
1191 same = "j = [1, 2, 3]\n"
1192 with self.assertRaises(black.NothingChanged):
1193 black.format_file_contents(same, mode=mode, fast=False)
1194 different = "j = [1,2,3]"
1196 actual = black.format_file_contents(different, mode=mode, fast=False)
1197 self.assertEqual(expected, actual)
1198 invalid = "return if you can"
1199 with self.assertRaises(black.InvalidInput) as e:
1200 black.format_file_contents(invalid, mode=mode, fast=False)
1201 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
1203 def test_endmarker(self) -> None:
1204 n = black.lib2to3_parse("\n")
1205 self.assertEqual(n.type, black.syms.file_input)
1206 self.assertEqual(len(n.children), 1)
1207 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
1209 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
1210 def test_assertFormatEqual(self) -> None:
1214 def out(msg: str, **kwargs: Any) -> None:
1215 out_lines.append(msg)
1217 def err(msg: str, **kwargs: Any) -> None:
1218 err_lines.append(msg)
1220 with patch("black.out", out), patch("black.err", err):
1221 with self.assertRaises(AssertionError):
1222 self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")
1224 out_str = "".join(out_lines)
1225 self.assertTrue("Expected tree:" in out_str)
1226 self.assertTrue("Actual tree:" in out_str)
1227 self.assertEqual("".join(err_lines), "")
1229 def test_cache_broken_file(self) -> None:
1230 mode = black.FileMode()
1231 with cache_dir() as workspace:
1232 cache_file = black.get_cache_file(mode)
1233 with cache_file.open("w") as fobj:
1234 fobj.write("this is not a pickle")
1235 self.assertEqual(black.read_cache(mode), {})
1236 src = (workspace / "test.py").resolve()
1237 with src.open("w") as fobj:
1238 fobj.write("print('hello')")
1239 self.invokeBlack([str(src)])
1240 cache = black.read_cache(mode)
1241 self.assertIn(src, cache)
1243 def test_cache_single_file_already_cached(self) -> None:
1244 mode = black.FileMode()
1245 with cache_dir() as workspace:
1246 src = (workspace / "test.py").resolve()
1247 with src.open("w") as fobj:
1248 fobj.write("print('hello')")
1249 black.write_cache({}, [src], mode)
1250 self.invokeBlack([str(src)])
1251 with src.open("r") as fobj:
1252 self.assertEqual(fobj.read(), "print('hello')")
1254 @event_loop(close=False)
1255 def test_cache_multiple_files(self) -> None:
1256 mode = black.FileMode()
1257 with cache_dir() as workspace, patch(
1258 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1260 one = (workspace / "one.py").resolve()
1261 with one.open("w") as fobj:
1262 fobj.write("print('hello')")
1263 two = (workspace / "two.py").resolve()
1264 with two.open("w") as fobj:
1265 fobj.write("print('hello')")
1266 black.write_cache({}, [one], mode)
1267 self.invokeBlack([str(workspace)])
1268 with one.open("r") as fobj:
1269 self.assertEqual(fobj.read(), "print('hello')")
1270 with two.open("r") as fobj:
1271 self.assertEqual(fobj.read(), 'print("hello")\n')
1272 cache = black.read_cache(mode)
1273 self.assertIn(one, cache)
1274 self.assertIn(two, cache)
1276 def test_no_cache_when_writeback_diff(self) -> None:
1277 mode = black.FileMode()
1278 with cache_dir() as workspace:
1279 src = (workspace / "test.py").resolve()
1280 with src.open("w") as fobj:
1281 fobj.write("print('hello')")
1282 self.invokeBlack([str(src), "--diff"])
1283 cache_file = black.get_cache_file(mode)
1284 self.assertFalse(cache_file.exists())
1286 def test_no_cache_when_stdin(self) -> None:
1287 mode = black.FileMode()
1289 result = CliRunner().invoke(
1290 black.main, ["-"], input=BytesIO(b"print('hello')")
1292 self.assertEqual(result.exit_code, 0)
1293 cache_file = black.get_cache_file(mode)
1294 self.assertFalse(cache_file.exists())
1296 def test_read_cache_no_cachefile(self) -> None:
1297 mode = black.FileMode()
1299 self.assertEqual(black.read_cache(mode), {})
1301 def test_write_cache_read_cache(self) -> None:
1302 mode = black.FileMode()
1303 with cache_dir() as workspace:
1304 src = (workspace / "test.py").resolve()
1306 black.write_cache({}, [src], mode)
1307 cache = black.read_cache(mode)
1308 self.assertIn(src, cache)
1309 self.assertEqual(cache[src], black.get_cache_info(src))
1311 def test_filter_cached(self) -> None:
1312 with TemporaryDirectory() as workspace:
1313 path = Path(workspace)
1314 uncached = (path / "uncached").resolve()
1315 cached = (path / "cached").resolve()
1316 cached_but_changed = (path / "changed").resolve()
1319 cached_but_changed.touch()
1320 cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
1321 todo, done = black.filter_cached(
1322 cache, {uncached, cached, cached_but_changed}
1324 self.assertEqual(todo, {uncached, cached_but_changed})
1325 self.assertEqual(done, {cached})
1327 def test_write_cache_creates_directory_if_needed(self) -> None:
1328 mode = black.FileMode()
1329 with cache_dir(exists=False) as workspace:
1330 self.assertFalse(workspace.exists())
1331 black.write_cache({}, [], mode)
1332 self.assertTrue(workspace.exists())
1334 @event_loop(close=False)
1335 def test_failed_formatting_does_not_get_cached(self) -> None:
1336 mode = black.FileMode()
1337 with cache_dir() as workspace, patch(
1338 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1340 failing = (workspace / "failing.py").resolve()
1341 with failing.open("w") as fobj:
1342 fobj.write("not actually python")
1343 clean = (workspace / "clean.py").resolve()
1344 with clean.open("w") as fobj:
1345 fobj.write('print("hello")\n')
1346 self.invokeBlack([str(workspace)], exit_code=123)
1347 cache = black.read_cache(mode)
1348 self.assertNotIn(failing, cache)
1349 self.assertIn(clean, cache)
1351 def test_write_cache_write_fail(self) -> None:
1352 mode = black.FileMode()
1353 with cache_dir(), patch.object(Path, "open") as mock:
1354 mock.side_effect = OSError
1355 black.write_cache({}, [], mode)
1357 @patch("black.ProcessPoolExecutor", autospec=True)
1358 def test_works_in_mono_process_only_environment(self, executor: MagicMock) -> None:
1359 executor.side_effect = OSError()
1360 with cache_dir() as workspace:
1362 (workspace / "one.py").resolve(),
1363 (workspace / "two.py").resolve(),
1365 f.write_text("print('hello')")
1366 self.invokeBlack([str(workspace)])
1368 @event_loop(close=False)
1369 def test_check_diff_use_together(self) -> None:
1371 # Files which will be reformatted.
1372 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1373 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1374 # Files which will not be reformatted.
1375 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1376 self.invokeBlack([str(src2), "--diff", "--check"])
1377 # Multi file command.
1378 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1380 def test_no_files(self) -> None:
1382 # Without an argument, black exits with error code 0.
1383 self.invokeBlack([])
1385 def test_broken_symlink(self) -> None:
1386 with cache_dir() as workspace:
1387 symlink = workspace / "broken_link.py"
1389 symlink.symlink_to("nonexistent.py")
1390 except OSError as e:
1391 self.skipTest(f"Can't create symlinks: {e}")
1392 self.invokeBlack([str(workspace.resolve())])
1394 def test_read_cache_line_lengths(self) -> None:
1395 mode = black.FileMode()
1396 short_mode = black.FileMode(line_length=1)
1397 with cache_dir() as workspace:
1398 path = (workspace / "file.py").resolve()
1400 black.write_cache({}, [path], mode)
1401 one = black.read_cache(mode)
1402 self.assertIn(path, one)
1403 two = black.read_cache(short_mode)
1404 self.assertNotIn(path, two)
1406 def test_tricky_unicode_symbols(self) -> None:
1407 source, expected = read_data("tricky_unicode_symbols")
1409 self.assertFormatEqual(expected, actual)
1410 black.assert_equivalent(source, actual)
1411 black.assert_stable(source, actual, black.FileMode())
1413 def test_single_file_force_pyi(self) -> None:
1414 reg_mode = black.FileMode()
1415 pyi_mode = black.FileMode(is_pyi=True)
1416 contents, expected = read_data("force_pyi")
1417 with cache_dir() as workspace:
1418 path = (workspace / "file.py").resolve()
1419 with open(path, "w") as fh:
1421 self.invokeBlack([str(path), "--pyi"])
1422 with open(path, "r") as fh:
1424 # verify cache with --pyi is separate
1425 pyi_cache = black.read_cache(pyi_mode)
1426 self.assertIn(path, pyi_cache)
1427 normal_cache = black.read_cache(reg_mode)
1428 self.assertNotIn(path, normal_cache)
1429 self.assertEqual(actual, expected)
1431 @event_loop(close=False)
1432 def test_multi_file_force_pyi(self) -> None:
1433 reg_mode = black.FileMode()
1434 pyi_mode = black.FileMode(is_pyi=True)
1435 contents, expected = read_data("force_pyi")
1436 with cache_dir() as workspace:
1438 (workspace / "file1.py").resolve(),
1439 (workspace / "file2.py").resolve(),
1442 with open(path, "w") as fh:
1444 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1446 with open(path, "r") as fh:
1448 self.assertEqual(actual, expected)
1449 # verify cache with --pyi is separate
1450 pyi_cache = black.read_cache(pyi_mode)
1451 normal_cache = black.read_cache(reg_mode)
1453 self.assertIn(path, pyi_cache)
1454 self.assertNotIn(path, normal_cache)
1456 def test_pipe_force_pyi(self) -> None:
1457 source, expected = read_data("force_pyi")
1458 result = CliRunner().invoke(
1459 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1461 self.assertEqual(result.exit_code, 0)
1462 actual = result.output
1463 self.assertFormatEqual(actual, expected)
1465 def test_single_file_force_py36(self) -> None:
1466 reg_mode = black.FileMode()
1467 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1468 source, expected = read_data("force_py36")
1469 with cache_dir() as workspace:
1470 path = (workspace / "file.py").resolve()
1471 with open(path, "w") as fh:
1473 self.invokeBlack([str(path), *PY36_ARGS])
1474 with open(path, "r") as fh:
1476 # verify cache with --target-version is separate
1477 py36_cache = black.read_cache(py36_mode)
1478 self.assertIn(path, py36_cache)
1479 normal_cache = black.read_cache(reg_mode)
1480 self.assertNotIn(path, normal_cache)
1481 self.assertEqual(actual, expected)
1483 @event_loop(close=False)
1484 def test_multi_file_force_py36(self) -> None:
1485 reg_mode = black.FileMode()
1486 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1487 source, expected = read_data("force_py36")
1488 with cache_dir() as workspace:
1490 (workspace / "file1.py").resolve(),
1491 (workspace / "file2.py").resolve(),
1494 with open(path, "w") as fh:
1496 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1498 with open(path, "r") as fh:
1500 self.assertEqual(actual, expected)
1501 # verify cache with --target-version is separate
1502 pyi_cache = black.read_cache(py36_mode)
1503 normal_cache = black.read_cache(reg_mode)
1505 self.assertIn(path, pyi_cache)
1506 self.assertNotIn(path, normal_cache)
1508 def test_collections(self) -> None:
1509 source, expected = read_data("collections")
1511 self.assertFormatEqual(expected, actual)
1512 black.assert_equivalent(source, actual)
1513 black.assert_stable(source, actual, black.FileMode())
1515 def test_pipe_force_py36(self) -> None:
1516 source, expected = read_data("force_py36")
1517 result = CliRunner().invoke(
1519 ["-", "-q", "--target-version=py36"],
1520 input=BytesIO(source.encode("utf8")),
1522 self.assertEqual(result.exit_code, 0)
1523 actual = result.output
1524 self.assertFormatEqual(actual, expected)
1526 def test_include_exclude(self) -> None:
1527 path = THIS_DIR / "data" / "include_exclude_tests"
1528 include = re.compile(r"\.pyi?$")
1529 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1530 report = black.Report()
1531 gitignore = PathSpec.from_lines("gitwildmatch", [])
1532 sources: List[Path] = []
1534 Path(path / "b/dont_exclude/a.py"),
1535 Path(path / "b/dont_exclude/a.pyi"),
1537 this_abs = THIS_DIR.resolve()
1539 black.gen_python_files_in_dir(
1540 path, this_abs, include, exclude, report, gitignore
1543 self.assertEqual(sorted(expected), sorted(sources))
1545 def test_gitignore_exclude(self) -> None:
1546 path = THIS_DIR / "data" / "include_exclude_tests"
1547 include = re.compile(r"\.pyi?$")
1548 exclude = re.compile(r"")
1549 report = black.Report()
1550 gitignore = PathSpec.from_lines(
1551 "gitwildmatch", ["exclude/", ".definitely_exclude"]
1553 sources: List[Path] = []
1555 Path(path / "b/dont_exclude/a.py"),
1556 Path(path / "b/dont_exclude/a.pyi"),
1558 this_abs = THIS_DIR.resolve()
1560 black.gen_python_files_in_dir(
1561 path, this_abs, include, exclude, report, gitignore
1564 self.assertEqual(sorted(expected), sorted(sources))
1566 def test_empty_include(self) -> None:
1567 path = THIS_DIR / "data" / "include_exclude_tests"
1568 report = black.Report()
1569 gitignore = PathSpec.from_lines("gitwildmatch", [])
1570 empty = re.compile(r"")
1571 sources: List[Path] = []
1573 Path(path / "b/exclude/a.pie"),
1574 Path(path / "b/exclude/a.py"),
1575 Path(path / "b/exclude/a.pyi"),
1576 Path(path / "b/dont_exclude/a.pie"),
1577 Path(path / "b/dont_exclude/a.py"),
1578 Path(path / "b/dont_exclude/a.pyi"),
1579 Path(path / "b/.definitely_exclude/a.pie"),
1580 Path(path / "b/.definitely_exclude/a.py"),
1581 Path(path / "b/.definitely_exclude/a.pyi"),
1583 this_abs = THIS_DIR.resolve()
1585 black.gen_python_files_in_dir(
1589 re.compile(black.DEFAULT_EXCLUDES),
1594 self.assertEqual(sorted(expected), sorted(sources))
1596 def test_empty_exclude(self) -> None:
1597 path = THIS_DIR / "data" / "include_exclude_tests"
1598 report = black.Report()
1599 gitignore = PathSpec.from_lines("gitwildmatch", [])
1600 empty = re.compile(r"")
1601 sources: List[Path] = []
1603 Path(path / "b/dont_exclude/a.py"),
1604 Path(path / "b/dont_exclude/a.pyi"),
1605 Path(path / "b/exclude/a.py"),
1606 Path(path / "b/exclude/a.pyi"),
1607 Path(path / "b/.definitely_exclude/a.py"),
1608 Path(path / "b/.definitely_exclude/a.pyi"),
1610 this_abs = THIS_DIR.resolve()
1612 black.gen_python_files_in_dir(
1615 re.compile(black.DEFAULT_INCLUDES),
1621 self.assertEqual(sorted(expected), sorted(sources))
1623 def test_invalid_include_exclude(self) -> None:
1624 for option in ["--include", "--exclude"]:
1625 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1627 def test_preserves_line_endings(self) -> None:
1628 with TemporaryDirectory() as workspace:
1629 test_file = Path(workspace) / "test.py"
1630 for nl in ["\n", "\r\n"]:
1631 contents = nl.join(["def f( ):", " pass"])
1632 test_file.write_bytes(contents.encode())
1633 ff(test_file, write_back=black.WriteBack.YES)
1634 updated_contents: bytes = test_file.read_bytes()
1635 self.assertIn(nl.encode(), updated_contents)
1637 self.assertNotIn(b"\r\n", updated_contents)
1639 def test_preserves_line_endings_via_stdin(self) -> None:
1640 for nl in ["\n", "\r\n"]:
1641 contents = nl.join(["def f( ):", " pass"])
1642 runner = BlackRunner()
1643 result = runner.invoke(
1644 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1646 self.assertEqual(result.exit_code, 0)
1647 output = runner.stdout_bytes
1648 self.assertIn(nl.encode("utf8"), output)
1650 self.assertNotIn(b"\r\n", output)
1652 def test_assert_equivalent_different_asts(self) -> None:
1653 with self.assertRaises(AssertionError):
1654 black.assert_equivalent("{}", "None")
1656 def test_symlink_out_of_root_directory(self) -> None:
1660 include = re.compile(black.DEFAULT_INCLUDES)
1661 exclude = re.compile(black.DEFAULT_EXCLUDES)
1662 report = black.Report()
1663 gitignore = PathSpec.from_lines("gitwildmatch", [])
1664 # `child` should behave like a symlink which resolved path is clearly
1665 # outside of the `root` directory.
1666 path.iterdir.return_value = [child]
1667 child.resolve.return_value = Path("/a/b/c")
1668 child.as_posix.return_value = "/a/b/c"
1669 child.is_symlink.return_value = True
1672 black.gen_python_files_in_dir(
1673 path, root, include, exclude, report, gitignore
1676 except ValueError as ve:
1677 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1678 path.iterdir.assert_called_once()
1679 child.resolve.assert_called_once()
1680 child.is_symlink.assert_called_once()
1681 # `child` should behave like a strange file which resolved path is clearly
1682 # outside of the `root` directory.
1683 child.is_symlink.return_value = False
1684 with self.assertRaises(ValueError):
1686 black.gen_python_files_in_dir(
1687 path, root, include, exclude, report, gitignore
1690 path.iterdir.assert_called()
1691 self.assertEqual(path.iterdir.call_count, 2)
1692 child.resolve.assert_called()
1693 self.assertEqual(child.resolve.call_count, 2)
1694 child.is_symlink.assert_called()
1695 self.assertEqual(child.is_symlink.call_count, 2)
1697 def test_shhh_click(self) -> None:
1699 from click import _unicodefun # type: ignore
1700 except ModuleNotFoundError:
1701 self.skipTest("Incompatible Click version")
1702 if not hasattr(_unicodefun, "_verify_python3_env"):
1703 self.skipTest("Incompatible Click version")
1704 # First, let's see if Click is crashing with a preferred ASCII charset.
1705 with patch("locale.getpreferredencoding") as gpe:
1706 gpe.return_value = "ASCII"
1707 with self.assertRaises(RuntimeError):
1708 _unicodefun._verify_python3_env()
1709 # Now, let's silence Click...
1711 # ...and confirm it's silent.
1712 with patch("locale.getpreferredencoding") as gpe:
1713 gpe.return_value = "ASCII"
1715 _unicodefun._verify_python3_env()
1716 except RuntimeError as re:
1717 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1719 def test_root_logger_not_used_directly(self) -> None:
1720 def fail(*args: Any, **kwargs: Any) -> None:
1721 self.fail("Record created with root logger")
1723 with patch.multiple(
1734 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1735 def test_blackd_main(self) -> None:
1736 with patch("blackd.web.run_app"):
1737 result = CliRunner().invoke(blackd.main, [])
1738 if result.exception is not None:
1739 raise result.exception
1740 self.assertEqual(result.exit_code, 0)
1742 def test_invalid_config_return_code(self) -> None:
1743 tmp_file = Path(black.dump_to_file())
1745 tmp_config = Path(black.dump_to_file())
1747 args = ["--config", str(tmp_config), str(tmp_file)]
1748 self.invokeBlack(args, exit_code=2, ignore_config=False)
1753 class BlackDTestCase(AioHTTPTestCase):
1754 async def get_application(self) -> web.Application:
1755 return blackd.make_app()
1757 # TODO: remove these decorators once the below is released
1758 # https://github.com/aio-libs/aiohttp/pull/3727
1759 @skip_if_exception("ClientOSError")
1760 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1762 async def test_blackd_request_needs_formatting(self) -> None:
1763 response = await self.client.post("/", data=b"print('hello world')")
1764 self.assertEqual(response.status, 200)
1765 self.assertEqual(response.charset, "utf8")
1766 self.assertEqual(await response.read(), b'print("hello world")\n')
1768 @skip_if_exception("ClientOSError")
1769 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1771 async def test_blackd_request_no_change(self) -> None:
1772 response = await self.client.post("/", data=b'print("hello world")\n')
1773 self.assertEqual(response.status, 204)
1774 self.assertEqual(await response.read(), b"")
1776 @skip_if_exception("ClientOSError")
1777 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1779 async def test_blackd_request_syntax_error(self) -> None:
1780 response = await self.client.post("/", data=b"what even ( is")
1781 self.assertEqual(response.status, 400)
1782 content = await response.text()
1784 content.startswith("Cannot parse"),
1785 msg=f"Expected error to start with 'Cannot parse', got {repr(content)}",
1788 @skip_if_exception("ClientOSError")
1789 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1791 async def test_blackd_unsupported_version(self) -> None:
1792 response = await self.client.post(
1793 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "2"}
1795 self.assertEqual(response.status, 501)
1797 @skip_if_exception("ClientOSError")
1798 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1800 async def test_blackd_supported_version(self) -> None:
1801 response = await self.client.post(
1802 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "1"}
1804 self.assertEqual(response.status, 200)
1806 @skip_if_exception("ClientOSError")
1807 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1809 async def test_blackd_invalid_python_variant(self) -> None:
1810 async def check(header_value: str, expected_status: int = 400) -> None:
1811 response = await self.client.post(
1812 "/", data=b"what", headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1814 self.assertEqual(response.status, expected_status)
1817 await check("ruby3.5")
1818 await check("pyi3.6")
1819 await check("py1.5")
1821 await check("py2.8")
1823 await check("pypy3.0")
1824 await check("jython3.4")
1826 @skip_if_exception("ClientOSError")
1827 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1829 async def test_blackd_pyi(self) -> None:
1830 source, expected = read_data("stub.pyi")
1831 response = await self.client.post(
1832 "/", data=source, headers={blackd.PYTHON_VARIANT_HEADER: "pyi"}
1834 self.assertEqual(response.status, 200)
1835 self.assertEqual(await response.text(), expected)
1837 @skip_if_exception("ClientOSError")
1838 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1840 async def test_blackd_diff(self) -> None:
1841 diff_header = re.compile(
1842 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"
1845 source, _ = read_data("blackd_diff.py")
1846 expected, _ = read_data("blackd_diff.diff")
1848 response = await self.client.post(
1849 "/", data=source, headers={blackd.DIFF_HEADER: "true"}
1851 self.assertEqual(response.status, 200)
1853 actual = await response.text()
1854 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
1855 self.assertEqual(actual, expected)
1857 @skip_if_exception("ClientOSError")
1858 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1860 async def test_blackd_python_variant(self) -> None:
1863 " and_has_a_bunch_of,\n"
1864 " very_long_arguments_too,\n"
1865 " and_lots_of_them_as_well_lol,\n"
1866 " **and_very_long_keyword_arguments\n"
1871 async def check(header_value: str, expected_status: int) -> None:
1872 response = await self.client.post(
1873 "/", data=code, headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1876 response.status, expected_status, msg=await response.text()
1879 await check("3.6", 200)
1880 await check("py3.6", 200)
1881 await check("3.6,3.7", 200)
1882 await check("3.6,py3.7", 200)
1883 await check("py36,py37", 200)
1884 await check("36", 200)
1885 await check("3.6.4", 200)
1887 await check("2", 204)
1888 await check("2.7", 204)
1889 await check("py2.7", 204)
1890 await check("3.4", 204)
1891 await check("py3.4", 204)
1892 await check("py34,py36", 204)
1893 await check("34", 204)
1895 @skip_if_exception("ClientOSError")
1896 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1898 async def test_blackd_line_length(self) -> None:
1899 response = await self.client.post(
1900 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "7"}
1902 self.assertEqual(response.status, 200)
1904 @skip_if_exception("ClientOSError")
1905 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1907 async def test_blackd_invalid_line_length(self) -> None:
1908 response = await self.client.post(
1909 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "NaN"}
1911 self.assertEqual(response.status, 400)
1913 @skip_if_exception("ClientOSError")
1914 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1916 async def test_blackd_response_black_version_header(self) -> None:
1917 response = await self.client.post("/")
1918 self.assertIsNotNone(response.headers.get(blackd.BLACK_VERSION_HEADER))
1921 if __name__ == "__main__":
1922 unittest.main(module="test_black")