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_fmtonoff(self) -> None:
603 source, expected = read_data("fmtonoff")
605 self.assertFormatEqual(expected, actual)
606 black.assert_equivalent(source, actual)
607 black.assert_stable(source, actual, black.FileMode())
609 @patch("black.dump_to_file", dump_to_stderr)
610 def test_fmtonoff2(self) -> None:
611 source, expected = read_data("fmtonoff2")
613 self.assertFormatEqual(expected, actual)
614 black.assert_equivalent(source, actual)
615 black.assert_stable(source, actual, black.FileMode())
617 @patch("black.dump_to_file", dump_to_stderr)
618 def test_fmtonoff3(self) -> None:
619 source, expected = read_data("fmtonoff3")
621 self.assertFormatEqual(expected, actual)
622 black.assert_equivalent(source, actual)
623 black.assert_stable(source, actual, black.FileMode())
625 @patch("black.dump_to_file", dump_to_stderr)
626 def test_remove_empty_parentheses_after_class(self) -> None:
627 source, expected = read_data("class_blank_parentheses")
629 self.assertFormatEqual(expected, actual)
630 black.assert_equivalent(source, actual)
631 black.assert_stable(source, actual, black.FileMode())
633 @patch("black.dump_to_file", dump_to_stderr)
634 def test_new_line_between_class_and_code(self) -> None:
635 source, expected = read_data("class_methods_new_line")
637 self.assertFormatEqual(expected, actual)
638 black.assert_equivalent(source, actual)
639 black.assert_stable(source, actual, black.FileMode())
641 @patch("black.dump_to_file", dump_to_stderr)
642 def test_bracket_match(self) -> None:
643 source, expected = read_data("bracketmatch")
645 self.assertFormatEqual(expected, actual)
646 black.assert_equivalent(source, actual)
647 black.assert_stable(source, actual, black.FileMode())
649 @patch("black.dump_to_file", dump_to_stderr)
650 def test_tuple_assign(self) -> None:
651 source, expected = read_data("tupleassign")
653 self.assertFormatEqual(expected, actual)
654 black.assert_equivalent(source, actual)
655 black.assert_stable(source, actual, black.FileMode())
657 @patch("black.dump_to_file", dump_to_stderr)
658 def test_beginning_backslash(self) -> None:
659 source, expected = read_data("beginning_backslash")
661 self.assertFormatEqual(expected, actual)
662 black.assert_equivalent(source, actual)
663 black.assert_stable(source, actual, black.FileMode())
665 def test_tab_comment_indentation(self) -> None:
666 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
667 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
668 self.assertFormatEqual(contents_spc, fs(contents_spc))
669 self.assertFormatEqual(contents_spc, fs(contents_tab))
671 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
672 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
673 self.assertFormatEqual(contents_spc, fs(contents_spc))
674 self.assertFormatEqual(contents_spc, fs(contents_tab))
676 # mixed tabs and spaces (valid Python 2 code)
677 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t# comment\n pass\n"
678 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
679 self.assertFormatEqual(contents_spc, fs(contents_spc))
680 self.assertFormatEqual(contents_spc, fs(contents_tab))
682 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t\t# comment\n pass\n"
683 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
684 self.assertFormatEqual(contents_spc, fs(contents_spc))
685 self.assertFormatEqual(contents_spc, fs(contents_tab))
687 def test_report_verbose(self) -> None:
688 report = black.Report(verbose=True)
692 def out(msg: str, **kwargs: Any) -> None:
693 out_lines.append(msg)
695 def err(msg: str, **kwargs: Any) -> None:
696 err_lines.append(msg)
698 with patch("black.out", out), patch("black.err", err):
699 report.done(Path("f1"), black.Changed.NO)
700 self.assertEqual(len(out_lines), 1)
701 self.assertEqual(len(err_lines), 0)
702 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
703 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
704 self.assertEqual(report.return_code, 0)
705 report.done(Path("f2"), black.Changed.YES)
706 self.assertEqual(len(out_lines), 2)
707 self.assertEqual(len(err_lines), 0)
708 self.assertEqual(out_lines[-1], "reformatted f2")
710 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
712 report.done(Path("f3"), black.Changed.CACHED)
713 self.assertEqual(len(out_lines), 3)
714 self.assertEqual(len(err_lines), 0)
716 out_lines[-1], "f3 wasn't modified on disk since last run."
719 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
721 self.assertEqual(report.return_code, 0)
723 self.assertEqual(report.return_code, 1)
725 report.failed(Path("e1"), "boom")
726 self.assertEqual(len(out_lines), 3)
727 self.assertEqual(len(err_lines), 1)
728 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
730 unstyle(str(report)),
731 "1 file reformatted, 2 files left unchanged, "
732 "1 file failed to reformat.",
734 self.assertEqual(report.return_code, 123)
735 report.done(Path("f3"), black.Changed.YES)
736 self.assertEqual(len(out_lines), 4)
737 self.assertEqual(len(err_lines), 1)
738 self.assertEqual(out_lines[-1], "reformatted f3")
740 unstyle(str(report)),
741 "2 files reformatted, 2 files left unchanged, "
742 "1 file failed to reformat.",
744 self.assertEqual(report.return_code, 123)
745 report.failed(Path("e2"), "boom")
746 self.assertEqual(len(out_lines), 4)
747 self.assertEqual(len(err_lines), 2)
748 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
750 unstyle(str(report)),
751 "2 files reformatted, 2 files left unchanged, "
752 "2 files failed to reformat.",
754 self.assertEqual(report.return_code, 123)
755 report.path_ignored(Path("wat"), "no match")
756 self.assertEqual(len(out_lines), 5)
757 self.assertEqual(len(err_lines), 2)
758 self.assertEqual(out_lines[-1], "wat ignored: no match")
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.done(Path("f4"), black.Changed.NO)
766 self.assertEqual(len(out_lines), 6)
767 self.assertEqual(len(err_lines), 2)
768 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
770 unstyle(str(report)),
771 "2 files reformatted, 3 files left unchanged, "
772 "2 files failed to reformat.",
774 self.assertEqual(report.return_code, 123)
777 unstyle(str(report)),
778 "2 files would be reformatted, 3 files would be left unchanged, "
779 "2 files would fail to reformat.",
784 unstyle(str(report)),
785 "2 files would be reformatted, 3 files would be left unchanged, "
786 "2 files would fail to reformat.",
789 def test_report_quiet(self) -> None:
790 report = black.Report(quiet=True)
794 def out(msg: str, **kwargs: Any) -> None:
795 out_lines.append(msg)
797 def err(msg: str, **kwargs: Any) -> None:
798 err_lines.append(msg)
800 with patch("black.out", out), patch("black.err", err):
801 report.done(Path("f1"), black.Changed.NO)
802 self.assertEqual(len(out_lines), 0)
803 self.assertEqual(len(err_lines), 0)
804 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
805 self.assertEqual(report.return_code, 0)
806 report.done(Path("f2"), black.Changed.YES)
807 self.assertEqual(len(out_lines), 0)
808 self.assertEqual(len(err_lines), 0)
810 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
812 report.done(Path("f3"), black.Changed.CACHED)
813 self.assertEqual(len(out_lines), 0)
814 self.assertEqual(len(err_lines), 0)
816 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
818 self.assertEqual(report.return_code, 0)
820 self.assertEqual(report.return_code, 1)
822 report.failed(Path("e1"), "boom")
823 self.assertEqual(len(out_lines), 0)
824 self.assertEqual(len(err_lines), 1)
825 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
827 unstyle(str(report)),
828 "1 file reformatted, 2 files left unchanged, "
829 "1 file failed to reformat.",
831 self.assertEqual(report.return_code, 123)
832 report.done(Path("f3"), black.Changed.YES)
833 self.assertEqual(len(out_lines), 0)
834 self.assertEqual(len(err_lines), 1)
836 unstyle(str(report)),
837 "2 files reformatted, 2 files left unchanged, "
838 "1 file failed to reformat.",
840 self.assertEqual(report.return_code, 123)
841 report.failed(Path("e2"), "boom")
842 self.assertEqual(len(out_lines), 0)
843 self.assertEqual(len(err_lines), 2)
844 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
846 unstyle(str(report)),
847 "2 files reformatted, 2 files left unchanged, "
848 "2 files failed to reformat.",
850 self.assertEqual(report.return_code, 123)
851 report.path_ignored(Path("wat"), "no match")
852 self.assertEqual(len(out_lines), 0)
853 self.assertEqual(len(err_lines), 2)
855 unstyle(str(report)),
856 "2 files reformatted, 2 files left unchanged, "
857 "2 files failed to reformat.",
859 self.assertEqual(report.return_code, 123)
860 report.done(Path("f4"), black.Changed.NO)
861 self.assertEqual(len(out_lines), 0)
862 self.assertEqual(len(err_lines), 2)
864 unstyle(str(report)),
865 "2 files reformatted, 3 files left unchanged, "
866 "2 files failed to reformat.",
868 self.assertEqual(report.return_code, 123)
871 unstyle(str(report)),
872 "2 files would be reformatted, 3 files would be left unchanged, "
873 "2 files would fail to reformat.",
878 unstyle(str(report)),
879 "2 files would be reformatted, 3 files would be left unchanged, "
880 "2 files would fail to reformat.",
883 def test_report_normal(self) -> None:
884 report = black.Report()
888 def out(msg: str, **kwargs: Any) -> None:
889 out_lines.append(msg)
891 def err(msg: str, **kwargs: Any) -> None:
892 err_lines.append(msg)
894 with patch("black.out", out), patch("black.err", err):
895 report.done(Path("f1"), black.Changed.NO)
896 self.assertEqual(len(out_lines), 0)
897 self.assertEqual(len(err_lines), 0)
898 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
899 self.assertEqual(report.return_code, 0)
900 report.done(Path("f2"), black.Changed.YES)
901 self.assertEqual(len(out_lines), 1)
902 self.assertEqual(len(err_lines), 0)
903 self.assertEqual(out_lines[-1], "reformatted f2")
905 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
907 report.done(Path("f3"), black.Changed.CACHED)
908 self.assertEqual(len(out_lines), 1)
909 self.assertEqual(len(err_lines), 0)
910 self.assertEqual(out_lines[-1], "reformatted f2")
912 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
914 self.assertEqual(report.return_code, 0)
916 self.assertEqual(report.return_code, 1)
918 report.failed(Path("e1"), "boom")
919 self.assertEqual(len(out_lines), 1)
920 self.assertEqual(len(err_lines), 1)
921 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
923 unstyle(str(report)),
924 "1 file reformatted, 2 files left unchanged, "
925 "1 file failed to reformat.",
927 self.assertEqual(report.return_code, 123)
928 report.done(Path("f3"), black.Changed.YES)
929 self.assertEqual(len(out_lines), 2)
930 self.assertEqual(len(err_lines), 1)
931 self.assertEqual(out_lines[-1], "reformatted f3")
933 unstyle(str(report)),
934 "2 files reformatted, 2 files left unchanged, "
935 "1 file failed to reformat.",
937 self.assertEqual(report.return_code, 123)
938 report.failed(Path("e2"), "boom")
939 self.assertEqual(len(out_lines), 2)
940 self.assertEqual(len(err_lines), 2)
941 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
943 unstyle(str(report)),
944 "2 files reformatted, 2 files left unchanged, "
945 "2 files failed to reformat.",
947 self.assertEqual(report.return_code, 123)
948 report.path_ignored(Path("wat"), "no match")
949 self.assertEqual(len(out_lines), 2)
950 self.assertEqual(len(err_lines), 2)
952 unstyle(str(report)),
953 "2 files reformatted, 2 files left unchanged, "
954 "2 files failed to reformat.",
956 self.assertEqual(report.return_code, 123)
957 report.done(Path("f4"), black.Changed.NO)
958 self.assertEqual(len(out_lines), 2)
959 self.assertEqual(len(err_lines), 2)
961 unstyle(str(report)),
962 "2 files reformatted, 3 files left unchanged, "
963 "2 files failed to reformat.",
965 self.assertEqual(report.return_code, 123)
968 unstyle(str(report)),
969 "2 files would be reformatted, 3 files would be left unchanged, "
970 "2 files would fail to reformat.",
975 unstyle(str(report)),
976 "2 files would be reformatted, 3 files would be left unchanged, "
977 "2 files would fail to reformat.",
980 def test_lib2to3_parse(self) -> None:
981 with self.assertRaises(black.InvalidInput):
982 black.lib2to3_parse("invalid syntax")
985 black.lib2to3_parse(straddling)
986 black.lib2to3_parse(straddling, {TargetVersion.PY27})
987 black.lib2to3_parse(straddling, {TargetVersion.PY36})
988 black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
991 black.lib2to3_parse(py2_only)
992 black.lib2to3_parse(py2_only, {TargetVersion.PY27})
993 with self.assertRaises(black.InvalidInput):
994 black.lib2to3_parse(py2_only, {TargetVersion.PY36})
995 with self.assertRaises(black.InvalidInput):
996 black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
998 py3_only = "exec(x, end=y)"
999 black.lib2to3_parse(py3_only)
1000 with self.assertRaises(black.InvalidInput):
1001 black.lib2to3_parse(py3_only, {TargetVersion.PY27})
1002 black.lib2to3_parse(py3_only, {TargetVersion.PY36})
1003 black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
1005 def test_get_features_used(self) -> None:
1006 node = black.lib2to3_parse("def f(*, arg): ...\n")
1007 self.assertEqual(black.get_features_used(node), set())
1008 node = black.lib2to3_parse("def f(*, arg,): ...\n")
1009 self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
1010 node = black.lib2to3_parse("f(*arg,)\n")
1012 black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
1014 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
1015 self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
1016 node = black.lib2to3_parse("123_456\n")
1017 self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
1018 node = black.lib2to3_parse("123456\n")
1019 self.assertEqual(black.get_features_used(node), set())
1020 source, expected = read_data("function")
1021 node = black.lib2to3_parse(source)
1022 expected_features = {
1023 Feature.TRAILING_COMMA_IN_CALL,
1024 Feature.TRAILING_COMMA_IN_DEF,
1027 self.assertEqual(black.get_features_used(node), expected_features)
1028 node = black.lib2to3_parse(expected)
1029 self.assertEqual(black.get_features_used(node), expected_features)
1030 source, expected = read_data("expression")
1031 node = black.lib2to3_parse(source)
1032 self.assertEqual(black.get_features_used(node), set())
1033 node = black.lib2to3_parse(expected)
1034 self.assertEqual(black.get_features_used(node), set())
1036 def test_get_future_imports(self) -> None:
1037 node = black.lib2to3_parse("\n")
1038 self.assertEqual(set(), black.get_future_imports(node))
1039 node = black.lib2to3_parse("from __future__ import black\n")
1040 self.assertEqual({"black"}, black.get_future_imports(node))
1041 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
1042 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1043 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
1044 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
1045 node = black.lib2to3_parse(
1046 "from __future__ import multiple\nfrom __future__ import imports\n"
1048 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1049 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
1050 self.assertEqual({"black"}, black.get_future_imports(node))
1051 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
1052 self.assertEqual({"black"}, black.get_future_imports(node))
1053 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
1054 self.assertEqual(set(), black.get_future_imports(node))
1055 node = black.lib2to3_parse("from some.module import black\n")
1056 self.assertEqual(set(), black.get_future_imports(node))
1057 node = black.lib2to3_parse(
1058 "from __future__ import unicode_literals as _unicode_literals"
1060 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
1061 node = black.lib2to3_parse(
1062 "from __future__ import unicode_literals as _lol, print"
1064 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
1066 def test_debug_visitor(self) -> None:
1067 source, _ = read_data("debug_visitor.py")
1068 expected, _ = read_data("debug_visitor.out")
1072 def out(msg: str, **kwargs: Any) -> None:
1073 out_lines.append(msg)
1075 def err(msg: str, **kwargs: Any) -> None:
1076 err_lines.append(msg)
1078 with patch("black.out", out), patch("black.err", err):
1079 black.DebugVisitor.show(source)
1080 actual = "\n".join(out_lines) + "\n"
1082 if expected != actual:
1083 log_name = black.dump_to_file(*out_lines)
1087 f"AST print out is different. Actual version dumped to {log_name}",
1090 def test_format_file_contents(self) -> None:
1092 mode = black.FileMode()
1093 with self.assertRaises(black.NothingChanged):
1094 black.format_file_contents(empty, mode=mode, fast=False)
1096 with self.assertRaises(black.NothingChanged):
1097 black.format_file_contents(just_nl, mode=mode, fast=False)
1098 same = "j = [1, 2, 3]\n"
1099 with self.assertRaises(black.NothingChanged):
1100 black.format_file_contents(same, mode=mode, fast=False)
1101 different = "j = [1,2,3]"
1103 actual = black.format_file_contents(different, mode=mode, fast=False)
1104 self.assertEqual(expected, actual)
1105 invalid = "return if you can"
1106 with self.assertRaises(black.InvalidInput) as e:
1107 black.format_file_contents(invalid, mode=mode, fast=False)
1108 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
1110 def test_endmarker(self) -> None:
1111 n = black.lib2to3_parse("\n")
1112 self.assertEqual(n.type, black.syms.file_input)
1113 self.assertEqual(len(n.children), 1)
1114 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
1116 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
1117 def test_assertFormatEqual(self) -> None:
1121 def out(msg: str, **kwargs: Any) -> None:
1122 out_lines.append(msg)
1124 def err(msg: str, **kwargs: Any) -> None:
1125 err_lines.append(msg)
1127 with patch("black.out", out), patch("black.err", err):
1128 with self.assertRaises(AssertionError):
1129 self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")
1131 out_str = "".join(out_lines)
1132 self.assertTrue("Expected tree:" in out_str)
1133 self.assertTrue("Actual tree:" in out_str)
1134 self.assertEqual("".join(err_lines), "")
1136 def test_cache_broken_file(self) -> None:
1137 mode = black.FileMode()
1138 with cache_dir() as workspace:
1139 cache_file = black.get_cache_file(mode)
1140 with cache_file.open("w") as fobj:
1141 fobj.write("this is not a pickle")
1142 self.assertEqual(black.read_cache(mode), {})
1143 src = (workspace / "test.py").resolve()
1144 with src.open("w") as fobj:
1145 fobj.write("print('hello')")
1146 self.invokeBlack([str(src)])
1147 cache = black.read_cache(mode)
1148 self.assertIn(src, cache)
1150 def test_cache_single_file_already_cached(self) -> None:
1151 mode = black.FileMode()
1152 with cache_dir() as workspace:
1153 src = (workspace / "test.py").resolve()
1154 with src.open("w") as fobj:
1155 fobj.write("print('hello')")
1156 black.write_cache({}, [src], mode)
1157 self.invokeBlack([str(src)])
1158 with src.open("r") as fobj:
1159 self.assertEqual(fobj.read(), "print('hello')")
1161 @event_loop(close=False)
1162 def test_cache_multiple_files(self) -> None:
1163 mode = black.FileMode()
1164 with cache_dir() as workspace, patch(
1165 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1167 one = (workspace / "one.py").resolve()
1168 with one.open("w") as fobj:
1169 fobj.write("print('hello')")
1170 two = (workspace / "two.py").resolve()
1171 with two.open("w") as fobj:
1172 fobj.write("print('hello')")
1173 black.write_cache({}, [one], mode)
1174 self.invokeBlack([str(workspace)])
1175 with one.open("r") as fobj:
1176 self.assertEqual(fobj.read(), "print('hello')")
1177 with two.open("r") as fobj:
1178 self.assertEqual(fobj.read(), 'print("hello")\n')
1179 cache = black.read_cache(mode)
1180 self.assertIn(one, cache)
1181 self.assertIn(two, cache)
1183 def test_no_cache_when_writeback_diff(self) -> None:
1184 mode = black.FileMode()
1185 with cache_dir() as workspace:
1186 src = (workspace / "test.py").resolve()
1187 with src.open("w") as fobj:
1188 fobj.write("print('hello')")
1189 self.invokeBlack([str(src), "--diff"])
1190 cache_file = black.get_cache_file(mode)
1191 self.assertFalse(cache_file.exists())
1193 def test_no_cache_when_stdin(self) -> None:
1194 mode = black.FileMode()
1196 result = CliRunner().invoke(
1197 black.main, ["-"], input=BytesIO(b"print('hello')")
1199 self.assertEqual(result.exit_code, 0)
1200 cache_file = black.get_cache_file(mode)
1201 self.assertFalse(cache_file.exists())
1203 def test_read_cache_no_cachefile(self) -> None:
1204 mode = black.FileMode()
1206 self.assertEqual(black.read_cache(mode), {})
1208 def test_write_cache_read_cache(self) -> None:
1209 mode = black.FileMode()
1210 with cache_dir() as workspace:
1211 src = (workspace / "test.py").resolve()
1213 black.write_cache({}, [src], mode)
1214 cache = black.read_cache(mode)
1215 self.assertIn(src, cache)
1216 self.assertEqual(cache[src], black.get_cache_info(src))
1218 def test_filter_cached(self) -> None:
1219 with TemporaryDirectory() as workspace:
1220 path = Path(workspace)
1221 uncached = (path / "uncached").resolve()
1222 cached = (path / "cached").resolve()
1223 cached_but_changed = (path / "changed").resolve()
1226 cached_but_changed.touch()
1227 cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
1228 todo, done = black.filter_cached(
1229 cache, {uncached, cached, cached_but_changed}
1231 self.assertEqual(todo, {uncached, cached_but_changed})
1232 self.assertEqual(done, {cached})
1234 def test_write_cache_creates_directory_if_needed(self) -> None:
1235 mode = black.FileMode()
1236 with cache_dir(exists=False) as workspace:
1237 self.assertFalse(workspace.exists())
1238 black.write_cache({}, [], mode)
1239 self.assertTrue(workspace.exists())
1241 @event_loop(close=False)
1242 def test_failed_formatting_does_not_get_cached(self) -> None:
1243 mode = black.FileMode()
1244 with cache_dir() as workspace, patch(
1245 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1247 failing = (workspace / "failing.py").resolve()
1248 with failing.open("w") as fobj:
1249 fobj.write("not actually python")
1250 clean = (workspace / "clean.py").resolve()
1251 with clean.open("w") as fobj:
1252 fobj.write('print("hello")\n')
1253 self.invokeBlack([str(workspace)], exit_code=123)
1254 cache = black.read_cache(mode)
1255 self.assertNotIn(failing, cache)
1256 self.assertIn(clean, cache)
1258 def test_write_cache_write_fail(self) -> None:
1259 mode = black.FileMode()
1260 with cache_dir(), patch.object(Path, "open") as mock:
1261 mock.side_effect = OSError
1262 black.write_cache({}, [], mode)
1264 @event_loop(close=False)
1265 def test_check_diff_use_together(self) -> None:
1267 # Files which will be reformatted.
1268 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1269 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1270 # Files which will not be reformatted.
1271 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1272 self.invokeBlack([str(src2), "--diff", "--check"])
1273 # Multi file command.
1274 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1276 def test_no_files(self) -> None:
1278 # Without an argument, black exits with error code 0.
1279 self.invokeBlack([])
1281 def test_broken_symlink(self) -> None:
1282 with cache_dir() as workspace:
1283 symlink = workspace / "broken_link.py"
1285 symlink.symlink_to("nonexistent.py")
1286 except OSError as e:
1287 self.skipTest(f"Can't create symlinks: {e}")
1288 self.invokeBlack([str(workspace.resolve())])
1290 def test_read_cache_line_lengths(self) -> None:
1291 mode = black.FileMode()
1292 short_mode = black.FileMode(line_length=1)
1293 with cache_dir() as workspace:
1294 path = (workspace / "file.py").resolve()
1296 black.write_cache({}, [path], mode)
1297 one = black.read_cache(mode)
1298 self.assertIn(path, one)
1299 two = black.read_cache(short_mode)
1300 self.assertNotIn(path, two)
1302 def test_tricky_unicode_symbols(self) -> None:
1303 source, expected = read_data("tricky_unicode_symbols")
1305 self.assertFormatEqual(expected, actual)
1306 black.assert_equivalent(source, actual)
1307 black.assert_stable(source, actual, black.FileMode())
1309 def test_single_file_force_pyi(self) -> None:
1310 reg_mode = black.FileMode()
1311 pyi_mode = black.FileMode(is_pyi=True)
1312 contents, expected = read_data("force_pyi")
1313 with cache_dir() as workspace:
1314 path = (workspace / "file.py").resolve()
1315 with open(path, "w") as fh:
1317 self.invokeBlack([str(path), "--pyi"])
1318 with open(path, "r") as fh:
1320 # verify cache with --pyi is separate
1321 pyi_cache = black.read_cache(pyi_mode)
1322 self.assertIn(path, pyi_cache)
1323 normal_cache = black.read_cache(reg_mode)
1324 self.assertNotIn(path, normal_cache)
1325 self.assertEqual(actual, expected)
1327 @event_loop(close=False)
1328 def test_multi_file_force_pyi(self) -> None:
1329 reg_mode = black.FileMode()
1330 pyi_mode = black.FileMode(is_pyi=True)
1331 contents, expected = read_data("force_pyi")
1332 with cache_dir() as workspace:
1334 (workspace / "file1.py").resolve(),
1335 (workspace / "file2.py").resolve(),
1338 with open(path, "w") as fh:
1340 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1342 with open(path, "r") as fh:
1344 self.assertEqual(actual, expected)
1345 # verify cache with --pyi is separate
1346 pyi_cache = black.read_cache(pyi_mode)
1347 normal_cache = black.read_cache(reg_mode)
1349 self.assertIn(path, pyi_cache)
1350 self.assertNotIn(path, normal_cache)
1352 def test_pipe_force_pyi(self) -> None:
1353 source, expected = read_data("force_pyi")
1354 result = CliRunner().invoke(
1355 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1357 self.assertEqual(result.exit_code, 0)
1358 actual = result.output
1359 self.assertFormatEqual(actual, expected)
1361 def test_single_file_force_py36(self) -> None:
1362 reg_mode = black.FileMode()
1363 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1364 source, expected = read_data("force_py36")
1365 with cache_dir() as workspace:
1366 path = (workspace / "file.py").resolve()
1367 with open(path, "w") as fh:
1369 self.invokeBlack([str(path), *PY36_ARGS])
1370 with open(path, "r") as fh:
1372 # verify cache with --target-version is separate
1373 py36_cache = black.read_cache(py36_mode)
1374 self.assertIn(path, py36_cache)
1375 normal_cache = black.read_cache(reg_mode)
1376 self.assertNotIn(path, normal_cache)
1377 self.assertEqual(actual, expected)
1379 @event_loop(close=False)
1380 def test_multi_file_force_py36(self) -> None:
1381 reg_mode = black.FileMode()
1382 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1383 source, expected = read_data("force_py36")
1384 with cache_dir() as workspace:
1386 (workspace / "file1.py").resolve(),
1387 (workspace / "file2.py").resolve(),
1390 with open(path, "w") as fh:
1392 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1394 with open(path, "r") as fh:
1396 self.assertEqual(actual, expected)
1397 # verify cache with --target-version is separate
1398 pyi_cache = black.read_cache(py36_mode)
1399 normal_cache = black.read_cache(reg_mode)
1401 self.assertIn(path, pyi_cache)
1402 self.assertNotIn(path, normal_cache)
1404 def test_collections(self) -> None:
1405 source, expected = read_data("collections")
1407 self.assertFormatEqual(expected, actual)
1408 black.assert_equivalent(source, actual)
1409 black.assert_stable(source, actual, black.FileMode())
1411 def test_pipe_force_py36(self) -> None:
1412 source, expected = read_data("force_py36")
1413 result = CliRunner().invoke(
1415 ["-", "-q", "--target-version=py36"],
1416 input=BytesIO(source.encode("utf8")),
1418 self.assertEqual(result.exit_code, 0)
1419 actual = result.output
1420 self.assertFormatEqual(actual, expected)
1422 def test_include_exclude(self) -> None:
1423 path = THIS_DIR / "data" / "include_exclude_tests"
1424 include = re.compile(r"\.pyi?$")
1425 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1426 report = black.Report()
1427 gitignore = PathSpec.from_lines("gitwildmatch", [])
1428 sources: List[Path] = []
1430 Path(path / "b/dont_exclude/a.py"),
1431 Path(path / "b/dont_exclude/a.pyi"),
1433 this_abs = THIS_DIR.resolve()
1435 black.gen_python_files_in_dir(
1436 path, this_abs, include, exclude, report, gitignore
1439 self.assertEqual(sorted(expected), sorted(sources))
1441 def test_gitignore_exclude(self) -> None:
1442 path = THIS_DIR / "data" / "include_exclude_tests"
1443 include = re.compile(r"\.pyi?$")
1444 exclude = re.compile(r"")
1445 report = black.Report()
1446 gitignore = PathSpec.from_lines(
1447 "gitwildmatch", ["exclude/", ".definitely_exclude"]
1449 sources: List[Path] = []
1451 Path(path / "b/dont_exclude/a.py"),
1452 Path(path / "b/dont_exclude/a.pyi"),
1454 this_abs = THIS_DIR.resolve()
1456 black.gen_python_files_in_dir(
1457 path, this_abs, include, exclude, report, gitignore
1460 self.assertEqual(sorted(expected), sorted(sources))
1462 def test_empty_include(self) -> None:
1463 path = THIS_DIR / "data" / "include_exclude_tests"
1464 report = black.Report()
1465 gitignore = PathSpec.from_lines("gitwildmatch", [])
1466 empty = re.compile(r"")
1467 sources: List[Path] = []
1469 Path(path / "b/exclude/a.pie"),
1470 Path(path / "b/exclude/a.py"),
1471 Path(path / "b/exclude/a.pyi"),
1472 Path(path / "b/dont_exclude/a.pie"),
1473 Path(path / "b/dont_exclude/a.py"),
1474 Path(path / "b/dont_exclude/a.pyi"),
1475 Path(path / "b/.definitely_exclude/a.pie"),
1476 Path(path / "b/.definitely_exclude/a.py"),
1477 Path(path / "b/.definitely_exclude/a.pyi"),
1479 this_abs = THIS_DIR.resolve()
1481 black.gen_python_files_in_dir(
1485 re.compile(black.DEFAULT_EXCLUDES),
1490 self.assertEqual(sorted(expected), sorted(sources))
1492 def test_empty_exclude(self) -> None:
1493 path = THIS_DIR / "data" / "include_exclude_tests"
1494 report = black.Report()
1495 gitignore = PathSpec.from_lines("gitwildmatch", [])
1496 empty = re.compile(r"")
1497 sources: List[Path] = []
1499 Path(path / "b/dont_exclude/a.py"),
1500 Path(path / "b/dont_exclude/a.pyi"),
1501 Path(path / "b/exclude/a.py"),
1502 Path(path / "b/exclude/a.pyi"),
1503 Path(path / "b/.definitely_exclude/a.py"),
1504 Path(path / "b/.definitely_exclude/a.pyi"),
1506 this_abs = THIS_DIR.resolve()
1508 black.gen_python_files_in_dir(
1511 re.compile(black.DEFAULT_INCLUDES),
1517 self.assertEqual(sorted(expected), sorted(sources))
1519 def test_invalid_include_exclude(self) -> None:
1520 for option in ["--include", "--exclude"]:
1521 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1523 def test_preserves_line_endings(self) -> None:
1524 with TemporaryDirectory() as workspace:
1525 test_file = Path(workspace) / "test.py"
1526 for nl in ["\n", "\r\n"]:
1527 contents = nl.join(["def f( ):", " pass"])
1528 test_file.write_bytes(contents.encode())
1529 ff(test_file, write_back=black.WriteBack.YES)
1530 updated_contents: bytes = test_file.read_bytes()
1531 self.assertIn(nl.encode(), updated_contents)
1533 self.assertNotIn(b"\r\n", updated_contents)
1535 def test_preserves_line_endings_via_stdin(self) -> None:
1536 for nl in ["\n", "\r\n"]:
1537 contents = nl.join(["def f( ):", " pass"])
1538 runner = BlackRunner()
1539 result = runner.invoke(
1540 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1542 self.assertEqual(result.exit_code, 0)
1543 output = runner.stdout_bytes
1544 self.assertIn(nl.encode("utf8"), output)
1546 self.assertNotIn(b"\r\n", output)
1548 def test_assert_equivalent_different_asts(self) -> None:
1549 with self.assertRaises(AssertionError):
1550 black.assert_equivalent("{}", "None")
1552 def test_symlink_out_of_root_directory(self) -> None:
1556 include = re.compile(black.DEFAULT_INCLUDES)
1557 exclude = re.compile(black.DEFAULT_EXCLUDES)
1558 report = black.Report()
1559 gitignore = PathSpec.from_lines("gitwildmatch", [])
1560 # `child` should behave like a symlink which resolved path is clearly
1561 # outside of the `root` directory.
1562 path.iterdir.return_value = [child]
1563 child.resolve.return_value = Path("/a/b/c")
1564 child.as_posix.return_value = "/a/b/c"
1565 child.is_symlink.return_value = True
1568 black.gen_python_files_in_dir(
1569 path, root, include, exclude, report, gitignore
1572 except ValueError as ve:
1573 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1574 path.iterdir.assert_called_once()
1575 child.resolve.assert_called_once()
1576 child.is_symlink.assert_called_once()
1577 # `child` should behave like a strange file which resolved path is clearly
1578 # outside of the `root` directory.
1579 child.is_symlink.return_value = False
1580 with self.assertRaises(ValueError):
1582 black.gen_python_files_in_dir(
1583 path, root, include, exclude, report, gitignore
1586 path.iterdir.assert_called()
1587 self.assertEqual(path.iterdir.call_count, 2)
1588 child.resolve.assert_called()
1589 self.assertEqual(child.resolve.call_count, 2)
1590 child.is_symlink.assert_called()
1591 self.assertEqual(child.is_symlink.call_count, 2)
1593 def test_shhh_click(self) -> None:
1595 from click import _unicodefun # type: ignore
1596 except ModuleNotFoundError:
1597 self.skipTest("Incompatible Click version")
1598 if not hasattr(_unicodefun, "_verify_python3_env"):
1599 self.skipTest("Incompatible Click version")
1600 # First, let's see if Click is crashing with a preferred ASCII charset.
1601 with patch("locale.getpreferredencoding") as gpe:
1602 gpe.return_value = "ASCII"
1603 with self.assertRaises(RuntimeError):
1604 _unicodefun._verify_python3_env()
1605 # Now, let's silence Click...
1607 # ...and confirm it's silent.
1608 with patch("locale.getpreferredencoding") as gpe:
1609 gpe.return_value = "ASCII"
1611 _unicodefun._verify_python3_env()
1612 except RuntimeError as re:
1613 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1615 def test_root_logger_not_used_directly(self) -> None:
1616 def fail(*args: Any, **kwargs: Any) -> None:
1617 self.fail("Record created with root logger")
1619 with patch.multiple(
1630 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1631 def test_blackd_main(self) -> None:
1632 with patch("blackd.web.run_app"):
1633 result = CliRunner().invoke(blackd.main, [])
1634 if result.exception is not None:
1635 raise result.exception
1636 self.assertEqual(result.exit_code, 0)
1639 class BlackDTestCase(AioHTTPTestCase):
1640 async def get_application(self) -> web.Application:
1641 return blackd.make_app()
1643 # TODO: remove these decorators once the below is released
1644 # https://github.com/aio-libs/aiohttp/pull/3727
1645 @skip_if_exception("ClientOSError")
1646 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1648 async def test_blackd_request_needs_formatting(self) -> None:
1649 response = await self.client.post("/", data=b"print('hello world')")
1650 self.assertEqual(response.status, 200)
1651 self.assertEqual(response.charset, "utf8")
1652 self.assertEqual(await response.read(), b'print("hello world")\n')
1654 @skip_if_exception("ClientOSError")
1655 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1657 async def test_blackd_request_no_change(self) -> None:
1658 response = await self.client.post("/", data=b'print("hello world")\n')
1659 self.assertEqual(response.status, 204)
1660 self.assertEqual(await response.read(), b"")
1662 @skip_if_exception("ClientOSError")
1663 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1665 async def test_blackd_request_syntax_error(self) -> None:
1666 response = await self.client.post("/", data=b"what even ( is")
1667 self.assertEqual(response.status, 400)
1668 content = await response.text()
1670 content.startswith("Cannot parse"),
1671 msg=f"Expected error to start with 'Cannot parse', got {repr(content)}",
1674 @skip_if_exception("ClientOSError")
1675 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1677 async def test_blackd_unsupported_version(self) -> None:
1678 response = await self.client.post(
1679 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "2"}
1681 self.assertEqual(response.status, 501)
1683 @skip_if_exception("ClientOSError")
1684 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1686 async def test_blackd_supported_version(self) -> None:
1687 response = await self.client.post(
1688 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "1"}
1690 self.assertEqual(response.status, 200)
1692 @skip_if_exception("ClientOSError")
1693 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1695 async def test_blackd_invalid_python_variant(self) -> None:
1696 async def check(header_value: str, expected_status: int = 400) -> None:
1697 response = await self.client.post(
1698 "/", data=b"what", headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1700 self.assertEqual(response.status, expected_status)
1703 await check("ruby3.5")
1704 await check("pyi3.6")
1705 await check("py1.5")
1707 await check("py2.8")
1709 await check("pypy3.0")
1710 await check("jython3.4")
1712 @skip_if_exception("ClientOSError")
1713 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1715 async def test_blackd_pyi(self) -> None:
1716 source, expected = read_data("stub.pyi")
1717 response = await self.client.post(
1718 "/", data=source, headers={blackd.PYTHON_VARIANT_HEADER: "pyi"}
1720 self.assertEqual(response.status, 200)
1721 self.assertEqual(await response.text(), expected)
1723 @skip_if_exception("ClientOSError")
1724 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1726 async def test_blackd_diff(self) -> None:
1727 diff_header = re.compile(
1728 rf"(In|Out)\t\d\d\d\d-\d\d-\d\d "
1729 rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
1732 source, _ = read_data("blackd_diff.py")
1733 expected, _ = read_data("blackd_diff.diff")
1735 response = await self.client.post(
1736 "/", data=source, headers={blackd.DIFF_HEADER: "true"}
1738 self.assertEqual(response.status, 200)
1740 actual = await response.text()
1741 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
1742 self.assertEqual(actual, expected)
1744 @skip_if_exception("ClientOSError")
1745 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1747 async def test_blackd_python_variant(self) -> None:
1750 " and_has_a_bunch_of,\n"
1751 " very_long_arguments_too,\n"
1752 " and_lots_of_them_as_well_lol,\n"
1753 " **and_very_long_keyword_arguments\n"
1758 async def check(header_value: str, expected_status: int) -> None:
1759 response = await self.client.post(
1760 "/", data=code, headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1763 response.status, expected_status, msg=await response.text()
1766 await check("3.6", 200)
1767 await check("py3.6", 200)
1768 await check("3.6,3.7", 200)
1769 await check("3.6,py3.7", 200)
1770 await check("py36,py37", 200)
1771 await check("36", 200)
1772 await check("3.6.4", 200)
1774 await check("2", 204)
1775 await check("2.7", 204)
1776 await check("py2.7", 204)
1777 await check("3.4", 204)
1778 await check("py3.4", 204)
1779 await check("py34,py36", 204)
1780 await check("34", 204)
1782 @skip_if_exception("ClientOSError")
1783 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1785 async def test_blackd_line_length(self) -> None:
1786 response = await self.client.post(
1787 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "7"}
1789 self.assertEqual(response.status, 200)
1791 @skip_if_exception("ClientOSError")
1792 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1794 async def test_blackd_invalid_line_length(self) -> None:
1795 response = await self.client.post(
1796 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "NaN"}
1798 self.assertEqual(response.status, 400)
1800 @skip_if_exception("ClientOSError")
1801 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1803 async def test_blackd_response_black_version_header(self) -> None:
1804 response = await self.client.post("/")
1805 self.assertIsNotNone(response.headers.get(blackd.BLACK_VERSION_HEADER))
1808 if __name__ == "__main__":
1809 unittest.main(module="test_black")