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_remove_empty_parentheses_after_class(self) -> None:
619 source, expected = read_data("class_blank_parentheses")
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_new_line_between_class_and_code(self) -> None:
627 source, expected = read_data("class_methods_new_line")
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_bracket_match(self) -> None:
635 source, expected = read_data("bracketmatch")
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_tuple_assign(self) -> None:
643 source, expected = read_data("tupleassign")
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_beginning_backslash(self) -> None:
651 source, expected = read_data("beginning_backslash")
653 self.assertFormatEqual(expected, actual)
654 black.assert_equivalent(source, actual)
655 black.assert_stable(source, actual, black.FileMode())
657 def test_tab_comment_indentation(self) -> None:
658 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
659 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
660 self.assertFormatEqual(contents_spc, fs(contents_spc))
661 self.assertFormatEqual(contents_spc, fs(contents_tab))
663 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
664 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
665 self.assertFormatEqual(contents_spc, fs(contents_spc))
666 self.assertFormatEqual(contents_spc, fs(contents_tab))
668 # mixed tabs and spaces (valid Python 2 code)
669 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t# comment\n pass\n"
670 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
671 self.assertFormatEqual(contents_spc, fs(contents_spc))
672 self.assertFormatEqual(contents_spc, fs(contents_tab))
674 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t\t# comment\n pass\n"
675 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
676 self.assertFormatEqual(contents_spc, fs(contents_spc))
677 self.assertFormatEqual(contents_spc, fs(contents_tab))
679 def test_report_verbose(self) -> None:
680 report = black.Report(verbose=True)
684 def out(msg: str, **kwargs: Any) -> None:
685 out_lines.append(msg)
687 def err(msg: str, **kwargs: Any) -> None:
688 err_lines.append(msg)
690 with patch("black.out", out), patch("black.err", err):
691 report.done(Path("f1"), black.Changed.NO)
692 self.assertEqual(len(out_lines), 1)
693 self.assertEqual(len(err_lines), 0)
694 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
695 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
696 self.assertEqual(report.return_code, 0)
697 report.done(Path("f2"), black.Changed.YES)
698 self.assertEqual(len(out_lines), 2)
699 self.assertEqual(len(err_lines), 0)
700 self.assertEqual(out_lines[-1], "reformatted f2")
702 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
704 report.done(Path("f3"), black.Changed.CACHED)
705 self.assertEqual(len(out_lines), 3)
706 self.assertEqual(len(err_lines), 0)
708 out_lines[-1], "f3 wasn't modified on disk since last run."
711 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
713 self.assertEqual(report.return_code, 0)
715 self.assertEqual(report.return_code, 1)
717 report.failed(Path("e1"), "boom")
718 self.assertEqual(len(out_lines), 3)
719 self.assertEqual(len(err_lines), 1)
720 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
722 unstyle(str(report)),
723 "1 file reformatted, 2 files left unchanged, "
724 "1 file failed to reformat.",
726 self.assertEqual(report.return_code, 123)
727 report.done(Path("f3"), black.Changed.YES)
728 self.assertEqual(len(out_lines), 4)
729 self.assertEqual(len(err_lines), 1)
730 self.assertEqual(out_lines[-1], "reformatted f3")
732 unstyle(str(report)),
733 "2 files reformatted, 2 files left unchanged, "
734 "1 file failed to reformat.",
736 self.assertEqual(report.return_code, 123)
737 report.failed(Path("e2"), "boom")
738 self.assertEqual(len(out_lines), 4)
739 self.assertEqual(len(err_lines), 2)
740 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
742 unstyle(str(report)),
743 "2 files reformatted, 2 files left unchanged, "
744 "2 files failed to reformat.",
746 self.assertEqual(report.return_code, 123)
747 report.path_ignored(Path("wat"), "no match")
748 self.assertEqual(len(out_lines), 5)
749 self.assertEqual(len(err_lines), 2)
750 self.assertEqual(out_lines[-1], "wat ignored: no match")
752 unstyle(str(report)),
753 "2 files reformatted, 2 files left unchanged, "
754 "2 files failed to reformat.",
756 self.assertEqual(report.return_code, 123)
757 report.done(Path("f4"), black.Changed.NO)
758 self.assertEqual(len(out_lines), 6)
759 self.assertEqual(len(err_lines), 2)
760 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
762 unstyle(str(report)),
763 "2 files reformatted, 3 files left unchanged, "
764 "2 files failed to reformat.",
766 self.assertEqual(report.return_code, 123)
769 unstyle(str(report)),
770 "2 files would be reformatted, 3 files would be left unchanged, "
771 "2 files would fail to reformat.",
774 def test_report_quiet(self) -> None:
775 report = black.Report(quiet=True)
779 def out(msg: str, **kwargs: Any) -> None:
780 out_lines.append(msg)
782 def err(msg: str, **kwargs: Any) -> None:
783 err_lines.append(msg)
785 with patch("black.out", out), patch("black.err", err):
786 report.done(Path("f1"), black.Changed.NO)
787 self.assertEqual(len(out_lines), 0)
788 self.assertEqual(len(err_lines), 0)
789 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
790 self.assertEqual(report.return_code, 0)
791 report.done(Path("f2"), black.Changed.YES)
792 self.assertEqual(len(out_lines), 0)
793 self.assertEqual(len(err_lines), 0)
795 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
797 report.done(Path("f3"), black.Changed.CACHED)
798 self.assertEqual(len(out_lines), 0)
799 self.assertEqual(len(err_lines), 0)
801 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
803 self.assertEqual(report.return_code, 0)
805 self.assertEqual(report.return_code, 1)
807 report.failed(Path("e1"), "boom")
808 self.assertEqual(len(out_lines), 0)
809 self.assertEqual(len(err_lines), 1)
810 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
812 unstyle(str(report)),
813 "1 file reformatted, 2 files left unchanged, "
814 "1 file failed to reformat.",
816 self.assertEqual(report.return_code, 123)
817 report.done(Path("f3"), black.Changed.YES)
818 self.assertEqual(len(out_lines), 0)
819 self.assertEqual(len(err_lines), 1)
821 unstyle(str(report)),
822 "2 files reformatted, 2 files left unchanged, "
823 "1 file failed to reformat.",
825 self.assertEqual(report.return_code, 123)
826 report.failed(Path("e2"), "boom")
827 self.assertEqual(len(out_lines), 0)
828 self.assertEqual(len(err_lines), 2)
829 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
831 unstyle(str(report)),
832 "2 files reformatted, 2 files left unchanged, "
833 "2 files failed to reformat.",
835 self.assertEqual(report.return_code, 123)
836 report.path_ignored(Path("wat"), "no match")
837 self.assertEqual(len(out_lines), 0)
838 self.assertEqual(len(err_lines), 2)
840 unstyle(str(report)),
841 "2 files reformatted, 2 files left unchanged, "
842 "2 files failed to reformat.",
844 self.assertEqual(report.return_code, 123)
845 report.done(Path("f4"), black.Changed.NO)
846 self.assertEqual(len(out_lines), 0)
847 self.assertEqual(len(err_lines), 2)
849 unstyle(str(report)),
850 "2 files reformatted, 3 files left unchanged, "
851 "2 files failed to reformat.",
853 self.assertEqual(report.return_code, 123)
856 unstyle(str(report)),
857 "2 files would be reformatted, 3 files would be left unchanged, "
858 "2 files would fail to reformat.",
861 def test_report_normal(self) -> None:
862 report = black.Report()
866 def out(msg: str, **kwargs: Any) -> None:
867 out_lines.append(msg)
869 def err(msg: str, **kwargs: Any) -> None:
870 err_lines.append(msg)
872 with patch("black.out", out), patch("black.err", err):
873 report.done(Path("f1"), black.Changed.NO)
874 self.assertEqual(len(out_lines), 0)
875 self.assertEqual(len(err_lines), 0)
876 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
877 self.assertEqual(report.return_code, 0)
878 report.done(Path("f2"), black.Changed.YES)
879 self.assertEqual(len(out_lines), 1)
880 self.assertEqual(len(err_lines), 0)
881 self.assertEqual(out_lines[-1], "reformatted f2")
883 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
885 report.done(Path("f3"), black.Changed.CACHED)
886 self.assertEqual(len(out_lines), 1)
887 self.assertEqual(len(err_lines), 0)
888 self.assertEqual(out_lines[-1], "reformatted f2")
890 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
892 self.assertEqual(report.return_code, 0)
894 self.assertEqual(report.return_code, 1)
896 report.failed(Path("e1"), "boom")
897 self.assertEqual(len(out_lines), 1)
898 self.assertEqual(len(err_lines), 1)
899 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
901 unstyle(str(report)),
902 "1 file reformatted, 2 files left unchanged, "
903 "1 file failed to reformat.",
905 self.assertEqual(report.return_code, 123)
906 report.done(Path("f3"), black.Changed.YES)
907 self.assertEqual(len(out_lines), 2)
908 self.assertEqual(len(err_lines), 1)
909 self.assertEqual(out_lines[-1], "reformatted f3")
911 unstyle(str(report)),
912 "2 files reformatted, 2 files left unchanged, "
913 "1 file failed to reformat.",
915 self.assertEqual(report.return_code, 123)
916 report.failed(Path("e2"), "boom")
917 self.assertEqual(len(out_lines), 2)
918 self.assertEqual(len(err_lines), 2)
919 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
921 unstyle(str(report)),
922 "2 files reformatted, 2 files left unchanged, "
923 "2 files failed to reformat.",
925 self.assertEqual(report.return_code, 123)
926 report.path_ignored(Path("wat"), "no match")
927 self.assertEqual(len(out_lines), 2)
928 self.assertEqual(len(err_lines), 2)
930 unstyle(str(report)),
931 "2 files reformatted, 2 files left unchanged, "
932 "2 files failed to reformat.",
934 self.assertEqual(report.return_code, 123)
935 report.done(Path("f4"), black.Changed.NO)
936 self.assertEqual(len(out_lines), 2)
937 self.assertEqual(len(err_lines), 2)
939 unstyle(str(report)),
940 "2 files reformatted, 3 files left unchanged, "
941 "2 files failed to reformat.",
943 self.assertEqual(report.return_code, 123)
946 unstyle(str(report)),
947 "2 files would be reformatted, 3 files would be left unchanged, "
948 "2 files would fail to reformat.",
951 def test_lib2to3_parse(self) -> None:
952 with self.assertRaises(black.InvalidInput):
953 black.lib2to3_parse("invalid syntax")
956 black.lib2to3_parse(straddling)
957 black.lib2to3_parse(straddling, {TargetVersion.PY27})
958 black.lib2to3_parse(straddling, {TargetVersion.PY36})
959 black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
962 black.lib2to3_parse(py2_only)
963 black.lib2to3_parse(py2_only, {TargetVersion.PY27})
964 with self.assertRaises(black.InvalidInput):
965 black.lib2to3_parse(py2_only, {TargetVersion.PY36})
966 with self.assertRaises(black.InvalidInput):
967 black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
969 py3_only = "exec(x, end=y)"
970 black.lib2to3_parse(py3_only)
971 with self.assertRaises(black.InvalidInput):
972 black.lib2to3_parse(py3_only, {TargetVersion.PY27})
973 black.lib2to3_parse(py3_only, {TargetVersion.PY36})
974 black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
976 def test_get_features_used(self) -> None:
977 node = black.lib2to3_parse("def f(*, arg): ...\n")
978 self.assertEqual(black.get_features_used(node), set())
979 node = black.lib2to3_parse("def f(*, arg,): ...\n")
980 self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
981 node = black.lib2to3_parse("f(*arg,)\n")
983 black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
985 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
986 self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
987 node = black.lib2to3_parse("123_456\n")
988 self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
989 node = black.lib2to3_parse("123456\n")
990 self.assertEqual(black.get_features_used(node), set())
991 source, expected = read_data("function")
992 node = black.lib2to3_parse(source)
993 expected_features = {
994 Feature.TRAILING_COMMA_IN_CALL,
995 Feature.TRAILING_COMMA_IN_DEF,
998 self.assertEqual(black.get_features_used(node), expected_features)
999 node = black.lib2to3_parse(expected)
1000 self.assertEqual(black.get_features_used(node), expected_features)
1001 source, expected = read_data("expression")
1002 node = black.lib2to3_parse(source)
1003 self.assertEqual(black.get_features_used(node), set())
1004 node = black.lib2to3_parse(expected)
1005 self.assertEqual(black.get_features_used(node), set())
1007 def test_get_future_imports(self) -> None:
1008 node = black.lib2to3_parse("\n")
1009 self.assertEqual(set(), black.get_future_imports(node))
1010 node = black.lib2to3_parse("from __future__ import black\n")
1011 self.assertEqual({"black"}, black.get_future_imports(node))
1012 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
1013 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1014 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
1015 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
1016 node = black.lib2to3_parse(
1017 "from __future__ import multiple\nfrom __future__ import imports\n"
1019 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1020 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
1021 self.assertEqual({"black"}, black.get_future_imports(node))
1022 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
1023 self.assertEqual({"black"}, black.get_future_imports(node))
1024 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
1025 self.assertEqual(set(), black.get_future_imports(node))
1026 node = black.lib2to3_parse("from some.module import black\n")
1027 self.assertEqual(set(), black.get_future_imports(node))
1028 node = black.lib2to3_parse(
1029 "from __future__ import unicode_literals as _unicode_literals"
1031 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
1032 node = black.lib2to3_parse(
1033 "from __future__ import unicode_literals as _lol, print"
1035 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
1037 def test_debug_visitor(self) -> None:
1038 source, _ = read_data("debug_visitor.py")
1039 expected, _ = read_data("debug_visitor.out")
1043 def out(msg: str, **kwargs: Any) -> None:
1044 out_lines.append(msg)
1046 def err(msg: str, **kwargs: Any) -> None:
1047 err_lines.append(msg)
1049 with patch("black.out", out), patch("black.err", err):
1050 black.DebugVisitor.show(source)
1051 actual = "\n".join(out_lines) + "\n"
1053 if expected != actual:
1054 log_name = black.dump_to_file(*out_lines)
1058 f"AST print out is different. Actual version dumped to {log_name}",
1061 def test_format_file_contents(self) -> None:
1063 mode = black.FileMode()
1064 with self.assertRaises(black.NothingChanged):
1065 black.format_file_contents(empty, mode=mode, fast=False)
1067 with self.assertRaises(black.NothingChanged):
1068 black.format_file_contents(just_nl, mode=mode, fast=False)
1069 same = "j = [1, 2, 3]\n"
1070 with self.assertRaises(black.NothingChanged):
1071 black.format_file_contents(same, mode=mode, fast=False)
1072 different = "j = [1,2,3]"
1074 actual = black.format_file_contents(different, mode=mode, fast=False)
1075 self.assertEqual(expected, actual)
1076 invalid = "return if you can"
1077 with self.assertRaises(black.InvalidInput) as e:
1078 black.format_file_contents(invalid, mode=mode, fast=False)
1079 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
1081 def test_endmarker(self) -> None:
1082 n = black.lib2to3_parse("\n")
1083 self.assertEqual(n.type, black.syms.file_input)
1084 self.assertEqual(len(n.children), 1)
1085 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
1087 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
1088 def test_assertFormatEqual(self) -> None:
1092 def out(msg: str, **kwargs: Any) -> None:
1093 out_lines.append(msg)
1095 def err(msg: str, **kwargs: Any) -> None:
1096 err_lines.append(msg)
1098 with patch("black.out", out), patch("black.err", err):
1099 with self.assertRaises(AssertionError):
1100 self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")
1102 out_str = "".join(out_lines)
1103 self.assertTrue("Expected tree:" in out_str)
1104 self.assertTrue("Actual tree:" in out_str)
1105 self.assertEqual("".join(err_lines), "")
1107 def test_cache_broken_file(self) -> None:
1108 mode = black.FileMode()
1109 with cache_dir() as workspace:
1110 cache_file = black.get_cache_file(mode)
1111 with cache_file.open("w") as fobj:
1112 fobj.write("this is not a pickle")
1113 self.assertEqual(black.read_cache(mode), {})
1114 src = (workspace / "test.py").resolve()
1115 with src.open("w") as fobj:
1116 fobj.write("print('hello')")
1117 self.invokeBlack([str(src)])
1118 cache = black.read_cache(mode)
1119 self.assertIn(src, cache)
1121 def test_cache_single_file_already_cached(self) -> None:
1122 mode = black.FileMode()
1123 with cache_dir() as workspace:
1124 src = (workspace / "test.py").resolve()
1125 with src.open("w") as fobj:
1126 fobj.write("print('hello')")
1127 black.write_cache({}, [src], mode)
1128 self.invokeBlack([str(src)])
1129 with src.open("r") as fobj:
1130 self.assertEqual(fobj.read(), "print('hello')")
1132 @event_loop(close=False)
1133 def test_cache_multiple_files(self) -> None:
1134 mode = black.FileMode()
1135 with cache_dir() as workspace, patch(
1136 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1138 one = (workspace / "one.py").resolve()
1139 with one.open("w") as fobj:
1140 fobj.write("print('hello')")
1141 two = (workspace / "two.py").resolve()
1142 with two.open("w") as fobj:
1143 fobj.write("print('hello')")
1144 black.write_cache({}, [one], mode)
1145 self.invokeBlack([str(workspace)])
1146 with one.open("r") as fobj:
1147 self.assertEqual(fobj.read(), "print('hello')")
1148 with two.open("r") as fobj:
1149 self.assertEqual(fobj.read(), 'print("hello")\n')
1150 cache = black.read_cache(mode)
1151 self.assertIn(one, cache)
1152 self.assertIn(two, cache)
1154 def test_no_cache_when_writeback_diff(self) -> None:
1155 mode = black.FileMode()
1156 with cache_dir() as workspace:
1157 src = (workspace / "test.py").resolve()
1158 with src.open("w") as fobj:
1159 fobj.write("print('hello')")
1160 self.invokeBlack([str(src), "--diff"])
1161 cache_file = black.get_cache_file(mode)
1162 self.assertFalse(cache_file.exists())
1164 def test_no_cache_when_stdin(self) -> None:
1165 mode = black.FileMode()
1167 result = CliRunner().invoke(
1168 black.main, ["-"], input=BytesIO(b"print('hello')")
1170 self.assertEqual(result.exit_code, 0)
1171 cache_file = black.get_cache_file(mode)
1172 self.assertFalse(cache_file.exists())
1174 def test_read_cache_no_cachefile(self) -> None:
1175 mode = black.FileMode()
1177 self.assertEqual(black.read_cache(mode), {})
1179 def test_write_cache_read_cache(self) -> None:
1180 mode = black.FileMode()
1181 with cache_dir() as workspace:
1182 src = (workspace / "test.py").resolve()
1184 black.write_cache({}, [src], mode)
1185 cache = black.read_cache(mode)
1186 self.assertIn(src, cache)
1187 self.assertEqual(cache[src], black.get_cache_info(src))
1189 def test_filter_cached(self) -> None:
1190 with TemporaryDirectory() as workspace:
1191 path = Path(workspace)
1192 uncached = (path / "uncached").resolve()
1193 cached = (path / "cached").resolve()
1194 cached_but_changed = (path / "changed").resolve()
1197 cached_but_changed.touch()
1198 cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
1199 todo, done = black.filter_cached(
1200 cache, {uncached, cached, cached_but_changed}
1202 self.assertEqual(todo, {uncached, cached_but_changed})
1203 self.assertEqual(done, {cached})
1205 def test_write_cache_creates_directory_if_needed(self) -> None:
1206 mode = black.FileMode()
1207 with cache_dir(exists=False) as workspace:
1208 self.assertFalse(workspace.exists())
1209 black.write_cache({}, [], mode)
1210 self.assertTrue(workspace.exists())
1212 @event_loop(close=False)
1213 def test_failed_formatting_does_not_get_cached(self) -> None:
1214 mode = black.FileMode()
1215 with cache_dir() as workspace, patch(
1216 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1218 failing = (workspace / "failing.py").resolve()
1219 with failing.open("w") as fobj:
1220 fobj.write("not actually python")
1221 clean = (workspace / "clean.py").resolve()
1222 with clean.open("w") as fobj:
1223 fobj.write('print("hello")\n')
1224 self.invokeBlack([str(workspace)], exit_code=123)
1225 cache = black.read_cache(mode)
1226 self.assertNotIn(failing, cache)
1227 self.assertIn(clean, cache)
1229 def test_write_cache_write_fail(self) -> None:
1230 mode = black.FileMode()
1231 with cache_dir(), patch.object(Path, "open") as mock:
1232 mock.side_effect = OSError
1233 black.write_cache({}, [], mode)
1235 @event_loop(close=False)
1236 def test_check_diff_use_together(self) -> None:
1238 # Files which will be reformatted.
1239 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1240 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1241 # Files which will not be reformatted.
1242 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1243 self.invokeBlack([str(src2), "--diff", "--check"])
1244 # Multi file command.
1245 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1247 def test_no_files(self) -> None:
1249 # Without an argument, black exits with error code 0.
1250 self.invokeBlack([])
1252 def test_broken_symlink(self) -> None:
1253 with cache_dir() as workspace:
1254 symlink = workspace / "broken_link.py"
1256 symlink.symlink_to("nonexistent.py")
1257 except OSError as e:
1258 self.skipTest(f"Can't create symlinks: {e}")
1259 self.invokeBlack([str(workspace.resolve())])
1261 def test_read_cache_line_lengths(self) -> None:
1262 mode = black.FileMode()
1263 short_mode = black.FileMode(line_length=1)
1264 with cache_dir() as workspace:
1265 path = (workspace / "file.py").resolve()
1267 black.write_cache({}, [path], mode)
1268 one = black.read_cache(mode)
1269 self.assertIn(path, one)
1270 two = black.read_cache(short_mode)
1271 self.assertNotIn(path, two)
1273 def test_tricky_unicode_symbols(self) -> None:
1274 source, expected = read_data("tricky_unicode_symbols")
1276 self.assertFormatEqual(expected, actual)
1277 black.assert_equivalent(source, actual)
1278 black.assert_stable(source, actual, black.FileMode())
1280 def test_single_file_force_pyi(self) -> None:
1281 reg_mode = black.FileMode()
1282 pyi_mode = black.FileMode(is_pyi=True)
1283 contents, expected = read_data("force_pyi")
1284 with cache_dir() as workspace:
1285 path = (workspace / "file.py").resolve()
1286 with open(path, "w") as fh:
1288 self.invokeBlack([str(path), "--pyi"])
1289 with open(path, "r") as fh:
1291 # verify cache with --pyi is separate
1292 pyi_cache = black.read_cache(pyi_mode)
1293 self.assertIn(path, pyi_cache)
1294 normal_cache = black.read_cache(reg_mode)
1295 self.assertNotIn(path, normal_cache)
1296 self.assertEqual(actual, expected)
1298 @event_loop(close=False)
1299 def test_multi_file_force_pyi(self) -> None:
1300 reg_mode = black.FileMode()
1301 pyi_mode = black.FileMode(is_pyi=True)
1302 contents, expected = read_data("force_pyi")
1303 with cache_dir() as workspace:
1305 (workspace / "file1.py").resolve(),
1306 (workspace / "file2.py").resolve(),
1309 with open(path, "w") as fh:
1311 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1313 with open(path, "r") as fh:
1315 self.assertEqual(actual, expected)
1316 # verify cache with --pyi is separate
1317 pyi_cache = black.read_cache(pyi_mode)
1318 normal_cache = black.read_cache(reg_mode)
1320 self.assertIn(path, pyi_cache)
1321 self.assertNotIn(path, normal_cache)
1323 def test_pipe_force_pyi(self) -> None:
1324 source, expected = read_data("force_pyi")
1325 result = CliRunner().invoke(
1326 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1328 self.assertEqual(result.exit_code, 0)
1329 actual = result.output
1330 self.assertFormatEqual(actual, expected)
1332 def test_single_file_force_py36(self) -> None:
1333 reg_mode = black.FileMode()
1334 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1335 source, expected = read_data("force_py36")
1336 with cache_dir() as workspace:
1337 path = (workspace / "file.py").resolve()
1338 with open(path, "w") as fh:
1340 self.invokeBlack([str(path), *PY36_ARGS])
1341 with open(path, "r") as fh:
1343 # verify cache with --target-version is separate
1344 py36_cache = black.read_cache(py36_mode)
1345 self.assertIn(path, py36_cache)
1346 normal_cache = black.read_cache(reg_mode)
1347 self.assertNotIn(path, normal_cache)
1348 self.assertEqual(actual, expected)
1350 @event_loop(close=False)
1351 def test_multi_file_force_py36(self) -> None:
1352 reg_mode = black.FileMode()
1353 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1354 source, expected = read_data("force_py36")
1355 with cache_dir() as workspace:
1357 (workspace / "file1.py").resolve(),
1358 (workspace / "file2.py").resolve(),
1361 with open(path, "w") as fh:
1363 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1365 with open(path, "r") as fh:
1367 self.assertEqual(actual, expected)
1368 # verify cache with --target-version is separate
1369 pyi_cache = black.read_cache(py36_mode)
1370 normal_cache = black.read_cache(reg_mode)
1372 self.assertIn(path, pyi_cache)
1373 self.assertNotIn(path, normal_cache)
1375 def test_collections(self) -> None:
1376 source, expected = read_data("collections")
1378 self.assertFormatEqual(expected, actual)
1379 black.assert_equivalent(source, actual)
1380 black.assert_stable(source, actual, black.FileMode())
1382 def test_pipe_force_py36(self) -> None:
1383 source, expected = read_data("force_py36")
1384 result = CliRunner().invoke(
1386 ["-", "-q", "--target-version=py36"],
1387 input=BytesIO(source.encode("utf8")),
1389 self.assertEqual(result.exit_code, 0)
1390 actual = result.output
1391 self.assertFormatEqual(actual, expected)
1393 def test_include_exclude(self) -> None:
1394 path = THIS_DIR / "data" / "include_exclude_tests"
1395 include = re.compile(r"\.pyi?$")
1396 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1397 report = black.Report()
1398 gitignore = PathSpec.from_lines("gitwildmatch", [])
1399 sources: List[Path] = []
1401 Path(path / "b/dont_exclude/a.py"),
1402 Path(path / "b/dont_exclude/a.pyi"),
1404 this_abs = THIS_DIR.resolve()
1406 black.gen_python_files_in_dir(
1407 path, this_abs, include, exclude, report, gitignore
1410 self.assertEqual(sorted(expected), sorted(sources))
1412 def test_gitignore_exclude(self) -> None:
1413 path = THIS_DIR / "data" / "include_exclude_tests"
1414 include = re.compile(r"\.pyi?$")
1415 exclude = re.compile(r"")
1416 report = black.Report()
1417 gitignore = PathSpec.from_lines(
1418 "gitwildmatch", ["exclude/", ".definitely_exclude"]
1420 sources: List[Path] = []
1422 Path(path / "b/dont_exclude/a.py"),
1423 Path(path / "b/dont_exclude/a.pyi"),
1425 this_abs = THIS_DIR.resolve()
1427 black.gen_python_files_in_dir(
1428 path, this_abs, include, exclude, report, gitignore
1431 self.assertEqual(sorted(expected), sorted(sources))
1433 def test_empty_include(self) -> None:
1434 path = THIS_DIR / "data" / "include_exclude_tests"
1435 report = black.Report()
1436 gitignore = PathSpec.from_lines("gitwildmatch", [])
1437 empty = re.compile(r"")
1438 sources: List[Path] = []
1440 Path(path / "b/exclude/a.pie"),
1441 Path(path / "b/exclude/a.py"),
1442 Path(path / "b/exclude/a.pyi"),
1443 Path(path / "b/dont_exclude/a.pie"),
1444 Path(path / "b/dont_exclude/a.py"),
1445 Path(path / "b/dont_exclude/a.pyi"),
1446 Path(path / "b/.definitely_exclude/a.pie"),
1447 Path(path / "b/.definitely_exclude/a.py"),
1448 Path(path / "b/.definitely_exclude/a.pyi"),
1450 this_abs = THIS_DIR.resolve()
1452 black.gen_python_files_in_dir(
1456 re.compile(black.DEFAULT_EXCLUDES),
1461 self.assertEqual(sorted(expected), sorted(sources))
1463 def test_empty_exclude(self) -> None:
1464 path = THIS_DIR / "data" / "include_exclude_tests"
1465 report = black.Report()
1466 gitignore = PathSpec.from_lines("gitwildmatch", [])
1467 empty = re.compile(r"")
1468 sources: List[Path] = []
1470 Path(path / "b/dont_exclude/a.py"),
1471 Path(path / "b/dont_exclude/a.pyi"),
1472 Path(path / "b/exclude/a.py"),
1473 Path(path / "b/exclude/a.pyi"),
1474 Path(path / "b/.definitely_exclude/a.py"),
1475 Path(path / "b/.definitely_exclude/a.pyi"),
1477 this_abs = THIS_DIR.resolve()
1479 black.gen_python_files_in_dir(
1482 re.compile(black.DEFAULT_INCLUDES),
1488 self.assertEqual(sorted(expected), sorted(sources))
1490 def test_invalid_include_exclude(self) -> None:
1491 for option in ["--include", "--exclude"]:
1492 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1494 def test_preserves_line_endings(self) -> None:
1495 with TemporaryDirectory() as workspace:
1496 test_file = Path(workspace) / "test.py"
1497 for nl in ["\n", "\r\n"]:
1498 contents = nl.join(["def f( ):", " pass"])
1499 test_file.write_bytes(contents.encode())
1500 ff(test_file, write_back=black.WriteBack.YES)
1501 updated_contents: bytes = test_file.read_bytes()
1502 self.assertIn(nl.encode(), updated_contents)
1504 self.assertNotIn(b"\r\n", updated_contents)
1506 def test_preserves_line_endings_via_stdin(self) -> None:
1507 for nl in ["\n", "\r\n"]:
1508 contents = nl.join(["def f( ):", " pass"])
1509 runner = BlackRunner()
1510 result = runner.invoke(
1511 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1513 self.assertEqual(result.exit_code, 0)
1514 output = runner.stdout_bytes
1515 self.assertIn(nl.encode("utf8"), output)
1517 self.assertNotIn(b"\r\n", output)
1519 def test_assert_equivalent_different_asts(self) -> None:
1520 with self.assertRaises(AssertionError):
1521 black.assert_equivalent("{}", "None")
1523 def test_symlink_out_of_root_directory(self) -> None:
1527 include = re.compile(black.DEFAULT_INCLUDES)
1528 exclude = re.compile(black.DEFAULT_EXCLUDES)
1529 report = black.Report()
1530 gitignore = PathSpec.from_lines("gitwildmatch", [])
1531 # `child` should behave like a symlink which resolved path is clearly
1532 # outside of the `root` directory.
1533 path.iterdir.return_value = [child]
1534 child.resolve.return_value = Path("/a/b/c")
1535 child.is_symlink.return_value = True
1538 black.gen_python_files_in_dir(
1539 path, root, include, exclude, report, gitignore
1542 except ValueError as ve:
1543 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1544 path.iterdir.assert_called_once()
1545 child.resolve.assert_called_once()
1546 child.is_symlink.assert_called_once()
1547 # `child` should behave like a strange file which resolved path is clearly
1548 # outside of the `root` directory.
1549 child.is_symlink.return_value = False
1550 with self.assertRaises(ValueError):
1552 black.gen_python_files_in_dir(
1553 path, root, include, exclude, report, gitignore
1556 path.iterdir.assert_called()
1557 self.assertEqual(path.iterdir.call_count, 2)
1558 child.resolve.assert_called()
1559 self.assertEqual(child.resolve.call_count, 2)
1560 child.is_symlink.assert_called()
1561 self.assertEqual(child.is_symlink.call_count, 2)
1563 def test_shhh_click(self) -> None:
1565 from click import _unicodefun # type: ignore
1566 except ModuleNotFoundError:
1567 self.skipTest("Incompatible Click version")
1568 if not hasattr(_unicodefun, "_verify_python3_env"):
1569 self.skipTest("Incompatible Click version")
1570 # First, let's see if Click is crashing with a preferred ASCII charset.
1571 with patch("locale.getpreferredencoding") as gpe:
1572 gpe.return_value = "ASCII"
1573 with self.assertRaises(RuntimeError):
1574 _unicodefun._verify_python3_env()
1575 # Now, let's silence Click...
1577 # ...and confirm it's silent.
1578 with patch("locale.getpreferredencoding") as gpe:
1579 gpe.return_value = "ASCII"
1581 _unicodefun._verify_python3_env()
1582 except RuntimeError as re:
1583 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1585 def test_root_logger_not_used_directly(self) -> None:
1586 def fail(*args: Any, **kwargs: Any) -> None:
1587 self.fail("Record created with root logger")
1589 with patch.multiple(
1600 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1601 def test_blackd_main(self) -> None:
1602 with patch("blackd.web.run_app"):
1603 result = CliRunner().invoke(blackd.main, [])
1604 if result.exception is not None:
1605 raise result.exception
1606 self.assertEqual(result.exit_code, 0)
1609 class BlackDTestCase(AioHTTPTestCase):
1610 async def get_application(self) -> web.Application:
1611 return blackd.make_app()
1613 # TODO: remove these decorators once the below is released
1614 # https://github.com/aio-libs/aiohttp/pull/3727
1615 @skip_if_exception("ClientOSError")
1616 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1618 async def test_blackd_request_needs_formatting(self) -> None:
1619 response = await self.client.post("/", data=b"print('hello world')")
1620 self.assertEqual(response.status, 200)
1621 self.assertEqual(response.charset, "utf8")
1622 self.assertEqual(await response.read(), b'print("hello world")\n')
1624 @skip_if_exception("ClientOSError")
1625 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1627 async def test_blackd_request_no_change(self) -> None:
1628 response = await self.client.post("/", data=b'print("hello world")\n')
1629 self.assertEqual(response.status, 204)
1630 self.assertEqual(await response.read(), b"")
1632 @skip_if_exception("ClientOSError")
1633 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1635 async def test_blackd_request_syntax_error(self) -> None:
1636 response = await self.client.post("/", data=b"what even ( is")
1637 self.assertEqual(response.status, 400)
1638 content = await response.text()
1640 content.startswith("Cannot parse"),
1641 msg=f"Expected error to start with 'Cannot parse', got {repr(content)}",
1644 @skip_if_exception("ClientOSError")
1645 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1647 async def test_blackd_unsupported_version(self) -> None:
1648 response = await self.client.post(
1649 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "2"}
1651 self.assertEqual(response.status, 501)
1653 @skip_if_exception("ClientOSError")
1654 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1656 async def test_blackd_supported_version(self) -> None:
1657 response = await self.client.post(
1658 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "1"}
1660 self.assertEqual(response.status, 200)
1662 @skip_if_exception("ClientOSError")
1663 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1665 async def test_blackd_invalid_python_variant(self) -> None:
1666 async def check(header_value: str, expected_status: int = 400) -> None:
1667 response = await self.client.post(
1668 "/", data=b"what", headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1670 self.assertEqual(response.status, expected_status)
1673 await check("ruby3.5")
1674 await check("pyi3.6")
1675 await check("py1.5")
1677 await check("py2.8")
1679 await check("pypy3.0")
1680 await check("jython3.4")
1682 @skip_if_exception("ClientOSError")
1683 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1685 async def test_blackd_pyi(self) -> None:
1686 source, expected = read_data("stub.pyi")
1687 response = await self.client.post(
1688 "/", data=source, headers={blackd.PYTHON_VARIANT_HEADER: "pyi"}
1690 self.assertEqual(response.status, 200)
1691 self.assertEqual(await response.text(), expected)
1693 @skip_if_exception("ClientOSError")
1694 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1696 async def test_blackd_diff(self) -> None:
1697 diff_header = re.compile(
1698 rf"(In|Out)\t\d\d\d\d-\d\d-\d\d "
1699 rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
1702 source, _ = read_data("blackd_diff.py")
1703 expected, _ = read_data("blackd_diff.diff")
1705 response = await self.client.post(
1706 "/", data=source, headers={blackd.DIFF_HEADER: "true"}
1708 self.assertEqual(response.status, 200)
1710 actual = await response.text()
1711 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
1712 self.assertEqual(actual, expected)
1714 @skip_if_exception("ClientOSError")
1715 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1717 async def test_blackd_python_variant(self) -> None:
1720 " and_has_a_bunch_of,\n"
1721 " very_long_arguments_too,\n"
1722 " and_lots_of_them_as_well_lol,\n"
1723 " **and_very_long_keyword_arguments\n"
1728 async def check(header_value: str, expected_status: int) -> None:
1729 response = await self.client.post(
1730 "/", data=code, headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1733 response.status, expected_status, msg=await response.text()
1736 await check("3.6", 200)
1737 await check("py3.6", 200)
1738 await check("3.6,3.7", 200)
1739 await check("3.6,py3.7", 200)
1740 await check("py36,py37", 200)
1741 await check("36", 200)
1742 await check("3.6.4", 200)
1744 await check("2", 204)
1745 await check("2.7", 204)
1746 await check("py2.7", 204)
1747 await check("3.4", 204)
1748 await check("py3.4", 204)
1749 await check("py34,py36", 204)
1750 await check("34", 204)
1752 @skip_if_exception("ClientOSError")
1753 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1755 async def test_blackd_line_length(self) -> None:
1756 response = await self.client.post(
1757 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "7"}
1759 self.assertEqual(response.status, 200)
1761 @skip_if_exception("ClientOSError")
1762 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1764 async def test_blackd_invalid_line_length(self) -> None:
1765 response = await self.client.post(
1766 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "NaN"}
1768 self.assertEqual(response.status, 400)
1770 @skip_if_exception("ClientOSError")
1771 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1773 async def test_blackd_response_black_version_header(self) -> None:
1774 response = await self.client.post("/")
1775 self.assertIsNotNone(response.headers.get(blackd.BLACK_VERSION_HEADER))
1778 if __name__ == "__main__":
1779 unittest.main(module="test_black")