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 self.skipTest("this test fails when run with the rest of the suite")
1360 executor.side_effect = OSError()
1361 with cache_dir() as workspace:
1363 (workspace / "one.py").resolve(),
1364 (workspace / "two.py").resolve(),
1366 f.write_text("print('hello')")
1367 self.invokeBlack([str(workspace)])
1369 @event_loop(close=False)
1370 def test_check_diff_use_together(self) -> None:
1372 # Files which will be reformatted.
1373 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1374 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1375 # Files which will not be reformatted.
1376 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1377 self.invokeBlack([str(src2), "--diff", "--check"])
1378 # Multi file command.
1379 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1381 def test_no_files(self) -> None:
1383 # Without an argument, black exits with error code 0.
1384 self.invokeBlack([])
1386 def test_broken_symlink(self) -> None:
1387 with cache_dir() as workspace:
1388 symlink = workspace / "broken_link.py"
1390 symlink.symlink_to("nonexistent.py")
1391 except OSError as e:
1392 self.skipTest(f"Can't create symlinks: {e}")
1393 self.invokeBlack([str(workspace.resolve())])
1395 def test_read_cache_line_lengths(self) -> None:
1396 mode = black.FileMode()
1397 short_mode = black.FileMode(line_length=1)
1398 with cache_dir() as workspace:
1399 path = (workspace / "file.py").resolve()
1401 black.write_cache({}, [path], mode)
1402 one = black.read_cache(mode)
1403 self.assertIn(path, one)
1404 two = black.read_cache(short_mode)
1405 self.assertNotIn(path, two)
1407 def test_tricky_unicode_symbols(self) -> None:
1408 source, expected = read_data("tricky_unicode_symbols")
1410 self.assertFormatEqual(expected, actual)
1411 black.assert_equivalent(source, actual)
1412 black.assert_stable(source, actual, black.FileMode())
1414 def test_single_file_force_pyi(self) -> None:
1415 reg_mode = black.FileMode()
1416 pyi_mode = black.FileMode(is_pyi=True)
1417 contents, expected = read_data("force_pyi")
1418 with cache_dir() as workspace:
1419 path = (workspace / "file.py").resolve()
1420 with open(path, "w") as fh:
1422 self.invokeBlack([str(path), "--pyi"])
1423 with open(path, "r") as fh:
1425 # verify cache with --pyi is separate
1426 pyi_cache = black.read_cache(pyi_mode)
1427 self.assertIn(path, pyi_cache)
1428 normal_cache = black.read_cache(reg_mode)
1429 self.assertNotIn(path, normal_cache)
1430 self.assertEqual(actual, expected)
1432 @event_loop(close=False)
1433 def test_multi_file_force_pyi(self) -> None:
1434 reg_mode = black.FileMode()
1435 pyi_mode = black.FileMode(is_pyi=True)
1436 contents, expected = read_data("force_pyi")
1437 with cache_dir() as workspace:
1439 (workspace / "file1.py").resolve(),
1440 (workspace / "file2.py").resolve(),
1443 with open(path, "w") as fh:
1445 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1447 with open(path, "r") as fh:
1449 self.assertEqual(actual, expected)
1450 # verify cache with --pyi is separate
1451 pyi_cache = black.read_cache(pyi_mode)
1452 normal_cache = black.read_cache(reg_mode)
1454 self.assertIn(path, pyi_cache)
1455 self.assertNotIn(path, normal_cache)
1457 def test_pipe_force_pyi(self) -> None:
1458 source, expected = read_data("force_pyi")
1459 result = CliRunner().invoke(
1460 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1462 self.assertEqual(result.exit_code, 0)
1463 actual = result.output
1464 self.assertFormatEqual(actual, expected)
1466 def test_single_file_force_py36(self) -> None:
1467 reg_mode = black.FileMode()
1468 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1469 source, expected = read_data("force_py36")
1470 with cache_dir() as workspace:
1471 path = (workspace / "file.py").resolve()
1472 with open(path, "w") as fh:
1474 self.invokeBlack([str(path), *PY36_ARGS])
1475 with open(path, "r") as fh:
1477 # verify cache with --target-version is separate
1478 py36_cache = black.read_cache(py36_mode)
1479 self.assertIn(path, py36_cache)
1480 normal_cache = black.read_cache(reg_mode)
1481 self.assertNotIn(path, normal_cache)
1482 self.assertEqual(actual, expected)
1484 @event_loop(close=False)
1485 def test_multi_file_force_py36(self) -> None:
1486 reg_mode = black.FileMode()
1487 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1488 source, expected = read_data("force_py36")
1489 with cache_dir() as workspace:
1491 (workspace / "file1.py").resolve(),
1492 (workspace / "file2.py").resolve(),
1495 with open(path, "w") as fh:
1497 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1499 with open(path, "r") as fh:
1501 self.assertEqual(actual, expected)
1502 # verify cache with --target-version is separate
1503 pyi_cache = black.read_cache(py36_mode)
1504 normal_cache = black.read_cache(reg_mode)
1506 self.assertIn(path, pyi_cache)
1507 self.assertNotIn(path, normal_cache)
1509 def test_collections(self) -> None:
1510 source, expected = read_data("collections")
1512 self.assertFormatEqual(expected, actual)
1513 black.assert_equivalent(source, actual)
1514 black.assert_stable(source, actual, black.FileMode())
1516 def test_pipe_force_py36(self) -> None:
1517 source, expected = read_data("force_py36")
1518 result = CliRunner().invoke(
1520 ["-", "-q", "--target-version=py36"],
1521 input=BytesIO(source.encode("utf8")),
1523 self.assertEqual(result.exit_code, 0)
1524 actual = result.output
1525 self.assertFormatEqual(actual, expected)
1527 def test_include_exclude(self) -> None:
1528 path = THIS_DIR / "data" / "include_exclude_tests"
1529 include = re.compile(r"\.pyi?$")
1530 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1531 report = black.Report()
1532 gitignore = PathSpec.from_lines("gitwildmatch", [])
1533 sources: List[Path] = []
1535 Path(path / "b/dont_exclude/a.py"),
1536 Path(path / "b/dont_exclude/a.pyi"),
1538 this_abs = THIS_DIR.resolve()
1540 black.gen_python_files_in_dir(
1541 path, this_abs, include, exclude, report, gitignore
1544 self.assertEqual(sorted(expected), sorted(sources))
1546 def test_gitignore_exclude(self) -> None:
1547 path = THIS_DIR / "data" / "include_exclude_tests"
1548 include = re.compile(r"\.pyi?$")
1549 exclude = re.compile(r"")
1550 report = black.Report()
1551 gitignore = PathSpec.from_lines(
1552 "gitwildmatch", ["exclude/", ".definitely_exclude"]
1554 sources: List[Path] = []
1556 Path(path / "b/dont_exclude/a.py"),
1557 Path(path / "b/dont_exclude/a.pyi"),
1559 this_abs = THIS_DIR.resolve()
1561 black.gen_python_files_in_dir(
1562 path, this_abs, include, exclude, report, gitignore
1565 self.assertEqual(sorted(expected), sorted(sources))
1567 def test_empty_include(self) -> None:
1568 path = THIS_DIR / "data" / "include_exclude_tests"
1569 report = black.Report()
1570 gitignore = PathSpec.from_lines("gitwildmatch", [])
1571 empty = re.compile(r"")
1572 sources: List[Path] = []
1574 Path(path / "b/exclude/a.pie"),
1575 Path(path / "b/exclude/a.py"),
1576 Path(path / "b/exclude/a.pyi"),
1577 Path(path / "b/dont_exclude/a.pie"),
1578 Path(path / "b/dont_exclude/a.py"),
1579 Path(path / "b/dont_exclude/a.pyi"),
1580 Path(path / "b/.definitely_exclude/a.pie"),
1581 Path(path / "b/.definitely_exclude/a.py"),
1582 Path(path / "b/.definitely_exclude/a.pyi"),
1584 this_abs = THIS_DIR.resolve()
1586 black.gen_python_files_in_dir(
1590 re.compile(black.DEFAULT_EXCLUDES),
1595 self.assertEqual(sorted(expected), sorted(sources))
1597 def test_empty_exclude(self) -> None:
1598 path = THIS_DIR / "data" / "include_exclude_tests"
1599 report = black.Report()
1600 gitignore = PathSpec.from_lines("gitwildmatch", [])
1601 empty = re.compile(r"")
1602 sources: List[Path] = []
1604 Path(path / "b/dont_exclude/a.py"),
1605 Path(path / "b/dont_exclude/a.pyi"),
1606 Path(path / "b/exclude/a.py"),
1607 Path(path / "b/exclude/a.pyi"),
1608 Path(path / "b/.definitely_exclude/a.py"),
1609 Path(path / "b/.definitely_exclude/a.pyi"),
1611 this_abs = THIS_DIR.resolve()
1613 black.gen_python_files_in_dir(
1616 re.compile(black.DEFAULT_INCLUDES),
1622 self.assertEqual(sorted(expected), sorted(sources))
1624 def test_invalid_include_exclude(self) -> None:
1625 for option in ["--include", "--exclude"]:
1626 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1628 def test_preserves_line_endings(self) -> None:
1629 with TemporaryDirectory() as workspace:
1630 test_file = Path(workspace) / "test.py"
1631 for nl in ["\n", "\r\n"]:
1632 contents = nl.join(["def f( ):", " pass"])
1633 test_file.write_bytes(contents.encode())
1634 ff(test_file, write_back=black.WriteBack.YES)
1635 updated_contents: bytes = test_file.read_bytes()
1636 self.assertIn(nl.encode(), updated_contents)
1638 self.assertNotIn(b"\r\n", updated_contents)
1640 def test_preserves_line_endings_via_stdin(self) -> None:
1641 for nl in ["\n", "\r\n"]:
1642 contents = nl.join(["def f( ):", " pass"])
1643 runner = BlackRunner()
1644 result = runner.invoke(
1645 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1647 self.assertEqual(result.exit_code, 0)
1648 output = runner.stdout_bytes
1649 self.assertIn(nl.encode("utf8"), output)
1651 self.assertNotIn(b"\r\n", output)
1653 def test_assert_equivalent_different_asts(self) -> None:
1654 with self.assertRaises(AssertionError):
1655 black.assert_equivalent("{}", "None")
1657 def test_symlink_out_of_root_directory(self) -> None:
1661 include = re.compile(black.DEFAULT_INCLUDES)
1662 exclude = re.compile(black.DEFAULT_EXCLUDES)
1663 report = black.Report()
1664 gitignore = PathSpec.from_lines("gitwildmatch", [])
1665 # `child` should behave like a symlink which resolved path is clearly
1666 # outside of the `root` directory.
1667 path.iterdir.return_value = [child]
1668 child.resolve.return_value = Path("/a/b/c")
1669 child.as_posix.return_value = "/a/b/c"
1670 child.is_symlink.return_value = True
1673 black.gen_python_files_in_dir(
1674 path, root, include, exclude, report, gitignore
1677 except ValueError as ve:
1678 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1679 path.iterdir.assert_called_once()
1680 child.resolve.assert_called_once()
1681 child.is_symlink.assert_called_once()
1682 # `child` should behave like a strange file which resolved path is clearly
1683 # outside of the `root` directory.
1684 child.is_symlink.return_value = False
1685 with self.assertRaises(ValueError):
1687 black.gen_python_files_in_dir(
1688 path, root, include, exclude, report, gitignore
1691 path.iterdir.assert_called()
1692 self.assertEqual(path.iterdir.call_count, 2)
1693 child.resolve.assert_called()
1694 self.assertEqual(child.resolve.call_count, 2)
1695 child.is_symlink.assert_called()
1696 self.assertEqual(child.is_symlink.call_count, 2)
1698 def test_shhh_click(self) -> None:
1700 from click import _unicodefun # type: ignore
1701 except ModuleNotFoundError:
1702 self.skipTest("Incompatible Click version")
1703 if not hasattr(_unicodefun, "_verify_python3_env"):
1704 self.skipTest("Incompatible Click version")
1705 # First, let's see if Click is crashing with a preferred ASCII charset.
1706 with patch("locale.getpreferredencoding") as gpe:
1707 gpe.return_value = "ASCII"
1708 with self.assertRaises(RuntimeError):
1709 _unicodefun._verify_python3_env()
1710 # Now, let's silence Click...
1712 # ...and confirm it's silent.
1713 with patch("locale.getpreferredencoding") as gpe:
1714 gpe.return_value = "ASCII"
1716 _unicodefun._verify_python3_env()
1717 except RuntimeError as re:
1718 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1720 def test_root_logger_not_used_directly(self) -> None:
1721 def fail(*args: Any, **kwargs: Any) -> None:
1722 self.fail("Record created with root logger")
1724 with patch.multiple(
1735 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1736 def test_blackd_main(self) -> None:
1737 with patch("blackd.web.run_app"):
1738 result = CliRunner().invoke(blackd.main, [])
1739 if result.exception is not None:
1740 raise result.exception
1741 self.assertEqual(result.exit_code, 0)
1743 def test_invalid_config_return_code(self) -> None:
1744 tmp_file = Path(black.dump_to_file())
1746 tmp_config = Path(black.dump_to_file())
1748 args = ["--config", str(tmp_config), str(tmp_file)]
1749 self.invokeBlack(args, exit_code=2, ignore_config=False)
1754 class BlackDTestCase(AioHTTPTestCase):
1755 async def get_application(self) -> web.Application:
1756 return blackd.make_app()
1758 # TODO: remove these decorators once the below is released
1759 # https://github.com/aio-libs/aiohttp/pull/3727
1760 @skip_if_exception("ClientOSError")
1761 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1763 async def test_blackd_request_needs_formatting(self) -> None:
1764 response = await self.client.post("/", data=b"print('hello world')")
1765 self.assertEqual(response.status, 200)
1766 self.assertEqual(response.charset, "utf8")
1767 self.assertEqual(await response.read(), b'print("hello world")\n')
1769 @skip_if_exception("ClientOSError")
1770 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1772 async def test_blackd_request_no_change(self) -> None:
1773 response = await self.client.post("/", data=b'print("hello world")\n')
1774 self.assertEqual(response.status, 204)
1775 self.assertEqual(await response.read(), b"")
1777 @skip_if_exception("ClientOSError")
1778 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1780 async def test_blackd_request_syntax_error(self) -> None:
1781 response = await self.client.post("/", data=b"what even ( is")
1782 self.assertEqual(response.status, 400)
1783 content = await response.text()
1785 content.startswith("Cannot parse"),
1786 msg=f"Expected error to start with 'Cannot parse', got {repr(content)}",
1789 @skip_if_exception("ClientOSError")
1790 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1792 async def test_blackd_unsupported_version(self) -> None:
1793 response = await self.client.post(
1794 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "2"}
1796 self.assertEqual(response.status, 501)
1798 @skip_if_exception("ClientOSError")
1799 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1801 async def test_blackd_supported_version(self) -> None:
1802 response = await self.client.post(
1803 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "1"}
1805 self.assertEqual(response.status, 200)
1807 @skip_if_exception("ClientOSError")
1808 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1810 async def test_blackd_invalid_python_variant(self) -> None:
1811 async def check(header_value: str, expected_status: int = 400) -> None:
1812 response = await self.client.post(
1813 "/", data=b"what", headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1815 self.assertEqual(response.status, expected_status)
1818 await check("ruby3.5")
1819 await check("pyi3.6")
1820 await check("py1.5")
1822 await check("py2.8")
1824 await check("pypy3.0")
1825 await check("jython3.4")
1827 @skip_if_exception("ClientOSError")
1828 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1830 async def test_blackd_pyi(self) -> None:
1831 source, expected = read_data("stub.pyi")
1832 response = await self.client.post(
1833 "/", data=source, headers={blackd.PYTHON_VARIANT_HEADER: "pyi"}
1835 self.assertEqual(response.status, 200)
1836 self.assertEqual(await response.text(), expected)
1838 @skip_if_exception("ClientOSError")
1839 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1841 async def test_blackd_diff(self) -> None:
1842 diff_header = re.compile(
1843 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"
1846 source, _ = read_data("blackd_diff.py")
1847 expected, _ = read_data("blackd_diff.diff")
1849 response = await self.client.post(
1850 "/", data=source, headers={blackd.DIFF_HEADER: "true"}
1852 self.assertEqual(response.status, 200)
1854 actual = await response.text()
1855 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
1856 self.assertEqual(actual, expected)
1858 @skip_if_exception("ClientOSError")
1859 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1861 async def test_blackd_python_variant(self) -> None:
1864 " and_has_a_bunch_of,\n"
1865 " very_long_arguments_too,\n"
1866 " and_lots_of_them_as_well_lol,\n"
1867 " **and_very_long_keyword_arguments\n"
1872 async def check(header_value: str, expected_status: int) -> None:
1873 response = await self.client.post(
1874 "/", data=code, headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1877 response.status, expected_status, msg=await response.text()
1880 await check("3.6", 200)
1881 await check("py3.6", 200)
1882 await check("3.6,3.7", 200)
1883 await check("3.6,py3.7", 200)
1884 await check("py36,py37", 200)
1885 await check("36", 200)
1886 await check("3.6.4", 200)
1888 await check("2", 204)
1889 await check("2.7", 204)
1890 await check("py2.7", 204)
1891 await check("3.4", 204)
1892 await check("py3.4", 204)
1893 await check("py34,py36", 204)
1894 await check("34", 204)
1896 @skip_if_exception("ClientOSError")
1897 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1899 async def test_blackd_line_length(self) -> None:
1900 response = await self.client.post(
1901 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "7"}
1903 self.assertEqual(response.status, 200)
1905 @skip_if_exception("ClientOSError")
1906 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1908 async def test_blackd_invalid_line_length(self) -> None:
1909 response = await self.client.post(
1910 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "NaN"}
1912 self.assertEqual(response.status, 400)
1914 @skip_if_exception("ClientOSError")
1915 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1917 async def test_blackd_response_black_version_header(self) -> None:
1918 response = await self.client.post("/")
1919 self.assertIsNotNone(response.headers.get(blackd.BLACK_VERSION_HEADER))
1922 if __name__ == "__main__":
1923 unittest.main(module="test_black")