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_remove_empty_parentheses_after_class(self) -> None:
637 source, expected = read_data("class_blank_parentheses")
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_new_line_between_class_and_code(self) -> None:
645 source, expected = read_data("class_methods_new_line")
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_bracket_match(self) -> None:
653 source, expected = read_data("bracketmatch")
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_tuple_assign(self) -> None:
661 source, expected = read_data("tupleassign")
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_beginning_backslash(self) -> None:
669 source, expected = read_data("beginning_backslash")
671 self.assertFormatEqual(expected, actual)
672 black.assert_equivalent(source, actual)
673 black.assert_stable(source, actual, black.FileMode())
675 def test_tab_comment_indentation(self) -> None:
676 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
677 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
678 self.assertFormatEqual(contents_spc, fs(contents_spc))
679 self.assertFormatEqual(contents_spc, fs(contents_tab))
681 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
682 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
683 self.assertFormatEqual(contents_spc, fs(contents_spc))
684 self.assertFormatEqual(contents_spc, fs(contents_tab))
686 # mixed tabs and spaces (valid Python 2 code)
687 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t# comment\n pass\n"
688 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
689 self.assertFormatEqual(contents_spc, fs(contents_spc))
690 self.assertFormatEqual(contents_spc, fs(contents_tab))
692 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t\t# comment\n pass\n"
693 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
694 self.assertFormatEqual(contents_spc, fs(contents_spc))
695 self.assertFormatEqual(contents_spc, fs(contents_tab))
697 def test_report_verbose(self) -> None:
698 report = black.Report(verbose=True)
702 def out(msg: str, **kwargs: Any) -> None:
703 out_lines.append(msg)
705 def err(msg: str, **kwargs: Any) -> None:
706 err_lines.append(msg)
708 with patch("black.out", out), patch("black.err", err):
709 report.done(Path("f1"), black.Changed.NO)
710 self.assertEqual(len(out_lines), 1)
711 self.assertEqual(len(err_lines), 0)
712 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
713 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
714 self.assertEqual(report.return_code, 0)
715 report.done(Path("f2"), black.Changed.YES)
716 self.assertEqual(len(out_lines), 2)
717 self.assertEqual(len(err_lines), 0)
718 self.assertEqual(out_lines[-1], "reformatted f2")
720 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
722 report.done(Path("f3"), black.Changed.CACHED)
723 self.assertEqual(len(out_lines), 3)
724 self.assertEqual(len(err_lines), 0)
726 out_lines[-1], "f3 wasn't modified on disk since last run."
729 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
731 self.assertEqual(report.return_code, 0)
733 self.assertEqual(report.return_code, 1)
735 report.failed(Path("e1"), "boom")
736 self.assertEqual(len(out_lines), 3)
737 self.assertEqual(len(err_lines), 1)
738 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
740 unstyle(str(report)),
741 "1 file reformatted, 2 files left unchanged, "
742 "1 file failed to reformat.",
744 self.assertEqual(report.return_code, 123)
745 report.done(Path("f3"), black.Changed.YES)
746 self.assertEqual(len(out_lines), 4)
747 self.assertEqual(len(err_lines), 1)
748 self.assertEqual(out_lines[-1], "reformatted f3")
750 unstyle(str(report)),
751 "2 files reformatted, 2 files left unchanged, "
752 "1 file failed to reformat.",
754 self.assertEqual(report.return_code, 123)
755 report.failed(Path("e2"), "boom")
756 self.assertEqual(len(out_lines), 4)
757 self.assertEqual(len(err_lines), 2)
758 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
760 unstyle(str(report)),
761 "2 files reformatted, 2 files left unchanged, "
762 "2 files failed to reformat.",
764 self.assertEqual(report.return_code, 123)
765 report.path_ignored(Path("wat"), "no match")
766 self.assertEqual(len(out_lines), 5)
767 self.assertEqual(len(err_lines), 2)
768 self.assertEqual(out_lines[-1], "wat ignored: no match")
770 unstyle(str(report)),
771 "2 files reformatted, 2 files left unchanged, "
772 "2 files failed to reformat.",
774 self.assertEqual(report.return_code, 123)
775 report.done(Path("f4"), black.Changed.NO)
776 self.assertEqual(len(out_lines), 6)
777 self.assertEqual(len(err_lines), 2)
778 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
780 unstyle(str(report)),
781 "2 files reformatted, 3 files left unchanged, "
782 "2 files failed to reformat.",
784 self.assertEqual(report.return_code, 123)
787 unstyle(str(report)),
788 "2 files would be reformatted, 3 files would be left unchanged, "
789 "2 files would fail to reformat.",
794 unstyle(str(report)),
795 "2 files would be reformatted, 3 files would be left unchanged, "
796 "2 files would fail to reformat.",
799 def test_report_quiet(self) -> None:
800 report = black.Report(quiet=True)
804 def out(msg: str, **kwargs: Any) -> None:
805 out_lines.append(msg)
807 def err(msg: str, **kwargs: Any) -> None:
808 err_lines.append(msg)
810 with patch("black.out", out), patch("black.err", err):
811 report.done(Path("f1"), black.Changed.NO)
812 self.assertEqual(len(out_lines), 0)
813 self.assertEqual(len(err_lines), 0)
814 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
815 self.assertEqual(report.return_code, 0)
816 report.done(Path("f2"), black.Changed.YES)
817 self.assertEqual(len(out_lines), 0)
818 self.assertEqual(len(err_lines), 0)
820 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
822 report.done(Path("f3"), black.Changed.CACHED)
823 self.assertEqual(len(out_lines), 0)
824 self.assertEqual(len(err_lines), 0)
826 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
828 self.assertEqual(report.return_code, 0)
830 self.assertEqual(report.return_code, 1)
832 report.failed(Path("e1"), "boom")
833 self.assertEqual(len(out_lines), 0)
834 self.assertEqual(len(err_lines), 1)
835 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
837 unstyle(str(report)),
838 "1 file reformatted, 2 files left unchanged, "
839 "1 file failed to reformat.",
841 self.assertEqual(report.return_code, 123)
842 report.done(Path("f3"), black.Changed.YES)
843 self.assertEqual(len(out_lines), 0)
844 self.assertEqual(len(err_lines), 1)
846 unstyle(str(report)),
847 "2 files reformatted, 2 files left unchanged, "
848 "1 file failed to reformat.",
850 self.assertEqual(report.return_code, 123)
851 report.failed(Path("e2"), "boom")
852 self.assertEqual(len(out_lines), 0)
853 self.assertEqual(len(err_lines), 2)
854 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
856 unstyle(str(report)),
857 "2 files reformatted, 2 files left unchanged, "
858 "2 files failed to reformat.",
860 self.assertEqual(report.return_code, 123)
861 report.path_ignored(Path("wat"), "no match")
862 self.assertEqual(len(out_lines), 0)
863 self.assertEqual(len(err_lines), 2)
865 unstyle(str(report)),
866 "2 files reformatted, 2 files left unchanged, "
867 "2 files failed to reformat.",
869 self.assertEqual(report.return_code, 123)
870 report.done(Path("f4"), black.Changed.NO)
871 self.assertEqual(len(out_lines), 0)
872 self.assertEqual(len(err_lines), 2)
874 unstyle(str(report)),
875 "2 files reformatted, 3 files left unchanged, "
876 "2 files failed to reformat.",
878 self.assertEqual(report.return_code, 123)
881 unstyle(str(report)),
882 "2 files would be reformatted, 3 files would be left unchanged, "
883 "2 files would fail to reformat.",
888 unstyle(str(report)),
889 "2 files would be reformatted, 3 files would be left unchanged, "
890 "2 files would fail to reformat.",
893 def test_report_normal(self) -> None:
894 report = black.Report()
898 def out(msg: str, **kwargs: Any) -> None:
899 out_lines.append(msg)
901 def err(msg: str, **kwargs: Any) -> None:
902 err_lines.append(msg)
904 with patch("black.out", out), patch("black.err", err):
905 report.done(Path("f1"), black.Changed.NO)
906 self.assertEqual(len(out_lines), 0)
907 self.assertEqual(len(err_lines), 0)
908 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
909 self.assertEqual(report.return_code, 0)
910 report.done(Path("f2"), black.Changed.YES)
911 self.assertEqual(len(out_lines), 1)
912 self.assertEqual(len(err_lines), 0)
913 self.assertEqual(out_lines[-1], "reformatted f2")
915 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
917 report.done(Path("f3"), black.Changed.CACHED)
918 self.assertEqual(len(out_lines), 1)
919 self.assertEqual(len(err_lines), 0)
920 self.assertEqual(out_lines[-1], "reformatted f2")
922 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
924 self.assertEqual(report.return_code, 0)
926 self.assertEqual(report.return_code, 1)
928 report.failed(Path("e1"), "boom")
929 self.assertEqual(len(out_lines), 1)
930 self.assertEqual(len(err_lines), 1)
931 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
933 unstyle(str(report)),
934 "1 file reformatted, 2 files left unchanged, "
935 "1 file failed to reformat.",
937 self.assertEqual(report.return_code, 123)
938 report.done(Path("f3"), black.Changed.YES)
939 self.assertEqual(len(out_lines), 2)
940 self.assertEqual(len(err_lines), 1)
941 self.assertEqual(out_lines[-1], "reformatted f3")
943 unstyle(str(report)),
944 "2 files reformatted, 2 files left unchanged, "
945 "1 file failed to reformat.",
947 self.assertEqual(report.return_code, 123)
948 report.failed(Path("e2"), "boom")
949 self.assertEqual(len(out_lines), 2)
950 self.assertEqual(len(err_lines), 2)
951 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
953 unstyle(str(report)),
954 "2 files reformatted, 2 files left unchanged, "
955 "2 files failed to reformat.",
957 self.assertEqual(report.return_code, 123)
958 report.path_ignored(Path("wat"), "no match")
959 self.assertEqual(len(out_lines), 2)
960 self.assertEqual(len(err_lines), 2)
962 unstyle(str(report)),
963 "2 files reformatted, 2 files left unchanged, "
964 "2 files failed to reformat.",
966 self.assertEqual(report.return_code, 123)
967 report.done(Path("f4"), black.Changed.NO)
968 self.assertEqual(len(out_lines), 2)
969 self.assertEqual(len(err_lines), 2)
971 unstyle(str(report)),
972 "2 files reformatted, 3 files left unchanged, "
973 "2 files failed to reformat.",
975 self.assertEqual(report.return_code, 123)
978 unstyle(str(report)),
979 "2 files would be reformatted, 3 files would be left unchanged, "
980 "2 files would fail to reformat.",
985 unstyle(str(report)),
986 "2 files would be reformatted, 3 files would be left unchanged, "
987 "2 files would fail to reformat.",
990 def test_lib2to3_parse(self) -> None:
991 with self.assertRaises(black.InvalidInput):
992 black.lib2to3_parse("invalid syntax")
995 black.lib2to3_parse(straddling)
996 black.lib2to3_parse(straddling, {TargetVersion.PY27})
997 black.lib2to3_parse(straddling, {TargetVersion.PY36})
998 black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
1000 py2_only = "print x"
1001 black.lib2to3_parse(py2_only)
1002 black.lib2to3_parse(py2_only, {TargetVersion.PY27})
1003 with self.assertRaises(black.InvalidInput):
1004 black.lib2to3_parse(py2_only, {TargetVersion.PY36})
1005 with self.assertRaises(black.InvalidInput):
1006 black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
1008 py3_only = "exec(x, end=y)"
1009 black.lib2to3_parse(py3_only)
1010 with self.assertRaises(black.InvalidInput):
1011 black.lib2to3_parse(py3_only, {TargetVersion.PY27})
1012 black.lib2to3_parse(py3_only, {TargetVersion.PY36})
1013 black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
1015 def test_get_features_used(self) -> None:
1016 node = black.lib2to3_parse("def f(*, arg): ...\n")
1017 self.assertEqual(black.get_features_used(node), set())
1018 node = black.lib2to3_parse("def f(*, arg,): ...\n")
1019 self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
1020 node = black.lib2to3_parse("f(*arg,)\n")
1022 black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
1024 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
1025 self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
1026 node = black.lib2to3_parse("123_456\n")
1027 self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
1028 node = black.lib2to3_parse("123456\n")
1029 self.assertEqual(black.get_features_used(node), set())
1030 source, expected = read_data("function")
1031 node = black.lib2to3_parse(source)
1032 expected_features = {
1033 Feature.TRAILING_COMMA_IN_CALL,
1034 Feature.TRAILING_COMMA_IN_DEF,
1037 self.assertEqual(black.get_features_used(node), expected_features)
1038 node = black.lib2to3_parse(expected)
1039 self.assertEqual(black.get_features_used(node), expected_features)
1040 source, expected = read_data("expression")
1041 node = black.lib2to3_parse(source)
1042 self.assertEqual(black.get_features_used(node), set())
1043 node = black.lib2to3_parse(expected)
1044 self.assertEqual(black.get_features_used(node), set())
1046 def test_get_future_imports(self) -> None:
1047 node = black.lib2to3_parse("\n")
1048 self.assertEqual(set(), black.get_future_imports(node))
1049 node = black.lib2to3_parse("from __future__ import black\n")
1050 self.assertEqual({"black"}, black.get_future_imports(node))
1051 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
1052 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1053 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
1054 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
1055 node = black.lib2to3_parse(
1056 "from __future__ import multiple\nfrom __future__ import imports\n"
1058 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1059 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
1060 self.assertEqual({"black"}, black.get_future_imports(node))
1061 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
1062 self.assertEqual({"black"}, black.get_future_imports(node))
1063 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
1064 self.assertEqual(set(), black.get_future_imports(node))
1065 node = black.lib2to3_parse("from some.module import black\n")
1066 self.assertEqual(set(), black.get_future_imports(node))
1067 node = black.lib2to3_parse(
1068 "from __future__ import unicode_literals as _unicode_literals"
1070 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
1071 node = black.lib2to3_parse(
1072 "from __future__ import unicode_literals as _lol, print"
1074 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
1076 def test_debug_visitor(self) -> None:
1077 source, _ = read_data("debug_visitor.py")
1078 expected, _ = read_data("debug_visitor.out")
1082 def out(msg: str, **kwargs: Any) -> None:
1083 out_lines.append(msg)
1085 def err(msg: str, **kwargs: Any) -> None:
1086 err_lines.append(msg)
1088 with patch("black.out", out), patch("black.err", err):
1089 black.DebugVisitor.show(source)
1090 actual = "\n".join(out_lines) + "\n"
1092 if expected != actual:
1093 log_name = black.dump_to_file(*out_lines)
1097 f"AST print out is different. Actual version dumped to {log_name}",
1100 def test_format_file_contents(self) -> None:
1102 mode = black.FileMode()
1103 with self.assertRaises(black.NothingChanged):
1104 black.format_file_contents(empty, mode=mode, fast=False)
1106 with self.assertRaises(black.NothingChanged):
1107 black.format_file_contents(just_nl, mode=mode, fast=False)
1108 same = "j = [1, 2, 3]\n"
1109 with self.assertRaises(black.NothingChanged):
1110 black.format_file_contents(same, mode=mode, fast=False)
1111 different = "j = [1,2,3]"
1113 actual = black.format_file_contents(different, mode=mode, fast=False)
1114 self.assertEqual(expected, actual)
1115 invalid = "return if you can"
1116 with self.assertRaises(black.InvalidInput) as e:
1117 black.format_file_contents(invalid, mode=mode, fast=False)
1118 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
1120 def test_endmarker(self) -> None:
1121 n = black.lib2to3_parse("\n")
1122 self.assertEqual(n.type, black.syms.file_input)
1123 self.assertEqual(len(n.children), 1)
1124 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
1126 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
1127 def test_assertFormatEqual(self) -> None:
1131 def out(msg: str, **kwargs: Any) -> None:
1132 out_lines.append(msg)
1134 def err(msg: str, **kwargs: Any) -> None:
1135 err_lines.append(msg)
1137 with patch("black.out", out), patch("black.err", err):
1138 with self.assertRaises(AssertionError):
1139 self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")
1141 out_str = "".join(out_lines)
1142 self.assertTrue("Expected tree:" in out_str)
1143 self.assertTrue("Actual tree:" in out_str)
1144 self.assertEqual("".join(err_lines), "")
1146 def test_cache_broken_file(self) -> None:
1147 mode = black.FileMode()
1148 with cache_dir() as workspace:
1149 cache_file = black.get_cache_file(mode)
1150 with cache_file.open("w") as fobj:
1151 fobj.write("this is not a pickle")
1152 self.assertEqual(black.read_cache(mode), {})
1153 src = (workspace / "test.py").resolve()
1154 with src.open("w") as fobj:
1155 fobj.write("print('hello')")
1156 self.invokeBlack([str(src)])
1157 cache = black.read_cache(mode)
1158 self.assertIn(src, cache)
1160 def test_cache_single_file_already_cached(self) -> None:
1161 mode = black.FileMode()
1162 with cache_dir() as workspace:
1163 src = (workspace / "test.py").resolve()
1164 with src.open("w") as fobj:
1165 fobj.write("print('hello')")
1166 black.write_cache({}, [src], mode)
1167 self.invokeBlack([str(src)])
1168 with src.open("r") as fobj:
1169 self.assertEqual(fobj.read(), "print('hello')")
1171 @event_loop(close=False)
1172 def test_cache_multiple_files(self) -> None:
1173 mode = black.FileMode()
1174 with cache_dir() as workspace, patch(
1175 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1177 one = (workspace / "one.py").resolve()
1178 with one.open("w") as fobj:
1179 fobj.write("print('hello')")
1180 two = (workspace / "two.py").resolve()
1181 with two.open("w") as fobj:
1182 fobj.write("print('hello')")
1183 black.write_cache({}, [one], mode)
1184 self.invokeBlack([str(workspace)])
1185 with one.open("r") as fobj:
1186 self.assertEqual(fobj.read(), "print('hello')")
1187 with two.open("r") as fobj:
1188 self.assertEqual(fobj.read(), 'print("hello")\n')
1189 cache = black.read_cache(mode)
1190 self.assertIn(one, cache)
1191 self.assertIn(two, cache)
1193 def test_no_cache_when_writeback_diff(self) -> None:
1194 mode = black.FileMode()
1195 with cache_dir() as workspace:
1196 src = (workspace / "test.py").resolve()
1197 with src.open("w") as fobj:
1198 fobj.write("print('hello')")
1199 self.invokeBlack([str(src), "--diff"])
1200 cache_file = black.get_cache_file(mode)
1201 self.assertFalse(cache_file.exists())
1203 def test_no_cache_when_stdin(self) -> None:
1204 mode = black.FileMode()
1206 result = CliRunner().invoke(
1207 black.main, ["-"], input=BytesIO(b"print('hello')")
1209 self.assertEqual(result.exit_code, 0)
1210 cache_file = black.get_cache_file(mode)
1211 self.assertFalse(cache_file.exists())
1213 def test_read_cache_no_cachefile(self) -> None:
1214 mode = black.FileMode()
1216 self.assertEqual(black.read_cache(mode), {})
1218 def test_write_cache_read_cache(self) -> None:
1219 mode = black.FileMode()
1220 with cache_dir() as workspace:
1221 src = (workspace / "test.py").resolve()
1223 black.write_cache({}, [src], mode)
1224 cache = black.read_cache(mode)
1225 self.assertIn(src, cache)
1226 self.assertEqual(cache[src], black.get_cache_info(src))
1228 def test_filter_cached(self) -> None:
1229 with TemporaryDirectory() as workspace:
1230 path = Path(workspace)
1231 uncached = (path / "uncached").resolve()
1232 cached = (path / "cached").resolve()
1233 cached_but_changed = (path / "changed").resolve()
1236 cached_but_changed.touch()
1237 cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
1238 todo, done = black.filter_cached(
1239 cache, {uncached, cached, cached_but_changed}
1241 self.assertEqual(todo, {uncached, cached_but_changed})
1242 self.assertEqual(done, {cached})
1244 def test_write_cache_creates_directory_if_needed(self) -> None:
1245 mode = black.FileMode()
1246 with cache_dir(exists=False) as workspace:
1247 self.assertFalse(workspace.exists())
1248 black.write_cache({}, [], mode)
1249 self.assertTrue(workspace.exists())
1251 @event_loop(close=False)
1252 def test_failed_formatting_does_not_get_cached(self) -> None:
1253 mode = black.FileMode()
1254 with cache_dir() as workspace, patch(
1255 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1257 failing = (workspace / "failing.py").resolve()
1258 with failing.open("w") as fobj:
1259 fobj.write("not actually python")
1260 clean = (workspace / "clean.py").resolve()
1261 with clean.open("w") as fobj:
1262 fobj.write('print("hello")\n')
1263 self.invokeBlack([str(workspace)], exit_code=123)
1264 cache = black.read_cache(mode)
1265 self.assertNotIn(failing, cache)
1266 self.assertIn(clean, cache)
1268 def test_write_cache_write_fail(self) -> None:
1269 mode = black.FileMode()
1270 with cache_dir(), patch.object(Path, "open") as mock:
1271 mock.side_effect = OSError
1272 black.write_cache({}, [], mode)
1274 @event_loop(close=False)
1275 def test_check_diff_use_together(self) -> None:
1277 # Files which will be reformatted.
1278 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1279 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1280 # Files which will not be reformatted.
1281 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1282 self.invokeBlack([str(src2), "--diff", "--check"])
1283 # Multi file command.
1284 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1286 def test_no_files(self) -> None:
1288 # Without an argument, black exits with error code 0.
1289 self.invokeBlack([])
1291 def test_broken_symlink(self) -> None:
1292 with cache_dir() as workspace:
1293 symlink = workspace / "broken_link.py"
1295 symlink.symlink_to("nonexistent.py")
1296 except OSError as e:
1297 self.skipTest(f"Can't create symlinks: {e}")
1298 self.invokeBlack([str(workspace.resolve())])
1300 def test_read_cache_line_lengths(self) -> None:
1301 mode = black.FileMode()
1302 short_mode = black.FileMode(line_length=1)
1303 with cache_dir() as workspace:
1304 path = (workspace / "file.py").resolve()
1306 black.write_cache({}, [path], mode)
1307 one = black.read_cache(mode)
1308 self.assertIn(path, one)
1309 two = black.read_cache(short_mode)
1310 self.assertNotIn(path, two)
1312 def test_tricky_unicode_symbols(self) -> None:
1313 source, expected = read_data("tricky_unicode_symbols")
1315 self.assertFormatEqual(expected, actual)
1316 black.assert_equivalent(source, actual)
1317 black.assert_stable(source, actual, black.FileMode())
1319 def test_single_file_force_pyi(self) -> None:
1320 reg_mode = black.FileMode()
1321 pyi_mode = black.FileMode(is_pyi=True)
1322 contents, expected = read_data("force_pyi")
1323 with cache_dir() as workspace:
1324 path = (workspace / "file.py").resolve()
1325 with open(path, "w") as fh:
1327 self.invokeBlack([str(path), "--pyi"])
1328 with open(path, "r") as fh:
1330 # verify cache with --pyi is separate
1331 pyi_cache = black.read_cache(pyi_mode)
1332 self.assertIn(path, pyi_cache)
1333 normal_cache = black.read_cache(reg_mode)
1334 self.assertNotIn(path, normal_cache)
1335 self.assertEqual(actual, expected)
1337 @event_loop(close=False)
1338 def test_multi_file_force_pyi(self) -> None:
1339 reg_mode = black.FileMode()
1340 pyi_mode = black.FileMode(is_pyi=True)
1341 contents, expected = read_data("force_pyi")
1342 with cache_dir() as workspace:
1344 (workspace / "file1.py").resolve(),
1345 (workspace / "file2.py").resolve(),
1348 with open(path, "w") as fh:
1350 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1352 with open(path, "r") as fh:
1354 self.assertEqual(actual, expected)
1355 # verify cache with --pyi is separate
1356 pyi_cache = black.read_cache(pyi_mode)
1357 normal_cache = black.read_cache(reg_mode)
1359 self.assertIn(path, pyi_cache)
1360 self.assertNotIn(path, normal_cache)
1362 def test_pipe_force_pyi(self) -> None:
1363 source, expected = read_data("force_pyi")
1364 result = CliRunner().invoke(
1365 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1367 self.assertEqual(result.exit_code, 0)
1368 actual = result.output
1369 self.assertFormatEqual(actual, expected)
1371 def test_single_file_force_py36(self) -> None:
1372 reg_mode = black.FileMode()
1373 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1374 source, expected = read_data("force_py36")
1375 with cache_dir() as workspace:
1376 path = (workspace / "file.py").resolve()
1377 with open(path, "w") as fh:
1379 self.invokeBlack([str(path), *PY36_ARGS])
1380 with open(path, "r") as fh:
1382 # verify cache with --target-version is separate
1383 py36_cache = black.read_cache(py36_mode)
1384 self.assertIn(path, py36_cache)
1385 normal_cache = black.read_cache(reg_mode)
1386 self.assertNotIn(path, normal_cache)
1387 self.assertEqual(actual, expected)
1389 @event_loop(close=False)
1390 def test_multi_file_force_py36(self) -> None:
1391 reg_mode = black.FileMode()
1392 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1393 source, expected = read_data("force_py36")
1394 with cache_dir() as workspace:
1396 (workspace / "file1.py").resolve(),
1397 (workspace / "file2.py").resolve(),
1400 with open(path, "w") as fh:
1402 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1404 with open(path, "r") as fh:
1406 self.assertEqual(actual, expected)
1407 # verify cache with --target-version is separate
1408 pyi_cache = black.read_cache(py36_mode)
1409 normal_cache = black.read_cache(reg_mode)
1411 self.assertIn(path, pyi_cache)
1412 self.assertNotIn(path, normal_cache)
1414 def test_collections(self) -> None:
1415 source, expected = read_data("collections")
1417 self.assertFormatEqual(expected, actual)
1418 black.assert_equivalent(source, actual)
1419 black.assert_stable(source, actual, black.FileMode())
1421 def test_pipe_force_py36(self) -> None:
1422 source, expected = read_data("force_py36")
1423 result = CliRunner().invoke(
1425 ["-", "-q", "--target-version=py36"],
1426 input=BytesIO(source.encode("utf8")),
1428 self.assertEqual(result.exit_code, 0)
1429 actual = result.output
1430 self.assertFormatEqual(actual, expected)
1432 def test_include_exclude(self) -> None:
1433 path = THIS_DIR / "data" / "include_exclude_tests"
1434 include = re.compile(r"\.pyi?$")
1435 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1436 report = black.Report()
1437 gitignore = PathSpec.from_lines("gitwildmatch", [])
1438 sources: List[Path] = []
1440 Path(path / "b/dont_exclude/a.py"),
1441 Path(path / "b/dont_exclude/a.pyi"),
1443 this_abs = THIS_DIR.resolve()
1445 black.gen_python_files_in_dir(
1446 path, this_abs, include, exclude, report, gitignore
1449 self.assertEqual(sorted(expected), sorted(sources))
1451 def test_gitignore_exclude(self) -> None:
1452 path = THIS_DIR / "data" / "include_exclude_tests"
1453 include = re.compile(r"\.pyi?$")
1454 exclude = re.compile(r"")
1455 report = black.Report()
1456 gitignore = PathSpec.from_lines(
1457 "gitwildmatch", ["exclude/", ".definitely_exclude"]
1459 sources: List[Path] = []
1461 Path(path / "b/dont_exclude/a.py"),
1462 Path(path / "b/dont_exclude/a.pyi"),
1464 this_abs = THIS_DIR.resolve()
1466 black.gen_python_files_in_dir(
1467 path, this_abs, include, exclude, report, gitignore
1470 self.assertEqual(sorted(expected), sorted(sources))
1472 def test_empty_include(self) -> None:
1473 path = THIS_DIR / "data" / "include_exclude_tests"
1474 report = black.Report()
1475 gitignore = PathSpec.from_lines("gitwildmatch", [])
1476 empty = re.compile(r"")
1477 sources: List[Path] = []
1479 Path(path / "b/exclude/a.pie"),
1480 Path(path / "b/exclude/a.py"),
1481 Path(path / "b/exclude/a.pyi"),
1482 Path(path / "b/dont_exclude/a.pie"),
1483 Path(path / "b/dont_exclude/a.py"),
1484 Path(path / "b/dont_exclude/a.pyi"),
1485 Path(path / "b/.definitely_exclude/a.pie"),
1486 Path(path / "b/.definitely_exclude/a.py"),
1487 Path(path / "b/.definitely_exclude/a.pyi"),
1489 this_abs = THIS_DIR.resolve()
1491 black.gen_python_files_in_dir(
1495 re.compile(black.DEFAULT_EXCLUDES),
1500 self.assertEqual(sorted(expected), sorted(sources))
1502 def test_empty_exclude(self) -> None:
1503 path = THIS_DIR / "data" / "include_exclude_tests"
1504 report = black.Report()
1505 gitignore = PathSpec.from_lines("gitwildmatch", [])
1506 empty = re.compile(r"")
1507 sources: List[Path] = []
1509 Path(path / "b/dont_exclude/a.py"),
1510 Path(path / "b/dont_exclude/a.pyi"),
1511 Path(path / "b/exclude/a.py"),
1512 Path(path / "b/exclude/a.pyi"),
1513 Path(path / "b/.definitely_exclude/a.py"),
1514 Path(path / "b/.definitely_exclude/a.pyi"),
1516 this_abs = THIS_DIR.resolve()
1518 black.gen_python_files_in_dir(
1521 re.compile(black.DEFAULT_INCLUDES),
1527 self.assertEqual(sorted(expected), sorted(sources))
1529 def test_invalid_include_exclude(self) -> None:
1530 for option in ["--include", "--exclude"]:
1531 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1533 def test_preserves_line_endings(self) -> None:
1534 with TemporaryDirectory() as workspace:
1535 test_file = Path(workspace) / "test.py"
1536 for nl in ["\n", "\r\n"]:
1537 contents = nl.join(["def f( ):", " pass"])
1538 test_file.write_bytes(contents.encode())
1539 ff(test_file, write_back=black.WriteBack.YES)
1540 updated_contents: bytes = test_file.read_bytes()
1541 self.assertIn(nl.encode(), updated_contents)
1543 self.assertNotIn(b"\r\n", updated_contents)
1545 def test_preserves_line_endings_via_stdin(self) -> None:
1546 for nl in ["\n", "\r\n"]:
1547 contents = nl.join(["def f( ):", " pass"])
1548 runner = BlackRunner()
1549 result = runner.invoke(
1550 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1552 self.assertEqual(result.exit_code, 0)
1553 output = runner.stdout_bytes
1554 self.assertIn(nl.encode("utf8"), output)
1556 self.assertNotIn(b"\r\n", output)
1558 def test_assert_equivalent_different_asts(self) -> None:
1559 with self.assertRaises(AssertionError):
1560 black.assert_equivalent("{}", "None")
1562 def test_symlink_out_of_root_directory(self) -> None:
1566 include = re.compile(black.DEFAULT_INCLUDES)
1567 exclude = re.compile(black.DEFAULT_EXCLUDES)
1568 report = black.Report()
1569 gitignore = PathSpec.from_lines("gitwildmatch", [])
1570 # `child` should behave like a symlink which resolved path is clearly
1571 # outside of the `root` directory.
1572 path.iterdir.return_value = [child]
1573 child.resolve.return_value = Path("/a/b/c")
1574 child.as_posix.return_value = "/a/b/c"
1575 child.is_symlink.return_value = True
1578 black.gen_python_files_in_dir(
1579 path, root, include, exclude, report, gitignore
1582 except ValueError as ve:
1583 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1584 path.iterdir.assert_called_once()
1585 child.resolve.assert_called_once()
1586 child.is_symlink.assert_called_once()
1587 # `child` should behave like a strange file which resolved path is clearly
1588 # outside of the `root` directory.
1589 child.is_symlink.return_value = False
1590 with self.assertRaises(ValueError):
1592 black.gen_python_files_in_dir(
1593 path, root, include, exclude, report, gitignore
1596 path.iterdir.assert_called()
1597 self.assertEqual(path.iterdir.call_count, 2)
1598 child.resolve.assert_called()
1599 self.assertEqual(child.resolve.call_count, 2)
1600 child.is_symlink.assert_called()
1601 self.assertEqual(child.is_symlink.call_count, 2)
1603 def test_shhh_click(self) -> None:
1605 from click import _unicodefun # type: ignore
1606 except ModuleNotFoundError:
1607 self.skipTest("Incompatible Click version")
1608 if not hasattr(_unicodefun, "_verify_python3_env"):
1609 self.skipTest("Incompatible Click version")
1610 # First, let's see if Click is crashing with a preferred ASCII charset.
1611 with patch("locale.getpreferredencoding") as gpe:
1612 gpe.return_value = "ASCII"
1613 with self.assertRaises(RuntimeError):
1614 _unicodefun._verify_python3_env()
1615 # Now, let's silence Click...
1617 # ...and confirm it's silent.
1618 with patch("locale.getpreferredencoding") as gpe:
1619 gpe.return_value = "ASCII"
1621 _unicodefun._verify_python3_env()
1622 except RuntimeError as re:
1623 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1625 def test_root_logger_not_used_directly(self) -> None:
1626 def fail(*args: Any, **kwargs: Any) -> None:
1627 self.fail("Record created with root logger")
1629 with patch.multiple(
1640 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1641 def test_blackd_main(self) -> None:
1642 with patch("blackd.web.run_app"):
1643 result = CliRunner().invoke(blackd.main, [])
1644 if result.exception is not None:
1645 raise result.exception
1646 self.assertEqual(result.exit_code, 0)
1648 def test_invalid_config_return_code(self) -> None:
1649 tmp_file = Path(black.dump_to_file())
1651 tmp_config = Path(black.dump_to_file())
1653 args = ["--config", str(tmp_config), str(tmp_file)]
1654 self.invokeBlack(args, exit_code=2, ignore_config=False)
1659 class BlackDTestCase(AioHTTPTestCase):
1660 async def get_application(self) -> web.Application:
1661 return blackd.make_app()
1663 # TODO: remove these decorators once the below is released
1664 # https://github.com/aio-libs/aiohttp/pull/3727
1665 @skip_if_exception("ClientOSError")
1666 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1668 async def test_blackd_request_needs_formatting(self) -> None:
1669 response = await self.client.post("/", data=b"print('hello world')")
1670 self.assertEqual(response.status, 200)
1671 self.assertEqual(response.charset, "utf8")
1672 self.assertEqual(await response.read(), b'print("hello world")\n')
1674 @skip_if_exception("ClientOSError")
1675 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1677 async def test_blackd_request_no_change(self) -> None:
1678 response = await self.client.post("/", data=b'print("hello world")\n')
1679 self.assertEqual(response.status, 204)
1680 self.assertEqual(await response.read(), b"")
1682 @skip_if_exception("ClientOSError")
1683 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1685 async def test_blackd_request_syntax_error(self) -> None:
1686 response = await self.client.post("/", data=b"what even ( is")
1687 self.assertEqual(response.status, 400)
1688 content = await response.text()
1690 content.startswith("Cannot parse"),
1691 msg=f"Expected error to start with 'Cannot parse', got {repr(content)}",
1694 @skip_if_exception("ClientOSError")
1695 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1697 async def test_blackd_unsupported_version(self) -> None:
1698 response = await self.client.post(
1699 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "2"}
1701 self.assertEqual(response.status, 501)
1703 @skip_if_exception("ClientOSError")
1704 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1706 async def test_blackd_supported_version(self) -> None:
1707 response = await self.client.post(
1708 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "1"}
1710 self.assertEqual(response.status, 200)
1712 @skip_if_exception("ClientOSError")
1713 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1715 async def test_blackd_invalid_python_variant(self) -> None:
1716 async def check(header_value: str, expected_status: int = 400) -> None:
1717 response = await self.client.post(
1718 "/", data=b"what", headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1720 self.assertEqual(response.status, expected_status)
1723 await check("ruby3.5")
1724 await check("pyi3.6")
1725 await check("py1.5")
1727 await check("py2.8")
1729 await check("pypy3.0")
1730 await check("jython3.4")
1732 @skip_if_exception("ClientOSError")
1733 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1735 async def test_blackd_pyi(self) -> None:
1736 source, expected = read_data("stub.pyi")
1737 response = await self.client.post(
1738 "/", data=source, headers={blackd.PYTHON_VARIANT_HEADER: "pyi"}
1740 self.assertEqual(response.status, 200)
1741 self.assertEqual(await response.text(), expected)
1743 @skip_if_exception("ClientOSError")
1744 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1746 async def test_blackd_diff(self) -> None:
1747 diff_header = re.compile(
1748 rf"(In|Out)\t\d\d\d\d-\d\d-\d\d "
1749 rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
1752 source, _ = read_data("blackd_diff.py")
1753 expected, _ = read_data("blackd_diff.diff")
1755 response = await self.client.post(
1756 "/", data=source, headers={blackd.DIFF_HEADER: "true"}
1758 self.assertEqual(response.status, 200)
1760 actual = await response.text()
1761 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
1762 self.assertEqual(actual, expected)
1764 @skip_if_exception("ClientOSError")
1765 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1767 async def test_blackd_python_variant(self) -> None:
1770 " and_has_a_bunch_of,\n"
1771 " very_long_arguments_too,\n"
1772 " and_lots_of_them_as_well_lol,\n"
1773 " **and_very_long_keyword_arguments\n"
1778 async def check(header_value: str, expected_status: int) -> None:
1779 response = await self.client.post(
1780 "/", data=code, headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1783 response.status, expected_status, msg=await response.text()
1786 await check("3.6", 200)
1787 await check("py3.6", 200)
1788 await check("3.6,3.7", 200)
1789 await check("3.6,py3.7", 200)
1790 await check("py36,py37", 200)
1791 await check("36", 200)
1792 await check("3.6.4", 200)
1794 await check("2", 204)
1795 await check("2.7", 204)
1796 await check("py2.7", 204)
1797 await check("3.4", 204)
1798 await check("py3.4", 204)
1799 await check("py34,py36", 204)
1800 await check("34", 204)
1802 @skip_if_exception("ClientOSError")
1803 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1805 async def test_blackd_line_length(self) -> None:
1806 response = await self.client.post(
1807 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "7"}
1809 self.assertEqual(response.status, 200)
1811 @skip_if_exception("ClientOSError")
1812 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1814 async def test_blackd_invalid_line_length(self) -> None:
1815 response = await self.client.post(
1816 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "NaN"}
1818 self.assertEqual(response.status, 400)
1820 @skip_if_exception("ClientOSError")
1821 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1823 async def test_blackd_response_black_version_header(self) -> None:
1824 response = await self.client.post("/")
1825 self.assertIsNotNone(response.headers.get(blackd.BLACK_VERSION_HEADER))
1828 if __name__ == "__main__":
1829 unittest.main(module="test_black")