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.",
782 def test_report_quiet(self) -> None:
783 report = black.Report(quiet=True)
787 def out(msg: str, **kwargs: Any) -> None:
788 out_lines.append(msg)
790 def err(msg: str, **kwargs: Any) -> None:
791 err_lines.append(msg)
793 with patch("black.out", out), patch("black.err", err):
794 report.done(Path("f1"), black.Changed.NO)
795 self.assertEqual(len(out_lines), 0)
796 self.assertEqual(len(err_lines), 0)
797 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
798 self.assertEqual(report.return_code, 0)
799 report.done(Path("f2"), black.Changed.YES)
800 self.assertEqual(len(out_lines), 0)
801 self.assertEqual(len(err_lines), 0)
803 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
805 report.done(Path("f3"), black.Changed.CACHED)
806 self.assertEqual(len(out_lines), 0)
807 self.assertEqual(len(err_lines), 0)
809 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
811 self.assertEqual(report.return_code, 0)
813 self.assertEqual(report.return_code, 1)
815 report.failed(Path("e1"), "boom")
816 self.assertEqual(len(out_lines), 0)
817 self.assertEqual(len(err_lines), 1)
818 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
820 unstyle(str(report)),
821 "1 file reformatted, 2 files left unchanged, "
822 "1 file failed to reformat.",
824 self.assertEqual(report.return_code, 123)
825 report.done(Path("f3"), black.Changed.YES)
826 self.assertEqual(len(out_lines), 0)
827 self.assertEqual(len(err_lines), 1)
829 unstyle(str(report)),
830 "2 files reformatted, 2 files left unchanged, "
831 "1 file failed to reformat.",
833 self.assertEqual(report.return_code, 123)
834 report.failed(Path("e2"), "boom")
835 self.assertEqual(len(out_lines), 0)
836 self.assertEqual(len(err_lines), 2)
837 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
839 unstyle(str(report)),
840 "2 files reformatted, 2 files left unchanged, "
841 "2 files failed to reformat.",
843 self.assertEqual(report.return_code, 123)
844 report.path_ignored(Path("wat"), "no match")
845 self.assertEqual(len(out_lines), 0)
846 self.assertEqual(len(err_lines), 2)
848 unstyle(str(report)),
849 "2 files reformatted, 2 files left unchanged, "
850 "2 files failed to reformat.",
852 self.assertEqual(report.return_code, 123)
853 report.done(Path("f4"), black.Changed.NO)
854 self.assertEqual(len(out_lines), 0)
855 self.assertEqual(len(err_lines), 2)
857 unstyle(str(report)),
858 "2 files reformatted, 3 files left unchanged, "
859 "2 files failed to reformat.",
861 self.assertEqual(report.return_code, 123)
864 unstyle(str(report)),
865 "2 files would be reformatted, 3 files would be left unchanged, "
866 "2 files would fail to reformat.",
869 def test_report_normal(self) -> None:
870 report = black.Report()
874 def out(msg: str, **kwargs: Any) -> None:
875 out_lines.append(msg)
877 def err(msg: str, **kwargs: Any) -> None:
878 err_lines.append(msg)
880 with patch("black.out", out), patch("black.err", err):
881 report.done(Path("f1"), black.Changed.NO)
882 self.assertEqual(len(out_lines), 0)
883 self.assertEqual(len(err_lines), 0)
884 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
885 self.assertEqual(report.return_code, 0)
886 report.done(Path("f2"), black.Changed.YES)
887 self.assertEqual(len(out_lines), 1)
888 self.assertEqual(len(err_lines), 0)
889 self.assertEqual(out_lines[-1], "reformatted f2")
891 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
893 report.done(Path("f3"), black.Changed.CACHED)
894 self.assertEqual(len(out_lines), 1)
895 self.assertEqual(len(err_lines), 0)
896 self.assertEqual(out_lines[-1], "reformatted f2")
898 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
900 self.assertEqual(report.return_code, 0)
902 self.assertEqual(report.return_code, 1)
904 report.failed(Path("e1"), "boom")
905 self.assertEqual(len(out_lines), 1)
906 self.assertEqual(len(err_lines), 1)
907 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
909 unstyle(str(report)),
910 "1 file reformatted, 2 files left unchanged, "
911 "1 file failed to reformat.",
913 self.assertEqual(report.return_code, 123)
914 report.done(Path("f3"), black.Changed.YES)
915 self.assertEqual(len(out_lines), 2)
916 self.assertEqual(len(err_lines), 1)
917 self.assertEqual(out_lines[-1], "reformatted f3")
919 unstyle(str(report)),
920 "2 files reformatted, 2 files left unchanged, "
921 "1 file failed to reformat.",
923 self.assertEqual(report.return_code, 123)
924 report.failed(Path("e2"), "boom")
925 self.assertEqual(len(out_lines), 2)
926 self.assertEqual(len(err_lines), 2)
927 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
929 unstyle(str(report)),
930 "2 files reformatted, 2 files left unchanged, "
931 "2 files failed to reformat.",
933 self.assertEqual(report.return_code, 123)
934 report.path_ignored(Path("wat"), "no match")
935 self.assertEqual(len(out_lines), 2)
936 self.assertEqual(len(err_lines), 2)
938 unstyle(str(report)),
939 "2 files reformatted, 2 files left unchanged, "
940 "2 files failed to reformat.",
942 self.assertEqual(report.return_code, 123)
943 report.done(Path("f4"), black.Changed.NO)
944 self.assertEqual(len(out_lines), 2)
945 self.assertEqual(len(err_lines), 2)
947 unstyle(str(report)),
948 "2 files reformatted, 3 files left unchanged, "
949 "2 files failed to reformat.",
951 self.assertEqual(report.return_code, 123)
954 unstyle(str(report)),
955 "2 files would be reformatted, 3 files would be left unchanged, "
956 "2 files would fail to reformat.",
959 def test_lib2to3_parse(self) -> None:
960 with self.assertRaises(black.InvalidInput):
961 black.lib2to3_parse("invalid syntax")
964 black.lib2to3_parse(straddling)
965 black.lib2to3_parse(straddling, {TargetVersion.PY27})
966 black.lib2to3_parse(straddling, {TargetVersion.PY36})
967 black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
970 black.lib2to3_parse(py2_only)
971 black.lib2to3_parse(py2_only, {TargetVersion.PY27})
972 with self.assertRaises(black.InvalidInput):
973 black.lib2to3_parse(py2_only, {TargetVersion.PY36})
974 with self.assertRaises(black.InvalidInput):
975 black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
977 py3_only = "exec(x, end=y)"
978 black.lib2to3_parse(py3_only)
979 with self.assertRaises(black.InvalidInput):
980 black.lib2to3_parse(py3_only, {TargetVersion.PY27})
981 black.lib2to3_parse(py3_only, {TargetVersion.PY36})
982 black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
984 def test_get_features_used(self) -> None:
985 node = black.lib2to3_parse("def f(*, arg): ...\n")
986 self.assertEqual(black.get_features_used(node), set())
987 node = black.lib2to3_parse("def f(*, arg,): ...\n")
988 self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
989 node = black.lib2to3_parse("f(*arg,)\n")
991 black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
993 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
994 self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
995 node = black.lib2to3_parse("123_456\n")
996 self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
997 node = black.lib2to3_parse("123456\n")
998 self.assertEqual(black.get_features_used(node), set())
999 source, expected = read_data("function")
1000 node = black.lib2to3_parse(source)
1001 expected_features = {
1002 Feature.TRAILING_COMMA_IN_CALL,
1003 Feature.TRAILING_COMMA_IN_DEF,
1006 self.assertEqual(black.get_features_used(node), expected_features)
1007 node = black.lib2to3_parse(expected)
1008 self.assertEqual(black.get_features_used(node), expected_features)
1009 source, expected = read_data("expression")
1010 node = black.lib2to3_parse(source)
1011 self.assertEqual(black.get_features_used(node), set())
1012 node = black.lib2to3_parse(expected)
1013 self.assertEqual(black.get_features_used(node), set())
1015 def test_get_future_imports(self) -> None:
1016 node = black.lib2to3_parse("\n")
1017 self.assertEqual(set(), black.get_future_imports(node))
1018 node = black.lib2to3_parse("from __future__ import black\n")
1019 self.assertEqual({"black"}, black.get_future_imports(node))
1020 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
1021 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1022 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
1023 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
1024 node = black.lib2to3_parse(
1025 "from __future__ import multiple\nfrom __future__ import imports\n"
1027 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1028 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
1029 self.assertEqual({"black"}, black.get_future_imports(node))
1030 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
1031 self.assertEqual({"black"}, black.get_future_imports(node))
1032 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
1033 self.assertEqual(set(), black.get_future_imports(node))
1034 node = black.lib2to3_parse("from some.module import black\n")
1035 self.assertEqual(set(), black.get_future_imports(node))
1036 node = black.lib2to3_parse(
1037 "from __future__ import unicode_literals as _unicode_literals"
1039 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
1040 node = black.lib2to3_parse(
1041 "from __future__ import unicode_literals as _lol, print"
1043 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
1045 def test_debug_visitor(self) -> None:
1046 source, _ = read_data("debug_visitor.py")
1047 expected, _ = read_data("debug_visitor.out")
1051 def out(msg: str, **kwargs: Any) -> None:
1052 out_lines.append(msg)
1054 def err(msg: str, **kwargs: Any) -> None:
1055 err_lines.append(msg)
1057 with patch("black.out", out), patch("black.err", err):
1058 black.DebugVisitor.show(source)
1059 actual = "\n".join(out_lines) + "\n"
1061 if expected != actual:
1062 log_name = black.dump_to_file(*out_lines)
1066 f"AST print out is different. Actual version dumped to {log_name}",
1069 def test_format_file_contents(self) -> None:
1071 mode = black.FileMode()
1072 with self.assertRaises(black.NothingChanged):
1073 black.format_file_contents(empty, mode=mode, fast=False)
1075 with self.assertRaises(black.NothingChanged):
1076 black.format_file_contents(just_nl, mode=mode, fast=False)
1077 same = "j = [1, 2, 3]\n"
1078 with self.assertRaises(black.NothingChanged):
1079 black.format_file_contents(same, mode=mode, fast=False)
1080 different = "j = [1,2,3]"
1082 actual = black.format_file_contents(different, mode=mode, fast=False)
1083 self.assertEqual(expected, actual)
1084 invalid = "return if you can"
1085 with self.assertRaises(black.InvalidInput) as e:
1086 black.format_file_contents(invalid, mode=mode, fast=False)
1087 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
1089 def test_endmarker(self) -> None:
1090 n = black.lib2to3_parse("\n")
1091 self.assertEqual(n.type, black.syms.file_input)
1092 self.assertEqual(len(n.children), 1)
1093 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
1095 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
1096 def test_assertFormatEqual(self) -> None:
1100 def out(msg: str, **kwargs: Any) -> None:
1101 out_lines.append(msg)
1103 def err(msg: str, **kwargs: Any) -> None:
1104 err_lines.append(msg)
1106 with patch("black.out", out), patch("black.err", err):
1107 with self.assertRaises(AssertionError):
1108 self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")
1110 out_str = "".join(out_lines)
1111 self.assertTrue("Expected tree:" in out_str)
1112 self.assertTrue("Actual tree:" in out_str)
1113 self.assertEqual("".join(err_lines), "")
1115 def test_cache_broken_file(self) -> None:
1116 mode = black.FileMode()
1117 with cache_dir() as workspace:
1118 cache_file = black.get_cache_file(mode)
1119 with cache_file.open("w") as fobj:
1120 fobj.write("this is not a pickle")
1121 self.assertEqual(black.read_cache(mode), {})
1122 src = (workspace / "test.py").resolve()
1123 with src.open("w") as fobj:
1124 fobj.write("print('hello')")
1125 self.invokeBlack([str(src)])
1126 cache = black.read_cache(mode)
1127 self.assertIn(src, cache)
1129 def test_cache_single_file_already_cached(self) -> None:
1130 mode = black.FileMode()
1131 with cache_dir() as workspace:
1132 src = (workspace / "test.py").resolve()
1133 with src.open("w") as fobj:
1134 fobj.write("print('hello')")
1135 black.write_cache({}, [src], mode)
1136 self.invokeBlack([str(src)])
1137 with src.open("r") as fobj:
1138 self.assertEqual(fobj.read(), "print('hello')")
1140 @event_loop(close=False)
1141 def test_cache_multiple_files(self) -> None:
1142 mode = black.FileMode()
1143 with cache_dir() as workspace, patch(
1144 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1146 one = (workspace / "one.py").resolve()
1147 with one.open("w") as fobj:
1148 fobj.write("print('hello')")
1149 two = (workspace / "two.py").resolve()
1150 with two.open("w") as fobj:
1151 fobj.write("print('hello')")
1152 black.write_cache({}, [one], mode)
1153 self.invokeBlack([str(workspace)])
1154 with one.open("r") as fobj:
1155 self.assertEqual(fobj.read(), "print('hello')")
1156 with two.open("r") as fobj:
1157 self.assertEqual(fobj.read(), 'print("hello")\n')
1158 cache = black.read_cache(mode)
1159 self.assertIn(one, cache)
1160 self.assertIn(two, cache)
1162 def test_no_cache_when_writeback_diff(self) -> None:
1163 mode = black.FileMode()
1164 with cache_dir() as workspace:
1165 src = (workspace / "test.py").resolve()
1166 with src.open("w") as fobj:
1167 fobj.write("print('hello')")
1168 self.invokeBlack([str(src), "--diff"])
1169 cache_file = black.get_cache_file(mode)
1170 self.assertFalse(cache_file.exists())
1172 def test_no_cache_when_stdin(self) -> None:
1173 mode = black.FileMode()
1175 result = CliRunner().invoke(
1176 black.main, ["-"], input=BytesIO(b"print('hello')")
1178 self.assertEqual(result.exit_code, 0)
1179 cache_file = black.get_cache_file(mode)
1180 self.assertFalse(cache_file.exists())
1182 def test_read_cache_no_cachefile(self) -> None:
1183 mode = black.FileMode()
1185 self.assertEqual(black.read_cache(mode), {})
1187 def test_write_cache_read_cache(self) -> None:
1188 mode = black.FileMode()
1189 with cache_dir() as workspace:
1190 src = (workspace / "test.py").resolve()
1192 black.write_cache({}, [src], mode)
1193 cache = black.read_cache(mode)
1194 self.assertIn(src, cache)
1195 self.assertEqual(cache[src], black.get_cache_info(src))
1197 def test_filter_cached(self) -> None:
1198 with TemporaryDirectory() as workspace:
1199 path = Path(workspace)
1200 uncached = (path / "uncached").resolve()
1201 cached = (path / "cached").resolve()
1202 cached_but_changed = (path / "changed").resolve()
1205 cached_but_changed.touch()
1206 cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
1207 todo, done = black.filter_cached(
1208 cache, {uncached, cached, cached_but_changed}
1210 self.assertEqual(todo, {uncached, cached_but_changed})
1211 self.assertEqual(done, {cached})
1213 def test_write_cache_creates_directory_if_needed(self) -> None:
1214 mode = black.FileMode()
1215 with cache_dir(exists=False) as workspace:
1216 self.assertFalse(workspace.exists())
1217 black.write_cache({}, [], mode)
1218 self.assertTrue(workspace.exists())
1220 @event_loop(close=False)
1221 def test_failed_formatting_does_not_get_cached(self) -> None:
1222 mode = black.FileMode()
1223 with cache_dir() as workspace, patch(
1224 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1226 failing = (workspace / "failing.py").resolve()
1227 with failing.open("w") as fobj:
1228 fobj.write("not actually python")
1229 clean = (workspace / "clean.py").resolve()
1230 with clean.open("w") as fobj:
1231 fobj.write('print("hello")\n')
1232 self.invokeBlack([str(workspace)], exit_code=123)
1233 cache = black.read_cache(mode)
1234 self.assertNotIn(failing, cache)
1235 self.assertIn(clean, cache)
1237 def test_write_cache_write_fail(self) -> None:
1238 mode = black.FileMode()
1239 with cache_dir(), patch.object(Path, "open") as mock:
1240 mock.side_effect = OSError
1241 black.write_cache({}, [], mode)
1243 @event_loop(close=False)
1244 def test_check_diff_use_together(self) -> None:
1246 # Files which will be reformatted.
1247 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1248 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1249 # Files which will not be reformatted.
1250 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1251 self.invokeBlack([str(src2), "--diff", "--check"])
1252 # Multi file command.
1253 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1255 def test_no_files(self) -> None:
1257 # Without an argument, black exits with error code 0.
1258 self.invokeBlack([])
1260 def test_broken_symlink(self) -> None:
1261 with cache_dir() as workspace:
1262 symlink = workspace / "broken_link.py"
1264 symlink.symlink_to("nonexistent.py")
1265 except OSError as e:
1266 self.skipTest(f"Can't create symlinks: {e}")
1267 self.invokeBlack([str(workspace.resolve())])
1269 def test_read_cache_line_lengths(self) -> None:
1270 mode = black.FileMode()
1271 short_mode = black.FileMode(line_length=1)
1272 with cache_dir() as workspace:
1273 path = (workspace / "file.py").resolve()
1275 black.write_cache({}, [path], mode)
1276 one = black.read_cache(mode)
1277 self.assertIn(path, one)
1278 two = black.read_cache(short_mode)
1279 self.assertNotIn(path, two)
1281 def test_tricky_unicode_symbols(self) -> None:
1282 source, expected = read_data("tricky_unicode_symbols")
1284 self.assertFormatEqual(expected, actual)
1285 black.assert_equivalent(source, actual)
1286 black.assert_stable(source, actual, black.FileMode())
1288 def test_single_file_force_pyi(self) -> None:
1289 reg_mode = black.FileMode()
1290 pyi_mode = black.FileMode(is_pyi=True)
1291 contents, expected = read_data("force_pyi")
1292 with cache_dir() as workspace:
1293 path = (workspace / "file.py").resolve()
1294 with open(path, "w") as fh:
1296 self.invokeBlack([str(path), "--pyi"])
1297 with open(path, "r") as fh:
1299 # verify cache with --pyi is separate
1300 pyi_cache = black.read_cache(pyi_mode)
1301 self.assertIn(path, pyi_cache)
1302 normal_cache = black.read_cache(reg_mode)
1303 self.assertNotIn(path, normal_cache)
1304 self.assertEqual(actual, expected)
1306 @event_loop(close=False)
1307 def test_multi_file_force_pyi(self) -> None:
1308 reg_mode = black.FileMode()
1309 pyi_mode = black.FileMode(is_pyi=True)
1310 contents, expected = read_data("force_pyi")
1311 with cache_dir() as workspace:
1313 (workspace / "file1.py").resolve(),
1314 (workspace / "file2.py").resolve(),
1317 with open(path, "w") as fh:
1319 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1321 with open(path, "r") as fh:
1323 self.assertEqual(actual, expected)
1324 # verify cache with --pyi is separate
1325 pyi_cache = black.read_cache(pyi_mode)
1326 normal_cache = black.read_cache(reg_mode)
1328 self.assertIn(path, pyi_cache)
1329 self.assertNotIn(path, normal_cache)
1331 def test_pipe_force_pyi(self) -> None:
1332 source, expected = read_data("force_pyi")
1333 result = CliRunner().invoke(
1334 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1336 self.assertEqual(result.exit_code, 0)
1337 actual = result.output
1338 self.assertFormatEqual(actual, expected)
1340 def test_single_file_force_py36(self) -> None:
1341 reg_mode = black.FileMode()
1342 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1343 source, expected = read_data("force_py36")
1344 with cache_dir() as workspace:
1345 path = (workspace / "file.py").resolve()
1346 with open(path, "w") as fh:
1348 self.invokeBlack([str(path), *PY36_ARGS])
1349 with open(path, "r") as fh:
1351 # verify cache with --target-version is separate
1352 py36_cache = black.read_cache(py36_mode)
1353 self.assertIn(path, py36_cache)
1354 normal_cache = black.read_cache(reg_mode)
1355 self.assertNotIn(path, normal_cache)
1356 self.assertEqual(actual, expected)
1358 @event_loop(close=False)
1359 def test_multi_file_force_py36(self) -> None:
1360 reg_mode = black.FileMode()
1361 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1362 source, expected = read_data("force_py36")
1363 with cache_dir() as workspace:
1365 (workspace / "file1.py").resolve(),
1366 (workspace / "file2.py").resolve(),
1369 with open(path, "w") as fh:
1371 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1373 with open(path, "r") as fh:
1375 self.assertEqual(actual, expected)
1376 # verify cache with --target-version is separate
1377 pyi_cache = black.read_cache(py36_mode)
1378 normal_cache = black.read_cache(reg_mode)
1380 self.assertIn(path, pyi_cache)
1381 self.assertNotIn(path, normal_cache)
1383 def test_collections(self) -> None:
1384 source, expected = read_data("collections")
1386 self.assertFormatEqual(expected, actual)
1387 black.assert_equivalent(source, actual)
1388 black.assert_stable(source, actual, black.FileMode())
1390 def test_pipe_force_py36(self) -> None:
1391 source, expected = read_data("force_py36")
1392 result = CliRunner().invoke(
1394 ["-", "-q", "--target-version=py36"],
1395 input=BytesIO(source.encode("utf8")),
1397 self.assertEqual(result.exit_code, 0)
1398 actual = result.output
1399 self.assertFormatEqual(actual, expected)
1401 def test_include_exclude(self) -> None:
1402 path = THIS_DIR / "data" / "include_exclude_tests"
1403 include = re.compile(r"\.pyi?$")
1404 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1405 report = black.Report()
1406 gitignore = PathSpec.from_lines("gitwildmatch", [])
1407 sources: List[Path] = []
1409 Path(path / "b/dont_exclude/a.py"),
1410 Path(path / "b/dont_exclude/a.pyi"),
1412 this_abs = THIS_DIR.resolve()
1414 black.gen_python_files_in_dir(
1415 path, this_abs, include, exclude, report, gitignore
1418 self.assertEqual(sorted(expected), sorted(sources))
1420 def test_gitignore_exclude(self) -> None:
1421 path = THIS_DIR / "data" / "include_exclude_tests"
1422 include = re.compile(r"\.pyi?$")
1423 exclude = re.compile(r"")
1424 report = black.Report()
1425 gitignore = PathSpec.from_lines(
1426 "gitwildmatch", ["exclude/", ".definitely_exclude"]
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_empty_include(self) -> None:
1442 path = THIS_DIR / "data" / "include_exclude_tests"
1443 report = black.Report()
1444 gitignore = PathSpec.from_lines("gitwildmatch", [])
1445 empty = re.compile(r"")
1446 sources: List[Path] = []
1448 Path(path / "b/exclude/a.pie"),
1449 Path(path / "b/exclude/a.py"),
1450 Path(path / "b/exclude/a.pyi"),
1451 Path(path / "b/dont_exclude/a.pie"),
1452 Path(path / "b/dont_exclude/a.py"),
1453 Path(path / "b/dont_exclude/a.pyi"),
1454 Path(path / "b/.definitely_exclude/a.pie"),
1455 Path(path / "b/.definitely_exclude/a.py"),
1456 Path(path / "b/.definitely_exclude/a.pyi"),
1458 this_abs = THIS_DIR.resolve()
1460 black.gen_python_files_in_dir(
1464 re.compile(black.DEFAULT_EXCLUDES),
1469 self.assertEqual(sorted(expected), sorted(sources))
1471 def test_empty_exclude(self) -> None:
1472 path = THIS_DIR / "data" / "include_exclude_tests"
1473 report = black.Report()
1474 gitignore = PathSpec.from_lines("gitwildmatch", [])
1475 empty = re.compile(r"")
1476 sources: List[Path] = []
1478 Path(path / "b/dont_exclude/a.py"),
1479 Path(path / "b/dont_exclude/a.pyi"),
1480 Path(path / "b/exclude/a.py"),
1481 Path(path / "b/exclude/a.pyi"),
1482 Path(path / "b/.definitely_exclude/a.py"),
1483 Path(path / "b/.definitely_exclude/a.pyi"),
1485 this_abs = THIS_DIR.resolve()
1487 black.gen_python_files_in_dir(
1490 re.compile(black.DEFAULT_INCLUDES),
1496 self.assertEqual(sorted(expected), sorted(sources))
1498 def test_invalid_include_exclude(self) -> None:
1499 for option in ["--include", "--exclude"]:
1500 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1502 def test_preserves_line_endings(self) -> None:
1503 with TemporaryDirectory() as workspace:
1504 test_file = Path(workspace) / "test.py"
1505 for nl in ["\n", "\r\n"]:
1506 contents = nl.join(["def f( ):", " pass"])
1507 test_file.write_bytes(contents.encode())
1508 ff(test_file, write_back=black.WriteBack.YES)
1509 updated_contents: bytes = test_file.read_bytes()
1510 self.assertIn(nl.encode(), updated_contents)
1512 self.assertNotIn(b"\r\n", updated_contents)
1514 def test_preserves_line_endings_via_stdin(self) -> None:
1515 for nl in ["\n", "\r\n"]:
1516 contents = nl.join(["def f( ):", " pass"])
1517 runner = BlackRunner()
1518 result = runner.invoke(
1519 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1521 self.assertEqual(result.exit_code, 0)
1522 output = runner.stdout_bytes
1523 self.assertIn(nl.encode("utf8"), output)
1525 self.assertNotIn(b"\r\n", output)
1527 def test_assert_equivalent_different_asts(self) -> None:
1528 with self.assertRaises(AssertionError):
1529 black.assert_equivalent("{}", "None")
1531 def test_symlink_out_of_root_directory(self) -> None:
1535 include = re.compile(black.DEFAULT_INCLUDES)
1536 exclude = re.compile(black.DEFAULT_EXCLUDES)
1537 report = black.Report()
1538 gitignore = PathSpec.from_lines("gitwildmatch", [])
1539 # `child` should behave like a symlink which resolved path is clearly
1540 # outside of the `root` directory.
1541 path.iterdir.return_value = [child]
1542 child.resolve.return_value = Path("/a/b/c")
1543 child.as_posix.return_value = "/a/b/c"
1544 child.is_symlink.return_value = True
1547 black.gen_python_files_in_dir(
1548 path, root, include, exclude, report, gitignore
1551 except ValueError as ve:
1552 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1553 path.iterdir.assert_called_once()
1554 child.resolve.assert_called_once()
1555 child.is_symlink.assert_called_once()
1556 # `child` should behave like a strange file which resolved path is clearly
1557 # outside of the `root` directory.
1558 child.is_symlink.return_value = False
1559 with self.assertRaises(ValueError):
1561 black.gen_python_files_in_dir(
1562 path, root, include, exclude, report, gitignore
1565 path.iterdir.assert_called()
1566 self.assertEqual(path.iterdir.call_count, 2)
1567 child.resolve.assert_called()
1568 self.assertEqual(child.resolve.call_count, 2)
1569 child.is_symlink.assert_called()
1570 self.assertEqual(child.is_symlink.call_count, 2)
1572 def test_shhh_click(self) -> None:
1574 from click import _unicodefun # type: ignore
1575 except ModuleNotFoundError:
1576 self.skipTest("Incompatible Click version")
1577 if not hasattr(_unicodefun, "_verify_python3_env"):
1578 self.skipTest("Incompatible Click version")
1579 # First, let's see if Click is crashing with a preferred ASCII charset.
1580 with patch("locale.getpreferredencoding") as gpe:
1581 gpe.return_value = "ASCII"
1582 with self.assertRaises(RuntimeError):
1583 _unicodefun._verify_python3_env()
1584 # Now, let's silence Click...
1586 # ...and confirm it's silent.
1587 with patch("locale.getpreferredencoding") as gpe:
1588 gpe.return_value = "ASCII"
1590 _unicodefun._verify_python3_env()
1591 except RuntimeError as re:
1592 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1594 def test_root_logger_not_used_directly(self) -> None:
1595 def fail(*args: Any, **kwargs: Any) -> None:
1596 self.fail("Record created with root logger")
1598 with patch.multiple(
1609 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1610 def test_blackd_main(self) -> None:
1611 with patch("blackd.web.run_app"):
1612 result = CliRunner().invoke(blackd.main, [])
1613 if result.exception is not None:
1614 raise result.exception
1615 self.assertEqual(result.exit_code, 0)
1618 class BlackDTestCase(AioHTTPTestCase):
1619 async def get_application(self) -> web.Application:
1620 return blackd.make_app()
1622 # TODO: remove these decorators once the below is released
1623 # https://github.com/aio-libs/aiohttp/pull/3727
1624 @skip_if_exception("ClientOSError")
1625 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1627 async def test_blackd_request_needs_formatting(self) -> None:
1628 response = await self.client.post("/", data=b"print('hello world')")
1629 self.assertEqual(response.status, 200)
1630 self.assertEqual(response.charset, "utf8")
1631 self.assertEqual(await response.read(), b'print("hello world")\n')
1633 @skip_if_exception("ClientOSError")
1634 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1636 async def test_blackd_request_no_change(self) -> None:
1637 response = await self.client.post("/", data=b'print("hello world")\n')
1638 self.assertEqual(response.status, 204)
1639 self.assertEqual(await response.read(), b"")
1641 @skip_if_exception("ClientOSError")
1642 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1644 async def test_blackd_request_syntax_error(self) -> None:
1645 response = await self.client.post("/", data=b"what even ( is")
1646 self.assertEqual(response.status, 400)
1647 content = await response.text()
1649 content.startswith("Cannot parse"),
1650 msg=f"Expected error to start with 'Cannot parse', got {repr(content)}",
1653 @skip_if_exception("ClientOSError")
1654 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1656 async def test_blackd_unsupported_version(self) -> None:
1657 response = await self.client.post(
1658 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "2"}
1660 self.assertEqual(response.status, 501)
1662 @skip_if_exception("ClientOSError")
1663 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1665 async def test_blackd_supported_version(self) -> None:
1666 response = await self.client.post(
1667 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "1"}
1669 self.assertEqual(response.status, 200)
1671 @skip_if_exception("ClientOSError")
1672 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1674 async def test_blackd_invalid_python_variant(self) -> None:
1675 async def check(header_value: str, expected_status: int = 400) -> None:
1676 response = await self.client.post(
1677 "/", data=b"what", headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1679 self.assertEqual(response.status, expected_status)
1682 await check("ruby3.5")
1683 await check("pyi3.6")
1684 await check("py1.5")
1686 await check("py2.8")
1688 await check("pypy3.0")
1689 await check("jython3.4")
1691 @skip_if_exception("ClientOSError")
1692 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1694 async def test_blackd_pyi(self) -> None:
1695 source, expected = read_data("stub.pyi")
1696 response = await self.client.post(
1697 "/", data=source, headers={blackd.PYTHON_VARIANT_HEADER: "pyi"}
1699 self.assertEqual(response.status, 200)
1700 self.assertEqual(await response.text(), expected)
1702 @skip_if_exception("ClientOSError")
1703 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1705 async def test_blackd_diff(self) -> None:
1706 diff_header = re.compile(
1707 rf"(In|Out)\t\d\d\d\d-\d\d-\d\d "
1708 rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
1711 source, _ = read_data("blackd_diff.py")
1712 expected, _ = read_data("blackd_diff.diff")
1714 response = await self.client.post(
1715 "/", data=source, headers={blackd.DIFF_HEADER: "true"}
1717 self.assertEqual(response.status, 200)
1719 actual = await response.text()
1720 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
1721 self.assertEqual(actual, expected)
1723 @skip_if_exception("ClientOSError")
1724 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1726 async def test_blackd_python_variant(self) -> None:
1729 " and_has_a_bunch_of,\n"
1730 " very_long_arguments_too,\n"
1731 " and_lots_of_them_as_well_lol,\n"
1732 " **and_very_long_keyword_arguments\n"
1737 async def check(header_value: str, expected_status: int) -> None:
1738 response = await self.client.post(
1739 "/", data=code, headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1742 response.status, expected_status, msg=await response.text()
1745 await check("3.6", 200)
1746 await check("py3.6", 200)
1747 await check("3.6,3.7", 200)
1748 await check("3.6,py3.7", 200)
1749 await check("py36,py37", 200)
1750 await check("36", 200)
1751 await check("3.6.4", 200)
1753 await check("2", 204)
1754 await check("2.7", 204)
1755 await check("py2.7", 204)
1756 await check("3.4", 204)
1757 await check("py3.4", 204)
1758 await check("py34,py36", 204)
1759 await check("34", 204)
1761 @skip_if_exception("ClientOSError")
1762 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1764 async def test_blackd_line_length(self) -> None:
1765 response = await self.client.post(
1766 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "7"}
1768 self.assertEqual(response.status, 200)
1770 @skip_if_exception("ClientOSError")
1771 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1773 async def test_blackd_invalid_line_length(self) -> None:
1774 response = await self.client.post(
1775 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "NaN"}
1777 self.assertEqual(response.status, 400)
1779 @skip_if_exception("ClientOSError")
1780 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1782 async def test_blackd_response_black_version_header(self) -> None:
1783 response = await self.client.post("/")
1784 self.assertIsNotNone(response.headers.get(blackd.BLACK_VERSION_HEADER))
1787 if __name__ == "__main__":
1788 unittest.main(module="test_black")