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 @patch("black.dump_to_file", dump_to_stderr)
268 def test_function(self) -> None:
269 source, expected = read_data("function")
271 self.assertFormatEqual(expected, actual)
272 black.assert_equivalent(source, actual)
273 black.assert_stable(source, actual, black.FileMode())
275 @patch("black.dump_to_file", dump_to_stderr)
276 def test_function2(self) -> None:
277 source, expected = read_data("function2")
279 self.assertFormatEqual(expected, actual)
280 black.assert_equivalent(source, actual)
281 black.assert_stable(source, actual, black.FileMode())
283 @patch("black.dump_to_file", dump_to_stderr)
284 def test_function_trailing_comma(self) -> None:
285 source, expected = read_data("function_trailing_comma")
287 self.assertFormatEqual(expected, actual)
288 black.assert_equivalent(source, actual)
289 black.assert_stable(source, actual, black.FileMode())
291 @patch("black.dump_to_file", dump_to_stderr)
292 def test_expression(self) -> None:
293 source, expected = read_data("expression")
295 self.assertFormatEqual(expected, actual)
296 black.assert_equivalent(source, actual)
297 black.assert_stable(source, actual, black.FileMode())
299 @patch("black.dump_to_file", dump_to_stderr)
300 def test_pep_572(self) -> None:
301 source, expected = read_data("pep_572")
303 self.assertFormatEqual(expected, actual)
304 black.assert_stable(source, actual, black.FileMode())
305 if sys.version_info >= (3, 8):
306 black.assert_equivalent(source, actual)
308 def test_pep_572_version_detection(self) -> None:
309 source, _ = read_data("pep_572")
310 root = black.lib2to3_parse(source)
311 features = black.get_features_used(root)
312 self.assertIn(black.Feature.ASSIGNMENT_EXPRESSIONS, features)
313 versions = black.detect_target_versions(root)
314 self.assertIn(black.TargetVersion.PY38, versions)
316 def test_expression_ff(self) -> None:
317 source, expected = read_data("expression")
318 tmp_file = Path(black.dump_to_file(source))
320 self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
321 with open(tmp_file, encoding="utf8") as f:
325 self.assertFormatEqual(expected, actual)
326 with patch("black.dump_to_file", dump_to_stderr):
327 black.assert_equivalent(source, actual)
328 black.assert_stable(source, actual, black.FileMode())
330 def test_expression_diff(self) -> None:
331 source, _ = read_data("expression.py")
332 expected, _ = read_data("expression.diff")
333 tmp_file = Path(black.dump_to_file(source))
334 diff_header = re.compile(
335 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
336 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
339 result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
340 self.assertEqual(result.exit_code, 0)
343 actual = result.output
344 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
345 actual = actual.rstrip() + "\n" # the diff output has a trailing space
346 if expected != actual:
347 dump = black.dump_to_file(actual)
349 "Expected diff isn't equal to the actual. If you made changes to"
350 " expression.py and this is an anticipated difference, overwrite"
351 f" tests/data/expression.diff with {dump}"
353 self.assertEqual(expected, actual, msg)
355 @patch("black.dump_to_file", dump_to_stderr)
356 def test_fstring(self) -> None:
357 source, expected = read_data("fstring")
359 self.assertFormatEqual(expected, actual)
360 black.assert_equivalent(source, actual)
361 black.assert_stable(source, actual, black.FileMode())
363 @patch("black.dump_to_file", dump_to_stderr)
364 def test_pep_570(self) -> None:
365 source, expected = read_data("pep_570")
367 self.assertFormatEqual(expected, actual)
368 black.assert_stable(source, actual, black.FileMode())
369 if sys.version_info >= (3, 8):
370 black.assert_equivalent(source, actual)
372 def test_detect_pos_only_arguments(self) -> None:
373 source, _ = read_data("pep_570")
374 root = black.lib2to3_parse(source)
375 features = black.get_features_used(root)
376 self.assertIn(black.Feature.POS_ONLY_ARGUMENTS, features)
377 versions = black.detect_target_versions(root)
378 self.assertIn(black.TargetVersion.PY38, versions)
380 @patch("black.dump_to_file", dump_to_stderr)
381 def test_string_quotes(self) -> None:
382 source, expected = read_data("string_quotes")
384 self.assertFormatEqual(expected, actual)
385 black.assert_equivalent(source, actual)
386 black.assert_stable(source, actual, black.FileMode())
387 mode = black.FileMode(string_normalization=False)
388 not_normalized = fs(source, mode=mode)
389 self.assertFormatEqual(source.replace("\\\n", ""), not_normalized)
390 black.assert_equivalent(source, not_normalized)
391 black.assert_stable(source, not_normalized, mode=mode)
393 @patch("black.dump_to_file", dump_to_stderr)
394 def test_long_strings(self) -> None:
395 """Tests for splitting long strings."""
396 source, expected = read_data("long_strings")
398 self.assertFormatEqual(expected, actual)
399 black.assert_equivalent(source, actual)
400 black.assert_stable(source, actual, black.FileMode())
402 @patch("black.dump_to_file", dump_to_stderr)
403 def test_long_strings__edge_case(self) -> None:
404 """Edge-case tests for splitting long strings."""
405 source, expected = read_data("long_strings__edge_case")
407 self.assertFormatEqual(expected, actual)
408 black.assert_equivalent(source, actual)
409 black.assert_stable(source, actual, black.FileMode())
411 @patch("black.dump_to_file", dump_to_stderr)
412 def test_long_strings__regression(self) -> None:
413 """Regression tests for splitting long strings."""
414 source, expected = read_data("long_strings__regression")
416 self.assertFormatEqual(expected, actual)
417 black.assert_equivalent(source, actual)
418 black.assert_stable(source, actual, black.FileMode())
420 @patch("black.dump_to_file", dump_to_stderr)
421 def test_slices(self) -> None:
422 source, expected = read_data("slices")
424 self.assertFormatEqual(expected, actual)
425 black.assert_equivalent(source, actual)
426 black.assert_stable(source, actual, black.FileMode())
428 @patch("black.dump_to_file", dump_to_stderr)
429 def test_comments(self) -> None:
430 source, expected = read_data("comments")
432 self.assertFormatEqual(expected, actual)
433 black.assert_equivalent(source, actual)
434 black.assert_stable(source, actual, black.FileMode())
436 @patch("black.dump_to_file", dump_to_stderr)
437 def test_comments2(self) -> None:
438 source, expected = read_data("comments2")
440 self.assertFormatEqual(expected, actual)
441 black.assert_equivalent(source, actual)
442 black.assert_stable(source, actual, black.FileMode())
444 @patch("black.dump_to_file", dump_to_stderr)
445 def test_comments3(self) -> None:
446 source, expected = read_data("comments3")
448 self.assertFormatEqual(expected, actual)
449 black.assert_equivalent(source, actual)
450 black.assert_stable(source, actual, black.FileMode())
452 @patch("black.dump_to_file", dump_to_stderr)
453 def test_comments4(self) -> None:
454 source, expected = read_data("comments4")
456 self.assertFormatEqual(expected, actual)
457 black.assert_equivalent(source, actual)
458 black.assert_stable(source, actual, black.FileMode())
460 @patch("black.dump_to_file", dump_to_stderr)
461 def test_comments5(self) -> None:
462 source, expected = read_data("comments5")
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_comments6(self) -> None:
470 source, expected = read_data("comments6")
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_comments7(self) -> None:
478 source, expected = read_data("comments7")
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_comment_after_escaped_newline(self) -> None:
486 source, expected = read_data("comment_after_escaped_newline")
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_cantfit(self) -> None:
494 source, expected = read_data("cantfit")
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_import_spacing(self) -> None:
502 source, expected = read_data("import_spacing")
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_composition(self) -> None:
510 source, expected = read_data("composition")
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_empty_lines(self) -> None:
518 source, expected = read_data("empty_lines")
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_remove_parens(self) -> None:
526 source, expected = read_data("remove_parens")
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_string_prefixes(self) -> None:
534 source, expected = read_data("string_prefixes")
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_numeric_literals(self) -> None:
542 source, expected = read_data("numeric_literals")
543 mode = black.FileMode(target_versions=black.PY36_VERSIONS)
544 actual = fs(source, mode=mode)
545 self.assertFormatEqual(expected, actual)
546 black.assert_equivalent(source, actual)
547 black.assert_stable(source, actual, mode)
549 @patch("black.dump_to_file", dump_to_stderr)
550 def test_numeric_literals_ignoring_underscores(self) -> None:
551 source, expected = read_data("numeric_literals_skip_underscores")
552 mode = black.FileMode(target_versions=black.PY36_VERSIONS)
553 actual = fs(source, mode=mode)
554 self.assertFormatEqual(expected, actual)
555 black.assert_equivalent(source, actual)
556 black.assert_stable(source, actual, mode)
558 @patch("black.dump_to_file", dump_to_stderr)
559 def test_numeric_literals_py2(self) -> None:
560 source, expected = read_data("numeric_literals_py2")
562 self.assertFormatEqual(expected, actual)
563 black.assert_stable(source, actual, black.FileMode())
565 @patch("black.dump_to_file", dump_to_stderr)
566 def test_python2(self) -> None:
567 source, expected = read_data("python2")
569 self.assertFormatEqual(expected, actual)
570 black.assert_equivalent(source, actual)
571 black.assert_stable(source, actual, black.FileMode())
573 @patch("black.dump_to_file", dump_to_stderr)
574 def test_python2_print_function(self) -> None:
575 source, expected = read_data("python2_print_function")
576 mode = black.FileMode(target_versions={TargetVersion.PY27})
577 actual = fs(source, mode=mode)
578 self.assertFormatEqual(expected, actual)
579 black.assert_equivalent(source, actual)
580 black.assert_stable(source, actual, mode)
582 @patch("black.dump_to_file", dump_to_stderr)
583 def test_python2_unicode_literals(self) -> None:
584 source, expected = read_data("python2_unicode_literals")
586 self.assertFormatEqual(expected, actual)
587 black.assert_equivalent(source, actual)
588 black.assert_stable(source, actual, black.FileMode())
590 @patch("black.dump_to_file", dump_to_stderr)
591 def test_stub(self) -> None:
592 mode = black.FileMode(is_pyi=True)
593 source, expected = read_data("stub.pyi")
594 actual = fs(source, mode=mode)
595 self.assertFormatEqual(expected, actual)
596 black.assert_stable(source, actual, mode)
598 @patch("black.dump_to_file", dump_to_stderr)
599 def test_async_as_identifier(self) -> None:
600 source_path = (THIS_DIR / "data" / "async_as_identifier.py").resolve()
601 source, expected = read_data("async_as_identifier")
603 self.assertFormatEqual(expected, actual)
604 major, minor = sys.version_info[:2]
605 if major < 3 or (major <= 3 and minor < 7):
606 black.assert_equivalent(source, actual)
607 black.assert_stable(source, actual, black.FileMode())
608 # ensure black can parse this when the target is 3.6
609 self.invokeBlack([str(source_path), "--target-version", "py36"])
610 # but not on 3.7, because async/await is no longer an identifier
611 self.invokeBlack([str(source_path), "--target-version", "py37"], exit_code=123)
613 @patch("black.dump_to_file", dump_to_stderr)
614 def test_python37(self) -> None:
615 source_path = (THIS_DIR / "data" / "python37.py").resolve()
616 source, expected = read_data("python37")
618 self.assertFormatEqual(expected, actual)
619 major, minor = sys.version_info[:2]
620 if major > 3 or (major == 3 and minor >= 7):
621 black.assert_equivalent(source, actual)
622 black.assert_stable(source, actual, black.FileMode())
623 # ensure black can parse this when the target is 3.7
624 self.invokeBlack([str(source_path), "--target-version", "py37"])
625 # but not on 3.6, because we use async as a reserved keyword
626 self.invokeBlack([str(source_path), "--target-version", "py36"], exit_code=123)
628 @patch("black.dump_to_file", dump_to_stderr)
629 def test_python38(self) -> None:
630 source, expected = read_data("python38")
632 self.assertFormatEqual(expected, actual)
633 major, minor = sys.version_info[:2]
634 if major > 3 or (major == 3 and minor >= 8):
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_fmtonoff(self) -> None:
640 source, expected = read_data("fmtonoff")
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_fmtonoff2(self) -> None:
648 source, expected = read_data("fmtonoff2")
650 self.assertFormatEqual(expected, actual)
651 black.assert_equivalent(source, actual)
652 black.assert_stable(source, actual, black.FileMode())
654 @patch("black.dump_to_file", dump_to_stderr)
655 def test_fmtonoff3(self) -> None:
656 source, expected = read_data("fmtonoff3")
658 self.assertFormatEqual(expected, actual)
659 black.assert_equivalent(source, actual)
660 black.assert_stable(source, actual, black.FileMode())
662 @patch("black.dump_to_file", dump_to_stderr)
663 def test_fmtonoff4(self) -> None:
664 source, expected = read_data("fmtonoff4")
666 self.assertFormatEqual(expected, actual)
667 black.assert_equivalent(source, actual)
668 black.assert_stable(source, actual, black.FileMode())
670 @patch("black.dump_to_file", dump_to_stderr)
671 def test_remove_empty_parentheses_after_class(self) -> None:
672 source, expected = read_data("class_blank_parentheses")
674 self.assertFormatEqual(expected, actual)
675 black.assert_equivalent(source, actual)
676 black.assert_stable(source, actual, black.FileMode())
678 @patch("black.dump_to_file", dump_to_stderr)
679 def test_new_line_between_class_and_code(self) -> None:
680 source, expected = read_data("class_methods_new_line")
682 self.assertFormatEqual(expected, actual)
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_bracket_match(self) -> None:
688 source, expected = read_data("bracketmatch")
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_tuple_assign(self) -> None:
696 source, expected = read_data("tupleassign")
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_beginning_backslash(self) -> None:
704 source, expected = read_data("beginning_backslash")
706 self.assertFormatEqual(expected, actual)
707 black.assert_equivalent(source, actual)
708 black.assert_stable(source, actual, black.FileMode())
710 def test_tab_comment_indentation(self) -> None:
711 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
712 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
713 self.assertFormatEqual(contents_spc, fs(contents_spc))
714 self.assertFormatEqual(contents_spc, fs(contents_tab))
716 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
717 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
718 self.assertFormatEqual(contents_spc, fs(contents_spc))
719 self.assertFormatEqual(contents_spc, fs(contents_tab))
721 # mixed tabs and spaces (valid Python 2 code)
722 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t# comment\n pass\n"
723 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
724 self.assertFormatEqual(contents_spc, fs(contents_spc))
725 self.assertFormatEqual(contents_spc, fs(contents_tab))
727 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t\t# comment\n pass\n"
728 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
729 self.assertFormatEqual(contents_spc, fs(contents_spc))
730 self.assertFormatEqual(contents_spc, fs(contents_tab))
732 def test_report_verbose(self) -> None:
733 report = black.Report(verbose=True)
737 def out(msg: str, **kwargs: Any) -> None:
738 out_lines.append(msg)
740 def err(msg: str, **kwargs: Any) -> None:
741 err_lines.append(msg)
743 with patch("black.out", out), patch("black.err", err):
744 report.done(Path("f1"), black.Changed.NO)
745 self.assertEqual(len(out_lines), 1)
746 self.assertEqual(len(err_lines), 0)
747 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
748 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
749 self.assertEqual(report.return_code, 0)
750 report.done(Path("f2"), black.Changed.YES)
751 self.assertEqual(len(out_lines), 2)
752 self.assertEqual(len(err_lines), 0)
753 self.assertEqual(out_lines[-1], "reformatted f2")
755 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
757 report.done(Path("f3"), black.Changed.CACHED)
758 self.assertEqual(len(out_lines), 3)
759 self.assertEqual(len(err_lines), 0)
761 out_lines[-1], "f3 wasn't modified on disk since last run."
764 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
766 self.assertEqual(report.return_code, 0)
768 self.assertEqual(report.return_code, 1)
770 report.failed(Path("e1"), "boom")
771 self.assertEqual(len(out_lines), 3)
772 self.assertEqual(len(err_lines), 1)
773 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
775 unstyle(str(report)),
776 "1 file reformatted, 2 files left unchanged, 1 file failed to"
779 self.assertEqual(report.return_code, 123)
780 report.done(Path("f3"), black.Changed.YES)
781 self.assertEqual(len(out_lines), 4)
782 self.assertEqual(len(err_lines), 1)
783 self.assertEqual(out_lines[-1], "reformatted f3")
785 unstyle(str(report)),
786 "2 files reformatted, 2 files left unchanged, 1 file failed to"
789 self.assertEqual(report.return_code, 123)
790 report.failed(Path("e2"), "boom")
791 self.assertEqual(len(out_lines), 4)
792 self.assertEqual(len(err_lines), 2)
793 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
795 unstyle(str(report)),
796 "2 files reformatted, 2 files left unchanged, 2 files failed to"
799 self.assertEqual(report.return_code, 123)
800 report.path_ignored(Path("wat"), "no match")
801 self.assertEqual(len(out_lines), 5)
802 self.assertEqual(len(err_lines), 2)
803 self.assertEqual(out_lines[-1], "wat ignored: no match")
805 unstyle(str(report)),
806 "2 files reformatted, 2 files left unchanged, 2 files failed to"
809 self.assertEqual(report.return_code, 123)
810 report.done(Path("f4"), black.Changed.NO)
811 self.assertEqual(len(out_lines), 6)
812 self.assertEqual(len(err_lines), 2)
813 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
815 unstyle(str(report)),
816 "2 files reformatted, 3 files left unchanged, 2 files failed to"
819 self.assertEqual(report.return_code, 123)
822 unstyle(str(report)),
823 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
824 " would fail to reformat.",
829 unstyle(str(report)),
830 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
831 " would fail to reformat.",
834 def test_report_quiet(self) -> None:
835 report = black.Report(quiet=True)
839 def out(msg: str, **kwargs: Any) -> None:
840 out_lines.append(msg)
842 def err(msg: str, **kwargs: Any) -> None:
843 err_lines.append(msg)
845 with patch("black.out", out), patch("black.err", err):
846 report.done(Path("f1"), black.Changed.NO)
847 self.assertEqual(len(out_lines), 0)
848 self.assertEqual(len(err_lines), 0)
849 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
850 self.assertEqual(report.return_code, 0)
851 report.done(Path("f2"), black.Changed.YES)
852 self.assertEqual(len(out_lines), 0)
853 self.assertEqual(len(err_lines), 0)
855 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
857 report.done(Path("f3"), black.Changed.CACHED)
858 self.assertEqual(len(out_lines), 0)
859 self.assertEqual(len(err_lines), 0)
861 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
863 self.assertEqual(report.return_code, 0)
865 self.assertEqual(report.return_code, 1)
867 report.failed(Path("e1"), "boom")
868 self.assertEqual(len(out_lines), 0)
869 self.assertEqual(len(err_lines), 1)
870 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
872 unstyle(str(report)),
873 "1 file reformatted, 2 files left unchanged, 1 file failed to"
876 self.assertEqual(report.return_code, 123)
877 report.done(Path("f3"), black.Changed.YES)
878 self.assertEqual(len(out_lines), 0)
879 self.assertEqual(len(err_lines), 1)
881 unstyle(str(report)),
882 "2 files reformatted, 2 files left unchanged, 1 file failed to"
885 self.assertEqual(report.return_code, 123)
886 report.failed(Path("e2"), "boom")
887 self.assertEqual(len(out_lines), 0)
888 self.assertEqual(len(err_lines), 2)
889 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
891 unstyle(str(report)),
892 "2 files reformatted, 2 files left unchanged, 2 files failed to"
895 self.assertEqual(report.return_code, 123)
896 report.path_ignored(Path("wat"), "no match")
897 self.assertEqual(len(out_lines), 0)
898 self.assertEqual(len(err_lines), 2)
900 unstyle(str(report)),
901 "2 files reformatted, 2 files left unchanged, 2 files failed to"
904 self.assertEqual(report.return_code, 123)
905 report.done(Path("f4"), black.Changed.NO)
906 self.assertEqual(len(out_lines), 0)
907 self.assertEqual(len(err_lines), 2)
909 unstyle(str(report)),
910 "2 files reformatted, 3 files left unchanged, 2 files failed to"
913 self.assertEqual(report.return_code, 123)
916 unstyle(str(report)),
917 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
918 " would fail to reformat.",
923 unstyle(str(report)),
924 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
925 " would fail to reformat.",
928 def test_report_normal(self) -> None:
929 report = black.Report()
933 def out(msg: str, **kwargs: Any) -> None:
934 out_lines.append(msg)
936 def err(msg: str, **kwargs: Any) -> None:
937 err_lines.append(msg)
939 with patch("black.out", out), patch("black.err", err):
940 report.done(Path("f1"), black.Changed.NO)
941 self.assertEqual(len(out_lines), 0)
942 self.assertEqual(len(err_lines), 0)
943 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
944 self.assertEqual(report.return_code, 0)
945 report.done(Path("f2"), black.Changed.YES)
946 self.assertEqual(len(out_lines), 1)
947 self.assertEqual(len(err_lines), 0)
948 self.assertEqual(out_lines[-1], "reformatted f2")
950 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
952 report.done(Path("f3"), black.Changed.CACHED)
953 self.assertEqual(len(out_lines), 1)
954 self.assertEqual(len(err_lines), 0)
955 self.assertEqual(out_lines[-1], "reformatted f2")
957 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
959 self.assertEqual(report.return_code, 0)
961 self.assertEqual(report.return_code, 1)
963 report.failed(Path("e1"), "boom")
964 self.assertEqual(len(out_lines), 1)
965 self.assertEqual(len(err_lines), 1)
966 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
968 unstyle(str(report)),
969 "1 file reformatted, 2 files left unchanged, 1 file failed to"
972 self.assertEqual(report.return_code, 123)
973 report.done(Path("f3"), black.Changed.YES)
974 self.assertEqual(len(out_lines), 2)
975 self.assertEqual(len(err_lines), 1)
976 self.assertEqual(out_lines[-1], "reformatted f3")
978 unstyle(str(report)),
979 "2 files reformatted, 2 files left unchanged, 1 file failed to"
982 self.assertEqual(report.return_code, 123)
983 report.failed(Path("e2"), "boom")
984 self.assertEqual(len(out_lines), 2)
985 self.assertEqual(len(err_lines), 2)
986 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
988 unstyle(str(report)),
989 "2 files reformatted, 2 files left unchanged, 2 files failed to"
992 self.assertEqual(report.return_code, 123)
993 report.path_ignored(Path("wat"), "no match")
994 self.assertEqual(len(out_lines), 2)
995 self.assertEqual(len(err_lines), 2)
997 unstyle(str(report)),
998 "2 files reformatted, 2 files left unchanged, 2 files failed to"
1001 self.assertEqual(report.return_code, 123)
1002 report.done(Path("f4"), black.Changed.NO)
1003 self.assertEqual(len(out_lines), 2)
1004 self.assertEqual(len(err_lines), 2)
1006 unstyle(str(report)),
1007 "2 files reformatted, 3 files left unchanged, 2 files failed to"
1010 self.assertEqual(report.return_code, 123)
1013 unstyle(str(report)),
1014 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1015 " would fail to reformat.",
1017 report.check = False
1020 unstyle(str(report)),
1021 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1022 " would fail to reformat.",
1025 def test_lib2to3_parse(self) -> None:
1026 with self.assertRaises(black.InvalidInput):
1027 black.lib2to3_parse("invalid syntax")
1029 straddling = "x + y"
1030 black.lib2to3_parse(straddling)
1031 black.lib2to3_parse(straddling, {TargetVersion.PY27})
1032 black.lib2to3_parse(straddling, {TargetVersion.PY36})
1033 black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
1035 py2_only = "print x"
1036 black.lib2to3_parse(py2_only)
1037 black.lib2to3_parse(py2_only, {TargetVersion.PY27})
1038 with self.assertRaises(black.InvalidInput):
1039 black.lib2to3_parse(py2_only, {TargetVersion.PY36})
1040 with self.assertRaises(black.InvalidInput):
1041 black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
1043 py3_only = "exec(x, end=y)"
1044 black.lib2to3_parse(py3_only)
1045 with self.assertRaises(black.InvalidInput):
1046 black.lib2to3_parse(py3_only, {TargetVersion.PY27})
1047 black.lib2to3_parse(py3_only, {TargetVersion.PY36})
1048 black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
1050 def test_get_features_used(self) -> None:
1051 node = black.lib2to3_parse("def f(*, arg): ...\n")
1052 self.assertEqual(black.get_features_used(node), set())
1053 node = black.lib2to3_parse("def f(*, arg,): ...\n")
1054 self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
1055 node = black.lib2to3_parse("f(*arg,)\n")
1057 black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
1059 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
1060 self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
1061 node = black.lib2to3_parse("123_456\n")
1062 self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
1063 node = black.lib2to3_parse("123456\n")
1064 self.assertEqual(black.get_features_used(node), set())
1065 source, expected = read_data("function")
1066 node = black.lib2to3_parse(source)
1067 expected_features = {
1068 Feature.TRAILING_COMMA_IN_CALL,
1069 Feature.TRAILING_COMMA_IN_DEF,
1072 self.assertEqual(black.get_features_used(node), expected_features)
1073 node = black.lib2to3_parse(expected)
1074 self.assertEqual(black.get_features_used(node), expected_features)
1075 source, expected = read_data("expression")
1076 node = black.lib2to3_parse(source)
1077 self.assertEqual(black.get_features_used(node), set())
1078 node = black.lib2to3_parse(expected)
1079 self.assertEqual(black.get_features_used(node), set())
1081 def test_get_future_imports(self) -> None:
1082 node = black.lib2to3_parse("\n")
1083 self.assertEqual(set(), black.get_future_imports(node))
1084 node = black.lib2to3_parse("from __future__ import black\n")
1085 self.assertEqual({"black"}, black.get_future_imports(node))
1086 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
1087 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1088 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
1089 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
1090 node = black.lib2to3_parse(
1091 "from __future__ import multiple\nfrom __future__ import imports\n"
1093 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1094 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
1095 self.assertEqual({"black"}, black.get_future_imports(node))
1096 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
1097 self.assertEqual({"black"}, black.get_future_imports(node))
1098 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
1099 self.assertEqual(set(), black.get_future_imports(node))
1100 node = black.lib2to3_parse("from some.module import black\n")
1101 self.assertEqual(set(), black.get_future_imports(node))
1102 node = black.lib2to3_parse(
1103 "from __future__ import unicode_literals as _unicode_literals"
1105 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
1106 node = black.lib2to3_parse(
1107 "from __future__ import unicode_literals as _lol, print"
1109 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
1111 def test_debug_visitor(self) -> None:
1112 source, _ = read_data("debug_visitor.py")
1113 expected, _ = read_data("debug_visitor.out")
1117 def out(msg: str, **kwargs: Any) -> None:
1118 out_lines.append(msg)
1120 def err(msg: str, **kwargs: Any) -> None:
1121 err_lines.append(msg)
1123 with patch("black.out", out), patch("black.err", err):
1124 black.DebugVisitor.show(source)
1125 actual = "\n".join(out_lines) + "\n"
1127 if expected != actual:
1128 log_name = black.dump_to_file(*out_lines)
1132 f"AST print out is different. Actual version dumped to {log_name}",
1135 def test_format_file_contents(self) -> None:
1137 mode = black.FileMode()
1138 with self.assertRaises(black.NothingChanged):
1139 black.format_file_contents(empty, mode=mode, fast=False)
1141 with self.assertRaises(black.NothingChanged):
1142 black.format_file_contents(just_nl, mode=mode, fast=False)
1143 same = "j = [1, 2, 3]\n"
1144 with self.assertRaises(black.NothingChanged):
1145 black.format_file_contents(same, mode=mode, fast=False)
1146 different = "j = [1,2,3]"
1148 actual = black.format_file_contents(different, mode=mode, fast=False)
1149 self.assertEqual(expected, actual)
1150 invalid = "return if you can"
1151 with self.assertRaises(black.InvalidInput) as e:
1152 black.format_file_contents(invalid, mode=mode, fast=False)
1153 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
1155 def test_endmarker(self) -> None:
1156 n = black.lib2to3_parse("\n")
1157 self.assertEqual(n.type, black.syms.file_input)
1158 self.assertEqual(len(n.children), 1)
1159 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
1161 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
1162 def test_assertFormatEqual(self) -> None:
1166 def out(msg: str, **kwargs: Any) -> None:
1167 out_lines.append(msg)
1169 def err(msg: str, **kwargs: Any) -> None:
1170 err_lines.append(msg)
1172 with patch("black.out", out), patch("black.err", err):
1173 with self.assertRaises(AssertionError):
1174 self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")
1176 out_str = "".join(out_lines)
1177 self.assertTrue("Expected tree:" in out_str)
1178 self.assertTrue("Actual tree:" in out_str)
1179 self.assertEqual("".join(err_lines), "")
1181 def test_cache_broken_file(self) -> None:
1182 mode = black.FileMode()
1183 with cache_dir() as workspace:
1184 cache_file = black.get_cache_file(mode)
1185 with cache_file.open("w") as fobj:
1186 fobj.write("this is not a pickle")
1187 self.assertEqual(black.read_cache(mode), {})
1188 src = (workspace / "test.py").resolve()
1189 with src.open("w") as fobj:
1190 fobj.write("print('hello')")
1191 self.invokeBlack([str(src)])
1192 cache = black.read_cache(mode)
1193 self.assertIn(src, cache)
1195 def test_cache_single_file_already_cached(self) -> None:
1196 mode = black.FileMode()
1197 with cache_dir() as workspace:
1198 src = (workspace / "test.py").resolve()
1199 with src.open("w") as fobj:
1200 fobj.write("print('hello')")
1201 black.write_cache({}, [src], mode)
1202 self.invokeBlack([str(src)])
1203 with src.open("r") as fobj:
1204 self.assertEqual(fobj.read(), "print('hello')")
1206 @event_loop(close=False)
1207 def test_cache_multiple_files(self) -> None:
1208 mode = black.FileMode()
1209 with cache_dir() as workspace, patch(
1210 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1212 one = (workspace / "one.py").resolve()
1213 with one.open("w") as fobj:
1214 fobj.write("print('hello')")
1215 two = (workspace / "two.py").resolve()
1216 with two.open("w") as fobj:
1217 fobj.write("print('hello')")
1218 black.write_cache({}, [one], mode)
1219 self.invokeBlack([str(workspace)])
1220 with one.open("r") as fobj:
1221 self.assertEqual(fobj.read(), "print('hello')")
1222 with two.open("r") as fobj:
1223 self.assertEqual(fobj.read(), 'print("hello")\n')
1224 cache = black.read_cache(mode)
1225 self.assertIn(one, cache)
1226 self.assertIn(two, cache)
1228 def test_no_cache_when_writeback_diff(self) -> None:
1229 mode = black.FileMode()
1230 with cache_dir() as workspace:
1231 src = (workspace / "test.py").resolve()
1232 with src.open("w") as fobj:
1233 fobj.write("print('hello')")
1234 self.invokeBlack([str(src), "--diff"])
1235 cache_file = black.get_cache_file(mode)
1236 self.assertFalse(cache_file.exists())
1238 def test_no_cache_when_stdin(self) -> None:
1239 mode = black.FileMode()
1241 result = CliRunner().invoke(
1242 black.main, ["-"], input=BytesIO(b"print('hello')")
1244 self.assertEqual(result.exit_code, 0)
1245 cache_file = black.get_cache_file(mode)
1246 self.assertFalse(cache_file.exists())
1248 def test_read_cache_no_cachefile(self) -> None:
1249 mode = black.FileMode()
1251 self.assertEqual(black.read_cache(mode), {})
1253 def test_write_cache_read_cache(self) -> None:
1254 mode = black.FileMode()
1255 with cache_dir() as workspace:
1256 src = (workspace / "test.py").resolve()
1258 black.write_cache({}, [src], mode)
1259 cache = black.read_cache(mode)
1260 self.assertIn(src, cache)
1261 self.assertEqual(cache[src], black.get_cache_info(src))
1263 def test_filter_cached(self) -> None:
1264 with TemporaryDirectory() as workspace:
1265 path = Path(workspace)
1266 uncached = (path / "uncached").resolve()
1267 cached = (path / "cached").resolve()
1268 cached_but_changed = (path / "changed").resolve()
1271 cached_but_changed.touch()
1272 cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
1273 todo, done = black.filter_cached(
1274 cache, {uncached, cached, cached_but_changed}
1276 self.assertEqual(todo, {uncached, cached_but_changed})
1277 self.assertEqual(done, {cached})
1279 def test_write_cache_creates_directory_if_needed(self) -> None:
1280 mode = black.FileMode()
1281 with cache_dir(exists=False) as workspace:
1282 self.assertFalse(workspace.exists())
1283 black.write_cache({}, [], mode)
1284 self.assertTrue(workspace.exists())
1286 @event_loop(close=False)
1287 def test_failed_formatting_does_not_get_cached(self) -> None:
1288 mode = black.FileMode()
1289 with cache_dir() as workspace, patch(
1290 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1292 failing = (workspace / "failing.py").resolve()
1293 with failing.open("w") as fobj:
1294 fobj.write("not actually python")
1295 clean = (workspace / "clean.py").resolve()
1296 with clean.open("w") as fobj:
1297 fobj.write('print("hello")\n')
1298 self.invokeBlack([str(workspace)], exit_code=123)
1299 cache = black.read_cache(mode)
1300 self.assertNotIn(failing, cache)
1301 self.assertIn(clean, cache)
1303 def test_write_cache_write_fail(self) -> None:
1304 mode = black.FileMode()
1305 with cache_dir(), patch.object(Path, "open") as mock:
1306 mock.side_effect = OSError
1307 black.write_cache({}, [], mode)
1309 @event_loop(close=False)
1310 def test_check_diff_use_together(self) -> None:
1312 # Files which will be reformatted.
1313 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1314 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1315 # Files which will not be reformatted.
1316 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1317 self.invokeBlack([str(src2), "--diff", "--check"])
1318 # Multi file command.
1319 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1321 def test_no_files(self) -> None:
1323 # Without an argument, black exits with error code 0.
1324 self.invokeBlack([])
1326 def test_broken_symlink(self) -> None:
1327 with cache_dir() as workspace:
1328 symlink = workspace / "broken_link.py"
1330 symlink.symlink_to("nonexistent.py")
1331 except OSError as e:
1332 self.skipTest(f"Can't create symlinks: {e}")
1333 self.invokeBlack([str(workspace.resolve())])
1335 def test_read_cache_line_lengths(self) -> None:
1336 mode = black.FileMode()
1337 short_mode = black.FileMode(line_length=1)
1338 with cache_dir() as workspace:
1339 path = (workspace / "file.py").resolve()
1341 black.write_cache({}, [path], mode)
1342 one = black.read_cache(mode)
1343 self.assertIn(path, one)
1344 two = black.read_cache(short_mode)
1345 self.assertNotIn(path, two)
1347 def test_tricky_unicode_symbols(self) -> None:
1348 source, expected = read_data("tricky_unicode_symbols")
1350 self.assertFormatEqual(expected, actual)
1351 black.assert_equivalent(source, actual)
1352 black.assert_stable(source, actual, black.FileMode())
1354 def test_single_file_force_pyi(self) -> None:
1355 reg_mode = black.FileMode()
1356 pyi_mode = black.FileMode(is_pyi=True)
1357 contents, expected = read_data("force_pyi")
1358 with cache_dir() as workspace:
1359 path = (workspace / "file.py").resolve()
1360 with open(path, "w") as fh:
1362 self.invokeBlack([str(path), "--pyi"])
1363 with open(path, "r") as fh:
1365 # verify cache with --pyi is separate
1366 pyi_cache = black.read_cache(pyi_mode)
1367 self.assertIn(path, pyi_cache)
1368 normal_cache = black.read_cache(reg_mode)
1369 self.assertNotIn(path, normal_cache)
1370 self.assertEqual(actual, expected)
1372 @event_loop(close=False)
1373 def test_multi_file_force_pyi(self) -> None:
1374 reg_mode = black.FileMode()
1375 pyi_mode = black.FileMode(is_pyi=True)
1376 contents, expected = read_data("force_pyi")
1377 with cache_dir() as workspace:
1379 (workspace / "file1.py").resolve(),
1380 (workspace / "file2.py").resolve(),
1383 with open(path, "w") as fh:
1385 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1387 with open(path, "r") as fh:
1389 self.assertEqual(actual, expected)
1390 # verify cache with --pyi is separate
1391 pyi_cache = black.read_cache(pyi_mode)
1392 normal_cache = black.read_cache(reg_mode)
1394 self.assertIn(path, pyi_cache)
1395 self.assertNotIn(path, normal_cache)
1397 def test_pipe_force_pyi(self) -> None:
1398 source, expected = read_data("force_pyi")
1399 result = CliRunner().invoke(
1400 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1402 self.assertEqual(result.exit_code, 0)
1403 actual = result.output
1404 self.assertFormatEqual(actual, expected)
1406 def test_single_file_force_py36(self) -> None:
1407 reg_mode = black.FileMode()
1408 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1409 source, expected = read_data("force_py36")
1410 with cache_dir() as workspace:
1411 path = (workspace / "file.py").resolve()
1412 with open(path, "w") as fh:
1414 self.invokeBlack([str(path), *PY36_ARGS])
1415 with open(path, "r") as fh:
1417 # verify cache with --target-version is separate
1418 py36_cache = black.read_cache(py36_mode)
1419 self.assertIn(path, py36_cache)
1420 normal_cache = black.read_cache(reg_mode)
1421 self.assertNotIn(path, normal_cache)
1422 self.assertEqual(actual, expected)
1424 @event_loop(close=False)
1425 def test_multi_file_force_py36(self) -> None:
1426 reg_mode = black.FileMode()
1427 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1428 source, expected = read_data("force_py36")
1429 with cache_dir() as workspace:
1431 (workspace / "file1.py").resolve(),
1432 (workspace / "file2.py").resolve(),
1435 with open(path, "w") as fh:
1437 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1439 with open(path, "r") as fh:
1441 self.assertEqual(actual, expected)
1442 # verify cache with --target-version is separate
1443 pyi_cache = black.read_cache(py36_mode)
1444 normal_cache = black.read_cache(reg_mode)
1446 self.assertIn(path, pyi_cache)
1447 self.assertNotIn(path, normal_cache)
1449 def test_collections(self) -> None:
1450 source, expected = read_data("collections")
1452 self.assertFormatEqual(expected, actual)
1453 black.assert_equivalent(source, actual)
1454 black.assert_stable(source, actual, black.FileMode())
1456 def test_pipe_force_py36(self) -> None:
1457 source, expected = read_data("force_py36")
1458 result = CliRunner().invoke(
1460 ["-", "-q", "--target-version=py36"],
1461 input=BytesIO(source.encode("utf8")),
1463 self.assertEqual(result.exit_code, 0)
1464 actual = result.output
1465 self.assertFormatEqual(actual, expected)
1467 def test_include_exclude(self) -> None:
1468 path = THIS_DIR / "data" / "include_exclude_tests"
1469 include = re.compile(r"\.pyi?$")
1470 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1471 report = black.Report()
1472 gitignore = PathSpec.from_lines("gitwildmatch", [])
1473 sources: List[Path] = []
1475 Path(path / "b/dont_exclude/a.py"),
1476 Path(path / "b/dont_exclude/a.pyi"),
1478 this_abs = THIS_DIR.resolve()
1480 black.gen_python_files_in_dir(
1481 path, this_abs, include, exclude, report, gitignore
1484 self.assertEqual(sorted(expected), sorted(sources))
1486 def test_gitignore_exclude(self) -> None:
1487 path = THIS_DIR / "data" / "include_exclude_tests"
1488 include = re.compile(r"\.pyi?$")
1489 exclude = re.compile(r"")
1490 report = black.Report()
1491 gitignore = PathSpec.from_lines(
1492 "gitwildmatch", ["exclude/", ".definitely_exclude"]
1494 sources: List[Path] = []
1496 Path(path / "b/dont_exclude/a.py"),
1497 Path(path / "b/dont_exclude/a.pyi"),
1499 this_abs = THIS_DIR.resolve()
1501 black.gen_python_files_in_dir(
1502 path, this_abs, include, exclude, report, gitignore
1505 self.assertEqual(sorted(expected), sorted(sources))
1507 def test_empty_include(self) -> None:
1508 path = THIS_DIR / "data" / "include_exclude_tests"
1509 report = black.Report()
1510 gitignore = PathSpec.from_lines("gitwildmatch", [])
1511 empty = re.compile(r"")
1512 sources: List[Path] = []
1514 Path(path / "b/exclude/a.pie"),
1515 Path(path / "b/exclude/a.py"),
1516 Path(path / "b/exclude/a.pyi"),
1517 Path(path / "b/dont_exclude/a.pie"),
1518 Path(path / "b/dont_exclude/a.py"),
1519 Path(path / "b/dont_exclude/a.pyi"),
1520 Path(path / "b/.definitely_exclude/a.pie"),
1521 Path(path / "b/.definitely_exclude/a.py"),
1522 Path(path / "b/.definitely_exclude/a.pyi"),
1524 this_abs = THIS_DIR.resolve()
1526 black.gen_python_files_in_dir(
1530 re.compile(black.DEFAULT_EXCLUDES),
1535 self.assertEqual(sorted(expected), sorted(sources))
1537 def test_empty_exclude(self) -> None:
1538 path = THIS_DIR / "data" / "include_exclude_tests"
1539 report = black.Report()
1540 gitignore = PathSpec.from_lines("gitwildmatch", [])
1541 empty = re.compile(r"")
1542 sources: List[Path] = []
1544 Path(path / "b/dont_exclude/a.py"),
1545 Path(path / "b/dont_exclude/a.pyi"),
1546 Path(path / "b/exclude/a.py"),
1547 Path(path / "b/exclude/a.pyi"),
1548 Path(path / "b/.definitely_exclude/a.py"),
1549 Path(path / "b/.definitely_exclude/a.pyi"),
1551 this_abs = THIS_DIR.resolve()
1553 black.gen_python_files_in_dir(
1556 re.compile(black.DEFAULT_INCLUDES),
1562 self.assertEqual(sorted(expected), sorted(sources))
1564 def test_invalid_include_exclude(self) -> None:
1565 for option in ["--include", "--exclude"]:
1566 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1568 def test_preserves_line_endings(self) -> None:
1569 with TemporaryDirectory() as workspace:
1570 test_file = Path(workspace) / "test.py"
1571 for nl in ["\n", "\r\n"]:
1572 contents = nl.join(["def f( ):", " pass"])
1573 test_file.write_bytes(contents.encode())
1574 ff(test_file, write_back=black.WriteBack.YES)
1575 updated_contents: bytes = test_file.read_bytes()
1576 self.assertIn(nl.encode(), updated_contents)
1578 self.assertNotIn(b"\r\n", updated_contents)
1580 def test_preserves_line_endings_via_stdin(self) -> None:
1581 for nl in ["\n", "\r\n"]:
1582 contents = nl.join(["def f( ):", " pass"])
1583 runner = BlackRunner()
1584 result = runner.invoke(
1585 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1587 self.assertEqual(result.exit_code, 0)
1588 output = runner.stdout_bytes
1589 self.assertIn(nl.encode("utf8"), output)
1591 self.assertNotIn(b"\r\n", output)
1593 def test_assert_equivalent_different_asts(self) -> None:
1594 with self.assertRaises(AssertionError):
1595 black.assert_equivalent("{}", "None")
1597 def test_symlink_out_of_root_directory(self) -> None:
1601 include = re.compile(black.DEFAULT_INCLUDES)
1602 exclude = re.compile(black.DEFAULT_EXCLUDES)
1603 report = black.Report()
1604 gitignore = PathSpec.from_lines("gitwildmatch", [])
1605 # `child` should behave like a symlink which resolved path is clearly
1606 # outside of the `root` directory.
1607 path.iterdir.return_value = [child]
1608 child.resolve.return_value = Path("/a/b/c")
1609 child.as_posix.return_value = "/a/b/c"
1610 child.is_symlink.return_value = True
1613 black.gen_python_files_in_dir(
1614 path, root, include, exclude, report, gitignore
1617 except ValueError as ve:
1618 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1619 path.iterdir.assert_called_once()
1620 child.resolve.assert_called_once()
1621 child.is_symlink.assert_called_once()
1622 # `child` should behave like a strange file which resolved path is clearly
1623 # outside of the `root` directory.
1624 child.is_symlink.return_value = False
1625 with self.assertRaises(ValueError):
1627 black.gen_python_files_in_dir(
1628 path, root, include, exclude, report, gitignore
1631 path.iterdir.assert_called()
1632 self.assertEqual(path.iterdir.call_count, 2)
1633 child.resolve.assert_called()
1634 self.assertEqual(child.resolve.call_count, 2)
1635 child.is_symlink.assert_called()
1636 self.assertEqual(child.is_symlink.call_count, 2)
1638 def test_shhh_click(self) -> None:
1640 from click import _unicodefun # type: ignore
1641 except ModuleNotFoundError:
1642 self.skipTest("Incompatible Click version")
1643 if not hasattr(_unicodefun, "_verify_python3_env"):
1644 self.skipTest("Incompatible Click version")
1645 # First, let's see if Click is crashing with a preferred ASCII charset.
1646 with patch("locale.getpreferredencoding") as gpe:
1647 gpe.return_value = "ASCII"
1648 with self.assertRaises(RuntimeError):
1649 _unicodefun._verify_python3_env()
1650 # Now, let's silence Click...
1652 # ...and confirm it's silent.
1653 with patch("locale.getpreferredencoding") as gpe:
1654 gpe.return_value = "ASCII"
1656 _unicodefun._verify_python3_env()
1657 except RuntimeError as re:
1658 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1660 def test_root_logger_not_used_directly(self) -> None:
1661 def fail(*args: Any, **kwargs: Any) -> None:
1662 self.fail("Record created with root logger")
1664 with patch.multiple(
1675 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1676 def test_blackd_main(self) -> None:
1677 with patch("blackd.web.run_app"):
1678 result = CliRunner().invoke(blackd.main, [])
1679 if result.exception is not None:
1680 raise result.exception
1681 self.assertEqual(result.exit_code, 0)
1683 def test_invalid_config_return_code(self) -> None:
1684 tmp_file = Path(black.dump_to_file())
1686 tmp_config = Path(black.dump_to_file())
1688 args = ["--config", str(tmp_config), str(tmp_file)]
1689 self.invokeBlack(args, exit_code=2, ignore_config=False)
1694 class BlackDTestCase(AioHTTPTestCase):
1695 async def get_application(self) -> web.Application:
1696 return blackd.make_app()
1698 # TODO: remove these decorators once the below is released
1699 # https://github.com/aio-libs/aiohttp/pull/3727
1700 @skip_if_exception("ClientOSError")
1701 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1703 async def test_blackd_request_needs_formatting(self) -> None:
1704 response = await self.client.post("/", data=b"print('hello world')")
1705 self.assertEqual(response.status, 200)
1706 self.assertEqual(response.charset, "utf8")
1707 self.assertEqual(await response.read(), b'print("hello world")\n')
1709 @skip_if_exception("ClientOSError")
1710 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1712 async def test_blackd_request_no_change(self) -> None:
1713 response = await self.client.post("/", data=b'print("hello world")\n')
1714 self.assertEqual(response.status, 204)
1715 self.assertEqual(await response.read(), b"")
1717 @skip_if_exception("ClientOSError")
1718 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1720 async def test_blackd_request_syntax_error(self) -> None:
1721 response = await self.client.post("/", data=b"what even ( is")
1722 self.assertEqual(response.status, 400)
1723 content = await response.text()
1725 content.startswith("Cannot parse"),
1726 msg=f"Expected error to start with 'Cannot parse', got {repr(content)}",
1729 @skip_if_exception("ClientOSError")
1730 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1732 async def test_blackd_unsupported_version(self) -> None:
1733 response = await self.client.post(
1734 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "2"}
1736 self.assertEqual(response.status, 501)
1738 @skip_if_exception("ClientOSError")
1739 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1741 async def test_blackd_supported_version(self) -> None:
1742 response = await self.client.post(
1743 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "1"}
1745 self.assertEqual(response.status, 200)
1747 @skip_if_exception("ClientOSError")
1748 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1750 async def test_blackd_invalid_python_variant(self) -> None:
1751 async def check(header_value: str, expected_status: int = 400) -> None:
1752 response = await self.client.post(
1753 "/", data=b"what", headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1755 self.assertEqual(response.status, expected_status)
1758 await check("ruby3.5")
1759 await check("pyi3.6")
1760 await check("py1.5")
1762 await check("py2.8")
1764 await check("pypy3.0")
1765 await check("jython3.4")
1767 @skip_if_exception("ClientOSError")
1768 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1770 async def test_blackd_pyi(self) -> None:
1771 source, expected = read_data("stub.pyi")
1772 response = await self.client.post(
1773 "/", data=source, headers={blackd.PYTHON_VARIANT_HEADER: "pyi"}
1775 self.assertEqual(response.status, 200)
1776 self.assertEqual(await response.text(), expected)
1778 @skip_if_exception("ClientOSError")
1779 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1781 async def test_blackd_diff(self) -> None:
1782 diff_header = re.compile(
1783 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"
1786 source, _ = read_data("blackd_diff.py")
1787 expected, _ = read_data("blackd_diff.diff")
1789 response = await self.client.post(
1790 "/", data=source, headers={blackd.DIFF_HEADER: "true"}
1792 self.assertEqual(response.status, 200)
1794 actual = await response.text()
1795 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
1796 self.assertEqual(actual, expected)
1798 @skip_if_exception("ClientOSError")
1799 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1801 async def test_blackd_python_variant(self) -> None:
1804 " and_has_a_bunch_of,\n"
1805 " very_long_arguments_too,\n"
1806 " and_lots_of_them_as_well_lol,\n"
1807 " **and_very_long_keyword_arguments\n"
1812 async def check(header_value: str, expected_status: int) -> None:
1813 response = await self.client.post(
1814 "/", data=code, headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1817 response.status, expected_status, msg=await response.text()
1820 await check("3.6", 200)
1821 await check("py3.6", 200)
1822 await check("3.6,3.7", 200)
1823 await check("3.6,py3.7", 200)
1824 await check("py36,py37", 200)
1825 await check("36", 200)
1826 await check("3.6.4", 200)
1828 await check("2", 204)
1829 await check("2.7", 204)
1830 await check("py2.7", 204)
1831 await check("3.4", 204)
1832 await check("py3.4", 204)
1833 await check("py34,py36", 204)
1834 await check("34", 204)
1836 @skip_if_exception("ClientOSError")
1837 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1839 async def test_blackd_line_length(self) -> None:
1840 response = await self.client.post(
1841 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "7"}
1843 self.assertEqual(response.status, 200)
1845 @skip_if_exception("ClientOSError")
1846 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1848 async def test_blackd_invalid_line_length(self) -> None:
1849 response = await self.client.post(
1850 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "NaN"}
1852 self.assertEqual(response.status, 400)
1854 @skip_if_exception("ClientOSError")
1855 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1857 async def test_blackd_response_black_version_header(self) -> None:
1858 response = await self.client.post("/")
1859 self.assertIsNotNone(response.headers.get(blackd.BLACK_VERSION_HEADER))
1862 if __name__ == "__main__":
1863 unittest.main(module="test_black")