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 "
247 rf"\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 rf"\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 f"Expected diff isn't equal to the actual. If you made changes "
350 f"to expression.py and this is an anticipated difference, "
351 f"overwrite 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, 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_slices(self) -> None:
395 source, expected = read_data("slices")
397 self.assertFormatEqual(expected, actual)
398 black.assert_equivalent(source, actual)
399 black.assert_stable(source, actual, black.FileMode())
401 @patch("black.dump_to_file", dump_to_stderr)
402 def test_comments(self) -> None:
403 source, expected = read_data("comments")
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_comments2(self) -> None:
411 source, expected = read_data("comments2")
413 self.assertFormatEqual(expected, actual)
414 black.assert_equivalent(source, actual)
415 black.assert_stable(source, actual, black.FileMode())
417 @patch("black.dump_to_file", dump_to_stderr)
418 def test_comments3(self) -> None:
419 source, expected = read_data("comments3")
421 self.assertFormatEqual(expected, actual)
422 black.assert_equivalent(source, actual)
423 black.assert_stable(source, actual, black.FileMode())
425 @patch("black.dump_to_file", dump_to_stderr)
426 def test_comments4(self) -> None:
427 source, expected = read_data("comments4")
429 self.assertFormatEqual(expected, actual)
430 black.assert_equivalent(source, actual)
431 black.assert_stable(source, actual, black.FileMode())
433 @patch("black.dump_to_file", dump_to_stderr)
434 def test_comments5(self) -> None:
435 source, expected = read_data("comments5")
437 self.assertFormatEqual(expected, actual)
438 black.assert_equivalent(source, actual)
439 black.assert_stable(source, actual, black.FileMode())
441 @patch("black.dump_to_file", dump_to_stderr)
442 def test_comments6(self) -> None:
443 source, expected = read_data("comments6")
445 self.assertFormatEqual(expected, actual)
446 black.assert_equivalent(source, actual)
447 black.assert_stable(source, actual, black.FileMode())
449 @patch("black.dump_to_file", dump_to_stderr)
450 def test_comments7(self) -> None:
451 source, expected = read_data("comments7")
453 self.assertFormatEqual(expected, actual)
454 black.assert_equivalent(source, actual)
455 black.assert_stable(source, actual, black.FileMode())
457 @patch("black.dump_to_file", dump_to_stderr)
458 def test_comment_after_escaped_newline(self) -> None:
459 source, expected = read_data("comment_after_escaped_newline")
461 self.assertFormatEqual(expected, actual)
462 black.assert_equivalent(source, actual)
463 black.assert_stable(source, actual, black.FileMode())
465 @patch("black.dump_to_file", dump_to_stderr)
466 def test_cantfit(self) -> None:
467 source, expected = read_data("cantfit")
469 self.assertFormatEqual(expected, actual)
470 black.assert_equivalent(source, actual)
471 black.assert_stable(source, actual, black.FileMode())
473 @patch("black.dump_to_file", dump_to_stderr)
474 def test_import_spacing(self) -> None:
475 source, expected = read_data("import_spacing")
477 self.assertFormatEqual(expected, actual)
478 black.assert_equivalent(source, actual)
479 black.assert_stable(source, actual, black.FileMode())
481 @patch("black.dump_to_file", dump_to_stderr)
482 def test_composition(self) -> None:
483 source, expected = read_data("composition")
485 self.assertFormatEqual(expected, actual)
486 black.assert_equivalent(source, actual)
487 black.assert_stable(source, actual, black.FileMode())
489 @patch("black.dump_to_file", dump_to_stderr)
490 def test_empty_lines(self) -> None:
491 source, expected = read_data("empty_lines")
493 self.assertFormatEqual(expected, actual)
494 black.assert_equivalent(source, actual)
495 black.assert_stable(source, actual, black.FileMode())
497 @patch("black.dump_to_file", dump_to_stderr)
498 def test_remove_parens(self) -> None:
499 source, expected = read_data("remove_parens")
501 self.assertFormatEqual(expected, actual)
502 black.assert_equivalent(source, actual)
503 black.assert_stable(source, actual, black.FileMode())
505 @patch("black.dump_to_file", dump_to_stderr)
506 def test_string_prefixes(self) -> None:
507 source, expected = read_data("string_prefixes")
509 self.assertFormatEqual(expected, actual)
510 black.assert_equivalent(source, actual)
511 black.assert_stable(source, actual, black.FileMode())
513 @patch("black.dump_to_file", dump_to_stderr)
514 def test_numeric_literals(self) -> None:
515 source, expected = read_data("numeric_literals")
516 mode = black.FileMode(target_versions=black.PY36_VERSIONS)
517 actual = fs(source, mode=mode)
518 self.assertFormatEqual(expected, actual)
519 black.assert_equivalent(source, actual)
520 black.assert_stable(source, actual, mode)
522 @patch("black.dump_to_file", dump_to_stderr)
523 def test_numeric_literals_ignoring_underscores(self) -> None:
524 source, expected = read_data("numeric_literals_skip_underscores")
525 mode = black.FileMode(target_versions=black.PY36_VERSIONS)
526 actual = fs(source, mode=mode)
527 self.assertFormatEqual(expected, actual)
528 black.assert_equivalent(source, actual)
529 black.assert_stable(source, actual, mode)
531 @patch("black.dump_to_file", dump_to_stderr)
532 def test_numeric_literals_py2(self) -> None:
533 source, expected = read_data("numeric_literals_py2")
535 self.assertFormatEqual(expected, actual)
536 black.assert_stable(source, actual, black.FileMode())
538 @patch("black.dump_to_file", dump_to_stderr)
539 def test_python2(self) -> None:
540 source, expected = read_data("python2")
542 self.assertFormatEqual(expected, actual)
543 black.assert_equivalent(source, actual)
544 black.assert_stable(source, actual, black.FileMode())
546 @patch("black.dump_to_file", dump_to_stderr)
547 def test_python2_print_function(self) -> None:
548 source, expected = read_data("python2_print_function")
549 mode = black.FileMode(target_versions={TargetVersion.PY27})
550 actual = fs(source, mode=mode)
551 self.assertFormatEqual(expected, actual)
552 black.assert_equivalent(source, actual)
553 black.assert_stable(source, actual, mode)
555 @patch("black.dump_to_file", dump_to_stderr)
556 def test_python2_unicode_literals(self) -> None:
557 source, expected = read_data("python2_unicode_literals")
559 self.assertFormatEqual(expected, actual)
560 black.assert_equivalent(source, actual)
561 black.assert_stable(source, actual, black.FileMode())
563 @patch("black.dump_to_file", dump_to_stderr)
564 def test_stub(self) -> None:
565 mode = black.FileMode(is_pyi=True)
566 source, expected = read_data("stub.pyi")
567 actual = fs(source, mode=mode)
568 self.assertFormatEqual(expected, actual)
569 black.assert_stable(source, actual, mode)
571 @patch("black.dump_to_file", dump_to_stderr)
572 def test_async_as_identifier(self) -> None:
573 source_path = (THIS_DIR / "data" / "async_as_identifier.py").resolve()
574 source, expected = read_data("async_as_identifier")
576 self.assertFormatEqual(expected, actual)
577 major, minor = sys.version_info[:2]
578 if major < 3 or (major <= 3 and minor < 7):
579 black.assert_equivalent(source, actual)
580 black.assert_stable(source, actual, black.FileMode())
581 # ensure black can parse this when the target is 3.6
582 self.invokeBlack([str(source_path), "--target-version", "py36"])
583 # but not on 3.7, because async/await is no longer an identifier
584 self.invokeBlack([str(source_path), "--target-version", "py37"], exit_code=123)
586 @patch("black.dump_to_file", dump_to_stderr)
587 def test_python37(self) -> None:
588 source_path = (THIS_DIR / "data" / "python37.py").resolve()
589 source, expected = read_data("python37")
591 self.assertFormatEqual(expected, actual)
592 major, minor = sys.version_info[:2]
593 if major > 3 or (major == 3 and minor >= 7):
594 black.assert_equivalent(source, actual)
595 black.assert_stable(source, actual, black.FileMode())
596 # ensure black can parse this when the target is 3.7
597 self.invokeBlack([str(source_path), "--target-version", "py37"])
598 # but not on 3.6, because we use async as a reserved keyword
599 self.invokeBlack([str(source_path), "--target-version", "py36"], exit_code=123)
601 @patch("black.dump_to_file", dump_to_stderr)
602 def test_python38(self) -> None:
603 source, expected = read_data("python38")
605 self.assertFormatEqual(expected, actual)
606 major, minor = sys.version_info[:2]
607 if major > 3 or (major == 3 and minor >= 8):
608 black.assert_equivalent(source, actual)
609 black.assert_stable(source, actual, black.FileMode())
611 @patch("black.dump_to_file", dump_to_stderr)
612 def test_fmtonoff(self) -> None:
613 source, expected = read_data("fmtonoff")
615 self.assertFormatEqual(expected, actual)
616 black.assert_equivalent(source, actual)
617 black.assert_stable(source, actual, black.FileMode())
619 @patch("black.dump_to_file", dump_to_stderr)
620 def test_fmtonoff2(self) -> None:
621 source, expected = read_data("fmtonoff2")
623 self.assertFormatEqual(expected, actual)
624 black.assert_equivalent(source, actual)
625 black.assert_stable(source, actual, black.FileMode())
627 @patch("black.dump_to_file", dump_to_stderr)
628 def test_fmtonoff3(self) -> None:
629 source, expected = read_data("fmtonoff3")
631 self.assertFormatEqual(expected, actual)
632 black.assert_equivalent(source, actual)
633 black.assert_stable(source, actual, black.FileMode())
635 @patch("black.dump_to_file", dump_to_stderr)
636 def test_fmtonoff4(self) -> None:
637 source, expected = read_data("fmtonoff4")
639 self.assertFormatEqual(expected, actual)
640 black.assert_equivalent(source, actual)
641 black.assert_stable(source, actual, black.FileMode())
643 @patch("black.dump_to_file", dump_to_stderr)
644 def test_remove_empty_parentheses_after_class(self) -> None:
645 source, expected = read_data("class_blank_parentheses")
647 self.assertFormatEqual(expected, actual)
648 black.assert_equivalent(source, actual)
649 black.assert_stable(source, actual, black.FileMode())
651 @patch("black.dump_to_file", dump_to_stderr)
652 def test_new_line_between_class_and_code(self) -> None:
653 source, expected = read_data("class_methods_new_line")
655 self.assertFormatEqual(expected, actual)
656 black.assert_equivalent(source, actual)
657 black.assert_stable(source, actual, black.FileMode())
659 @patch("black.dump_to_file", dump_to_stderr)
660 def test_bracket_match(self) -> None:
661 source, expected = read_data("bracketmatch")
663 self.assertFormatEqual(expected, actual)
664 black.assert_equivalent(source, actual)
665 black.assert_stable(source, actual, black.FileMode())
667 @patch("black.dump_to_file", dump_to_stderr)
668 def test_tuple_assign(self) -> None:
669 source, expected = read_data("tupleassign")
671 self.assertFormatEqual(expected, actual)
672 black.assert_equivalent(source, actual)
673 black.assert_stable(source, actual, black.FileMode())
675 @patch("black.dump_to_file", dump_to_stderr)
676 def test_beginning_backslash(self) -> None:
677 source, expected = read_data("beginning_backslash")
679 self.assertFormatEqual(expected, actual)
680 black.assert_equivalent(source, actual)
681 black.assert_stable(source, actual, black.FileMode())
683 def test_tab_comment_indentation(self) -> None:
684 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
685 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
686 self.assertFormatEqual(contents_spc, fs(contents_spc))
687 self.assertFormatEqual(contents_spc, fs(contents_tab))
689 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
690 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
691 self.assertFormatEqual(contents_spc, fs(contents_spc))
692 self.assertFormatEqual(contents_spc, fs(contents_tab))
694 # mixed tabs and spaces (valid Python 2 code)
695 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t# comment\n pass\n"
696 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
697 self.assertFormatEqual(contents_spc, fs(contents_spc))
698 self.assertFormatEqual(contents_spc, fs(contents_tab))
700 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t\t# comment\n pass\n"
701 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
702 self.assertFormatEqual(contents_spc, fs(contents_spc))
703 self.assertFormatEqual(contents_spc, fs(contents_tab))
705 def test_report_verbose(self) -> None:
706 report = black.Report(verbose=True)
710 def out(msg: str, **kwargs: Any) -> None:
711 out_lines.append(msg)
713 def err(msg: str, **kwargs: Any) -> None:
714 err_lines.append(msg)
716 with patch("black.out", out), patch("black.err", err):
717 report.done(Path("f1"), black.Changed.NO)
718 self.assertEqual(len(out_lines), 1)
719 self.assertEqual(len(err_lines), 0)
720 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
721 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
722 self.assertEqual(report.return_code, 0)
723 report.done(Path("f2"), black.Changed.YES)
724 self.assertEqual(len(out_lines), 2)
725 self.assertEqual(len(err_lines), 0)
726 self.assertEqual(out_lines[-1], "reformatted f2")
728 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
730 report.done(Path("f3"), black.Changed.CACHED)
731 self.assertEqual(len(out_lines), 3)
732 self.assertEqual(len(err_lines), 0)
734 out_lines[-1], "f3 wasn't modified on disk since last run."
737 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
739 self.assertEqual(report.return_code, 0)
741 self.assertEqual(report.return_code, 1)
743 report.failed(Path("e1"), "boom")
744 self.assertEqual(len(out_lines), 3)
745 self.assertEqual(len(err_lines), 1)
746 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
748 unstyle(str(report)),
749 "1 file reformatted, 2 files left unchanged, "
750 "1 file failed to reformat.",
752 self.assertEqual(report.return_code, 123)
753 report.done(Path("f3"), black.Changed.YES)
754 self.assertEqual(len(out_lines), 4)
755 self.assertEqual(len(err_lines), 1)
756 self.assertEqual(out_lines[-1], "reformatted f3")
758 unstyle(str(report)),
759 "2 files reformatted, 2 files left unchanged, "
760 "1 file failed to reformat.",
762 self.assertEqual(report.return_code, 123)
763 report.failed(Path("e2"), "boom")
764 self.assertEqual(len(out_lines), 4)
765 self.assertEqual(len(err_lines), 2)
766 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
768 unstyle(str(report)),
769 "2 files reformatted, 2 files left unchanged, "
770 "2 files failed to reformat.",
772 self.assertEqual(report.return_code, 123)
773 report.path_ignored(Path("wat"), "no match")
774 self.assertEqual(len(out_lines), 5)
775 self.assertEqual(len(err_lines), 2)
776 self.assertEqual(out_lines[-1], "wat ignored: no match")
778 unstyle(str(report)),
779 "2 files reformatted, 2 files left unchanged, "
780 "2 files failed to reformat.",
782 self.assertEqual(report.return_code, 123)
783 report.done(Path("f4"), black.Changed.NO)
784 self.assertEqual(len(out_lines), 6)
785 self.assertEqual(len(err_lines), 2)
786 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
788 unstyle(str(report)),
789 "2 files reformatted, 3 files left unchanged, "
790 "2 files failed to reformat.",
792 self.assertEqual(report.return_code, 123)
795 unstyle(str(report)),
796 "2 files would be reformatted, 3 files would be left unchanged, "
797 "2 files would fail to reformat.",
802 unstyle(str(report)),
803 "2 files would be reformatted, 3 files would be left unchanged, "
804 "2 files would fail to reformat.",
807 def test_report_quiet(self) -> None:
808 report = black.Report(quiet=True)
812 def out(msg: str, **kwargs: Any) -> None:
813 out_lines.append(msg)
815 def err(msg: str, **kwargs: Any) -> None:
816 err_lines.append(msg)
818 with patch("black.out", out), patch("black.err", err):
819 report.done(Path("f1"), black.Changed.NO)
820 self.assertEqual(len(out_lines), 0)
821 self.assertEqual(len(err_lines), 0)
822 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
823 self.assertEqual(report.return_code, 0)
824 report.done(Path("f2"), black.Changed.YES)
825 self.assertEqual(len(out_lines), 0)
826 self.assertEqual(len(err_lines), 0)
828 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
830 report.done(Path("f3"), black.Changed.CACHED)
831 self.assertEqual(len(out_lines), 0)
832 self.assertEqual(len(err_lines), 0)
834 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
836 self.assertEqual(report.return_code, 0)
838 self.assertEqual(report.return_code, 1)
840 report.failed(Path("e1"), "boom")
841 self.assertEqual(len(out_lines), 0)
842 self.assertEqual(len(err_lines), 1)
843 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
845 unstyle(str(report)),
846 "1 file reformatted, 2 files left unchanged, "
847 "1 file failed to reformat.",
849 self.assertEqual(report.return_code, 123)
850 report.done(Path("f3"), black.Changed.YES)
851 self.assertEqual(len(out_lines), 0)
852 self.assertEqual(len(err_lines), 1)
854 unstyle(str(report)),
855 "2 files reformatted, 2 files left unchanged, "
856 "1 file failed to reformat.",
858 self.assertEqual(report.return_code, 123)
859 report.failed(Path("e2"), "boom")
860 self.assertEqual(len(out_lines), 0)
861 self.assertEqual(len(err_lines), 2)
862 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
864 unstyle(str(report)),
865 "2 files reformatted, 2 files left unchanged, "
866 "2 files failed to reformat.",
868 self.assertEqual(report.return_code, 123)
869 report.path_ignored(Path("wat"), "no match")
870 self.assertEqual(len(out_lines), 0)
871 self.assertEqual(len(err_lines), 2)
873 unstyle(str(report)),
874 "2 files reformatted, 2 files left unchanged, "
875 "2 files failed to reformat.",
877 self.assertEqual(report.return_code, 123)
878 report.done(Path("f4"), black.Changed.NO)
879 self.assertEqual(len(out_lines), 0)
880 self.assertEqual(len(err_lines), 2)
882 unstyle(str(report)),
883 "2 files reformatted, 3 files left unchanged, "
884 "2 files failed to reformat.",
886 self.assertEqual(report.return_code, 123)
889 unstyle(str(report)),
890 "2 files would be reformatted, 3 files would be left unchanged, "
891 "2 files would fail to reformat.",
896 unstyle(str(report)),
897 "2 files would be reformatted, 3 files would be left unchanged, "
898 "2 files would fail to reformat.",
901 def test_report_normal(self) -> None:
902 report = black.Report()
906 def out(msg: str, **kwargs: Any) -> None:
907 out_lines.append(msg)
909 def err(msg: str, **kwargs: Any) -> None:
910 err_lines.append(msg)
912 with patch("black.out", out), patch("black.err", err):
913 report.done(Path("f1"), black.Changed.NO)
914 self.assertEqual(len(out_lines), 0)
915 self.assertEqual(len(err_lines), 0)
916 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
917 self.assertEqual(report.return_code, 0)
918 report.done(Path("f2"), black.Changed.YES)
919 self.assertEqual(len(out_lines), 1)
920 self.assertEqual(len(err_lines), 0)
921 self.assertEqual(out_lines[-1], "reformatted f2")
923 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
925 report.done(Path("f3"), black.Changed.CACHED)
926 self.assertEqual(len(out_lines), 1)
927 self.assertEqual(len(err_lines), 0)
928 self.assertEqual(out_lines[-1], "reformatted f2")
930 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
932 self.assertEqual(report.return_code, 0)
934 self.assertEqual(report.return_code, 1)
936 report.failed(Path("e1"), "boom")
937 self.assertEqual(len(out_lines), 1)
938 self.assertEqual(len(err_lines), 1)
939 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
941 unstyle(str(report)),
942 "1 file reformatted, 2 files left unchanged, "
943 "1 file failed to reformat.",
945 self.assertEqual(report.return_code, 123)
946 report.done(Path("f3"), black.Changed.YES)
947 self.assertEqual(len(out_lines), 2)
948 self.assertEqual(len(err_lines), 1)
949 self.assertEqual(out_lines[-1], "reformatted f3")
951 unstyle(str(report)),
952 "2 files reformatted, 2 files left unchanged, "
953 "1 file failed to reformat.",
955 self.assertEqual(report.return_code, 123)
956 report.failed(Path("e2"), "boom")
957 self.assertEqual(len(out_lines), 2)
958 self.assertEqual(len(err_lines), 2)
959 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
961 unstyle(str(report)),
962 "2 files reformatted, 2 files left unchanged, "
963 "2 files failed to reformat.",
965 self.assertEqual(report.return_code, 123)
966 report.path_ignored(Path("wat"), "no match")
967 self.assertEqual(len(out_lines), 2)
968 self.assertEqual(len(err_lines), 2)
970 unstyle(str(report)),
971 "2 files reformatted, 2 files left unchanged, "
972 "2 files failed to reformat.",
974 self.assertEqual(report.return_code, 123)
975 report.done(Path("f4"), black.Changed.NO)
976 self.assertEqual(len(out_lines), 2)
977 self.assertEqual(len(err_lines), 2)
979 unstyle(str(report)),
980 "2 files reformatted, 3 files left unchanged, "
981 "2 files failed to reformat.",
983 self.assertEqual(report.return_code, 123)
986 unstyle(str(report)),
987 "2 files would be reformatted, 3 files would be left unchanged, "
988 "2 files would fail to reformat.",
993 unstyle(str(report)),
994 "2 files would be reformatted, 3 files would be left unchanged, "
995 "2 files would fail to reformat.",
998 def test_lib2to3_parse(self) -> None:
999 with self.assertRaises(black.InvalidInput):
1000 black.lib2to3_parse("invalid syntax")
1002 straddling = "x + y"
1003 black.lib2to3_parse(straddling)
1004 black.lib2to3_parse(straddling, {TargetVersion.PY27})
1005 black.lib2to3_parse(straddling, {TargetVersion.PY36})
1006 black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
1008 py2_only = "print x"
1009 black.lib2to3_parse(py2_only)
1010 black.lib2to3_parse(py2_only, {TargetVersion.PY27})
1011 with self.assertRaises(black.InvalidInput):
1012 black.lib2to3_parse(py2_only, {TargetVersion.PY36})
1013 with self.assertRaises(black.InvalidInput):
1014 black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
1016 py3_only = "exec(x, end=y)"
1017 black.lib2to3_parse(py3_only)
1018 with self.assertRaises(black.InvalidInput):
1019 black.lib2to3_parse(py3_only, {TargetVersion.PY27})
1020 black.lib2to3_parse(py3_only, {TargetVersion.PY36})
1021 black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
1023 def test_get_features_used(self) -> None:
1024 node = black.lib2to3_parse("def f(*, arg): ...\n")
1025 self.assertEqual(black.get_features_used(node), set())
1026 node = black.lib2to3_parse("def f(*, arg,): ...\n")
1027 self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
1028 node = black.lib2to3_parse("f(*arg,)\n")
1030 black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
1032 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
1033 self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
1034 node = black.lib2to3_parse("123_456\n")
1035 self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
1036 node = black.lib2to3_parse("123456\n")
1037 self.assertEqual(black.get_features_used(node), set())
1038 source, expected = read_data("function")
1039 node = black.lib2to3_parse(source)
1040 expected_features = {
1041 Feature.TRAILING_COMMA_IN_CALL,
1042 Feature.TRAILING_COMMA_IN_DEF,
1045 self.assertEqual(black.get_features_used(node), expected_features)
1046 node = black.lib2to3_parse(expected)
1047 self.assertEqual(black.get_features_used(node), expected_features)
1048 source, expected = read_data("expression")
1049 node = black.lib2to3_parse(source)
1050 self.assertEqual(black.get_features_used(node), set())
1051 node = black.lib2to3_parse(expected)
1052 self.assertEqual(black.get_features_used(node), set())
1054 def test_get_future_imports(self) -> None:
1055 node = black.lib2to3_parse("\n")
1056 self.assertEqual(set(), black.get_future_imports(node))
1057 node = black.lib2to3_parse("from __future__ import black\n")
1058 self.assertEqual({"black"}, black.get_future_imports(node))
1059 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
1060 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1061 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
1062 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
1063 node = black.lib2to3_parse(
1064 "from __future__ import multiple\nfrom __future__ import imports\n"
1066 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1067 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
1068 self.assertEqual({"black"}, black.get_future_imports(node))
1069 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
1070 self.assertEqual({"black"}, black.get_future_imports(node))
1071 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
1072 self.assertEqual(set(), black.get_future_imports(node))
1073 node = black.lib2to3_parse("from some.module import black\n")
1074 self.assertEqual(set(), black.get_future_imports(node))
1075 node = black.lib2to3_parse(
1076 "from __future__ import unicode_literals as _unicode_literals"
1078 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
1079 node = black.lib2to3_parse(
1080 "from __future__ import unicode_literals as _lol, print"
1082 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
1084 def test_debug_visitor(self) -> None:
1085 source, _ = read_data("debug_visitor.py")
1086 expected, _ = read_data("debug_visitor.out")
1090 def out(msg: str, **kwargs: Any) -> None:
1091 out_lines.append(msg)
1093 def err(msg: str, **kwargs: Any) -> None:
1094 err_lines.append(msg)
1096 with patch("black.out", out), patch("black.err", err):
1097 black.DebugVisitor.show(source)
1098 actual = "\n".join(out_lines) + "\n"
1100 if expected != actual:
1101 log_name = black.dump_to_file(*out_lines)
1105 f"AST print out is different. Actual version dumped to {log_name}",
1108 def test_format_file_contents(self) -> None:
1110 mode = black.FileMode()
1111 with self.assertRaises(black.NothingChanged):
1112 black.format_file_contents(empty, mode=mode, fast=False)
1114 with self.assertRaises(black.NothingChanged):
1115 black.format_file_contents(just_nl, mode=mode, fast=False)
1116 same = "j = [1, 2, 3]\n"
1117 with self.assertRaises(black.NothingChanged):
1118 black.format_file_contents(same, mode=mode, fast=False)
1119 different = "j = [1,2,3]"
1121 actual = black.format_file_contents(different, mode=mode, fast=False)
1122 self.assertEqual(expected, actual)
1123 invalid = "return if you can"
1124 with self.assertRaises(black.InvalidInput) as e:
1125 black.format_file_contents(invalid, mode=mode, fast=False)
1126 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
1128 def test_endmarker(self) -> None:
1129 n = black.lib2to3_parse("\n")
1130 self.assertEqual(n.type, black.syms.file_input)
1131 self.assertEqual(len(n.children), 1)
1132 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
1134 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
1135 def test_assertFormatEqual(self) -> None:
1139 def out(msg: str, **kwargs: Any) -> None:
1140 out_lines.append(msg)
1142 def err(msg: str, **kwargs: Any) -> None:
1143 err_lines.append(msg)
1145 with patch("black.out", out), patch("black.err", err):
1146 with self.assertRaises(AssertionError):
1147 self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")
1149 out_str = "".join(out_lines)
1150 self.assertTrue("Expected tree:" in out_str)
1151 self.assertTrue("Actual tree:" in out_str)
1152 self.assertEqual("".join(err_lines), "")
1154 def test_cache_broken_file(self) -> None:
1155 mode = black.FileMode()
1156 with cache_dir() as workspace:
1157 cache_file = black.get_cache_file(mode)
1158 with cache_file.open("w") as fobj:
1159 fobj.write("this is not a pickle")
1160 self.assertEqual(black.read_cache(mode), {})
1161 src = (workspace / "test.py").resolve()
1162 with src.open("w") as fobj:
1163 fobj.write("print('hello')")
1164 self.invokeBlack([str(src)])
1165 cache = black.read_cache(mode)
1166 self.assertIn(src, cache)
1168 def test_cache_single_file_already_cached(self) -> None:
1169 mode = black.FileMode()
1170 with cache_dir() as workspace:
1171 src = (workspace / "test.py").resolve()
1172 with src.open("w") as fobj:
1173 fobj.write("print('hello')")
1174 black.write_cache({}, [src], mode)
1175 self.invokeBlack([str(src)])
1176 with src.open("r") as fobj:
1177 self.assertEqual(fobj.read(), "print('hello')")
1179 @event_loop(close=False)
1180 def test_cache_multiple_files(self) -> None:
1181 mode = black.FileMode()
1182 with cache_dir() as workspace, patch(
1183 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1185 one = (workspace / "one.py").resolve()
1186 with one.open("w") as fobj:
1187 fobj.write("print('hello')")
1188 two = (workspace / "two.py").resolve()
1189 with two.open("w") as fobj:
1190 fobj.write("print('hello')")
1191 black.write_cache({}, [one], mode)
1192 self.invokeBlack([str(workspace)])
1193 with one.open("r") as fobj:
1194 self.assertEqual(fobj.read(), "print('hello')")
1195 with two.open("r") as fobj:
1196 self.assertEqual(fobj.read(), 'print("hello")\n')
1197 cache = black.read_cache(mode)
1198 self.assertIn(one, cache)
1199 self.assertIn(two, cache)
1201 def test_no_cache_when_writeback_diff(self) -> None:
1202 mode = black.FileMode()
1203 with cache_dir() as workspace:
1204 src = (workspace / "test.py").resolve()
1205 with src.open("w") as fobj:
1206 fobj.write("print('hello')")
1207 self.invokeBlack([str(src), "--diff"])
1208 cache_file = black.get_cache_file(mode)
1209 self.assertFalse(cache_file.exists())
1211 def test_no_cache_when_stdin(self) -> None:
1212 mode = black.FileMode()
1214 result = CliRunner().invoke(
1215 black.main, ["-"], input=BytesIO(b"print('hello')")
1217 self.assertEqual(result.exit_code, 0)
1218 cache_file = black.get_cache_file(mode)
1219 self.assertFalse(cache_file.exists())
1221 def test_read_cache_no_cachefile(self) -> None:
1222 mode = black.FileMode()
1224 self.assertEqual(black.read_cache(mode), {})
1226 def test_write_cache_read_cache(self) -> None:
1227 mode = black.FileMode()
1228 with cache_dir() as workspace:
1229 src = (workspace / "test.py").resolve()
1231 black.write_cache({}, [src], mode)
1232 cache = black.read_cache(mode)
1233 self.assertIn(src, cache)
1234 self.assertEqual(cache[src], black.get_cache_info(src))
1236 def test_filter_cached(self) -> None:
1237 with TemporaryDirectory() as workspace:
1238 path = Path(workspace)
1239 uncached = (path / "uncached").resolve()
1240 cached = (path / "cached").resolve()
1241 cached_but_changed = (path / "changed").resolve()
1244 cached_but_changed.touch()
1245 cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
1246 todo, done = black.filter_cached(
1247 cache, {uncached, cached, cached_but_changed}
1249 self.assertEqual(todo, {uncached, cached_but_changed})
1250 self.assertEqual(done, {cached})
1252 def test_write_cache_creates_directory_if_needed(self) -> None:
1253 mode = black.FileMode()
1254 with cache_dir(exists=False) as workspace:
1255 self.assertFalse(workspace.exists())
1256 black.write_cache({}, [], mode)
1257 self.assertTrue(workspace.exists())
1259 @event_loop(close=False)
1260 def test_failed_formatting_does_not_get_cached(self) -> None:
1261 mode = black.FileMode()
1262 with cache_dir() as workspace, patch(
1263 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1265 failing = (workspace / "failing.py").resolve()
1266 with failing.open("w") as fobj:
1267 fobj.write("not actually python")
1268 clean = (workspace / "clean.py").resolve()
1269 with clean.open("w") as fobj:
1270 fobj.write('print("hello")\n')
1271 self.invokeBlack([str(workspace)], exit_code=123)
1272 cache = black.read_cache(mode)
1273 self.assertNotIn(failing, cache)
1274 self.assertIn(clean, cache)
1276 def test_write_cache_write_fail(self) -> None:
1277 mode = black.FileMode()
1278 with cache_dir(), patch.object(Path, "open") as mock:
1279 mock.side_effect = OSError
1280 black.write_cache({}, [], mode)
1282 @event_loop(close=False)
1283 def test_check_diff_use_together(self) -> None:
1285 # Files which will be reformatted.
1286 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1287 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1288 # Files which will not be reformatted.
1289 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1290 self.invokeBlack([str(src2), "--diff", "--check"])
1291 # Multi file command.
1292 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1294 def test_no_files(self) -> None:
1296 # Without an argument, black exits with error code 0.
1297 self.invokeBlack([])
1299 def test_broken_symlink(self) -> None:
1300 with cache_dir() as workspace:
1301 symlink = workspace / "broken_link.py"
1303 symlink.symlink_to("nonexistent.py")
1304 except OSError as e:
1305 self.skipTest(f"Can't create symlinks: {e}")
1306 self.invokeBlack([str(workspace.resolve())])
1308 def test_read_cache_line_lengths(self) -> None:
1309 mode = black.FileMode()
1310 short_mode = black.FileMode(line_length=1)
1311 with cache_dir() as workspace:
1312 path = (workspace / "file.py").resolve()
1314 black.write_cache({}, [path], mode)
1315 one = black.read_cache(mode)
1316 self.assertIn(path, one)
1317 two = black.read_cache(short_mode)
1318 self.assertNotIn(path, two)
1320 def test_tricky_unicode_symbols(self) -> None:
1321 source, expected = read_data("tricky_unicode_symbols")
1323 self.assertFormatEqual(expected, actual)
1324 black.assert_equivalent(source, actual)
1325 black.assert_stable(source, actual, black.FileMode())
1327 def test_single_file_force_pyi(self) -> None:
1328 reg_mode = black.FileMode()
1329 pyi_mode = black.FileMode(is_pyi=True)
1330 contents, expected = read_data("force_pyi")
1331 with cache_dir() as workspace:
1332 path = (workspace / "file.py").resolve()
1333 with open(path, "w") as fh:
1335 self.invokeBlack([str(path), "--pyi"])
1336 with open(path, "r") as fh:
1338 # verify cache with --pyi is separate
1339 pyi_cache = black.read_cache(pyi_mode)
1340 self.assertIn(path, pyi_cache)
1341 normal_cache = black.read_cache(reg_mode)
1342 self.assertNotIn(path, normal_cache)
1343 self.assertEqual(actual, expected)
1345 @event_loop(close=False)
1346 def test_multi_file_force_pyi(self) -> None:
1347 reg_mode = black.FileMode()
1348 pyi_mode = black.FileMode(is_pyi=True)
1349 contents, expected = read_data("force_pyi")
1350 with cache_dir() as workspace:
1352 (workspace / "file1.py").resolve(),
1353 (workspace / "file2.py").resolve(),
1356 with open(path, "w") as fh:
1358 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1360 with open(path, "r") as fh:
1362 self.assertEqual(actual, expected)
1363 # verify cache with --pyi is separate
1364 pyi_cache = black.read_cache(pyi_mode)
1365 normal_cache = black.read_cache(reg_mode)
1367 self.assertIn(path, pyi_cache)
1368 self.assertNotIn(path, normal_cache)
1370 def test_pipe_force_pyi(self) -> None:
1371 source, expected = read_data("force_pyi")
1372 result = CliRunner().invoke(
1373 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1375 self.assertEqual(result.exit_code, 0)
1376 actual = result.output
1377 self.assertFormatEqual(actual, expected)
1379 def test_single_file_force_py36(self) -> None:
1380 reg_mode = black.FileMode()
1381 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1382 source, expected = read_data("force_py36")
1383 with cache_dir() as workspace:
1384 path = (workspace / "file.py").resolve()
1385 with open(path, "w") as fh:
1387 self.invokeBlack([str(path), *PY36_ARGS])
1388 with open(path, "r") as fh:
1390 # verify cache with --target-version is separate
1391 py36_cache = black.read_cache(py36_mode)
1392 self.assertIn(path, py36_cache)
1393 normal_cache = black.read_cache(reg_mode)
1394 self.assertNotIn(path, normal_cache)
1395 self.assertEqual(actual, expected)
1397 @event_loop(close=False)
1398 def test_multi_file_force_py36(self) -> None:
1399 reg_mode = black.FileMode()
1400 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1401 source, expected = read_data("force_py36")
1402 with cache_dir() as workspace:
1404 (workspace / "file1.py").resolve(),
1405 (workspace / "file2.py").resolve(),
1408 with open(path, "w") as fh:
1410 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1412 with open(path, "r") as fh:
1414 self.assertEqual(actual, expected)
1415 # verify cache with --target-version is separate
1416 pyi_cache = black.read_cache(py36_mode)
1417 normal_cache = black.read_cache(reg_mode)
1419 self.assertIn(path, pyi_cache)
1420 self.assertNotIn(path, normal_cache)
1422 def test_collections(self) -> None:
1423 source, expected = read_data("collections")
1425 self.assertFormatEqual(expected, actual)
1426 black.assert_equivalent(source, actual)
1427 black.assert_stable(source, actual, black.FileMode())
1429 def test_pipe_force_py36(self) -> None:
1430 source, expected = read_data("force_py36")
1431 result = CliRunner().invoke(
1433 ["-", "-q", "--target-version=py36"],
1434 input=BytesIO(source.encode("utf8")),
1436 self.assertEqual(result.exit_code, 0)
1437 actual = result.output
1438 self.assertFormatEqual(actual, expected)
1440 def test_include_exclude(self) -> None:
1441 path = THIS_DIR / "data" / "include_exclude_tests"
1442 include = re.compile(r"\.pyi?$")
1443 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1444 report = black.Report()
1445 gitignore = PathSpec.from_lines("gitwildmatch", [])
1446 sources: List[Path] = []
1448 Path(path / "b/dont_exclude/a.py"),
1449 Path(path / "b/dont_exclude/a.pyi"),
1451 this_abs = THIS_DIR.resolve()
1453 black.gen_python_files_in_dir(
1454 path, this_abs, include, exclude, report, gitignore
1457 self.assertEqual(sorted(expected), sorted(sources))
1459 def test_gitignore_exclude(self) -> None:
1460 path = THIS_DIR / "data" / "include_exclude_tests"
1461 include = re.compile(r"\.pyi?$")
1462 exclude = re.compile(r"")
1463 report = black.Report()
1464 gitignore = PathSpec.from_lines(
1465 "gitwildmatch", ["exclude/", ".definitely_exclude"]
1467 sources: List[Path] = []
1469 Path(path / "b/dont_exclude/a.py"),
1470 Path(path / "b/dont_exclude/a.pyi"),
1472 this_abs = THIS_DIR.resolve()
1474 black.gen_python_files_in_dir(
1475 path, this_abs, include, exclude, report, gitignore
1478 self.assertEqual(sorted(expected), sorted(sources))
1480 def test_empty_include(self) -> None:
1481 path = THIS_DIR / "data" / "include_exclude_tests"
1482 report = black.Report()
1483 gitignore = PathSpec.from_lines("gitwildmatch", [])
1484 empty = re.compile(r"")
1485 sources: List[Path] = []
1487 Path(path / "b/exclude/a.pie"),
1488 Path(path / "b/exclude/a.py"),
1489 Path(path / "b/exclude/a.pyi"),
1490 Path(path / "b/dont_exclude/a.pie"),
1491 Path(path / "b/dont_exclude/a.py"),
1492 Path(path / "b/dont_exclude/a.pyi"),
1493 Path(path / "b/.definitely_exclude/a.pie"),
1494 Path(path / "b/.definitely_exclude/a.py"),
1495 Path(path / "b/.definitely_exclude/a.pyi"),
1497 this_abs = THIS_DIR.resolve()
1499 black.gen_python_files_in_dir(
1503 re.compile(black.DEFAULT_EXCLUDES),
1508 self.assertEqual(sorted(expected), sorted(sources))
1510 def test_empty_exclude(self) -> None:
1511 path = THIS_DIR / "data" / "include_exclude_tests"
1512 report = black.Report()
1513 gitignore = PathSpec.from_lines("gitwildmatch", [])
1514 empty = re.compile(r"")
1515 sources: List[Path] = []
1517 Path(path / "b/dont_exclude/a.py"),
1518 Path(path / "b/dont_exclude/a.pyi"),
1519 Path(path / "b/exclude/a.py"),
1520 Path(path / "b/exclude/a.pyi"),
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(
1529 re.compile(black.DEFAULT_INCLUDES),
1535 self.assertEqual(sorted(expected), sorted(sources))
1537 def test_invalid_include_exclude(self) -> None:
1538 for option in ["--include", "--exclude"]:
1539 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1541 def test_preserves_line_endings(self) -> None:
1542 with TemporaryDirectory() as workspace:
1543 test_file = Path(workspace) / "test.py"
1544 for nl in ["\n", "\r\n"]:
1545 contents = nl.join(["def f( ):", " pass"])
1546 test_file.write_bytes(contents.encode())
1547 ff(test_file, write_back=black.WriteBack.YES)
1548 updated_contents: bytes = test_file.read_bytes()
1549 self.assertIn(nl.encode(), updated_contents)
1551 self.assertNotIn(b"\r\n", updated_contents)
1553 def test_preserves_line_endings_via_stdin(self) -> None:
1554 for nl in ["\n", "\r\n"]:
1555 contents = nl.join(["def f( ):", " pass"])
1556 runner = BlackRunner()
1557 result = runner.invoke(
1558 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1560 self.assertEqual(result.exit_code, 0)
1561 output = runner.stdout_bytes
1562 self.assertIn(nl.encode("utf8"), output)
1564 self.assertNotIn(b"\r\n", output)
1566 def test_assert_equivalent_different_asts(self) -> None:
1567 with self.assertRaises(AssertionError):
1568 black.assert_equivalent("{}", "None")
1570 def test_symlink_out_of_root_directory(self) -> None:
1574 include = re.compile(black.DEFAULT_INCLUDES)
1575 exclude = re.compile(black.DEFAULT_EXCLUDES)
1576 report = black.Report()
1577 gitignore = PathSpec.from_lines("gitwildmatch", [])
1578 # `child` should behave like a symlink which resolved path is clearly
1579 # outside of the `root` directory.
1580 path.iterdir.return_value = [child]
1581 child.resolve.return_value = Path("/a/b/c")
1582 child.as_posix.return_value = "/a/b/c"
1583 child.is_symlink.return_value = True
1586 black.gen_python_files_in_dir(
1587 path, root, include, exclude, report, gitignore
1590 except ValueError as ve:
1591 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1592 path.iterdir.assert_called_once()
1593 child.resolve.assert_called_once()
1594 child.is_symlink.assert_called_once()
1595 # `child` should behave like a strange file which resolved path is clearly
1596 # outside of the `root` directory.
1597 child.is_symlink.return_value = False
1598 with self.assertRaises(ValueError):
1600 black.gen_python_files_in_dir(
1601 path, root, include, exclude, report, gitignore
1604 path.iterdir.assert_called()
1605 self.assertEqual(path.iterdir.call_count, 2)
1606 child.resolve.assert_called()
1607 self.assertEqual(child.resolve.call_count, 2)
1608 child.is_symlink.assert_called()
1609 self.assertEqual(child.is_symlink.call_count, 2)
1611 def test_shhh_click(self) -> None:
1613 from click import _unicodefun # type: ignore
1614 except ModuleNotFoundError:
1615 self.skipTest("Incompatible Click version")
1616 if not hasattr(_unicodefun, "_verify_python3_env"):
1617 self.skipTest("Incompatible Click version")
1618 # First, let's see if Click is crashing with a preferred ASCII charset.
1619 with patch("locale.getpreferredencoding") as gpe:
1620 gpe.return_value = "ASCII"
1621 with self.assertRaises(RuntimeError):
1622 _unicodefun._verify_python3_env()
1623 # Now, let's silence Click...
1625 # ...and confirm it's silent.
1626 with patch("locale.getpreferredencoding") as gpe:
1627 gpe.return_value = "ASCII"
1629 _unicodefun._verify_python3_env()
1630 except RuntimeError as re:
1631 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1633 def test_root_logger_not_used_directly(self) -> None:
1634 def fail(*args: Any, **kwargs: Any) -> None:
1635 self.fail("Record created with root logger")
1637 with patch.multiple(
1648 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1649 def test_blackd_main(self) -> None:
1650 with patch("blackd.web.run_app"):
1651 result = CliRunner().invoke(blackd.main, [])
1652 if result.exception is not None:
1653 raise result.exception
1654 self.assertEqual(result.exit_code, 0)
1656 def test_invalid_config_return_code(self) -> None:
1657 tmp_file = Path(black.dump_to_file())
1659 tmp_config = Path(black.dump_to_file())
1661 args = ["--config", str(tmp_config), str(tmp_file)]
1662 self.invokeBlack(args, exit_code=2, ignore_config=False)
1667 class BlackDTestCase(AioHTTPTestCase):
1668 async def get_application(self) -> web.Application:
1669 return blackd.make_app()
1671 # TODO: remove these decorators once the below is released
1672 # https://github.com/aio-libs/aiohttp/pull/3727
1673 @skip_if_exception("ClientOSError")
1674 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1676 async def test_blackd_request_needs_formatting(self) -> None:
1677 response = await self.client.post("/", data=b"print('hello world')")
1678 self.assertEqual(response.status, 200)
1679 self.assertEqual(response.charset, "utf8")
1680 self.assertEqual(await response.read(), b'print("hello world")\n')
1682 @skip_if_exception("ClientOSError")
1683 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1685 async def test_blackd_request_no_change(self) -> None:
1686 response = await self.client.post("/", data=b'print("hello world")\n')
1687 self.assertEqual(response.status, 204)
1688 self.assertEqual(await response.read(), b"")
1690 @skip_if_exception("ClientOSError")
1691 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1693 async def test_blackd_request_syntax_error(self) -> None:
1694 response = await self.client.post("/", data=b"what even ( is")
1695 self.assertEqual(response.status, 400)
1696 content = await response.text()
1698 content.startswith("Cannot parse"),
1699 msg=f"Expected error to start with 'Cannot parse', got {repr(content)}",
1702 @skip_if_exception("ClientOSError")
1703 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1705 async def test_blackd_unsupported_version(self) -> None:
1706 response = await self.client.post(
1707 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "2"}
1709 self.assertEqual(response.status, 501)
1711 @skip_if_exception("ClientOSError")
1712 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1714 async def test_blackd_supported_version(self) -> None:
1715 response = await self.client.post(
1716 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "1"}
1718 self.assertEqual(response.status, 200)
1720 @skip_if_exception("ClientOSError")
1721 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1723 async def test_blackd_invalid_python_variant(self) -> None:
1724 async def check(header_value: str, expected_status: int = 400) -> None:
1725 response = await self.client.post(
1726 "/", data=b"what", headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1728 self.assertEqual(response.status, expected_status)
1731 await check("ruby3.5")
1732 await check("pyi3.6")
1733 await check("py1.5")
1735 await check("py2.8")
1737 await check("pypy3.0")
1738 await check("jython3.4")
1740 @skip_if_exception("ClientOSError")
1741 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1743 async def test_blackd_pyi(self) -> None:
1744 source, expected = read_data("stub.pyi")
1745 response = await self.client.post(
1746 "/", data=source, headers={blackd.PYTHON_VARIANT_HEADER: "pyi"}
1748 self.assertEqual(response.status, 200)
1749 self.assertEqual(await response.text(), expected)
1751 @skip_if_exception("ClientOSError")
1752 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1754 async def test_blackd_diff(self) -> None:
1755 diff_header = re.compile(
1756 rf"(In|Out)\t\d\d\d\d-\d\d-\d\d "
1757 rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
1760 source, _ = read_data("blackd_diff.py")
1761 expected, _ = read_data("blackd_diff.diff")
1763 response = await self.client.post(
1764 "/", data=source, headers={blackd.DIFF_HEADER: "true"}
1766 self.assertEqual(response.status, 200)
1768 actual = await response.text()
1769 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
1770 self.assertEqual(actual, expected)
1772 @skip_if_exception("ClientOSError")
1773 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1775 async def test_blackd_python_variant(self) -> None:
1778 " and_has_a_bunch_of,\n"
1779 " very_long_arguments_too,\n"
1780 " and_lots_of_them_as_well_lol,\n"
1781 " **and_very_long_keyword_arguments\n"
1786 async def check(header_value: str, expected_status: int) -> None:
1787 response = await self.client.post(
1788 "/", data=code, headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1791 response.status, expected_status, msg=await response.text()
1794 await check("3.6", 200)
1795 await check("py3.6", 200)
1796 await check("3.6,3.7", 200)
1797 await check("3.6,py3.7", 200)
1798 await check("py36,py37", 200)
1799 await check("36", 200)
1800 await check("3.6.4", 200)
1802 await check("2", 204)
1803 await check("2.7", 204)
1804 await check("py2.7", 204)
1805 await check("3.4", 204)
1806 await check("py3.4", 204)
1807 await check("py34,py36", 204)
1808 await check("34", 204)
1810 @skip_if_exception("ClientOSError")
1811 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1813 async def test_blackd_line_length(self) -> None:
1814 response = await self.client.post(
1815 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "7"}
1817 self.assertEqual(response.status, 200)
1819 @skip_if_exception("ClientOSError")
1820 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1822 async def test_blackd_invalid_line_length(self) -> None:
1823 response = await self.client.post(
1824 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "NaN"}
1826 self.assertEqual(response.status, 400)
1828 @skip_if_exception("ClientOSError")
1829 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1831 async def test_blackd_response_black_version_header(self) -> None:
1832 response = await self.client.post("/")
1833 self.assertIsNotNone(response.headers.get(blackd.BLACK_VERSION_HEADER))
1836 if __name__ == "__main__":
1837 unittest.main(module="test_black")