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_docstring(self) -> None:
395 source, expected = read_data("docstring")
397 self.assertFormatEqual(expected, actual)
398 black.assert_equivalent(source, actual)
399 black.assert_stable(source, actual, black.FileMode())
401 def test_long_strings(self) -> None:
402 """Tests for splitting long strings."""
403 source, expected = read_data("long_strings")
405 self.assertFormatEqual(expected, actual)
406 black.assert_equivalent(source, actual)
407 black.assert_stable(source, actual, black.FileMode())
409 @patch("black.dump_to_file", dump_to_stderr)
410 def test_long_strings__edge_case(self) -> None:
411 """Edge-case tests for splitting long strings."""
412 source, expected = read_data("long_strings__edge_case")
414 self.assertFormatEqual(expected, actual)
415 black.assert_equivalent(source, actual)
416 black.assert_stable(source, actual, black.FileMode())
418 @patch("black.dump_to_file", dump_to_stderr)
419 def test_long_strings__regression(self) -> None:
420 """Regression tests for splitting long strings."""
421 source, expected = read_data("long_strings__regression")
423 self.assertFormatEqual(expected, actual)
424 black.assert_equivalent(source, actual)
425 black.assert_stable(source, actual, black.FileMode())
427 @patch("black.dump_to_file", dump_to_stderr)
428 def test_slices(self) -> None:
429 source, expected = read_data("slices")
431 self.assertFormatEqual(expected, actual)
432 black.assert_equivalent(source, actual)
433 black.assert_stable(source, actual, black.FileMode())
435 @patch("black.dump_to_file", dump_to_stderr)
436 def test_comments(self) -> None:
437 source, expected = read_data("comments")
439 self.assertFormatEqual(expected, actual)
440 black.assert_equivalent(source, actual)
441 black.assert_stable(source, actual, black.FileMode())
443 @patch("black.dump_to_file", dump_to_stderr)
444 def test_comments2(self) -> None:
445 source, expected = read_data("comments2")
447 self.assertFormatEqual(expected, actual)
448 black.assert_equivalent(source, actual)
449 black.assert_stable(source, actual, black.FileMode())
451 @patch("black.dump_to_file", dump_to_stderr)
452 def test_comments3(self) -> None:
453 source, expected = read_data("comments3")
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_comments4(self) -> None:
461 source, expected = read_data("comments4")
463 self.assertFormatEqual(expected, actual)
464 black.assert_equivalent(source, actual)
465 black.assert_stable(source, actual, black.FileMode())
467 @patch("black.dump_to_file", dump_to_stderr)
468 def test_comments5(self) -> None:
469 source, expected = read_data("comments5")
471 self.assertFormatEqual(expected, actual)
472 black.assert_equivalent(source, actual)
473 black.assert_stable(source, actual, black.FileMode())
475 @patch("black.dump_to_file", dump_to_stderr)
476 def test_comments6(self) -> None:
477 source, expected = read_data("comments6")
479 self.assertFormatEqual(expected, actual)
480 black.assert_equivalent(source, actual)
481 black.assert_stable(source, actual, black.FileMode())
483 @patch("black.dump_to_file", dump_to_stderr)
484 def test_comments7(self) -> None:
485 source, expected = read_data("comments7")
487 self.assertFormatEqual(expected, actual)
488 black.assert_equivalent(source, actual)
489 black.assert_stable(source, actual, black.FileMode())
491 @patch("black.dump_to_file", dump_to_stderr)
492 def test_comment_after_escaped_newline(self) -> None:
493 source, expected = read_data("comment_after_escaped_newline")
495 self.assertFormatEqual(expected, actual)
496 black.assert_equivalent(source, actual)
497 black.assert_stable(source, actual, black.FileMode())
499 @patch("black.dump_to_file", dump_to_stderr)
500 def test_cantfit(self) -> None:
501 source, expected = read_data("cantfit")
503 self.assertFormatEqual(expected, actual)
504 black.assert_equivalent(source, actual)
505 black.assert_stable(source, actual, black.FileMode())
507 @patch("black.dump_to_file", dump_to_stderr)
508 def test_import_spacing(self) -> None:
509 source, expected = read_data("import_spacing")
511 self.assertFormatEqual(expected, actual)
512 black.assert_equivalent(source, actual)
513 black.assert_stable(source, actual, black.FileMode())
515 @patch("black.dump_to_file", dump_to_stderr)
516 def test_composition(self) -> None:
517 source, expected = read_data("composition")
519 self.assertFormatEqual(expected, actual)
520 black.assert_equivalent(source, actual)
521 black.assert_stable(source, actual, black.FileMode())
523 @patch("black.dump_to_file", dump_to_stderr)
524 def test_empty_lines(self) -> None:
525 source, expected = read_data("empty_lines")
527 self.assertFormatEqual(expected, actual)
528 black.assert_equivalent(source, actual)
529 black.assert_stable(source, actual, black.FileMode())
531 @patch("black.dump_to_file", dump_to_stderr)
532 def test_remove_parens(self) -> None:
533 source, expected = read_data("remove_parens")
535 self.assertFormatEqual(expected, actual)
536 black.assert_equivalent(source, actual)
537 black.assert_stable(source, actual, black.FileMode())
539 @patch("black.dump_to_file", dump_to_stderr)
540 def test_string_prefixes(self) -> None:
541 source, expected = read_data("string_prefixes")
543 self.assertFormatEqual(expected, actual)
544 black.assert_equivalent(source, actual)
545 black.assert_stable(source, actual, black.FileMode())
547 @patch("black.dump_to_file", dump_to_stderr)
548 def test_numeric_literals(self) -> None:
549 source, expected = read_data("numeric_literals")
550 mode = black.FileMode(target_versions=black.PY36_VERSIONS)
551 actual = fs(source, mode=mode)
552 self.assertFormatEqual(expected, actual)
553 black.assert_equivalent(source, actual)
554 black.assert_stable(source, actual, mode)
556 @patch("black.dump_to_file", dump_to_stderr)
557 def test_numeric_literals_ignoring_underscores(self) -> None:
558 source, expected = read_data("numeric_literals_skip_underscores")
559 mode = black.FileMode(target_versions=black.PY36_VERSIONS)
560 actual = fs(source, mode=mode)
561 self.assertFormatEqual(expected, actual)
562 black.assert_equivalent(source, actual)
563 black.assert_stable(source, actual, mode)
565 @patch("black.dump_to_file", dump_to_stderr)
566 def test_numeric_literals_py2(self) -> None:
567 source, expected = read_data("numeric_literals_py2")
569 self.assertFormatEqual(expected, actual)
570 black.assert_stable(source, actual, black.FileMode())
572 @patch("black.dump_to_file", dump_to_stderr)
573 def test_python2(self) -> None:
574 source, expected = read_data("python2")
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_python2_print_function(self) -> None:
582 source, expected = read_data("python2_print_function")
583 mode = black.FileMode(target_versions={TargetVersion.PY27})
584 actual = fs(source, mode=mode)
585 self.assertFormatEqual(expected, actual)
586 black.assert_equivalent(source, actual)
587 black.assert_stable(source, actual, mode)
589 @patch("black.dump_to_file", dump_to_stderr)
590 def test_python2_unicode_literals(self) -> None:
591 source, expected = read_data("python2_unicode_literals")
593 self.assertFormatEqual(expected, actual)
594 black.assert_equivalent(source, actual)
595 black.assert_stable(source, actual, black.FileMode())
597 @patch("black.dump_to_file", dump_to_stderr)
598 def test_stub(self) -> None:
599 mode = black.FileMode(is_pyi=True)
600 source, expected = read_data("stub.pyi")
601 actual = fs(source, mode=mode)
602 self.assertFormatEqual(expected, actual)
603 black.assert_stable(source, actual, mode)
605 @patch("black.dump_to_file", dump_to_stderr)
606 def test_async_as_identifier(self) -> None:
607 source_path = (THIS_DIR / "data" / "async_as_identifier.py").resolve()
608 source, expected = read_data("async_as_identifier")
610 self.assertFormatEqual(expected, actual)
611 major, minor = sys.version_info[:2]
612 if major < 3 or (major <= 3 and minor < 7):
613 black.assert_equivalent(source, actual)
614 black.assert_stable(source, actual, black.FileMode())
615 # ensure black can parse this when the target is 3.6
616 self.invokeBlack([str(source_path), "--target-version", "py36"])
617 # but not on 3.7, because async/await is no longer an identifier
618 self.invokeBlack([str(source_path), "--target-version", "py37"], exit_code=123)
620 @patch("black.dump_to_file", dump_to_stderr)
621 def test_python37(self) -> None:
622 source_path = (THIS_DIR / "data" / "python37.py").resolve()
623 source, expected = read_data("python37")
625 self.assertFormatEqual(expected, actual)
626 major, minor = sys.version_info[:2]
627 if major > 3 or (major == 3 and minor >= 7):
628 black.assert_equivalent(source, actual)
629 black.assert_stable(source, actual, black.FileMode())
630 # ensure black can parse this when the target is 3.7
631 self.invokeBlack([str(source_path), "--target-version", "py37"])
632 # but not on 3.6, because we use async as a reserved keyword
633 self.invokeBlack([str(source_path), "--target-version", "py36"], exit_code=123)
635 @patch("black.dump_to_file", dump_to_stderr)
636 def test_python38(self) -> None:
637 source, expected = read_data("python38")
639 self.assertFormatEqual(expected, actual)
640 major, minor = sys.version_info[:2]
641 if major > 3 or (major == 3 and minor >= 8):
642 black.assert_equivalent(source, actual)
643 black.assert_stable(source, actual, black.FileMode())
645 @patch("black.dump_to_file", dump_to_stderr)
646 def test_fmtonoff(self) -> None:
647 source, expected = read_data("fmtonoff")
649 self.assertFormatEqual(expected, actual)
650 black.assert_equivalent(source, actual)
651 black.assert_stable(source, actual, black.FileMode())
653 @patch("black.dump_to_file", dump_to_stderr)
654 def test_fmtonoff2(self) -> None:
655 source, expected = read_data("fmtonoff2")
657 self.assertFormatEqual(expected, actual)
658 black.assert_equivalent(source, actual)
659 black.assert_stable(source, actual, black.FileMode())
661 @patch("black.dump_to_file", dump_to_stderr)
662 def test_fmtonoff3(self) -> None:
663 source, expected = read_data("fmtonoff3")
665 self.assertFormatEqual(expected, actual)
666 black.assert_equivalent(source, actual)
667 black.assert_stable(source, actual, black.FileMode())
669 @patch("black.dump_to_file", dump_to_stderr)
670 def test_fmtonoff4(self) -> None:
671 source, expected = read_data("fmtonoff4")
673 self.assertFormatEqual(expected, actual)
674 black.assert_equivalent(source, actual)
675 black.assert_stable(source, actual, black.FileMode())
677 @patch("black.dump_to_file", dump_to_stderr)
678 def test_remove_empty_parentheses_after_class(self) -> None:
679 source, expected = read_data("class_blank_parentheses")
681 self.assertFormatEqual(expected, actual)
682 black.assert_equivalent(source, actual)
683 black.assert_stable(source, actual, black.FileMode())
685 @patch("black.dump_to_file", dump_to_stderr)
686 def test_new_line_between_class_and_code(self) -> None:
687 source, expected = read_data("class_methods_new_line")
689 self.assertFormatEqual(expected, actual)
690 black.assert_equivalent(source, actual)
691 black.assert_stable(source, actual, black.FileMode())
693 @patch("black.dump_to_file", dump_to_stderr)
694 def test_bracket_match(self) -> None:
695 source, expected = read_data("bracketmatch")
697 self.assertFormatEqual(expected, actual)
698 black.assert_equivalent(source, actual)
699 black.assert_stable(source, actual, black.FileMode())
701 @patch("black.dump_to_file", dump_to_stderr)
702 def test_tuple_assign(self) -> None:
703 source, expected = read_data("tupleassign")
705 self.assertFormatEqual(expected, actual)
706 black.assert_equivalent(source, actual)
707 black.assert_stable(source, actual, black.FileMode())
709 @patch("black.dump_to_file", dump_to_stderr)
710 def test_beginning_backslash(self) -> None:
711 source, expected = read_data("beginning_backslash")
713 self.assertFormatEqual(expected, actual)
714 black.assert_equivalent(source, actual)
715 black.assert_stable(source, actual, black.FileMode())
717 def test_tab_comment_indentation(self) -> None:
718 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
719 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
720 self.assertFormatEqual(contents_spc, fs(contents_spc))
721 self.assertFormatEqual(contents_spc, fs(contents_tab))
723 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
724 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
725 self.assertFormatEqual(contents_spc, fs(contents_spc))
726 self.assertFormatEqual(contents_spc, fs(contents_tab))
728 # mixed tabs and spaces (valid Python 2 code)
729 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t# comment\n pass\n"
730 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
731 self.assertFormatEqual(contents_spc, fs(contents_spc))
732 self.assertFormatEqual(contents_spc, fs(contents_tab))
734 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t\t# comment\n pass\n"
735 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
736 self.assertFormatEqual(contents_spc, fs(contents_spc))
737 self.assertFormatEqual(contents_spc, fs(contents_tab))
739 def test_report_verbose(self) -> None:
740 report = black.Report(verbose=True)
744 def out(msg: str, **kwargs: Any) -> None:
745 out_lines.append(msg)
747 def err(msg: str, **kwargs: Any) -> None:
748 err_lines.append(msg)
750 with patch("black.out", out), patch("black.err", err):
751 report.done(Path("f1"), black.Changed.NO)
752 self.assertEqual(len(out_lines), 1)
753 self.assertEqual(len(err_lines), 0)
754 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
755 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
756 self.assertEqual(report.return_code, 0)
757 report.done(Path("f2"), black.Changed.YES)
758 self.assertEqual(len(out_lines), 2)
759 self.assertEqual(len(err_lines), 0)
760 self.assertEqual(out_lines[-1], "reformatted f2")
762 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
764 report.done(Path("f3"), black.Changed.CACHED)
765 self.assertEqual(len(out_lines), 3)
766 self.assertEqual(len(err_lines), 0)
768 out_lines[-1], "f3 wasn't modified on disk since last run."
771 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
773 self.assertEqual(report.return_code, 0)
775 self.assertEqual(report.return_code, 1)
777 report.failed(Path("e1"), "boom")
778 self.assertEqual(len(out_lines), 3)
779 self.assertEqual(len(err_lines), 1)
780 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
782 unstyle(str(report)),
783 "1 file reformatted, 2 files left unchanged, 1 file failed to"
786 self.assertEqual(report.return_code, 123)
787 report.done(Path("f3"), black.Changed.YES)
788 self.assertEqual(len(out_lines), 4)
789 self.assertEqual(len(err_lines), 1)
790 self.assertEqual(out_lines[-1], "reformatted f3")
792 unstyle(str(report)),
793 "2 files reformatted, 2 files left unchanged, 1 file failed to"
796 self.assertEqual(report.return_code, 123)
797 report.failed(Path("e2"), "boom")
798 self.assertEqual(len(out_lines), 4)
799 self.assertEqual(len(err_lines), 2)
800 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
802 unstyle(str(report)),
803 "2 files reformatted, 2 files left unchanged, 2 files failed to"
806 self.assertEqual(report.return_code, 123)
807 report.path_ignored(Path("wat"), "no match")
808 self.assertEqual(len(out_lines), 5)
809 self.assertEqual(len(err_lines), 2)
810 self.assertEqual(out_lines[-1], "wat ignored: no match")
812 unstyle(str(report)),
813 "2 files reformatted, 2 files left unchanged, 2 files failed to"
816 self.assertEqual(report.return_code, 123)
817 report.done(Path("f4"), black.Changed.NO)
818 self.assertEqual(len(out_lines), 6)
819 self.assertEqual(len(err_lines), 2)
820 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
822 unstyle(str(report)),
823 "2 files reformatted, 3 files left unchanged, 2 files failed to"
826 self.assertEqual(report.return_code, 123)
829 unstyle(str(report)),
830 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
831 " would fail to reformat.",
836 unstyle(str(report)),
837 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
838 " would fail to reformat.",
841 def test_report_quiet(self) -> None:
842 report = black.Report(quiet=True)
846 def out(msg: str, **kwargs: Any) -> None:
847 out_lines.append(msg)
849 def err(msg: str, **kwargs: Any) -> None:
850 err_lines.append(msg)
852 with patch("black.out", out), patch("black.err", err):
853 report.done(Path("f1"), black.Changed.NO)
854 self.assertEqual(len(out_lines), 0)
855 self.assertEqual(len(err_lines), 0)
856 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
857 self.assertEqual(report.return_code, 0)
858 report.done(Path("f2"), black.Changed.YES)
859 self.assertEqual(len(out_lines), 0)
860 self.assertEqual(len(err_lines), 0)
862 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
864 report.done(Path("f3"), black.Changed.CACHED)
865 self.assertEqual(len(out_lines), 0)
866 self.assertEqual(len(err_lines), 0)
868 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
870 self.assertEqual(report.return_code, 0)
872 self.assertEqual(report.return_code, 1)
874 report.failed(Path("e1"), "boom")
875 self.assertEqual(len(out_lines), 0)
876 self.assertEqual(len(err_lines), 1)
877 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
879 unstyle(str(report)),
880 "1 file reformatted, 2 files left unchanged, 1 file failed to"
883 self.assertEqual(report.return_code, 123)
884 report.done(Path("f3"), black.Changed.YES)
885 self.assertEqual(len(out_lines), 0)
886 self.assertEqual(len(err_lines), 1)
888 unstyle(str(report)),
889 "2 files reformatted, 2 files left unchanged, 1 file failed to"
892 self.assertEqual(report.return_code, 123)
893 report.failed(Path("e2"), "boom")
894 self.assertEqual(len(out_lines), 0)
895 self.assertEqual(len(err_lines), 2)
896 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
898 unstyle(str(report)),
899 "2 files reformatted, 2 files left unchanged, 2 files failed to"
902 self.assertEqual(report.return_code, 123)
903 report.path_ignored(Path("wat"), "no match")
904 self.assertEqual(len(out_lines), 0)
905 self.assertEqual(len(err_lines), 2)
907 unstyle(str(report)),
908 "2 files reformatted, 2 files left unchanged, 2 files failed to"
911 self.assertEqual(report.return_code, 123)
912 report.done(Path("f4"), black.Changed.NO)
913 self.assertEqual(len(out_lines), 0)
914 self.assertEqual(len(err_lines), 2)
916 unstyle(str(report)),
917 "2 files reformatted, 3 files left unchanged, 2 files failed to"
920 self.assertEqual(report.return_code, 123)
923 unstyle(str(report)),
924 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
925 " would fail to reformat.",
930 unstyle(str(report)),
931 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
932 " would fail to reformat.",
935 def test_report_normal(self) -> None:
936 report = black.Report()
940 def out(msg: str, **kwargs: Any) -> None:
941 out_lines.append(msg)
943 def err(msg: str, **kwargs: Any) -> None:
944 err_lines.append(msg)
946 with patch("black.out", out), patch("black.err", err):
947 report.done(Path("f1"), black.Changed.NO)
948 self.assertEqual(len(out_lines), 0)
949 self.assertEqual(len(err_lines), 0)
950 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
951 self.assertEqual(report.return_code, 0)
952 report.done(Path("f2"), black.Changed.YES)
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, 1 file left unchanged."
959 report.done(Path("f3"), black.Changed.CACHED)
960 self.assertEqual(len(out_lines), 1)
961 self.assertEqual(len(err_lines), 0)
962 self.assertEqual(out_lines[-1], "reformatted f2")
964 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
966 self.assertEqual(report.return_code, 0)
968 self.assertEqual(report.return_code, 1)
970 report.failed(Path("e1"), "boom")
971 self.assertEqual(len(out_lines), 1)
972 self.assertEqual(len(err_lines), 1)
973 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
975 unstyle(str(report)),
976 "1 file reformatted, 2 files left unchanged, 1 file failed to"
979 self.assertEqual(report.return_code, 123)
980 report.done(Path("f3"), black.Changed.YES)
981 self.assertEqual(len(out_lines), 2)
982 self.assertEqual(len(err_lines), 1)
983 self.assertEqual(out_lines[-1], "reformatted f3")
985 unstyle(str(report)),
986 "2 files reformatted, 2 files left unchanged, 1 file failed to"
989 self.assertEqual(report.return_code, 123)
990 report.failed(Path("e2"), "boom")
991 self.assertEqual(len(out_lines), 2)
992 self.assertEqual(len(err_lines), 2)
993 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
995 unstyle(str(report)),
996 "2 files reformatted, 2 files left unchanged, 2 files failed to"
999 self.assertEqual(report.return_code, 123)
1000 report.path_ignored(Path("wat"), "no match")
1001 self.assertEqual(len(out_lines), 2)
1002 self.assertEqual(len(err_lines), 2)
1004 unstyle(str(report)),
1005 "2 files reformatted, 2 files left unchanged, 2 files failed to"
1008 self.assertEqual(report.return_code, 123)
1009 report.done(Path("f4"), black.Changed.NO)
1010 self.assertEqual(len(out_lines), 2)
1011 self.assertEqual(len(err_lines), 2)
1013 unstyle(str(report)),
1014 "2 files reformatted, 3 files left unchanged, 2 files failed to"
1017 self.assertEqual(report.return_code, 123)
1020 unstyle(str(report)),
1021 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1022 " would fail to reformat.",
1024 report.check = False
1027 unstyle(str(report)),
1028 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1029 " would fail to reformat.",
1032 def test_lib2to3_parse(self) -> None:
1033 with self.assertRaises(black.InvalidInput):
1034 black.lib2to3_parse("invalid syntax")
1036 straddling = "x + y"
1037 black.lib2to3_parse(straddling)
1038 black.lib2to3_parse(straddling, {TargetVersion.PY27})
1039 black.lib2to3_parse(straddling, {TargetVersion.PY36})
1040 black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
1042 py2_only = "print x"
1043 black.lib2to3_parse(py2_only)
1044 black.lib2to3_parse(py2_only, {TargetVersion.PY27})
1045 with self.assertRaises(black.InvalidInput):
1046 black.lib2to3_parse(py2_only, {TargetVersion.PY36})
1047 with self.assertRaises(black.InvalidInput):
1048 black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
1050 py3_only = "exec(x, end=y)"
1051 black.lib2to3_parse(py3_only)
1052 with self.assertRaises(black.InvalidInput):
1053 black.lib2to3_parse(py3_only, {TargetVersion.PY27})
1054 black.lib2to3_parse(py3_only, {TargetVersion.PY36})
1055 black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
1057 def test_get_features_used(self) -> None:
1058 node = black.lib2to3_parse("def f(*, arg): ...\n")
1059 self.assertEqual(black.get_features_used(node), set())
1060 node = black.lib2to3_parse("def f(*, arg,): ...\n")
1061 self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
1062 node = black.lib2to3_parse("f(*arg,)\n")
1064 black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
1066 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
1067 self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
1068 node = black.lib2to3_parse("123_456\n")
1069 self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
1070 node = black.lib2to3_parse("123456\n")
1071 self.assertEqual(black.get_features_used(node), set())
1072 source, expected = read_data("function")
1073 node = black.lib2to3_parse(source)
1074 expected_features = {
1075 Feature.TRAILING_COMMA_IN_CALL,
1076 Feature.TRAILING_COMMA_IN_DEF,
1079 self.assertEqual(black.get_features_used(node), expected_features)
1080 node = black.lib2to3_parse(expected)
1081 self.assertEqual(black.get_features_used(node), expected_features)
1082 source, expected = read_data("expression")
1083 node = black.lib2to3_parse(source)
1084 self.assertEqual(black.get_features_used(node), set())
1085 node = black.lib2to3_parse(expected)
1086 self.assertEqual(black.get_features_used(node), set())
1088 def test_get_future_imports(self) -> None:
1089 node = black.lib2to3_parse("\n")
1090 self.assertEqual(set(), black.get_future_imports(node))
1091 node = black.lib2to3_parse("from __future__ import black\n")
1092 self.assertEqual({"black"}, black.get_future_imports(node))
1093 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
1094 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1095 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
1096 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
1097 node = black.lib2to3_parse(
1098 "from __future__ import multiple\nfrom __future__ import imports\n"
1100 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1101 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
1102 self.assertEqual({"black"}, black.get_future_imports(node))
1103 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
1104 self.assertEqual({"black"}, black.get_future_imports(node))
1105 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
1106 self.assertEqual(set(), black.get_future_imports(node))
1107 node = black.lib2to3_parse("from some.module import black\n")
1108 self.assertEqual(set(), black.get_future_imports(node))
1109 node = black.lib2to3_parse(
1110 "from __future__ import unicode_literals as _unicode_literals"
1112 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
1113 node = black.lib2to3_parse(
1114 "from __future__ import unicode_literals as _lol, print"
1116 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
1118 def test_debug_visitor(self) -> None:
1119 source, _ = read_data("debug_visitor.py")
1120 expected, _ = read_data("debug_visitor.out")
1124 def out(msg: str, **kwargs: Any) -> None:
1125 out_lines.append(msg)
1127 def err(msg: str, **kwargs: Any) -> None:
1128 err_lines.append(msg)
1130 with patch("black.out", out), patch("black.err", err):
1131 black.DebugVisitor.show(source)
1132 actual = "\n".join(out_lines) + "\n"
1134 if expected != actual:
1135 log_name = black.dump_to_file(*out_lines)
1139 f"AST print out is different. Actual version dumped to {log_name}",
1142 def test_format_file_contents(self) -> None:
1144 mode = black.FileMode()
1145 with self.assertRaises(black.NothingChanged):
1146 black.format_file_contents(empty, mode=mode, fast=False)
1148 with self.assertRaises(black.NothingChanged):
1149 black.format_file_contents(just_nl, mode=mode, fast=False)
1150 same = "j = [1, 2, 3]\n"
1151 with self.assertRaises(black.NothingChanged):
1152 black.format_file_contents(same, mode=mode, fast=False)
1153 different = "j = [1,2,3]"
1155 actual = black.format_file_contents(different, mode=mode, fast=False)
1156 self.assertEqual(expected, actual)
1157 invalid = "return if you can"
1158 with self.assertRaises(black.InvalidInput) as e:
1159 black.format_file_contents(invalid, mode=mode, fast=False)
1160 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
1162 def test_endmarker(self) -> None:
1163 n = black.lib2to3_parse("\n")
1164 self.assertEqual(n.type, black.syms.file_input)
1165 self.assertEqual(len(n.children), 1)
1166 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
1168 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
1169 def test_assertFormatEqual(self) -> None:
1173 def out(msg: str, **kwargs: Any) -> None:
1174 out_lines.append(msg)
1176 def err(msg: str, **kwargs: Any) -> None:
1177 err_lines.append(msg)
1179 with patch("black.out", out), patch("black.err", err):
1180 with self.assertRaises(AssertionError):
1181 self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")
1183 out_str = "".join(out_lines)
1184 self.assertTrue("Expected tree:" in out_str)
1185 self.assertTrue("Actual tree:" in out_str)
1186 self.assertEqual("".join(err_lines), "")
1188 def test_cache_broken_file(self) -> None:
1189 mode = black.FileMode()
1190 with cache_dir() as workspace:
1191 cache_file = black.get_cache_file(mode)
1192 with cache_file.open("w") as fobj:
1193 fobj.write("this is not a pickle")
1194 self.assertEqual(black.read_cache(mode), {})
1195 src = (workspace / "test.py").resolve()
1196 with src.open("w") as fobj:
1197 fobj.write("print('hello')")
1198 self.invokeBlack([str(src)])
1199 cache = black.read_cache(mode)
1200 self.assertIn(src, cache)
1202 def test_cache_single_file_already_cached(self) -> None:
1203 mode = black.FileMode()
1204 with cache_dir() as workspace:
1205 src = (workspace / "test.py").resolve()
1206 with src.open("w") as fobj:
1207 fobj.write("print('hello')")
1208 black.write_cache({}, [src], mode)
1209 self.invokeBlack([str(src)])
1210 with src.open("r") as fobj:
1211 self.assertEqual(fobj.read(), "print('hello')")
1213 @event_loop(close=False)
1214 def test_cache_multiple_files(self) -> None:
1215 mode = black.FileMode()
1216 with cache_dir() as workspace, patch(
1217 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1219 one = (workspace / "one.py").resolve()
1220 with one.open("w") as fobj:
1221 fobj.write("print('hello')")
1222 two = (workspace / "two.py").resolve()
1223 with two.open("w") as fobj:
1224 fobj.write("print('hello')")
1225 black.write_cache({}, [one], mode)
1226 self.invokeBlack([str(workspace)])
1227 with one.open("r") as fobj:
1228 self.assertEqual(fobj.read(), "print('hello')")
1229 with two.open("r") as fobj:
1230 self.assertEqual(fobj.read(), 'print("hello")\n')
1231 cache = black.read_cache(mode)
1232 self.assertIn(one, cache)
1233 self.assertIn(two, cache)
1235 def test_no_cache_when_writeback_diff(self) -> None:
1236 mode = black.FileMode()
1237 with cache_dir() as workspace:
1238 src = (workspace / "test.py").resolve()
1239 with src.open("w") as fobj:
1240 fobj.write("print('hello')")
1241 self.invokeBlack([str(src), "--diff"])
1242 cache_file = black.get_cache_file(mode)
1243 self.assertFalse(cache_file.exists())
1245 def test_no_cache_when_stdin(self) -> None:
1246 mode = black.FileMode()
1248 result = CliRunner().invoke(
1249 black.main, ["-"], input=BytesIO(b"print('hello')")
1251 self.assertEqual(result.exit_code, 0)
1252 cache_file = black.get_cache_file(mode)
1253 self.assertFalse(cache_file.exists())
1255 def test_read_cache_no_cachefile(self) -> None:
1256 mode = black.FileMode()
1258 self.assertEqual(black.read_cache(mode), {})
1260 def test_write_cache_read_cache(self) -> None:
1261 mode = black.FileMode()
1262 with cache_dir() as workspace:
1263 src = (workspace / "test.py").resolve()
1265 black.write_cache({}, [src], mode)
1266 cache = black.read_cache(mode)
1267 self.assertIn(src, cache)
1268 self.assertEqual(cache[src], black.get_cache_info(src))
1270 def test_filter_cached(self) -> None:
1271 with TemporaryDirectory() as workspace:
1272 path = Path(workspace)
1273 uncached = (path / "uncached").resolve()
1274 cached = (path / "cached").resolve()
1275 cached_but_changed = (path / "changed").resolve()
1278 cached_but_changed.touch()
1279 cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
1280 todo, done = black.filter_cached(
1281 cache, {uncached, cached, cached_but_changed}
1283 self.assertEqual(todo, {uncached, cached_but_changed})
1284 self.assertEqual(done, {cached})
1286 def test_write_cache_creates_directory_if_needed(self) -> None:
1287 mode = black.FileMode()
1288 with cache_dir(exists=False) as workspace:
1289 self.assertFalse(workspace.exists())
1290 black.write_cache({}, [], mode)
1291 self.assertTrue(workspace.exists())
1293 @event_loop(close=False)
1294 def test_failed_formatting_does_not_get_cached(self) -> None:
1295 mode = black.FileMode()
1296 with cache_dir() as workspace, patch(
1297 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1299 failing = (workspace / "failing.py").resolve()
1300 with failing.open("w") as fobj:
1301 fobj.write("not actually python")
1302 clean = (workspace / "clean.py").resolve()
1303 with clean.open("w") as fobj:
1304 fobj.write('print("hello")\n')
1305 self.invokeBlack([str(workspace)], exit_code=123)
1306 cache = black.read_cache(mode)
1307 self.assertNotIn(failing, cache)
1308 self.assertIn(clean, cache)
1310 def test_write_cache_write_fail(self) -> None:
1311 mode = black.FileMode()
1312 with cache_dir(), patch.object(Path, "open") as mock:
1313 mock.side_effect = OSError
1314 black.write_cache({}, [], mode)
1316 @event_loop(close=False)
1317 def test_check_diff_use_together(self) -> None:
1319 # Files which will be reformatted.
1320 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1321 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1322 # Files which will not be reformatted.
1323 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1324 self.invokeBlack([str(src2), "--diff", "--check"])
1325 # Multi file command.
1326 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1328 def test_no_files(self) -> None:
1330 # Without an argument, black exits with error code 0.
1331 self.invokeBlack([])
1333 def test_broken_symlink(self) -> None:
1334 with cache_dir() as workspace:
1335 symlink = workspace / "broken_link.py"
1337 symlink.symlink_to("nonexistent.py")
1338 except OSError as e:
1339 self.skipTest(f"Can't create symlinks: {e}")
1340 self.invokeBlack([str(workspace.resolve())])
1342 def test_read_cache_line_lengths(self) -> None:
1343 mode = black.FileMode()
1344 short_mode = black.FileMode(line_length=1)
1345 with cache_dir() as workspace:
1346 path = (workspace / "file.py").resolve()
1348 black.write_cache({}, [path], mode)
1349 one = black.read_cache(mode)
1350 self.assertIn(path, one)
1351 two = black.read_cache(short_mode)
1352 self.assertNotIn(path, two)
1354 def test_tricky_unicode_symbols(self) -> None:
1355 source, expected = read_data("tricky_unicode_symbols")
1357 self.assertFormatEqual(expected, actual)
1358 black.assert_equivalent(source, actual)
1359 black.assert_stable(source, actual, black.FileMode())
1361 def test_single_file_force_pyi(self) -> None:
1362 reg_mode = black.FileMode()
1363 pyi_mode = black.FileMode(is_pyi=True)
1364 contents, expected = read_data("force_pyi")
1365 with cache_dir() as workspace:
1366 path = (workspace / "file.py").resolve()
1367 with open(path, "w") as fh:
1369 self.invokeBlack([str(path), "--pyi"])
1370 with open(path, "r") as fh:
1372 # verify cache with --pyi is separate
1373 pyi_cache = black.read_cache(pyi_mode)
1374 self.assertIn(path, pyi_cache)
1375 normal_cache = black.read_cache(reg_mode)
1376 self.assertNotIn(path, normal_cache)
1377 self.assertEqual(actual, expected)
1379 @event_loop(close=False)
1380 def test_multi_file_force_pyi(self) -> None:
1381 reg_mode = black.FileMode()
1382 pyi_mode = black.FileMode(is_pyi=True)
1383 contents, expected = read_data("force_pyi")
1384 with cache_dir() as workspace:
1386 (workspace / "file1.py").resolve(),
1387 (workspace / "file2.py").resolve(),
1390 with open(path, "w") as fh:
1392 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1394 with open(path, "r") as fh:
1396 self.assertEqual(actual, expected)
1397 # verify cache with --pyi is separate
1398 pyi_cache = black.read_cache(pyi_mode)
1399 normal_cache = black.read_cache(reg_mode)
1401 self.assertIn(path, pyi_cache)
1402 self.assertNotIn(path, normal_cache)
1404 def test_pipe_force_pyi(self) -> None:
1405 source, expected = read_data("force_pyi")
1406 result = CliRunner().invoke(
1407 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1409 self.assertEqual(result.exit_code, 0)
1410 actual = result.output
1411 self.assertFormatEqual(actual, expected)
1413 def test_single_file_force_py36(self) -> None:
1414 reg_mode = black.FileMode()
1415 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1416 source, expected = read_data("force_py36")
1417 with cache_dir() as workspace:
1418 path = (workspace / "file.py").resolve()
1419 with open(path, "w") as fh:
1421 self.invokeBlack([str(path), *PY36_ARGS])
1422 with open(path, "r") as fh:
1424 # verify cache with --target-version is separate
1425 py36_cache = black.read_cache(py36_mode)
1426 self.assertIn(path, py36_cache)
1427 normal_cache = black.read_cache(reg_mode)
1428 self.assertNotIn(path, normal_cache)
1429 self.assertEqual(actual, expected)
1431 @event_loop(close=False)
1432 def test_multi_file_force_py36(self) -> None:
1433 reg_mode = black.FileMode()
1434 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1435 source, expected = read_data("force_py36")
1436 with cache_dir() as workspace:
1438 (workspace / "file1.py").resolve(),
1439 (workspace / "file2.py").resolve(),
1442 with open(path, "w") as fh:
1444 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1446 with open(path, "r") as fh:
1448 self.assertEqual(actual, expected)
1449 # verify cache with --target-version is separate
1450 pyi_cache = black.read_cache(py36_mode)
1451 normal_cache = black.read_cache(reg_mode)
1453 self.assertIn(path, pyi_cache)
1454 self.assertNotIn(path, normal_cache)
1456 def test_collections(self) -> None:
1457 source, expected = read_data("collections")
1459 self.assertFormatEqual(expected, actual)
1460 black.assert_equivalent(source, actual)
1461 black.assert_stable(source, actual, black.FileMode())
1463 def test_pipe_force_py36(self) -> None:
1464 source, expected = read_data("force_py36")
1465 result = CliRunner().invoke(
1467 ["-", "-q", "--target-version=py36"],
1468 input=BytesIO(source.encode("utf8")),
1470 self.assertEqual(result.exit_code, 0)
1471 actual = result.output
1472 self.assertFormatEqual(actual, expected)
1474 def test_include_exclude(self) -> None:
1475 path = THIS_DIR / "data" / "include_exclude_tests"
1476 include = re.compile(r"\.pyi?$")
1477 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1478 report = black.Report()
1479 gitignore = PathSpec.from_lines("gitwildmatch", [])
1480 sources: List[Path] = []
1482 Path(path / "b/dont_exclude/a.py"),
1483 Path(path / "b/dont_exclude/a.pyi"),
1485 this_abs = THIS_DIR.resolve()
1487 black.gen_python_files_in_dir(
1488 path, this_abs, include, exclude, report, gitignore
1491 self.assertEqual(sorted(expected), sorted(sources))
1493 def test_gitignore_exclude(self) -> None:
1494 path = THIS_DIR / "data" / "include_exclude_tests"
1495 include = re.compile(r"\.pyi?$")
1496 exclude = re.compile(r"")
1497 report = black.Report()
1498 gitignore = PathSpec.from_lines(
1499 "gitwildmatch", ["exclude/", ".definitely_exclude"]
1501 sources: List[Path] = []
1503 Path(path / "b/dont_exclude/a.py"),
1504 Path(path / "b/dont_exclude/a.pyi"),
1506 this_abs = THIS_DIR.resolve()
1508 black.gen_python_files_in_dir(
1509 path, this_abs, include, exclude, report, gitignore
1512 self.assertEqual(sorted(expected), sorted(sources))
1514 def test_empty_include(self) -> None:
1515 path = THIS_DIR / "data" / "include_exclude_tests"
1516 report = black.Report()
1517 gitignore = PathSpec.from_lines("gitwildmatch", [])
1518 empty = re.compile(r"")
1519 sources: List[Path] = []
1521 Path(path / "b/exclude/a.pie"),
1522 Path(path / "b/exclude/a.py"),
1523 Path(path / "b/exclude/a.pyi"),
1524 Path(path / "b/dont_exclude/a.pie"),
1525 Path(path / "b/dont_exclude/a.py"),
1526 Path(path / "b/dont_exclude/a.pyi"),
1527 Path(path / "b/.definitely_exclude/a.pie"),
1528 Path(path / "b/.definitely_exclude/a.py"),
1529 Path(path / "b/.definitely_exclude/a.pyi"),
1531 this_abs = THIS_DIR.resolve()
1533 black.gen_python_files_in_dir(
1537 re.compile(black.DEFAULT_EXCLUDES),
1542 self.assertEqual(sorted(expected), sorted(sources))
1544 def test_empty_exclude(self) -> None:
1545 path = THIS_DIR / "data" / "include_exclude_tests"
1546 report = black.Report()
1547 gitignore = PathSpec.from_lines("gitwildmatch", [])
1548 empty = re.compile(r"")
1549 sources: List[Path] = []
1551 Path(path / "b/dont_exclude/a.py"),
1552 Path(path / "b/dont_exclude/a.pyi"),
1553 Path(path / "b/exclude/a.py"),
1554 Path(path / "b/exclude/a.pyi"),
1555 Path(path / "b/.definitely_exclude/a.py"),
1556 Path(path / "b/.definitely_exclude/a.pyi"),
1558 this_abs = THIS_DIR.resolve()
1560 black.gen_python_files_in_dir(
1563 re.compile(black.DEFAULT_INCLUDES),
1569 self.assertEqual(sorted(expected), sorted(sources))
1571 def test_invalid_include_exclude(self) -> None:
1572 for option in ["--include", "--exclude"]:
1573 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1575 def test_preserves_line_endings(self) -> None:
1576 with TemporaryDirectory() as workspace:
1577 test_file = Path(workspace) / "test.py"
1578 for nl in ["\n", "\r\n"]:
1579 contents = nl.join(["def f( ):", " pass"])
1580 test_file.write_bytes(contents.encode())
1581 ff(test_file, write_back=black.WriteBack.YES)
1582 updated_contents: bytes = test_file.read_bytes()
1583 self.assertIn(nl.encode(), updated_contents)
1585 self.assertNotIn(b"\r\n", updated_contents)
1587 def test_preserves_line_endings_via_stdin(self) -> None:
1588 for nl in ["\n", "\r\n"]:
1589 contents = nl.join(["def f( ):", " pass"])
1590 runner = BlackRunner()
1591 result = runner.invoke(
1592 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1594 self.assertEqual(result.exit_code, 0)
1595 output = runner.stdout_bytes
1596 self.assertIn(nl.encode("utf8"), output)
1598 self.assertNotIn(b"\r\n", output)
1600 def test_assert_equivalent_different_asts(self) -> None:
1601 with self.assertRaises(AssertionError):
1602 black.assert_equivalent("{}", "None")
1604 def test_symlink_out_of_root_directory(self) -> None:
1608 include = re.compile(black.DEFAULT_INCLUDES)
1609 exclude = re.compile(black.DEFAULT_EXCLUDES)
1610 report = black.Report()
1611 gitignore = PathSpec.from_lines("gitwildmatch", [])
1612 # `child` should behave like a symlink which resolved path is clearly
1613 # outside of the `root` directory.
1614 path.iterdir.return_value = [child]
1615 child.resolve.return_value = Path("/a/b/c")
1616 child.as_posix.return_value = "/a/b/c"
1617 child.is_symlink.return_value = True
1620 black.gen_python_files_in_dir(
1621 path, root, include, exclude, report, gitignore
1624 except ValueError as ve:
1625 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1626 path.iterdir.assert_called_once()
1627 child.resolve.assert_called_once()
1628 child.is_symlink.assert_called_once()
1629 # `child` should behave like a strange file which resolved path is clearly
1630 # outside of the `root` directory.
1631 child.is_symlink.return_value = False
1632 with self.assertRaises(ValueError):
1634 black.gen_python_files_in_dir(
1635 path, root, include, exclude, report, gitignore
1638 path.iterdir.assert_called()
1639 self.assertEqual(path.iterdir.call_count, 2)
1640 child.resolve.assert_called()
1641 self.assertEqual(child.resolve.call_count, 2)
1642 child.is_symlink.assert_called()
1643 self.assertEqual(child.is_symlink.call_count, 2)
1645 def test_shhh_click(self) -> None:
1647 from click import _unicodefun # type: ignore
1648 except ModuleNotFoundError:
1649 self.skipTest("Incompatible Click version")
1650 if not hasattr(_unicodefun, "_verify_python3_env"):
1651 self.skipTest("Incompatible Click version")
1652 # First, let's see if Click is crashing with a preferred ASCII charset.
1653 with patch("locale.getpreferredencoding") as gpe:
1654 gpe.return_value = "ASCII"
1655 with self.assertRaises(RuntimeError):
1656 _unicodefun._verify_python3_env()
1657 # Now, let's silence Click...
1659 # ...and confirm it's silent.
1660 with patch("locale.getpreferredencoding") as gpe:
1661 gpe.return_value = "ASCII"
1663 _unicodefun._verify_python3_env()
1664 except RuntimeError as re:
1665 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1667 def test_root_logger_not_used_directly(self) -> None:
1668 def fail(*args: Any, **kwargs: Any) -> None:
1669 self.fail("Record created with root logger")
1671 with patch.multiple(
1682 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1683 def test_blackd_main(self) -> None:
1684 with patch("blackd.web.run_app"):
1685 result = CliRunner().invoke(blackd.main, [])
1686 if result.exception is not None:
1687 raise result.exception
1688 self.assertEqual(result.exit_code, 0)
1690 def test_invalid_config_return_code(self) -> None:
1691 tmp_file = Path(black.dump_to_file())
1693 tmp_config = Path(black.dump_to_file())
1695 args = ["--config", str(tmp_config), str(tmp_file)]
1696 self.invokeBlack(args, exit_code=2, ignore_config=False)
1701 class BlackDTestCase(AioHTTPTestCase):
1702 async def get_application(self) -> web.Application:
1703 return blackd.make_app()
1705 # TODO: remove these decorators once the below is released
1706 # https://github.com/aio-libs/aiohttp/pull/3727
1707 @skip_if_exception("ClientOSError")
1708 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1710 async def test_blackd_request_needs_formatting(self) -> None:
1711 response = await self.client.post("/", data=b"print('hello world')")
1712 self.assertEqual(response.status, 200)
1713 self.assertEqual(response.charset, "utf8")
1714 self.assertEqual(await response.read(), b'print("hello world")\n')
1716 @skip_if_exception("ClientOSError")
1717 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1719 async def test_blackd_request_no_change(self) -> None:
1720 response = await self.client.post("/", data=b'print("hello world")\n')
1721 self.assertEqual(response.status, 204)
1722 self.assertEqual(await response.read(), b"")
1724 @skip_if_exception("ClientOSError")
1725 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1727 async def test_blackd_request_syntax_error(self) -> None:
1728 response = await self.client.post("/", data=b"what even ( is")
1729 self.assertEqual(response.status, 400)
1730 content = await response.text()
1732 content.startswith("Cannot parse"),
1733 msg=f"Expected error to start with 'Cannot parse', got {repr(content)}",
1736 @skip_if_exception("ClientOSError")
1737 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1739 async def test_blackd_unsupported_version(self) -> None:
1740 response = await self.client.post(
1741 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "2"}
1743 self.assertEqual(response.status, 501)
1745 @skip_if_exception("ClientOSError")
1746 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1748 async def test_blackd_supported_version(self) -> None:
1749 response = await self.client.post(
1750 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "1"}
1752 self.assertEqual(response.status, 200)
1754 @skip_if_exception("ClientOSError")
1755 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1757 async def test_blackd_invalid_python_variant(self) -> None:
1758 async def check(header_value: str, expected_status: int = 400) -> None:
1759 response = await self.client.post(
1760 "/", data=b"what", headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1762 self.assertEqual(response.status, expected_status)
1765 await check("ruby3.5")
1766 await check("pyi3.6")
1767 await check("py1.5")
1769 await check("py2.8")
1771 await check("pypy3.0")
1772 await check("jython3.4")
1774 @skip_if_exception("ClientOSError")
1775 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1777 async def test_blackd_pyi(self) -> None:
1778 source, expected = read_data("stub.pyi")
1779 response = await self.client.post(
1780 "/", data=source, headers={blackd.PYTHON_VARIANT_HEADER: "pyi"}
1782 self.assertEqual(response.status, 200)
1783 self.assertEqual(await response.text(), expected)
1785 @skip_if_exception("ClientOSError")
1786 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1788 async def test_blackd_diff(self) -> None:
1789 diff_header = re.compile(
1790 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"
1793 source, _ = read_data("blackd_diff.py")
1794 expected, _ = read_data("blackd_diff.diff")
1796 response = await self.client.post(
1797 "/", data=source, headers={blackd.DIFF_HEADER: "true"}
1799 self.assertEqual(response.status, 200)
1801 actual = await response.text()
1802 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
1803 self.assertEqual(actual, expected)
1805 @skip_if_exception("ClientOSError")
1806 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1808 async def test_blackd_python_variant(self) -> None:
1811 " and_has_a_bunch_of,\n"
1812 " very_long_arguments_too,\n"
1813 " and_lots_of_them_as_well_lol,\n"
1814 " **and_very_long_keyword_arguments\n"
1819 async def check(header_value: str, expected_status: int) -> None:
1820 response = await self.client.post(
1821 "/", data=code, headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1824 response.status, expected_status, msg=await response.text()
1827 await check("3.6", 200)
1828 await check("py3.6", 200)
1829 await check("3.6,3.7", 200)
1830 await check("3.6,py3.7", 200)
1831 await check("py36,py37", 200)
1832 await check("36", 200)
1833 await check("3.6.4", 200)
1835 await check("2", 204)
1836 await check("2.7", 204)
1837 await check("py2.7", 204)
1838 await check("3.4", 204)
1839 await check("py3.4", 204)
1840 await check("py34,py36", 204)
1841 await check("34", 204)
1843 @skip_if_exception("ClientOSError")
1844 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1846 async def test_blackd_line_length(self) -> None:
1847 response = await self.client.post(
1848 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "7"}
1850 self.assertEqual(response.status, 200)
1852 @skip_if_exception("ClientOSError")
1853 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1855 async def test_blackd_invalid_line_length(self) -> None:
1856 response = await self.client.post(
1857 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "NaN"}
1859 self.assertEqual(response.status, 400)
1861 @skip_if_exception("ClientOSError")
1862 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1864 async def test_blackd_response_black_version_header(self) -> None:
1865 response = await self.client.post("/")
1866 self.assertIsNotNone(response.headers.get(blackd.BLACK_VERSION_HEADER))
1869 if __name__ == "__main__":
1870 unittest.main(module="test_black")