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() -> Iterator[None]:
86 policy = asyncio.get_event_loop_policy()
87 loop = policy.new_event_loop()
88 asyncio.set_event_loop(loop)
97 def skip_if_exception(e: str) -> Iterator[None]:
100 except Exception as exc:
101 if exc.__class__.__name__ == e:
102 unittest.skip(f"Encountered expected exception {exc}, skipping")
107 class BlackRunner(CliRunner):
108 """Modify CliRunner so that stderr is not merged with stdout.
110 This is a hack that can be removed once we depend on Click 7.x"""
112 def __init__(self) -> None:
113 self.stderrbuf = BytesIO()
114 self.stdoutbuf = BytesIO()
115 self.stdout_bytes = b""
116 self.stderr_bytes = b""
120 def isolation(self, *args: Any, **kwargs: Any) -> Generator[BinaryIO, None, None]:
121 with super().isolation(*args, **kwargs) as output:
123 hold_stderr = sys.stderr
124 sys.stderr = TextIOWrapper(self.stderrbuf, encoding=self.charset)
127 self.stdout_bytes = sys.stdout.buffer.getvalue() # type: ignore
128 self.stderr_bytes = sys.stderr.buffer.getvalue() # type: ignore
129 sys.stderr = hold_stderr
132 class BlackTestCase(unittest.TestCase):
135 def assertFormatEqual(self, expected: str, actual: str) -> None:
136 if actual != expected and not os.environ.get("SKIP_AST_PRINT"):
137 bdv: black.DebugVisitor[Any]
138 black.out("Expected tree:", fg="green")
140 exp_node = black.lib2to3_parse(expected)
141 bdv = black.DebugVisitor()
142 list(bdv.visit(exp_node))
143 except Exception as ve:
145 black.out("Actual tree:", fg="red")
147 exp_node = black.lib2to3_parse(actual)
148 bdv = black.DebugVisitor()
149 list(bdv.visit(exp_node))
150 except Exception as ve:
152 self.assertEqual(expected, actual)
155 self, args: List[str], exit_code: int = 0, ignore_config: bool = True
157 runner = BlackRunner()
159 args = ["--verbose", "--config", str(THIS_DIR / "empty.toml"), *args]
160 result = runner.invoke(black.main, args)
165 f"Failed with args: {args}\n"
166 f"stdout: {runner.stdout_bytes.decode()!r}\n"
167 f"stderr: {runner.stderr_bytes.decode()!r}\n"
168 f"exception: {result.exception}"
172 @patch("black.dump_to_file", dump_to_stderr)
173 def checkSourceFile(self, name: str) -> None:
174 path = THIS_DIR.parent / name
175 source, expected = read_data(str(path), data=False)
177 self.assertFormatEqual(expected, actual)
178 black.assert_equivalent(source, actual)
179 black.assert_stable(source, actual, black.FileMode())
180 self.assertFalse(ff(path))
182 @patch("black.dump_to_file", dump_to_stderr)
183 def test_empty(self) -> None:
184 source = expected = ""
186 self.assertFormatEqual(expected, actual)
187 black.assert_equivalent(source, actual)
188 black.assert_stable(source, actual, black.FileMode())
190 def test_empty_ff(self) -> None:
192 tmp_file = Path(black.dump_to_file())
194 self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES))
195 with open(tmp_file, encoding="utf8") as f:
199 self.assertFormatEqual(expected, actual)
201 def test_self(self) -> None:
202 self.checkSourceFile("tests/test_black.py")
204 def test_black(self) -> None:
205 self.checkSourceFile("black.py")
207 def test_pygram(self) -> None:
208 self.checkSourceFile("blib2to3/pygram.py")
210 def test_pytree(self) -> None:
211 self.checkSourceFile("blib2to3/pytree.py")
213 def test_conv(self) -> None:
214 self.checkSourceFile("blib2to3/pgen2/conv.py")
216 def test_driver(self) -> None:
217 self.checkSourceFile("blib2to3/pgen2/driver.py")
219 def test_grammar(self) -> None:
220 self.checkSourceFile("blib2to3/pgen2/grammar.py")
222 def test_literals(self) -> None:
223 self.checkSourceFile("blib2to3/pgen2/literals.py")
225 def test_parse(self) -> None:
226 self.checkSourceFile("blib2to3/pgen2/parse.py")
228 def test_pgen(self) -> None:
229 self.checkSourceFile("blib2to3/pgen2/pgen.py")
231 def test_tokenize(self) -> None:
232 self.checkSourceFile("blib2to3/pgen2/tokenize.py")
234 def test_token(self) -> None:
235 self.checkSourceFile("blib2to3/pgen2/token.py")
237 def test_setup(self) -> None:
238 self.checkSourceFile("setup.py")
240 def test_piping(self) -> None:
241 source, expected = read_data("../black", data=False)
242 result = BlackRunner().invoke(
244 ["-", "--fast", f"--line-length={black.DEFAULT_LINE_LENGTH}"],
245 input=BytesIO(source.encode("utf8")),
247 self.assertEqual(result.exit_code, 0)
248 self.assertFormatEqual(expected, result.output)
249 black.assert_equivalent(source, result.output)
250 black.assert_stable(source, result.output, black.FileMode())
252 def test_piping_diff(self) -> None:
253 diff_header = re.compile(
254 rf"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d\d\d\d "
257 source, _ = read_data("expression.py")
258 expected, _ = read_data("expression.diff")
259 config = THIS_DIR / "data" / "empty_pyproject.toml"
263 f"--line-length={black.DEFAULT_LINE_LENGTH}",
265 f"--config={config}",
267 result = BlackRunner().invoke(
268 black.main, args, input=BytesIO(source.encode("utf8"))
270 self.assertEqual(result.exit_code, 0)
271 actual = diff_header.sub(DETERMINISTIC_HEADER, result.output)
272 actual = actual.rstrip() + "\n" # the diff output has a trailing space
273 self.assertEqual(expected, actual)
275 def test_piping_diff_with_color(self) -> None:
276 source, _ = read_data("expression.py")
277 config = THIS_DIR / "data" / "empty_pyproject.toml"
281 f"--line-length={black.DEFAULT_LINE_LENGTH}",
284 f"--config={config}",
286 result = BlackRunner().invoke(
287 black.main, args, input=BytesIO(source.encode("utf8"))
289 actual = result.output
290 # Again, the contents are checked in a different test, so only look for colors.
291 self.assertIn("\033[1;37m", actual)
292 self.assertIn("\033[36m", actual)
293 self.assertIn("\033[32m", actual)
294 self.assertIn("\033[31m", actual)
295 self.assertIn("\033[0m", actual)
297 @patch("black.dump_to_file", dump_to_stderr)
298 def test_function(self) -> None:
299 source, expected = read_data("function")
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_function2(self) -> None:
307 source, expected = read_data("function2")
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_function_trailing_comma(self) -> None:
315 source, expected = read_data("function_trailing_comma")
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_expression(self) -> None:
323 source, expected = read_data("expression")
325 self.assertFormatEqual(expected, actual)
326 black.assert_equivalent(source, actual)
327 black.assert_stable(source, actual, black.FileMode())
329 @patch("black.dump_to_file", dump_to_stderr)
330 def test_pep_572(self) -> None:
331 source, expected = read_data("pep_572")
333 self.assertFormatEqual(expected, actual)
334 black.assert_stable(source, actual, black.FileMode())
335 if sys.version_info >= (3, 8):
336 black.assert_equivalent(source, actual)
338 def test_pep_572_version_detection(self) -> None:
339 source, _ = read_data("pep_572")
340 root = black.lib2to3_parse(source)
341 features = black.get_features_used(root)
342 self.assertIn(black.Feature.ASSIGNMENT_EXPRESSIONS, features)
343 versions = black.detect_target_versions(root)
344 self.assertIn(black.TargetVersion.PY38, versions)
346 def test_expression_ff(self) -> None:
347 source, expected = read_data("expression")
348 tmp_file = Path(black.dump_to_file(source))
350 self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
351 with open(tmp_file, encoding="utf8") as f:
355 self.assertFormatEqual(expected, actual)
356 with patch("black.dump_to_file", dump_to_stderr):
357 black.assert_equivalent(source, actual)
358 black.assert_stable(source, actual, black.FileMode())
360 def test_expression_diff(self) -> None:
361 source, _ = read_data("expression.py")
362 expected, _ = read_data("expression.diff")
363 tmp_file = Path(black.dump_to_file(source))
364 diff_header = re.compile(
365 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
366 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
369 result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
370 self.assertEqual(result.exit_code, 0)
373 actual = result.output
374 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
375 actual = actual.rstrip() + "\n" # the diff output has a trailing space
376 if expected != actual:
377 dump = black.dump_to_file(actual)
379 "Expected diff isn't equal to the actual. If you made changes to"
380 " expression.py and this is an anticipated difference, overwrite"
381 f" tests/data/expression.diff with {dump}"
383 self.assertEqual(expected, actual, msg)
385 def test_expression_diff_with_color(self) -> None:
386 source, _ = read_data("expression.py")
387 expected, _ = read_data("expression.diff")
388 tmp_file = Path(black.dump_to_file(source))
390 result = BlackRunner().invoke(
391 black.main, ["--diff", "--color", str(tmp_file)]
395 actual = result.output
396 # We check the contents of the diff in `test_expression_diff`. All
397 # we need to check here is that color codes exist in the result.
398 self.assertIn("\033[1;37m", actual)
399 self.assertIn("\033[36m", actual)
400 self.assertIn("\033[32m", actual)
401 self.assertIn("\033[31m", actual)
402 self.assertIn("\033[0m", actual)
404 @patch("black.dump_to_file", dump_to_stderr)
405 def test_fstring(self) -> None:
406 source, expected = read_data("fstring")
408 self.assertFormatEqual(expected, actual)
409 black.assert_equivalent(source, actual)
410 black.assert_stable(source, actual, black.FileMode())
412 @patch("black.dump_to_file", dump_to_stderr)
413 def test_pep_570(self) -> None:
414 source, expected = read_data("pep_570")
416 self.assertFormatEqual(expected, actual)
417 black.assert_stable(source, actual, black.FileMode())
418 if sys.version_info >= (3, 8):
419 black.assert_equivalent(source, actual)
421 def test_detect_pos_only_arguments(self) -> None:
422 source, _ = read_data("pep_570")
423 root = black.lib2to3_parse(source)
424 features = black.get_features_used(root)
425 self.assertIn(black.Feature.POS_ONLY_ARGUMENTS, features)
426 versions = black.detect_target_versions(root)
427 self.assertIn(black.TargetVersion.PY38, versions)
429 @patch("black.dump_to_file", dump_to_stderr)
430 def test_string_quotes(self) -> None:
431 source, expected = read_data("string_quotes")
433 self.assertFormatEqual(expected, actual)
434 black.assert_equivalent(source, actual)
435 black.assert_stable(source, actual, black.FileMode())
436 mode = black.FileMode(string_normalization=False)
437 not_normalized = fs(source, mode=mode)
438 self.assertFormatEqual(source.replace("\\\n", ""), not_normalized)
439 black.assert_equivalent(source, not_normalized)
440 black.assert_stable(source, not_normalized, mode=mode)
442 @patch("black.dump_to_file", dump_to_stderr)
443 def test_docstring(self) -> None:
444 source, expected = read_data("docstring")
446 self.assertFormatEqual(expected, actual)
447 black.assert_equivalent(source, actual)
448 black.assert_stable(source, actual, black.FileMode())
450 def test_long_strings(self) -> None:
451 """Tests for splitting long strings."""
452 source, expected = read_data("long_strings")
454 self.assertFormatEqual(expected, actual)
455 black.assert_equivalent(source, actual)
456 black.assert_stable(source, actual, black.FileMode())
458 @patch("black.dump_to_file", dump_to_stderr)
459 def test_long_strings__edge_case(self) -> None:
460 """Edge-case tests for splitting long strings."""
461 source, expected = read_data("long_strings__edge_case")
463 self.assertFormatEqual(expected, actual)
464 black.assert_equivalent(source, actual)
465 black.assert_stable(source, actual, black.FileMode())
467 @patch("black.dump_to_file", dump_to_stderr)
468 def test_long_strings__regression(self) -> None:
469 """Regression tests for splitting long strings."""
470 source, expected = read_data("long_strings__regression")
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_slices(self) -> None:
478 source, expected = read_data("slices")
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_comments(self) -> None:
486 source, expected = read_data("comments")
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_comments2(self) -> None:
494 source, expected = read_data("comments2")
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_comments3(self) -> None:
502 source, expected = read_data("comments3")
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_comments4(self) -> None:
510 source, expected = read_data("comments4")
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_comments5(self) -> None:
518 source, expected = read_data("comments5")
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_comments6(self) -> None:
526 source, expected = read_data("comments6")
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_comments7(self) -> None:
534 source, expected = read_data("comments7")
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_comment_after_escaped_newline(self) -> None:
542 source, expected = read_data("comment_after_escaped_newline")
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_cantfit(self) -> None:
550 source, expected = read_data("cantfit")
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_import_spacing(self) -> None:
558 source, expected = read_data("import_spacing")
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_composition(self) -> None:
566 source, expected = read_data("composition")
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_empty_lines(self) -> None:
574 source, expected = read_data("empty_lines")
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_remove_parens(self) -> None:
582 source, expected = read_data("remove_parens")
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_string_prefixes(self) -> None:
590 source, expected = read_data("string_prefixes")
592 self.assertFormatEqual(expected, actual)
593 black.assert_equivalent(source, actual)
594 black.assert_stable(source, actual, black.FileMode())
596 @patch("black.dump_to_file", dump_to_stderr)
597 def test_numeric_literals(self) -> None:
598 source, expected = read_data("numeric_literals")
599 mode = black.FileMode(target_versions=black.PY36_VERSIONS)
600 actual = fs(source, mode=mode)
601 self.assertFormatEqual(expected, actual)
602 black.assert_equivalent(source, actual)
603 black.assert_stable(source, actual, mode)
605 @patch("black.dump_to_file", dump_to_stderr)
606 def test_numeric_literals_ignoring_underscores(self) -> None:
607 source, expected = read_data("numeric_literals_skip_underscores")
608 mode = black.FileMode(target_versions=black.PY36_VERSIONS)
609 actual = fs(source, mode=mode)
610 self.assertFormatEqual(expected, actual)
611 black.assert_equivalent(source, actual)
612 black.assert_stable(source, actual, mode)
614 @patch("black.dump_to_file", dump_to_stderr)
615 def test_numeric_literals_py2(self) -> None:
616 source, expected = read_data("numeric_literals_py2")
618 self.assertFormatEqual(expected, actual)
619 black.assert_stable(source, actual, black.FileMode())
621 @patch("black.dump_to_file", dump_to_stderr)
622 def test_python2(self) -> None:
623 source, expected = read_data("python2")
625 self.assertFormatEqual(expected, actual)
626 black.assert_equivalent(source, actual)
627 black.assert_stable(source, actual, black.FileMode())
629 @patch("black.dump_to_file", dump_to_stderr)
630 def test_python2_print_function(self) -> None:
631 source, expected = read_data("python2_print_function")
632 mode = black.FileMode(target_versions={TargetVersion.PY27})
633 actual = fs(source, mode=mode)
634 self.assertFormatEqual(expected, actual)
635 black.assert_equivalent(source, actual)
636 black.assert_stable(source, actual, mode)
638 @patch("black.dump_to_file", dump_to_stderr)
639 def test_python2_unicode_literals(self) -> None:
640 source, expected = read_data("python2_unicode_literals")
642 self.assertFormatEqual(expected, actual)
643 black.assert_equivalent(source, actual)
644 black.assert_stable(source, actual, black.FileMode())
646 @patch("black.dump_to_file", dump_to_stderr)
647 def test_stub(self) -> None:
648 mode = black.FileMode(is_pyi=True)
649 source, expected = read_data("stub.pyi")
650 actual = fs(source, mode=mode)
651 self.assertFormatEqual(expected, actual)
652 black.assert_stable(source, actual, mode)
654 @patch("black.dump_to_file", dump_to_stderr)
655 def test_async_as_identifier(self) -> None:
656 source_path = (THIS_DIR / "data" / "async_as_identifier.py").resolve()
657 source, expected = read_data("async_as_identifier")
659 self.assertFormatEqual(expected, actual)
660 major, minor = sys.version_info[:2]
661 if major < 3 or (major <= 3 and minor < 7):
662 black.assert_equivalent(source, actual)
663 black.assert_stable(source, actual, black.FileMode())
664 # ensure black can parse this when the target is 3.6
665 self.invokeBlack([str(source_path), "--target-version", "py36"])
666 # but not on 3.7, because async/await is no longer an identifier
667 self.invokeBlack([str(source_path), "--target-version", "py37"], exit_code=123)
669 @patch("black.dump_to_file", dump_to_stderr)
670 def test_python37(self) -> None:
671 source_path = (THIS_DIR / "data" / "python37.py").resolve()
672 source, expected = read_data("python37")
674 self.assertFormatEqual(expected, actual)
675 major, minor = sys.version_info[:2]
676 if major > 3 or (major == 3 and minor >= 7):
677 black.assert_equivalent(source, actual)
678 black.assert_stable(source, actual, black.FileMode())
679 # ensure black can parse this when the target is 3.7
680 self.invokeBlack([str(source_path), "--target-version", "py37"])
681 # but not on 3.6, because we use async as a reserved keyword
682 self.invokeBlack([str(source_path), "--target-version", "py36"], exit_code=123)
684 @patch("black.dump_to_file", dump_to_stderr)
685 def test_python38(self) -> None:
686 source, expected = read_data("python38")
688 self.assertFormatEqual(expected, actual)
689 major, minor = sys.version_info[:2]
690 if major > 3 or (major == 3 and minor >= 8):
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_fmtonoff(self) -> None:
696 source, expected = read_data("fmtonoff")
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_fmtonoff2(self) -> None:
704 source, expected = read_data("fmtonoff2")
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_fmtonoff3(self) -> None:
712 source, expected = read_data("fmtonoff3")
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_fmtonoff4(self) -> None:
720 source, expected = read_data("fmtonoff4")
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_remove_empty_parentheses_after_class(self) -> None:
728 source, expected = read_data("class_blank_parentheses")
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_new_line_between_class_and_code(self) -> None:
736 source, expected = read_data("class_methods_new_line")
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_bracket_match(self) -> None:
744 source, expected = read_data("bracketmatch")
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_tuple_assign(self) -> None:
752 source, expected = read_data("tupleassign")
754 self.assertFormatEqual(expected, actual)
755 black.assert_equivalent(source, actual)
756 black.assert_stable(source, actual, black.FileMode())
758 @patch("black.dump_to_file", dump_to_stderr)
759 def test_beginning_backslash(self) -> None:
760 source, expected = read_data("beginning_backslash")
762 self.assertFormatEqual(expected, actual)
763 black.assert_equivalent(source, actual)
764 black.assert_stable(source, actual, black.FileMode())
766 def test_tab_comment_indentation(self) -> None:
767 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
768 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
769 self.assertFormatEqual(contents_spc, fs(contents_spc))
770 self.assertFormatEqual(contents_spc, fs(contents_tab))
772 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
773 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
774 self.assertFormatEqual(contents_spc, fs(contents_spc))
775 self.assertFormatEqual(contents_spc, fs(contents_tab))
777 # mixed tabs and spaces (valid Python 2 code)
778 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t# comment\n pass\n"
779 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
780 self.assertFormatEqual(contents_spc, fs(contents_spc))
781 self.assertFormatEqual(contents_spc, fs(contents_tab))
783 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t\t# comment\n pass\n"
784 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
785 self.assertFormatEqual(contents_spc, fs(contents_spc))
786 self.assertFormatEqual(contents_spc, fs(contents_tab))
788 def test_report_verbose(self) -> None:
789 report = black.Report(verbose=True)
793 def out(msg: str, **kwargs: Any) -> None:
794 out_lines.append(msg)
796 def err(msg: str, **kwargs: Any) -> None:
797 err_lines.append(msg)
799 with patch("black.out", out), patch("black.err", err):
800 report.done(Path("f1"), black.Changed.NO)
801 self.assertEqual(len(out_lines), 1)
802 self.assertEqual(len(err_lines), 0)
803 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
804 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
805 self.assertEqual(report.return_code, 0)
806 report.done(Path("f2"), black.Changed.YES)
807 self.assertEqual(len(out_lines), 2)
808 self.assertEqual(len(err_lines), 0)
809 self.assertEqual(out_lines[-1], "reformatted f2")
811 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
813 report.done(Path("f3"), black.Changed.CACHED)
814 self.assertEqual(len(out_lines), 3)
815 self.assertEqual(len(err_lines), 0)
817 out_lines[-1], "f3 wasn't modified on disk since last run."
820 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
822 self.assertEqual(report.return_code, 0)
824 self.assertEqual(report.return_code, 1)
826 report.failed(Path("e1"), "boom")
827 self.assertEqual(len(out_lines), 3)
828 self.assertEqual(len(err_lines), 1)
829 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
831 unstyle(str(report)),
832 "1 file reformatted, 2 files left unchanged, 1 file failed to"
835 self.assertEqual(report.return_code, 123)
836 report.done(Path("f3"), black.Changed.YES)
837 self.assertEqual(len(out_lines), 4)
838 self.assertEqual(len(err_lines), 1)
839 self.assertEqual(out_lines[-1], "reformatted f3")
841 unstyle(str(report)),
842 "2 files reformatted, 2 files left unchanged, 1 file failed to"
845 self.assertEqual(report.return_code, 123)
846 report.failed(Path("e2"), "boom")
847 self.assertEqual(len(out_lines), 4)
848 self.assertEqual(len(err_lines), 2)
849 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
851 unstyle(str(report)),
852 "2 files reformatted, 2 files left unchanged, 2 files failed to"
855 self.assertEqual(report.return_code, 123)
856 report.path_ignored(Path("wat"), "no match")
857 self.assertEqual(len(out_lines), 5)
858 self.assertEqual(len(err_lines), 2)
859 self.assertEqual(out_lines[-1], "wat ignored: no match")
861 unstyle(str(report)),
862 "2 files reformatted, 2 files left unchanged, 2 files failed to"
865 self.assertEqual(report.return_code, 123)
866 report.done(Path("f4"), black.Changed.NO)
867 self.assertEqual(len(out_lines), 6)
868 self.assertEqual(len(err_lines), 2)
869 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
871 unstyle(str(report)),
872 "2 files reformatted, 3 files left unchanged, 2 files failed to"
875 self.assertEqual(report.return_code, 123)
878 unstyle(str(report)),
879 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
880 " would fail to reformat.",
885 unstyle(str(report)),
886 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
887 " would fail to reformat.",
890 def test_report_quiet(self) -> None:
891 report = black.Report(quiet=True)
895 def out(msg: str, **kwargs: Any) -> None:
896 out_lines.append(msg)
898 def err(msg: str, **kwargs: Any) -> None:
899 err_lines.append(msg)
901 with patch("black.out", out), patch("black.err", err):
902 report.done(Path("f1"), black.Changed.NO)
903 self.assertEqual(len(out_lines), 0)
904 self.assertEqual(len(err_lines), 0)
905 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
906 self.assertEqual(report.return_code, 0)
907 report.done(Path("f2"), black.Changed.YES)
908 self.assertEqual(len(out_lines), 0)
909 self.assertEqual(len(err_lines), 0)
911 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
913 report.done(Path("f3"), black.Changed.CACHED)
914 self.assertEqual(len(out_lines), 0)
915 self.assertEqual(len(err_lines), 0)
917 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
919 self.assertEqual(report.return_code, 0)
921 self.assertEqual(report.return_code, 1)
923 report.failed(Path("e1"), "boom")
924 self.assertEqual(len(out_lines), 0)
925 self.assertEqual(len(err_lines), 1)
926 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
928 unstyle(str(report)),
929 "1 file reformatted, 2 files left unchanged, 1 file failed to"
932 self.assertEqual(report.return_code, 123)
933 report.done(Path("f3"), black.Changed.YES)
934 self.assertEqual(len(out_lines), 0)
935 self.assertEqual(len(err_lines), 1)
937 unstyle(str(report)),
938 "2 files reformatted, 2 files left unchanged, 1 file failed to"
941 self.assertEqual(report.return_code, 123)
942 report.failed(Path("e2"), "boom")
943 self.assertEqual(len(out_lines), 0)
944 self.assertEqual(len(err_lines), 2)
945 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
947 unstyle(str(report)),
948 "2 files reformatted, 2 files left unchanged, 2 files failed to"
951 self.assertEqual(report.return_code, 123)
952 report.path_ignored(Path("wat"), "no match")
953 self.assertEqual(len(out_lines), 0)
954 self.assertEqual(len(err_lines), 2)
956 unstyle(str(report)),
957 "2 files reformatted, 2 files left unchanged, 2 files failed to"
960 self.assertEqual(report.return_code, 123)
961 report.done(Path("f4"), black.Changed.NO)
962 self.assertEqual(len(out_lines), 0)
963 self.assertEqual(len(err_lines), 2)
965 unstyle(str(report)),
966 "2 files reformatted, 3 files left unchanged, 2 files failed to"
969 self.assertEqual(report.return_code, 123)
972 unstyle(str(report)),
973 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
974 " would fail to reformat.",
979 unstyle(str(report)),
980 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
981 " would fail to reformat.",
984 def test_report_normal(self) -> None:
985 report = black.Report()
989 def out(msg: str, **kwargs: Any) -> None:
990 out_lines.append(msg)
992 def err(msg: str, **kwargs: Any) -> None:
993 err_lines.append(msg)
995 with patch("black.out", out), patch("black.err", err):
996 report.done(Path("f1"), black.Changed.NO)
997 self.assertEqual(len(out_lines), 0)
998 self.assertEqual(len(err_lines), 0)
999 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
1000 self.assertEqual(report.return_code, 0)
1001 report.done(Path("f2"), black.Changed.YES)
1002 self.assertEqual(len(out_lines), 1)
1003 self.assertEqual(len(err_lines), 0)
1004 self.assertEqual(out_lines[-1], "reformatted f2")
1006 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
1008 report.done(Path("f3"), black.Changed.CACHED)
1009 self.assertEqual(len(out_lines), 1)
1010 self.assertEqual(len(err_lines), 0)
1011 self.assertEqual(out_lines[-1], "reformatted f2")
1013 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
1015 self.assertEqual(report.return_code, 0)
1017 self.assertEqual(report.return_code, 1)
1018 report.check = False
1019 report.failed(Path("e1"), "boom")
1020 self.assertEqual(len(out_lines), 1)
1021 self.assertEqual(len(err_lines), 1)
1022 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
1024 unstyle(str(report)),
1025 "1 file reformatted, 2 files left unchanged, 1 file failed to"
1028 self.assertEqual(report.return_code, 123)
1029 report.done(Path("f3"), black.Changed.YES)
1030 self.assertEqual(len(out_lines), 2)
1031 self.assertEqual(len(err_lines), 1)
1032 self.assertEqual(out_lines[-1], "reformatted f3")
1034 unstyle(str(report)),
1035 "2 files reformatted, 2 files left unchanged, 1 file failed to"
1038 self.assertEqual(report.return_code, 123)
1039 report.failed(Path("e2"), "boom")
1040 self.assertEqual(len(out_lines), 2)
1041 self.assertEqual(len(err_lines), 2)
1042 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
1044 unstyle(str(report)),
1045 "2 files reformatted, 2 files left unchanged, 2 files failed to"
1048 self.assertEqual(report.return_code, 123)
1049 report.path_ignored(Path("wat"), "no match")
1050 self.assertEqual(len(out_lines), 2)
1051 self.assertEqual(len(err_lines), 2)
1053 unstyle(str(report)),
1054 "2 files reformatted, 2 files left unchanged, 2 files failed to"
1057 self.assertEqual(report.return_code, 123)
1058 report.done(Path("f4"), black.Changed.NO)
1059 self.assertEqual(len(out_lines), 2)
1060 self.assertEqual(len(err_lines), 2)
1062 unstyle(str(report)),
1063 "2 files reformatted, 3 files left unchanged, 2 files failed to"
1066 self.assertEqual(report.return_code, 123)
1069 unstyle(str(report)),
1070 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1071 " would fail to reformat.",
1073 report.check = False
1076 unstyle(str(report)),
1077 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1078 " would fail to reformat.",
1081 def test_lib2to3_parse(self) -> None:
1082 with self.assertRaises(black.InvalidInput):
1083 black.lib2to3_parse("invalid syntax")
1085 straddling = "x + y"
1086 black.lib2to3_parse(straddling)
1087 black.lib2to3_parse(straddling, {TargetVersion.PY27})
1088 black.lib2to3_parse(straddling, {TargetVersion.PY36})
1089 black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
1091 py2_only = "print x"
1092 black.lib2to3_parse(py2_only)
1093 black.lib2to3_parse(py2_only, {TargetVersion.PY27})
1094 with self.assertRaises(black.InvalidInput):
1095 black.lib2to3_parse(py2_only, {TargetVersion.PY36})
1096 with self.assertRaises(black.InvalidInput):
1097 black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
1099 py3_only = "exec(x, end=y)"
1100 black.lib2to3_parse(py3_only)
1101 with self.assertRaises(black.InvalidInput):
1102 black.lib2to3_parse(py3_only, {TargetVersion.PY27})
1103 black.lib2to3_parse(py3_only, {TargetVersion.PY36})
1104 black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
1106 def test_get_features_used(self) -> None:
1107 node = black.lib2to3_parse("def f(*, arg): ...\n")
1108 self.assertEqual(black.get_features_used(node), set())
1109 node = black.lib2to3_parse("def f(*, arg,): ...\n")
1110 self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
1111 node = black.lib2to3_parse("f(*arg,)\n")
1113 black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
1115 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
1116 self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
1117 node = black.lib2to3_parse("123_456\n")
1118 self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
1119 node = black.lib2to3_parse("123456\n")
1120 self.assertEqual(black.get_features_used(node), set())
1121 source, expected = read_data("function")
1122 node = black.lib2to3_parse(source)
1123 expected_features = {
1124 Feature.TRAILING_COMMA_IN_CALL,
1125 Feature.TRAILING_COMMA_IN_DEF,
1128 self.assertEqual(black.get_features_used(node), expected_features)
1129 node = black.lib2to3_parse(expected)
1130 self.assertEqual(black.get_features_used(node), expected_features)
1131 source, expected = read_data("expression")
1132 node = black.lib2to3_parse(source)
1133 self.assertEqual(black.get_features_used(node), set())
1134 node = black.lib2to3_parse(expected)
1135 self.assertEqual(black.get_features_used(node), set())
1137 def test_get_future_imports(self) -> None:
1138 node = black.lib2to3_parse("\n")
1139 self.assertEqual(set(), black.get_future_imports(node))
1140 node = black.lib2to3_parse("from __future__ import black\n")
1141 self.assertEqual({"black"}, black.get_future_imports(node))
1142 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
1143 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1144 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
1145 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
1146 node = black.lib2to3_parse(
1147 "from __future__ import multiple\nfrom __future__ import imports\n"
1149 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1150 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
1151 self.assertEqual({"black"}, black.get_future_imports(node))
1152 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
1153 self.assertEqual({"black"}, black.get_future_imports(node))
1154 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
1155 self.assertEqual(set(), black.get_future_imports(node))
1156 node = black.lib2to3_parse("from some.module import black\n")
1157 self.assertEqual(set(), black.get_future_imports(node))
1158 node = black.lib2to3_parse(
1159 "from __future__ import unicode_literals as _unicode_literals"
1161 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
1162 node = black.lib2to3_parse(
1163 "from __future__ import unicode_literals as _lol, print"
1165 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
1167 def test_debug_visitor(self) -> None:
1168 source, _ = read_data("debug_visitor.py")
1169 expected, _ = read_data("debug_visitor.out")
1173 def out(msg: str, **kwargs: Any) -> None:
1174 out_lines.append(msg)
1176 def err(msg: str, **kwargs: Any) -> None:
1177 err_lines.append(msg)
1179 with patch("black.out", out), patch("black.err", err):
1180 black.DebugVisitor.show(source)
1181 actual = "\n".join(out_lines) + "\n"
1183 if expected != actual:
1184 log_name = black.dump_to_file(*out_lines)
1188 f"AST print out is different. Actual version dumped to {log_name}",
1191 def test_format_file_contents(self) -> None:
1193 mode = black.FileMode()
1194 with self.assertRaises(black.NothingChanged):
1195 black.format_file_contents(empty, mode=mode, fast=False)
1197 with self.assertRaises(black.NothingChanged):
1198 black.format_file_contents(just_nl, mode=mode, fast=False)
1199 same = "j = [1, 2, 3]\n"
1200 with self.assertRaises(black.NothingChanged):
1201 black.format_file_contents(same, mode=mode, fast=False)
1202 different = "j = [1,2,3]"
1204 actual = black.format_file_contents(different, mode=mode, fast=False)
1205 self.assertEqual(expected, actual)
1206 invalid = "return if you can"
1207 with self.assertRaises(black.InvalidInput) as e:
1208 black.format_file_contents(invalid, mode=mode, fast=False)
1209 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
1211 def test_endmarker(self) -> None:
1212 n = black.lib2to3_parse("\n")
1213 self.assertEqual(n.type, black.syms.file_input)
1214 self.assertEqual(len(n.children), 1)
1215 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
1217 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
1218 def test_assertFormatEqual(self) -> None:
1222 def out(msg: str, **kwargs: Any) -> None:
1223 out_lines.append(msg)
1225 def err(msg: str, **kwargs: Any) -> None:
1226 err_lines.append(msg)
1228 with patch("black.out", out), patch("black.err", err):
1229 with self.assertRaises(AssertionError):
1230 self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")
1232 out_str = "".join(out_lines)
1233 self.assertTrue("Expected tree:" in out_str)
1234 self.assertTrue("Actual tree:" in out_str)
1235 self.assertEqual("".join(err_lines), "")
1237 def test_cache_broken_file(self) -> None:
1238 mode = black.FileMode()
1239 with cache_dir() as workspace:
1240 cache_file = black.get_cache_file(mode)
1241 with cache_file.open("w") as fobj:
1242 fobj.write("this is not a pickle")
1243 self.assertEqual(black.read_cache(mode), {})
1244 src = (workspace / "test.py").resolve()
1245 with src.open("w") as fobj:
1246 fobj.write("print('hello')")
1247 self.invokeBlack([str(src)])
1248 cache = black.read_cache(mode)
1249 self.assertIn(src, cache)
1251 def test_cache_single_file_already_cached(self) -> None:
1252 mode = black.FileMode()
1253 with cache_dir() as workspace:
1254 src = (workspace / "test.py").resolve()
1255 with src.open("w") as fobj:
1256 fobj.write("print('hello')")
1257 black.write_cache({}, [src], mode)
1258 self.invokeBlack([str(src)])
1259 with src.open("r") as fobj:
1260 self.assertEqual(fobj.read(), "print('hello')")
1263 def test_cache_multiple_files(self) -> None:
1264 mode = black.FileMode()
1265 with cache_dir() as workspace, patch(
1266 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1268 one = (workspace / "one.py").resolve()
1269 with one.open("w") as fobj:
1270 fobj.write("print('hello')")
1271 two = (workspace / "two.py").resolve()
1272 with two.open("w") as fobj:
1273 fobj.write("print('hello')")
1274 black.write_cache({}, [one], mode)
1275 self.invokeBlack([str(workspace)])
1276 with one.open("r") as fobj:
1277 self.assertEqual(fobj.read(), "print('hello')")
1278 with two.open("r") as fobj:
1279 self.assertEqual(fobj.read(), 'print("hello")\n')
1280 cache = black.read_cache(mode)
1281 self.assertIn(one, cache)
1282 self.assertIn(two, cache)
1284 def test_no_cache_when_writeback_diff(self) -> None:
1285 mode = black.FileMode()
1286 with cache_dir() as workspace:
1287 src = (workspace / "test.py").resolve()
1288 with src.open("w") as fobj:
1289 fobj.write("print('hello')")
1290 self.invokeBlack([str(src), "--diff"])
1291 cache_file = black.get_cache_file(mode)
1292 self.assertFalse(cache_file.exists())
1294 def test_no_cache_when_stdin(self) -> None:
1295 mode = black.FileMode()
1297 result = CliRunner().invoke(
1298 black.main, ["-"], input=BytesIO(b"print('hello')")
1300 self.assertEqual(result.exit_code, 0)
1301 cache_file = black.get_cache_file(mode)
1302 self.assertFalse(cache_file.exists())
1304 def test_read_cache_no_cachefile(self) -> None:
1305 mode = black.FileMode()
1307 self.assertEqual(black.read_cache(mode), {})
1309 def test_write_cache_read_cache(self) -> None:
1310 mode = black.FileMode()
1311 with cache_dir() as workspace:
1312 src = (workspace / "test.py").resolve()
1314 black.write_cache({}, [src], mode)
1315 cache = black.read_cache(mode)
1316 self.assertIn(src, cache)
1317 self.assertEqual(cache[src], black.get_cache_info(src))
1319 def test_filter_cached(self) -> None:
1320 with TemporaryDirectory() as workspace:
1321 path = Path(workspace)
1322 uncached = (path / "uncached").resolve()
1323 cached = (path / "cached").resolve()
1324 cached_but_changed = (path / "changed").resolve()
1327 cached_but_changed.touch()
1328 cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
1329 todo, done = black.filter_cached(
1330 cache, {uncached, cached, cached_but_changed}
1332 self.assertEqual(todo, {uncached, cached_but_changed})
1333 self.assertEqual(done, {cached})
1335 def test_write_cache_creates_directory_if_needed(self) -> None:
1336 mode = black.FileMode()
1337 with cache_dir(exists=False) as workspace:
1338 self.assertFalse(workspace.exists())
1339 black.write_cache({}, [], mode)
1340 self.assertTrue(workspace.exists())
1343 def test_failed_formatting_does_not_get_cached(self) -> None:
1344 mode = black.FileMode()
1345 with cache_dir() as workspace, patch(
1346 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1348 failing = (workspace / "failing.py").resolve()
1349 with failing.open("w") as fobj:
1350 fobj.write("not actually python")
1351 clean = (workspace / "clean.py").resolve()
1352 with clean.open("w") as fobj:
1353 fobj.write('print("hello")\n')
1354 self.invokeBlack([str(workspace)], exit_code=123)
1355 cache = black.read_cache(mode)
1356 self.assertNotIn(failing, cache)
1357 self.assertIn(clean, cache)
1359 def test_write_cache_write_fail(self) -> None:
1360 mode = black.FileMode()
1361 with cache_dir(), patch.object(Path, "open") as mock:
1362 mock.side_effect = OSError
1363 black.write_cache({}, [], mode)
1366 @patch("black.ProcessPoolExecutor", MagicMock(side_effect=OSError))
1367 def test_works_in_mono_process_only_environment(self) -> None:
1368 with cache_dir() as workspace:
1370 (workspace / "one.py").resolve(),
1371 (workspace / "two.py").resolve(),
1373 f.write_text('print("hello")\n')
1374 self.invokeBlack([str(workspace)])
1377 def test_check_diff_use_together(self) -> None:
1379 # Files which will be reformatted.
1380 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1381 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1382 # Files which will not be reformatted.
1383 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1384 self.invokeBlack([str(src2), "--diff", "--check"])
1385 # Multi file command.
1386 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1388 def test_no_files(self) -> None:
1390 # Without an argument, black exits with error code 0.
1391 self.invokeBlack([])
1393 def test_broken_symlink(self) -> None:
1394 with cache_dir() as workspace:
1395 symlink = workspace / "broken_link.py"
1397 symlink.symlink_to("nonexistent.py")
1398 except OSError as e:
1399 self.skipTest(f"Can't create symlinks: {e}")
1400 self.invokeBlack([str(workspace.resolve())])
1402 def test_read_cache_line_lengths(self) -> None:
1403 mode = black.FileMode()
1404 short_mode = black.FileMode(line_length=1)
1405 with cache_dir() as workspace:
1406 path = (workspace / "file.py").resolve()
1408 black.write_cache({}, [path], mode)
1409 one = black.read_cache(mode)
1410 self.assertIn(path, one)
1411 two = black.read_cache(short_mode)
1412 self.assertNotIn(path, two)
1414 def test_tricky_unicode_symbols(self) -> None:
1415 source, expected = read_data("tricky_unicode_symbols")
1417 self.assertFormatEqual(expected, actual)
1418 black.assert_equivalent(source, actual)
1419 black.assert_stable(source, actual, black.FileMode())
1421 def test_single_file_force_pyi(self) -> None:
1422 reg_mode = black.FileMode()
1423 pyi_mode = black.FileMode(is_pyi=True)
1424 contents, expected = read_data("force_pyi")
1425 with cache_dir() as workspace:
1426 path = (workspace / "file.py").resolve()
1427 with open(path, "w") as fh:
1429 self.invokeBlack([str(path), "--pyi"])
1430 with open(path, "r") as fh:
1432 # verify cache with --pyi is separate
1433 pyi_cache = black.read_cache(pyi_mode)
1434 self.assertIn(path, pyi_cache)
1435 normal_cache = black.read_cache(reg_mode)
1436 self.assertNotIn(path, normal_cache)
1437 self.assertEqual(actual, expected)
1440 def test_multi_file_force_pyi(self) -> None:
1441 reg_mode = black.FileMode()
1442 pyi_mode = black.FileMode(is_pyi=True)
1443 contents, expected = read_data("force_pyi")
1444 with cache_dir() as workspace:
1446 (workspace / "file1.py").resolve(),
1447 (workspace / "file2.py").resolve(),
1450 with open(path, "w") as fh:
1452 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1454 with open(path, "r") as fh:
1456 self.assertEqual(actual, expected)
1457 # verify cache with --pyi is separate
1458 pyi_cache = black.read_cache(pyi_mode)
1459 normal_cache = black.read_cache(reg_mode)
1461 self.assertIn(path, pyi_cache)
1462 self.assertNotIn(path, normal_cache)
1464 def test_pipe_force_pyi(self) -> None:
1465 source, expected = read_data("force_pyi")
1466 result = CliRunner().invoke(
1467 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1469 self.assertEqual(result.exit_code, 0)
1470 actual = result.output
1471 self.assertFormatEqual(actual, expected)
1473 def test_single_file_force_py36(self) -> None:
1474 reg_mode = black.FileMode()
1475 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1476 source, expected = read_data("force_py36")
1477 with cache_dir() as workspace:
1478 path = (workspace / "file.py").resolve()
1479 with open(path, "w") as fh:
1481 self.invokeBlack([str(path), *PY36_ARGS])
1482 with open(path, "r") as fh:
1484 # verify cache with --target-version is separate
1485 py36_cache = black.read_cache(py36_mode)
1486 self.assertIn(path, py36_cache)
1487 normal_cache = black.read_cache(reg_mode)
1488 self.assertNotIn(path, normal_cache)
1489 self.assertEqual(actual, expected)
1492 def test_multi_file_force_py36(self) -> None:
1493 reg_mode = black.FileMode()
1494 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1495 source, expected = read_data("force_py36")
1496 with cache_dir() as workspace:
1498 (workspace / "file1.py").resolve(),
1499 (workspace / "file2.py").resolve(),
1502 with open(path, "w") as fh:
1504 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1506 with open(path, "r") as fh:
1508 self.assertEqual(actual, expected)
1509 # verify cache with --target-version is separate
1510 pyi_cache = black.read_cache(py36_mode)
1511 normal_cache = black.read_cache(reg_mode)
1513 self.assertIn(path, pyi_cache)
1514 self.assertNotIn(path, normal_cache)
1516 def test_collections(self) -> None:
1517 source, expected = read_data("collections")
1519 self.assertFormatEqual(expected, actual)
1520 black.assert_equivalent(source, actual)
1521 black.assert_stable(source, actual, black.FileMode())
1523 def test_pipe_force_py36(self) -> None:
1524 source, expected = read_data("force_py36")
1525 result = CliRunner().invoke(
1527 ["-", "-q", "--target-version=py36"],
1528 input=BytesIO(source.encode("utf8")),
1530 self.assertEqual(result.exit_code, 0)
1531 actual = result.output
1532 self.assertFormatEqual(actual, expected)
1534 def test_include_exclude(self) -> None:
1535 path = THIS_DIR / "data" / "include_exclude_tests"
1536 include = re.compile(r"\.pyi?$")
1537 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1538 report = black.Report()
1539 gitignore = PathSpec.from_lines("gitwildmatch", [])
1540 sources: List[Path] = []
1542 Path(path / "b/dont_exclude/a.py"),
1543 Path(path / "b/dont_exclude/a.pyi"),
1545 this_abs = THIS_DIR.resolve()
1547 black.gen_python_files(
1548 path.iterdir(), this_abs, include, [exclude], report, gitignore
1551 self.assertEqual(sorted(expected), sorted(sources))
1553 def test_gitignore_exclude(self) -> None:
1554 path = THIS_DIR / "data" / "include_exclude_tests"
1555 include = re.compile(r"\.pyi?$")
1556 exclude = re.compile(r"")
1557 report = black.Report()
1558 gitignore = PathSpec.from_lines(
1559 "gitwildmatch", ["exclude/", ".definitely_exclude"]
1561 sources: List[Path] = []
1563 Path(path / "b/dont_exclude/a.py"),
1564 Path(path / "b/dont_exclude/a.pyi"),
1566 this_abs = THIS_DIR.resolve()
1568 black.gen_python_files(
1569 path.iterdir(), this_abs, include, [exclude], report, gitignore
1572 self.assertEqual(sorted(expected), sorted(sources))
1574 def test_empty_include(self) -> None:
1575 path = THIS_DIR / "data" / "include_exclude_tests"
1576 report = black.Report()
1577 gitignore = PathSpec.from_lines("gitwildmatch", [])
1578 empty = re.compile(r"")
1579 sources: List[Path] = []
1581 Path(path / "b/exclude/a.pie"),
1582 Path(path / "b/exclude/a.py"),
1583 Path(path / "b/exclude/a.pyi"),
1584 Path(path / "b/dont_exclude/a.pie"),
1585 Path(path / "b/dont_exclude/a.py"),
1586 Path(path / "b/dont_exclude/a.pyi"),
1587 Path(path / "b/.definitely_exclude/a.pie"),
1588 Path(path / "b/.definitely_exclude/a.py"),
1589 Path(path / "b/.definitely_exclude/a.pyi"),
1591 this_abs = THIS_DIR.resolve()
1593 black.gen_python_files(
1597 [re.compile(black.DEFAULT_EXCLUDES)],
1602 self.assertEqual(sorted(expected), sorted(sources))
1604 def test_empty_exclude(self) -> None:
1605 path = THIS_DIR / "data" / "include_exclude_tests"
1606 report = black.Report()
1607 gitignore = PathSpec.from_lines("gitwildmatch", [])
1608 empty = re.compile(r"")
1609 sources: List[Path] = []
1611 Path(path / "b/dont_exclude/a.py"),
1612 Path(path / "b/dont_exclude/a.pyi"),
1613 Path(path / "b/exclude/a.py"),
1614 Path(path / "b/exclude/a.pyi"),
1615 Path(path / "b/.definitely_exclude/a.py"),
1616 Path(path / "b/.definitely_exclude/a.pyi"),
1618 this_abs = THIS_DIR.resolve()
1620 black.gen_python_files(
1623 re.compile(black.DEFAULT_INCLUDES),
1629 self.assertEqual(sorted(expected), sorted(sources))
1631 def test_invalid_include_exclude(self) -> None:
1632 for option in ["--include", "--exclude"]:
1633 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1635 def test_preserves_line_endings(self) -> None:
1636 with TemporaryDirectory() as workspace:
1637 test_file = Path(workspace) / "test.py"
1638 for nl in ["\n", "\r\n"]:
1639 contents = nl.join(["def f( ):", " pass"])
1640 test_file.write_bytes(contents.encode())
1641 ff(test_file, write_back=black.WriteBack.YES)
1642 updated_contents: bytes = test_file.read_bytes()
1643 self.assertIn(nl.encode(), updated_contents)
1645 self.assertNotIn(b"\r\n", updated_contents)
1647 def test_preserves_line_endings_via_stdin(self) -> None:
1648 for nl in ["\n", "\r\n"]:
1649 contents = nl.join(["def f( ):", " pass"])
1650 runner = BlackRunner()
1651 result = runner.invoke(
1652 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1654 self.assertEqual(result.exit_code, 0)
1655 output = runner.stdout_bytes
1656 self.assertIn(nl.encode("utf8"), output)
1658 self.assertNotIn(b"\r\n", output)
1660 def test_assert_equivalent_different_asts(self) -> None:
1661 with self.assertRaises(AssertionError):
1662 black.assert_equivalent("{}", "None")
1664 def test_symlink_out_of_root_directory(self) -> None:
1668 include = re.compile(black.DEFAULT_INCLUDES)
1669 exclude = re.compile(black.DEFAULT_EXCLUDES)
1670 report = black.Report()
1671 gitignore = PathSpec.from_lines("gitwildmatch", [])
1672 # `child` should behave like a symlink which resolved path is clearly
1673 # outside of the `root` directory.
1674 path.iterdir.return_value = [child]
1675 child.resolve.return_value = Path("/a/b/c")
1676 child.as_posix.return_value = "/a/b/c"
1677 child.is_symlink.return_value = True
1680 black.gen_python_files(
1681 path.iterdir(), root, include, exclude, report, gitignore
1684 except ValueError as ve:
1685 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1686 path.iterdir.assert_called_once()
1687 child.resolve.assert_called_once()
1688 child.is_symlink.assert_called_once()
1689 # `child` should behave like a strange file which resolved path is clearly
1690 # outside of the `root` directory.
1691 child.is_symlink.return_value = False
1692 with self.assertRaises(ValueError):
1694 black.gen_python_files(
1695 path.iterdir(), root, include, exclude, report, gitignore
1698 path.iterdir.assert_called()
1699 self.assertEqual(path.iterdir.call_count, 2)
1700 child.resolve.assert_called()
1701 self.assertEqual(child.resolve.call_count, 2)
1702 child.is_symlink.assert_called()
1703 self.assertEqual(child.is_symlink.call_count, 2)
1705 def test_shhh_click(self) -> None:
1707 from click import _unicodefun # type: ignore
1708 except ModuleNotFoundError:
1709 self.skipTest("Incompatible Click version")
1710 if not hasattr(_unicodefun, "_verify_python3_env"):
1711 self.skipTest("Incompatible Click version")
1712 # First, let's see if Click is crashing with a preferred ASCII charset.
1713 with patch("locale.getpreferredencoding") as gpe:
1714 gpe.return_value = "ASCII"
1715 with self.assertRaises(RuntimeError):
1716 _unicodefun._verify_python3_env()
1717 # Now, let's silence Click...
1719 # ...and confirm it's silent.
1720 with patch("locale.getpreferredencoding") as gpe:
1721 gpe.return_value = "ASCII"
1723 _unicodefun._verify_python3_env()
1724 except RuntimeError as re:
1725 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1727 def test_root_logger_not_used_directly(self) -> None:
1728 def fail(*args: Any, **kwargs: Any) -> None:
1729 self.fail("Record created with root logger")
1731 with patch.multiple(
1742 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1743 def test_blackd_main(self) -> None:
1744 with patch("blackd.web.run_app"):
1745 result = CliRunner().invoke(blackd.main, [])
1746 if result.exception is not None:
1747 raise result.exception
1748 self.assertEqual(result.exit_code, 0)
1750 def test_invalid_config_return_code(self) -> None:
1751 tmp_file = Path(black.dump_to_file())
1753 tmp_config = Path(black.dump_to_file())
1755 args = ["--config", str(tmp_config), str(tmp_file)]
1756 self.invokeBlack(args, exit_code=2, ignore_config=False)
1761 class BlackDTestCase(AioHTTPTestCase):
1762 async def get_application(self) -> web.Application:
1763 return blackd.make_app()
1765 # TODO: remove these decorators once the below is released
1766 # https://github.com/aio-libs/aiohttp/pull/3727
1767 @skip_if_exception("ClientOSError")
1768 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1770 async def test_blackd_request_needs_formatting(self) -> None:
1771 response = await self.client.post("/", data=b"print('hello world')")
1772 self.assertEqual(response.status, 200)
1773 self.assertEqual(response.charset, "utf8")
1774 self.assertEqual(await response.read(), b'print("hello world")\n')
1776 @skip_if_exception("ClientOSError")
1777 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1779 async def test_blackd_request_no_change(self) -> None:
1780 response = await self.client.post("/", data=b'print("hello world")\n')
1781 self.assertEqual(response.status, 204)
1782 self.assertEqual(await response.read(), b"")
1784 @skip_if_exception("ClientOSError")
1785 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1787 async def test_blackd_request_syntax_error(self) -> None:
1788 response = await self.client.post("/", data=b"what even ( is")
1789 self.assertEqual(response.status, 400)
1790 content = await response.text()
1792 content.startswith("Cannot parse"),
1793 msg=f"Expected error to start with 'Cannot parse', got {repr(content)}",
1796 @skip_if_exception("ClientOSError")
1797 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1799 async def test_blackd_unsupported_version(self) -> None:
1800 response = await self.client.post(
1801 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "2"}
1803 self.assertEqual(response.status, 501)
1805 @skip_if_exception("ClientOSError")
1806 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1808 async def test_blackd_supported_version(self) -> None:
1809 response = await self.client.post(
1810 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "1"}
1812 self.assertEqual(response.status, 200)
1814 @skip_if_exception("ClientOSError")
1815 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1817 async def test_blackd_invalid_python_variant(self) -> None:
1818 async def check(header_value: str, expected_status: int = 400) -> None:
1819 response = await self.client.post(
1820 "/", data=b"what", headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1822 self.assertEqual(response.status, expected_status)
1825 await check("ruby3.5")
1826 await check("pyi3.6")
1827 await check("py1.5")
1829 await check("py2.8")
1831 await check("pypy3.0")
1832 await check("jython3.4")
1834 @skip_if_exception("ClientOSError")
1835 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1837 async def test_blackd_pyi(self) -> None:
1838 source, expected = read_data("stub.pyi")
1839 response = await self.client.post(
1840 "/", data=source, headers={blackd.PYTHON_VARIANT_HEADER: "pyi"}
1842 self.assertEqual(response.status, 200)
1843 self.assertEqual(await response.text(), expected)
1845 @skip_if_exception("ClientOSError")
1846 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1848 async def test_blackd_diff(self) -> None:
1849 diff_header = re.compile(
1850 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"
1853 source, _ = read_data("blackd_diff.py")
1854 expected, _ = read_data("blackd_diff.diff")
1856 response = await self.client.post(
1857 "/", data=source, headers={blackd.DIFF_HEADER: "true"}
1859 self.assertEqual(response.status, 200)
1861 actual = await response.text()
1862 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
1863 self.assertEqual(actual, expected)
1865 @skip_if_exception("ClientOSError")
1866 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1868 async def test_blackd_python_variant(self) -> None:
1871 " and_has_a_bunch_of,\n"
1872 " very_long_arguments_too,\n"
1873 " and_lots_of_them_as_well_lol,\n"
1874 " **and_very_long_keyword_arguments\n"
1879 async def check(header_value: str, expected_status: int) -> None:
1880 response = await self.client.post(
1881 "/", data=code, headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1884 response.status, expected_status, msg=await response.text()
1887 await check("3.6", 200)
1888 await check("py3.6", 200)
1889 await check("3.6,3.7", 200)
1890 await check("3.6,py3.7", 200)
1891 await check("py36,py37", 200)
1892 await check("36", 200)
1893 await check("3.6.4", 200)
1895 await check("2", 204)
1896 await check("2.7", 204)
1897 await check("py2.7", 204)
1898 await check("3.4", 204)
1899 await check("py3.4", 204)
1900 await check("py34,py36", 204)
1901 await check("34", 204)
1903 @skip_if_exception("ClientOSError")
1904 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1906 async def test_blackd_line_length(self) -> None:
1907 response = await self.client.post(
1908 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "7"}
1910 self.assertEqual(response.status, 200)
1912 @skip_if_exception("ClientOSError")
1913 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1915 async def test_blackd_invalid_line_length(self) -> None:
1916 response = await self.client.post(
1917 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "NaN"}
1919 self.assertEqual(response.status, 400)
1921 @skip_if_exception("ClientOSError")
1922 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1924 async def test_blackd_response_black_version_header(self) -> None:
1925 response = await self.client.post("/")
1926 self.assertIsNotNone(response.headers.get(blackd.BLACK_VERSION_HEADER))
1929 if __name__ == "__main__":
1930 unittest.main(module="test_black")