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 @patch("black.ProcessPoolExecutor", autospec=True)
1277 def test_works_in_mono_process_only_environment(self, mock_executor) -> None:
1278 mock_executor.side_effect = OSError()
1279 mode = black.FileMode()
1280 with cache_dir() as workspace:
1281 one = (workspace / "one.py").resolve()
1282 with one.open("w") as fobj:
1283 fobj.write("print('hello')")
1284 two = (workspace / "two.py").resolve()
1285 with two.open("w") as fobj:
1286 fobj.write("print('hello')")
1287 black.write_cache({}, [one], mode)
1288 self.invokeBlack([str(workspace)])
1289 with one.open("r") as fobj:
1290 self.assertEqual(fobj.read(), "print('hello')")
1291 with two.open("r") as fobj:
1292 self.assertEqual(fobj.read(), 'print("hello")\n')
1293 cache = black.read_cache(mode)
1294 self.assertIn(one, cache)
1295 self.assertIn(two, cache)
1297 def test_no_cache_when_writeback_diff(self) -> None:
1298 mode = black.FileMode()
1299 with cache_dir() as workspace:
1300 src = (workspace / "test.py").resolve()
1301 with src.open("w") as fobj:
1302 fobj.write("print('hello')")
1303 self.invokeBlack([str(src), "--diff"])
1304 cache_file = black.get_cache_file(mode)
1305 self.assertFalse(cache_file.exists())
1307 def test_no_cache_when_stdin(self) -> None:
1308 mode = black.FileMode()
1310 result = CliRunner().invoke(
1311 black.main, ["-"], input=BytesIO(b"print('hello')")
1313 self.assertEqual(result.exit_code, 0)
1314 cache_file = black.get_cache_file(mode)
1315 self.assertFalse(cache_file.exists())
1317 def test_read_cache_no_cachefile(self) -> None:
1318 mode = black.FileMode()
1320 self.assertEqual(black.read_cache(mode), {})
1322 def test_write_cache_read_cache(self) -> None:
1323 mode = black.FileMode()
1324 with cache_dir() as workspace:
1325 src = (workspace / "test.py").resolve()
1327 black.write_cache({}, [src], mode)
1328 cache = black.read_cache(mode)
1329 self.assertIn(src, cache)
1330 self.assertEqual(cache[src], black.get_cache_info(src))
1332 def test_filter_cached(self) -> None:
1333 with TemporaryDirectory() as workspace:
1334 path = Path(workspace)
1335 uncached = (path / "uncached").resolve()
1336 cached = (path / "cached").resolve()
1337 cached_but_changed = (path / "changed").resolve()
1340 cached_but_changed.touch()
1341 cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
1342 todo, done = black.filter_cached(
1343 cache, {uncached, cached, cached_but_changed}
1345 self.assertEqual(todo, {uncached, cached_but_changed})
1346 self.assertEqual(done, {cached})
1348 def test_write_cache_creates_directory_if_needed(self) -> None:
1349 mode = black.FileMode()
1350 with cache_dir(exists=False) as workspace:
1351 self.assertFalse(workspace.exists())
1352 black.write_cache({}, [], mode)
1353 self.assertTrue(workspace.exists())
1355 @event_loop(close=False)
1356 def test_failed_formatting_does_not_get_cached(self) -> None:
1357 mode = black.FileMode()
1358 with cache_dir() as workspace, patch(
1359 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1361 failing = (workspace / "failing.py").resolve()
1362 with failing.open("w") as fobj:
1363 fobj.write("not actually python")
1364 clean = (workspace / "clean.py").resolve()
1365 with clean.open("w") as fobj:
1366 fobj.write('print("hello")\n')
1367 self.invokeBlack([str(workspace)], exit_code=123)
1368 cache = black.read_cache(mode)
1369 self.assertNotIn(failing, cache)
1370 self.assertIn(clean, cache)
1372 def test_write_cache_write_fail(self) -> None:
1373 mode = black.FileMode()
1374 with cache_dir(), patch.object(Path, "open") as mock:
1375 mock.side_effect = OSError
1376 black.write_cache({}, [], mode)
1378 @event_loop(close=False)
1379 def test_check_diff_use_together(self) -> None:
1381 # Files which will be reformatted.
1382 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1383 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1384 # Files which will not be reformatted.
1385 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1386 self.invokeBlack([str(src2), "--diff", "--check"])
1387 # Multi file command.
1388 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1390 def test_no_files(self) -> None:
1392 # Without an argument, black exits with error code 0.
1393 self.invokeBlack([])
1395 def test_broken_symlink(self) -> None:
1396 with cache_dir() as workspace:
1397 symlink = workspace / "broken_link.py"
1399 symlink.symlink_to("nonexistent.py")
1400 except OSError as e:
1401 self.skipTest(f"Can't create symlinks: {e}")
1402 self.invokeBlack([str(workspace.resolve())])
1404 def test_read_cache_line_lengths(self) -> None:
1405 mode = black.FileMode()
1406 short_mode = black.FileMode(line_length=1)
1407 with cache_dir() as workspace:
1408 path = (workspace / "file.py").resolve()
1410 black.write_cache({}, [path], mode)
1411 one = black.read_cache(mode)
1412 self.assertIn(path, one)
1413 two = black.read_cache(short_mode)
1414 self.assertNotIn(path, two)
1416 def test_tricky_unicode_symbols(self) -> None:
1417 source, expected = read_data("tricky_unicode_symbols")
1419 self.assertFormatEqual(expected, actual)
1420 black.assert_equivalent(source, actual)
1421 black.assert_stable(source, actual, black.FileMode())
1423 def test_single_file_force_pyi(self) -> None:
1424 reg_mode = black.FileMode()
1425 pyi_mode = black.FileMode(is_pyi=True)
1426 contents, expected = read_data("force_pyi")
1427 with cache_dir() as workspace:
1428 path = (workspace / "file.py").resolve()
1429 with open(path, "w") as fh:
1431 self.invokeBlack([str(path), "--pyi"])
1432 with open(path, "r") as fh:
1434 # verify cache with --pyi is separate
1435 pyi_cache = black.read_cache(pyi_mode)
1436 self.assertIn(path, pyi_cache)
1437 normal_cache = black.read_cache(reg_mode)
1438 self.assertNotIn(path, normal_cache)
1439 self.assertEqual(actual, expected)
1441 @event_loop(close=False)
1442 def test_multi_file_force_pyi(self) -> None:
1443 reg_mode = black.FileMode()
1444 pyi_mode = black.FileMode(is_pyi=True)
1445 contents, expected = read_data("force_pyi")
1446 with cache_dir() as workspace:
1448 (workspace / "file1.py").resolve(),
1449 (workspace / "file2.py").resolve(),
1452 with open(path, "w") as fh:
1454 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1456 with open(path, "r") as fh:
1458 self.assertEqual(actual, expected)
1459 # verify cache with --pyi is separate
1460 pyi_cache = black.read_cache(pyi_mode)
1461 normal_cache = black.read_cache(reg_mode)
1463 self.assertIn(path, pyi_cache)
1464 self.assertNotIn(path, normal_cache)
1466 def test_pipe_force_pyi(self) -> None:
1467 source, expected = read_data("force_pyi")
1468 result = CliRunner().invoke(
1469 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1471 self.assertEqual(result.exit_code, 0)
1472 actual = result.output
1473 self.assertFormatEqual(actual, expected)
1475 def test_single_file_force_py36(self) -> None:
1476 reg_mode = black.FileMode()
1477 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1478 source, expected = read_data("force_py36")
1479 with cache_dir() as workspace:
1480 path = (workspace / "file.py").resolve()
1481 with open(path, "w") as fh:
1483 self.invokeBlack([str(path), *PY36_ARGS])
1484 with open(path, "r") as fh:
1486 # verify cache with --target-version is separate
1487 py36_cache = black.read_cache(py36_mode)
1488 self.assertIn(path, py36_cache)
1489 normal_cache = black.read_cache(reg_mode)
1490 self.assertNotIn(path, normal_cache)
1491 self.assertEqual(actual, expected)
1493 @event_loop(close=False)
1494 def test_multi_file_force_py36(self) -> None:
1495 reg_mode = black.FileMode()
1496 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1497 source, expected = read_data("force_py36")
1498 with cache_dir() as workspace:
1500 (workspace / "file1.py").resolve(),
1501 (workspace / "file2.py").resolve(),
1504 with open(path, "w") as fh:
1506 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1508 with open(path, "r") as fh:
1510 self.assertEqual(actual, expected)
1511 # verify cache with --target-version is separate
1512 pyi_cache = black.read_cache(py36_mode)
1513 normal_cache = black.read_cache(reg_mode)
1515 self.assertIn(path, pyi_cache)
1516 self.assertNotIn(path, normal_cache)
1518 def test_collections(self) -> None:
1519 source, expected = read_data("collections")
1521 self.assertFormatEqual(expected, actual)
1522 black.assert_equivalent(source, actual)
1523 black.assert_stable(source, actual, black.FileMode())
1525 def test_pipe_force_py36(self) -> None:
1526 source, expected = read_data("force_py36")
1527 result = CliRunner().invoke(
1529 ["-", "-q", "--target-version=py36"],
1530 input=BytesIO(source.encode("utf8")),
1532 self.assertEqual(result.exit_code, 0)
1533 actual = result.output
1534 self.assertFormatEqual(actual, expected)
1536 def test_include_exclude(self) -> None:
1537 path = THIS_DIR / "data" / "include_exclude_tests"
1538 include = re.compile(r"\.pyi?$")
1539 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1540 report = black.Report()
1541 gitignore = PathSpec.from_lines("gitwildmatch", [])
1542 sources: List[Path] = []
1544 Path(path / "b/dont_exclude/a.py"),
1545 Path(path / "b/dont_exclude/a.pyi"),
1547 this_abs = THIS_DIR.resolve()
1549 black.gen_python_files_in_dir(
1550 path, this_abs, include, exclude, report, gitignore
1553 self.assertEqual(sorted(expected), sorted(sources))
1555 def test_gitignore_exclude(self) -> None:
1556 path = THIS_DIR / "data" / "include_exclude_tests"
1557 include = re.compile(r"\.pyi?$")
1558 exclude = re.compile(r"")
1559 report = black.Report()
1560 gitignore = PathSpec.from_lines(
1561 "gitwildmatch", ["exclude/", ".definitely_exclude"]
1563 sources: List[Path] = []
1565 Path(path / "b/dont_exclude/a.py"),
1566 Path(path / "b/dont_exclude/a.pyi"),
1568 this_abs = THIS_DIR.resolve()
1570 black.gen_python_files_in_dir(
1571 path, this_abs, include, exclude, report, gitignore
1574 self.assertEqual(sorted(expected), sorted(sources))
1576 def test_empty_include(self) -> None:
1577 path = THIS_DIR / "data" / "include_exclude_tests"
1578 report = black.Report()
1579 gitignore = PathSpec.from_lines("gitwildmatch", [])
1580 empty = re.compile(r"")
1581 sources: List[Path] = []
1583 Path(path / "b/exclude/a.pie"),
1584 Path(path / "b/exclude/a.py"),
1585 Path(path / "b/exclude/a.pyi"),
1586 Path(path / "b/dont_exclude/a.pie"),
1587 Path(path / "b/dont_exclude/a.py"),
1588 Path(path / "b/dont_exclude/a.pyi"),
1589 Path(path / "b/.definitely_exclude/a.pie"),
1590 Path(path / "b/.definitely_exclude/a.py"),
1591 Path(path / "b/.definitely_exclude/a.pyi"),
1593 this_abs = THIS_DIR.resolve()
1595 black.gen_python_files_in_dir(
1599 re.compile(black.DEFAULT_EXCLUDES),
1604 self.assertEqual(sorted(expected), sorted(sources))
1606 def test_empty_exclude(self) -> None:
1607 path = THIS_DIR / "data" / "include_exclude_tests"
1608 report = black.Report()
1609 gitignore = PathSpec.from_lines("gitwildmatch", [])
1610 empty = re.compile(r"")
1611 sources: List[Path] = []
1613 Path(path / "b/dont_exclude/a.py"),
1614 Path(path / "b/dont_exclude/a.pyi"),
1615 Path(path / "b/exclude/a.py"),
1616 Path(path / "b/exclude/a.pyi"),
1617 Path(path / "b/.definitely_exclude/a.py"),
1618 Path(path / "b/.definitely_exclude/a.pyi"),
1620 this_abs = THIS_DIR.resolve()
1622 black.gen_python_files_in_dir(
1625 re.compile(black.DEFAULT_INCLUDES),
1631 self.assertEqual(sorted(expected), sorted(sources))
1633 def test_invalid_include_exclude(self) -> None:
1634 for option in ["--include", "--exclude"]:
1635 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1637 def test_preserves_line_endings(self) -> None:
1638 with TemporaryDirectory() as workspace:
1639 test_file = Path(workspace) / "test.py"
1640 for nl in ["\n", "\r\n"]:
1641 contents = nl.join(["def f( ):", " pass"])
1642 test_file.write_bytes(contents.encode())
1643 ff(test_file, write_back=black.WriteBack.YES)
1644 updated_contents: bytes = test_file.read_bytes()
1645 self.assertIn(nl.encode(), updated_contents)
1647 self.assertNotIn(b"\r\n", updated_contents)
1649 def test_preserves_line_endings_via_stdin(self) -> None:
1650 for nl in ["\n", "\r\n"]:
1651 contents = nl.join(["def f( ):", " pass"])
1652 runner = BlackRunner()
1653 result = runner.invoke(
1654 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1656 self.assertEqual(result.exit_code, 0)
1657 output = runner.stdout_bytes
1658 self.assertIn(nl.encode("utf8"), output)
1660 self.assertNotIn(b"\r\n", output)
1662 def test_assert_equivalent_different_asts(self) -> None:
1663 with self.assertRaises(AssertionError):
1664 black.assert_equivalent("{}", "None")
1666 def test_symlink_out_of_root_directory(self) -> None:
1670 include = re.compile(black.DEFAULT_INCLUDES)
1671 exclude = re.compile(black.DEFAULT_EXCLUDES)
1672 report = black.Report()
1673 gitignore = PathSpec.from_lines("gitwildmatch", [])
1674 # `child` should behave like a symlink which resolved path is clearly
1675 # outside of the `root` directory.
1676 path.iterdir.return_value = [child]
1677 child.resolve.return_value = Path("/a/b/c")
1678 child.as_posix.return_value = "/a/b/c"
1679 child.is_symlink.return_value = True
1682 black.gen_python_files_in_dir(
1683 path, root, include, exclude, report, gitignore
1686 except ValueError as ve:
1687 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1688 path.iterdir.assert_called_once()
1689 child.resolve.assert_called_once()
1690 child.is_symlink.assert_called_once()
1691 # `child` should behave like a strange file which resolved path is clearly
1692 # outside of the `root` directory.
1693 child.is_symlink.return_value = False
1694 with self.assertRaises(ValueError):
1696 black.gen_python_files_in_dir(
1697 path, root, include, exclude, report, gitignore
1700 path.iterdir.assert_called()
1701 self.assertEqual(path.iterdir.call_count, 2)
1702 child.resolve.assert_called()
1703 self.assertEqual(child.resolve.call_count, 2)
1704 child.is_symlink.assert_called()
1705 self.assertEqual(child.is_symlink.call_count, 2)
1707 def test_shhh_click(self) -> None:
1709 from click import _unicodefun # type: ignore
1710 except ModuleNotFoundError:
1711 self.skipTest("Incompatible Click version")
1712 if not hasattr(_unicodefun, "_verify_python3_env"):
1713 self.skipTest("Incompatible Click version")
1714 # First, let's see if Click is crashing with a preferred ASCII charset.
1715 with patch("locale.getpreferredencoding") as gpe:
1716 gpe.return_value = "ASCII"
1717 with self.assertRaises(RuntimeError):
1718 _unicodefun._verify_python3_env()
1719 # Now, let's silence Click...
1721 # ...and confirm it's silent.
1722 with patch("locale.getpreferredencoding") as gpe:
1723 gpe.return_value = "ASCII"
1725 _unicodefun._verify_python3_env()
1726 except RuntimeError as re:
1727 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1729 def test_root_logger_not_used_directly(self) -> None:
1730 def fail(*args: Any, **kwargs: Any) -> None:
1731 self.fail("Record created with root logger")
1733 with patch.multiple(
1744 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1745 def test_blackd_main(self) -> None:
1746 with patch("blackd.web.run_app"):
1747 result = CliRunner().invoke(blackd.main, [])
1748 if result.exception is not None:
1749 raise result.exception
1750 self.assertEqual(result.exit_code, 0)
1752 def test_invalid_config_return_code(self) -> None:
1753 tmp_file = Path(black.dump_to_file())
1755 tmp_config = Path(black.dump_to_file())
1757 args = ["--config", str(tmp_config), str(tmp_file)]
1758 self.invokeBlack(args, exit_code=2, ignore_config=False)
1763 class BlackDTestCase(AioHTTPTestCase):
1764 async def get_application(self) -> web.Application:
1765 return blackd.make_app()
1767 # TODO: remove these decorators once the below is released
1768 # https://github.com/aio-libs/aiohttp/pull/3727
1769 @skip_if_exception("ClientOSError")
1770 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1772 async def test_blackd_request_needs_formatting(self) -> None:
1773 response = await self.client.post("/", data=b"print('hello world')")
1774 self.assertEqual(response.status, 200)
1775 self.assertEqual(response.charset, "utf8")
1776 self.assertEqual(await response.read(), b'print("hello world")\n')
1778 @skip_if_exception("ClientOSError")
1779 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1781 async def test_blackd_request_no_change(self) -> None:
1782 response = await self.client.post("/", data=b'print("hello world")\n')
1783 self.assertEqual(response.status, 204)
1784 self.assertEqual(await response.read(), b"")
1786 @skip_if_exception("ClientOSError")
1787 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1789 async def test_blackd_request_syntax_error(self) -> None:
1790 response = await self.client.post("/", data=b"what even ( is")
1791 self.assertEqual(response.status, 400)
1792 content = await response.text()
1794 content.startswith("Cannot parse"),
1795 msg=f"Expected error to start with 'Cannot parse', got {repr(content)}",
1798 @skip_if_exception("ClientOSError")
1799 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1801 async def test_blackd_unsupported_version(self) -> None:
1802 response = await self.client.post(
1803 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "2"}
1805 self.assertEqual(response.status, 501)
1807 @skip_if_exception("ClientOSError")
1808 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1810 async def test_blackd_supported_version(self) -> None:
1811 response = await self.client.post(
1812 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "1"}
1814 self.assertEqual(response.status, 200)
1816 @skip_if_exception("ClientOSError")
1817 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1819 async def test_blackd_invalid_python_variant(self) -> None:
1820 async def check(header_value: str, expected_status: int = 400) -> None:
1821 response = await self.client.post(
1822 "/", data=b"what", headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1824 self.assertEqual(response.status, expected_status)
1827 await check("ruby3.5")
1828 await check("pyi3.6")
1829 await check("py1.5")
1831 await check("py2.8")
1833 await check("pypy3.0")
1834 await check("jython3.4")
1836 @skip_if_exception("ClientOSError")
1837 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1839 async def test_blackd_pyi(self) -> None:
1840 source, expected = read_data("stub.pyi")
1841 response = await self.client.post(
1842 "/", data=source, headers={blackd.PYTHON_VARIANT_HEADER: "pyi"}
1844 self.assertEqual(response.status, 200)
1845 self.assertEqual(await response.text(), expected)
1847 @skip_if_exception("ClientOSError")
1848 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1850 async def test_blackd_diff(self) -> None:
1851 diff_header = re.compile(
1852 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"
1855 source, _ = read_data("blackd_diff.py")
1856 expected, _ = read_data("blackd_diff.diff")
1858 response = await self.client.post(
1859 "/", data=source, headers={blackd.DIFF_HEADER: "true"}
1861 self.assertEqual(response.status, 200)
1863 actual = await response.text()
1864 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
1865 self.assertEqual(actual, expected)
1867 @skip_if_exception("ClientOSError")
1868 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1870 async def test_blackd_python_variant(self) -> None:
1873 " and_has_a_bunch_of,\n"
1874 " very_long_arguments_too,\n"
1875 " and_lots_of_them_as_well_lol,\n"
1876 " **and_very_long_keyword_arguments\n"
1881 async def check(header_value: str, expected_status: int) -> None:
1882 response = await self.client.post(
1883 "/", data=code, headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1886 response.status, expected_status, msg=await response.text()
1889 await check("3.6", 200)
1890 await check("py3.6", 200)
1891 await check("3.6,3.7", 200)
1892 await check("3.6,py3.7", 200)
1893 await check("py36,py37", 200)
1894 await check("36", 200)
1895 await check("3.6.4", 200)
1897 await check("2", 204)
1898 await check("2.7", 204)
1899 await check("py2.7", 204)
1900 await check("3.4", 204)
1901 await check("py3.4", 204)
1902 await check("py34,py36", 204)
1903 await check("34", 204)
1905 @skip_if_exception("ClientOSError")
1906 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1908 async def test_blackd_line_length(self) -> None:
1909 response = await self.client.post(
1910 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "7"}
1912 self.assertEqual(response.status, 200)
1914 @skip_if_exception("ClientOSError")
1915 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1917 async def test_blackd_invalid_line_length(self) -> None:
1918 response = await self.client.post(
1919 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "NaN"}
1921 self.assertEqual(response.status, 400)
1923 @skip_if_exception("ClientOSError")
1924 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1926 async def test_blackd_response_black_version_header(self) -> None:
1927 response = await self.client.post("/")
1928 self.assertIsNotNone(response.headers.get(blackd.BLACK_VERSION_HEADER))
1931 if __name__ == "__main__":
1932 unittest.main(module="test_black")