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.
5 from concurrent.futures import ThreadPoolExecutor
6 from contextlib import contextmanager
7 from dataclasses import replace
8 from functools import partial
10 from io import BytesIO, TextIOWrapper
12 from pathlib import Path
13 from platform import system
16 from tempfile import TemporaryDirectory
30 from unittest.mock import patch, MagicMock
33 from click import unstyle
34 from click.testing import CliRunner
37 from black import Feature, TargetVersion
41 from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
42 from aiohttp import web
44 has_blackd_deps = False
46 has_blackd_deps = True
48 from pathspec import PathSpec
50 # Import other test classes
51 from .test_primer import PrimerCLITests # noqa: F401
54 DEFAULT_MODE = black.FileMode(experimental_string_processing=True)
55 ff = partial(black.format_file_in_place, mode=DEFAULT_MODE, fast=True)
56 fs = partial(black.format_str, mode=DEFAULT_MODE)
57 THIS_FILE = Path(__file__)
58 THIS_DIR = THIS_FILE.parent
59 PROJECT_ROOT = THIS_DIR.parent
60 DETERMINISTIC_HEADER = "[Deterministic header]"
61 EMPTY_LINE = "# EMPTY LINE WITH WHITESPACE" + " (this comment will be removed)"
63 f"--target-version={version.name.lower()}" for version in black.PY36_VERSIONS
69 def dump_to_stderr(*output: str) -> str:
70 return "\n" + "\n".join(output) + "\n"
73 def read_data(name: str, data: bool = True) -> Tuple[str, str]:
74 """read_data('test_name') -> 'input', 'output'"""
75 if not name.endswith((".py", ".pyi", ".out", ".diff")):
77 _input: List[str] = []
78 _output: List[str] = []
79 base_dir = THIS_DIR / "data" if data else PROJECT_ROOT
80 with open(base_dir / name, "r", encoding="utf8") as test:
81 lines = test.readlines()
84 line = line.replace(EMPTY_LINE, "")
85 if line.rstrip() == "# output":
90 if _input and not _output:
91 # If there's no output marker, treat the entire file as already pre-formatted.
93 return "".join(_input).strip() + "\n", "".join(_output).strip() + "\n"
97 def cache_dir(exists: bool = True) -> Iterator[Path]:
98 with TemporaryDirectory() as workspace:
99 cache_dir = Path(workspace)
101 cache_dir = cache_dir / "new"
102 with patch("black.CACHE_DIR", cache_dir):
107 def event_loop() -> Iterator[None]:
108 policy = asyncio.get_event_loop_policy()
109 loop = policy.new_event_loop()
110 asyncio.set_event_loop(loop)
119 def skip_if_exception(e: str) -> Iterator[None]:
122 except Exception as exc:
123 if exc.__class__.__name__ == e:
124 unittest.skip(f"Encountered expected exception {exc}, skipping")
129 class FakeContext(click.Context):
130 """A fake click Context for when calling functions that need it."""
132 def __init__(self) -> None:
133 self.default_map: Dict[str, Any] = {}
136 class FakeParameter(click.Parameter):
137 """A fake click Parameter for when calling functions that need it."""
139 def __init__(self) -> None:
143 class BlackRunner(CliRunner):
144 """Modify CliRunner so that stderr is not merged with stdout.
146 This is a hack that can be removed once we depend on Click 7.x"""
148 def __init__(self) -> None:
149 self.stderrbuf = BytesIO()
150 self.stdoutbuf = BytesIO()
151 self.stdout_bytes = b""
152 self.stderr_bytes = b""
156 def isolation(self, *args: Any, **kwargs: Any) -> Generator[BinaryIO, None, None]:
157 with super().isolation(*args, **kwargs) as output:
159 hold_stderr = sys.stderr
160 sys.stderr = TextIOWrapper(self.stderrbuf, encoding=self.charset)
163 self.stdout_bytes = sys.stdout.buffer.getvalue() # type: ignore
164 self.stderr_bytes = sys.stderr.buffer.getvalue() # type: ignore
165 sys.stderr = hold_stderr
168 class BlackTestCase(unittest.TestCase):
170 _diffThreshold = 2 ** 20
172 def assertFormatEqual(self, expected: str, actual: str) -> None:
173 if actual != expected and not os.environ.get("SKIP_AST_PRINT"):
174 bdv: black.DebugVisitor[Any]
175 black.out("Expected tree:", fg="green")
177 exp_node = black.lib2to3_parse(expected)
178 bdv = black.DebugVisitor()
179 list(bdv.visit(exp_node))
180 except Exception as ve:
182 black.out("Actual tree:", fg="red")
184 exp_node = black.lib2to3_parse(actual)
185 bdv = black.DebugVisitor()
186 list(bdv.visit(exp_node))
187 except Exception as ve:
189 self.assertMultiLineEqual(expected, actual)
192 self, args: List[str], exit_code: int = 0, ignore_config: bool = True
194 runner = BlackRunner()
196 args = ["--verbose", "--config", str(THIS_DIR / "empty.toml"), *args]
197 result = runner.invoke(black.main, args)
202 f"Failed with args: {args}\n"
203 f"stdout: {runner.stdout_bytes.decode()!r}\n"
204 f"stderr: {runner.stderr_bytes.decode()!r}\n"
205 f"exception: {result.exception}"
209 @patch("black.dump_to_file", dump_to_stderr)
210 def checkSourceFile(self, name: str, mode: black.FileMode = DEFAULT_MODE) -> None:
211 path = THIS_DIR.parent / name
212 source, expected = read_data(str(path), data=False)
213 actual = fs(source, mode=mode)
214 self.assertFormatEqual(expected, actual)
215 black.assert_equivalent(source, actual)
216 black.assert_stable(source, actual, mode)
217 self.assertFalse(ff(path))
219 @patch("black.dump_to_file", dump_to_stderr)
220 def test_empty(self) -> None:
221 source = expected = ""
223 self.assertFormatEqual(expected, actual)
224 black.assert_equivalent(source, actual)
225 black.assert_stable(source, actual, DEFAULT_MODE)
227 def test_empty_ff(self) -> None:
229 tmp_file = Path(black.dump_to_file())
231 self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES))
232 with open(tmp_file, encoding="utf8") as f:
236 self.assertFormatEqual(expected, actual)
238 def test_self(self) -> None:
239 self.checkSourceFile("tests/test_black.py")
241 def test_black(self) -> None:
242 self.checkSourceFile("src/black/__init__.py")
244 def test_pygram(self) -> None:
245 self.checkSourceFile("src/blib2to3/pygram.py")
247 def test_pytree(self) -> None:
248 self.checkSourceFile("src/blib2to3/pytree.py")
250 def test_conv(self) -> None:
251 self.checkSourceFile("src/blib2to3/pgen2/conv.py")
253 def test_driver(self) -> None:
254 self.checkSourceFile("src/blib2to3/pgen2/driver.py")
256 def test_grammar(self) -> None:
257 self.checkSourceFile("src/blib2to3/pgen2/grammar.py")
259 def test_literals(self) -> None:
260 self.checkSourceFile("src/blib2to3/pgen2/literals.py")
262 def test_parse(self) -> None:
263 self.checkSourceFile("src/blib2to3/pgen2/parse.py")
265 def test_pgen(self) -> None:
266 self.checkSourceFile("src/blib2to3/pgen2/pgen.py")
268 def test_tokenize(self) -> None:
269 self.checkSourceFile("src/blib2to3/pgen2/tokenize.py")
271 def test_token(self) -> None:
272 self.checkSourceFile("src/blib2to3/pgen2/token.py")
274 def test_setup(self) -> None:
275 self.checkSourceFile("setup.py")
277 def test_piping(self) -> None:
278 source, expected = read_data("src/black/__init__", data=False)
279 result = BlackRunner().invoke(
281 ["-", "--fast", f"--line-length={black.DEFAULT_LINE_LENGTH}"],
282 input=BytesIO(source.encode("utf8")),
284 self.assertEqual(result.exit_code, 0)
285 self.assertFormatEqual(expected, result.output)
286 black.assert_equivalent(source, result.output)
287 black.assert_stable(source, result.output, DEFAULT_MODE)
289 def test_piping_diff(self) -> None:
290 diff_header = re.compile(
291 r"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d\d\d\d "
294 source, _ = read_data("expression.py")
295 expected, _ = read_data("expression.diff")
296 config = THIS_DIR / "data" / "empty_pyproject.toml"
300 f"--line-length={black.DEFAULT_LINE_LENGTH}",
302 f"--config={config}",
304 result = BlackRunner().invoke(
305 black.main, args, input=BytesIO(source.encode("utf8"))
307 self.assertEqual(result.exit_code, 0)
308 actual = diff_header.sub(DETERMINISTIC_HEADER, result.output)
309 actual = actual.rstrip() + "\n" # the diff output has a trailing space
310 self.assertEqual(expected, actual)
312 def test_piping_diff_with_color(self) -> None:
313 source, _ = read_data("expression.py")
314 config = THIS_DIR / "data" / "empty_pyproject.toml"
318 f"--line-length={black.DEFAULT_LINE_LENGTH}",
321 f"--config={config}",
323 result = BlackRunner().invoke(
324 black.main, args, input=BytesIO(source.encode("utf8"))
326 actual = result.output
327 # Again, the contents are checked in a different test, so only look for colors.
328 self.assertIn("\033[1;37m", actual)
329 self.assertIn("\033[36m", actual)
330 self.assertIn("\033[32m", actual)
331 self.assertIn("\033[31m", actual)
332 self.assertIn("\033[0m", actual)
334 @patch("black.dump_to_file", dump_to_stderr)
335 def test_function(self) -> None:
336 source, expected = read_data("function")
338 self.assertFormatEqual(expected, actual)
339 black.assert_equivalent(source, actual)
340 black.assert_stable(source, actual, DEFAULT_MODE)
342 @patch("black.dump_to_file", dump_to_stderr)
343 def test_function2(self) -> None:
344 source, expected = read_data("function2")
346 self.assertFormatEqual(expected, actual)
347 black.assert_equivalent(source, actual)
348 black.assert_stable(source, actual, DEFAULT_MODE)
350 @patch("black.dump_to_file", dump_to_stderr)
351 def _test_wip(self) -> None:
352 source, expected = read_data("wip")
353 sys.settrace(tracefunc)
356 experimental_string_processing=False,
357 target_versions={black.TargetVersion.PY38},
359 actual = fs(source, mode=mode)
361 self.assertFormatEqual(expected, actual)
362 black.assert_equivalent(source, actual)
363 black.assert_stable(source, actual, black.FileMode())
365 @patch("black.dump_to_file", dump_to_stderr)
366 def test_function_trailing_comma(self) -> None:
367 source, expected = read_data("function_trailing_comma")
369 self.assertFormatEqual(expected, actual)
370 black.assert_equivalent(source, actual)
371 black.assert_stable(source, actual, DEFAULT_MODE)
373 @unittest.expectedFailure
374 @patch("black.dump_to_file", dump_to_stderr)
375 def test_trailing_comma_optional_parens_stability1(self) -> None:
376 source, _expected = read_data("trailing_comma_optional_parens1")
378 black.assert_stable(source, actual, DEFAULT_MODE)
380 @unittest.expectedFailure
381 @patch("black.dump_to_file", dump_to_stderr)
382 def test_trailing_comma_optional_parens_stability2(self) -> None:
383 source, _expected = read_data("trailing_comma_optional_parens2")
385 black.assert_stable(source, actual, DEFAULT_MODE)
387 @unittest.expectedFailure
388 @patch("black.dump_to_file", dump_to_stderr)
389 def test_trailing_comma_optional_parens_stability3(self) -> None:
390 source, _expected = read_data("trailing_comma_optional_parens3")
392 black.assert_stable(source, actual, DEFAULT_MODE)
394 @patch("black.dump_to_file", dump_to_stderr)
395 def test_expression(self) -> None:
396 source, expected = read_data("expression")
398 self.assertFormatEqual(expected, actual)
399 black.assert_equivalent(source, actual)
400 black.assert_stable(source, actual, DEFAULT_MODE)
402 @patch("black.dump_to_file", dump_to_stderr)
403 def test_pep_572(self) -> None:
404 source, expected = read_data("pep_572")
406 self.assertFormatEqual(expected, actual)
407 black.assert_stable(source, actual, DEFAULT_MODE)
408 if sys.version_info >= (3, 8):
409 black.assert_equivalent(source, actual)
411 def test_pep_572_version_detection(self) -> None:
412 source, _ = read_data("pep_572")
413 root = black.lib2to3_parse(source)
414 features = black.get_features_used(root)
415 self.assertIn(black.Feature.ASSIGNMENT_EXPRESSIONS, features)
416 versions = black.detect_target_versions(root)
417 self.assertIn(black.TargetVersion.PY38, versions)
419 def test_expression_ff(self) -> None:
420 source, expected = read_data("expression")
421 tmp_file = Path(black.dump_to_file(source))
423 self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
424 with open(tmp_file, encoding="utf8") as f:
428 self.assertFormatEqual(expected, actual)
429 with patch("black.dump_to_file", dump_to_stderr):
430 black.assert_equivalent(source, actual)
431 black.assert_stable(source, actual, DEFAULT_MODE)
433 def test_expression_diff(self) -> None:
434 source, _ = read_data("expression.py")
435 expected, _ = read_data("expression.diff")
436 tmp_file = Path(black.dump_to_file(source))
437 diff_header = re.compile(
438 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
439 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
442 result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
443 self.assertEqual(result.exit_code, 0)
446 actual = result.output
447 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
448 actual = actual.rstrip() + "\n" # the diff output has a trailing space
449 if expected != actual:
450 dump = black.dump_to_file(actual)
452 "Expected diff isn't equal to the actual. If you made changes to"
453 " expression.py and this is an anticipated difference, overwrite"
454 f" tests/data/expression.diff with {dump}"
456 self.assertEqual(expected, actual, msg)
458 def test_expression_diff_with_color(self) -> None:
459 source, _ = read_data("expression.py")
460 expected, _ = read_data("expression.diff")
461 tmp_file = Path(black.dump_to_file(source))
463 result = BlackRunner().invoke(
464 black.main, ["--diff", "--color", str(tmp_file)]
468 actual = result.output
469 # We check the contents of the diff in `test_expression_diff`. All
470 # we need to check here is that color codes exist in the result.
471 self.assertIn("\033[1;37m", actual)
472 self.assertIn("\033[36m", actual)
473 self.assertIn("\033[32m", actual)
474 self.assertIn("\033[31m", actual)
475 self.assertIn("\033[0m", actual)
477 @patch("black.dump_to_file", dump_to_stderr)
478 def test_fstring(self) -> None:
479 source, expected = read_data("fstring")
481 self.assertFormatEqual(expected, actual)
482 black.assert_equivalent(source, actual)
483 black.assert_stable(source, actual, DEFAULT_MODE)
485 @patch("black.dump_to_file", dump_to_stderr)
486 def test_pep_570(self) -> None:
487 source, expected = read_data("pep_570")
489 self.assertFormatEqual(expected, actual)
490 black.assert_stable(source, actual, DEFAULT_MODE)
491 if sys.version_info >= (3, 8):
492 black.assert_equivalent(source, actual)
494 def test_detect_pos_only_arguments(self) -> None:
495 source, _ = read_data("pep_570")
496 root = black.lib2to3_parse(source)
497 features = black.get_features_used(root)
498 self.assertIn(black.Feature.POS_ONLY_ARGUMENTS, features)
499 versions = black.detect_target_versions(root)
500 self.assertIn(black.TargetVersion.PY38, versions)
502 @patch("black.dump_to_file", dump_to_stderr)
503 def test_string_quotes(self) -> None:
504 source, expected = read_data("string_quotes")
506 self.assertFormatEqual(expected, actual)
507 black.assert_equivalent(source, actual)
508 black.assert_stable(source, actual, DEFAULT_MODE)
509 mode = replace(DEFAULT_MODE, string_normalization=False)
510 not_normalized = fs(source, mode=mode)
511 self.assertFormatEqual(source.replace("\\\n", ""), not_normalized)
512 black.assert_equivalent(source, not_normalized)
513 black.assert_stable(source, not_normalized, mode=mode)
515 @patch("black.dump_to_file", dump_to_stderr)
516 def test_docstring(self) -> None:
517 source, expected = read_data("docstring")
519 self.assertFormatEqual(expected, actual)
520 black.assert_equivalent(source, actual)
521 black.assert_stable(source, actual, DEFAULT_MODE)
523 @patch("black.dump_to_file", dump_to_stderr)
524 def test_docstring_no_string_normalization(self) -> None:
525 """Like test_docstring but with string normalization off."""
526 source, expected = read_data("docstring_no_string_normalization")
527 mode = replace(DEFAULT_MODE, string_normalization=False)
528 actual = fs(source, mode=mode)
529 self.assertFormatEqual(expected, actual)
530 black.assert_equivalent(source, actual)
531 black.assert_stable(source, actual, mode)
533 def test_long_strings(self) -> None:
534 """Tests for splitting long strings."""
535 source, expected = read_data("long_strings")
537 self.assertFormatEqual(expected, actual)
538 black.assert_equivalent(source, actual)
539 black.assert_stable(source, actual, DEFAULT_MODE)
541 def test_long_strings_flag_disabled(self) -> None:
542 """Tests for turning off the string processing logic."""
543 source, expected = read_data("long_strings_flag_disabled")
544 mode = replace(DEFAULT_MODE, experimental_string_processing=False)
545 actual = fs(source, mode=mode)
546 self.assertFormatEqual(expected, actual)
547 black.assert_stable(expected, actual, mode)
549 @patch("black.dump_to_file", dump_to_stderr)
550 def test_long_strings__edge_case(self) -> None:
551 """Edge-case tests for splitting long strings."""
552 source, expected = read_data("long_strings__edge_case")
554 self.assertFormatEqual(expected, actual)
555 black.assert_equivalent(source, actual)
556 black.assert_stable(source, actual, DEFAULT_MODE)
558 @patch("black.dump_to_file", dump_to_stderr)
559 def test_long_strings__regression(self) -> None:
560 """Regression tests for splitting long strings."""
561 source, expected = read_data("long_strings__regression")
563 self.assertFormatEqual(expected, actual)
564 black.assert_equivalent(source, actual)
565 black.assert_stable(source, actual, DEFAULT_MODE)
567 @patch("black.dump_to_file", dump_to_stderr)
568 def test_slices(self) -> None:
569 source, expected = read_data("slices")
571 self.assertFormatEqual(expected, actual)
572 black.assert_equivalent(source, actual)
573 black.assert_stable(source, actual, DEFAULT_MODE)
575 @patch("black.dump_to_file", dump_to_stderr)
576 def test_percent_precedence(self) -> None:
577 source, expected = read_data("percent_precedence")
579 self.assertFormatEqual(expected, actual)
580 black.assert_equivalent(source, actual)
581 black.assert_stable(source, actual, DEFAULT_MODE)
583 @patch("black.dump_to_file", dump_to_stderr)
584 def test_comments(self) -> None:
585 source, expected = read_data("comments")
587 self.assertFormatEqual(expected, actual)
588 black.assert_equivalent(source, actual)
589 black.assert_stable(source, actual, DEFAULT_MODE)
591 @patch("black.dump_to_file", dump_to_stderr)
592 def test_comments2(self) -> None:
593 source, expected = read_data("comments2")
595 self.assertFormatEqual(expected, actual)
596 black.assert_equivalent(source, actual)
597 black.assert_stable(source, actual, DEFAULT_MODE)
599 @patch("black.dump_to_file", dump_to_stderr)
600 def test_comments3(self) -> None:
601 source, expected = read_data("comments3")
603 self.assertFormatEqual(expected, actual)
604 black.assert_equivalent(source, actual)
605 black.assert_stable(source, actual, DEFAULT_MODE)
607 @patch("black.dump_to_file", dump_to_stderr)
608 def test_comments4(self) -> None:
609 source, expected = read_data("comments4")
611 self.assertFormatEqual(expected, actual)
612 black.assert_equivalent(source, actual)
613 black.assert_stable(source, actual, DEFAULT_MODE)
615 @patch("black.dump_to_file", dump_to_stderr)
616 def test_comments5(self) -> None:
617 source, expected = read_data("comments5")
619 self.assertFormatEqual(expected, actual)
620 black.assert_equivalent(source, actual)
621 black.assert_stable(source, actual, DEFAULT_MODE)
623 @patch("black.dump_to_file", dump_to_stderr)
624 def test_comments6(self) -> None:
625 source, expected = read_data("comments6")
627 self.assertFormatEqual(expected, actual)
628 black.assert_equivalent(source, actual)
629 black.assert_stable(source, actual, DEFAULT_MODE)
631 @patch("black.dump_to_file", dump_to_stderr)
632 def test_comments7(self) -> None:
633 source, expected = read_data("comments7")
634 mode = replace(DEFAULT_MODE, target_versions={black.TargetVersion.PY38})
635 actual = fs(source, mode=mode)
636 self.assertFormatEqual(expected, actual)
637 black.assert_equivalent(source, actual)
638 black.assert_stable(source, actual, DEFAULT_MODE)
640 @patch("black.dump_to_file", dump_to_stderr)
641 def test_comment_after_escaped_newline(self) -> None:
642 source, expected = read_data("comment_after_escaped_newline")
644 self.assertFormatEqual(expected, actual)
645 black.assert_equivalent(source, actual)
646 black.assert_stable(source, actual, DEFAULT_MODE)
648 @patch("black.dump_to_file", dump_to_stderr)
649 def test_cantfit(self) -> None:
650 source, expected = read_data("cantfit")
652 self.assertFormatEqual(expected, actual)
653 black.assert_equivalent(source, actual)
654 black.assert_stable(source, actual, DEFAULT_MODE)
656 @patch("black.dump_to_file", dump_to_stderr)
657 def test_import_spacing(self) -> None:
658 source, expected = read_data("import_spacing")
660 self.assertFormatEqual(expected, actual)
661 black.assert_equivalent(source, actual)
662 black.assert_stable(source, actual, DEFAULT_MODE)
664 @patch("black.dump_to_file", dump_to_stderr)
665 def test_composition(self) -> None:
666 source, expected = read_data("composition")
668 self.assertFormatEqual(expected, actual)
669 black.assert_equivalent(source, actual)
670 black.assert_stable(source, actual, DEFAULT_MODE)
672 @patch("black.dump_to_file", dump_to_stderr)
673 def test_composition_no_trailing_comma(self) -> None:
674 source, expected = read_data("composition_no_trailing_comma")
675 mode = replace(DEFAULT_MODE, target_versions={black.TargetVersion.PY38})
676 actual = fs(source, mode=mode)
677 self.assertFormatEqual(expected, actual)
678 black.assert_equivalent(source, actual)
679 black.assert_stable(source, actual, DEFAULT_MODE)
681 @patch("black.dump_to_file", dump_to_stderr)
682 def test_empty_lines(self) -> None:
683 source, expected = read_data("empty_lines")
685 self.assertFormatEqual(expected, actual)
686 black.assert_equivalent(source, actual)
687 black.assert_stable(source, actual, DEFAULT_MODE)
689 @patch("black.dump_to_file", dump_to_stderr)
690 def test_remove_parens(self) -> None:
691 source, expected = read_data("remove_parens")
693 self.assertFormatEqual(expected, actual)
694 black.assert_equivalent(source, actual)
695 black.assert_stable(source, actual, DEFAULT_MODE)
697 @patch("black.dump_to_file", dump_to_stderr)
698 def test_string_prefixes(self) -> None:
699 source, expected = read_data("string_prefixes")
701 self.assertFormatEqual(expected, actual)
702 black.assert_equivalent(source, actual)
703 black.assert_stable(source, actual, DEFAULT_MODE)
705 @patch("black.dump_to_file", dump_to_stderr)
706 def test_numeric_literals(self) -> None:
707 source, expected = read_data("numeric_literals")
708 mode = replace(DEFAULT_MODE, target_versions=black.PY36_VERSIONS)
709 actual = fs(source, mode=mode)
710 self.assertFormatEqual(expected, actual)
711 black.assert_equivalent(source, actual)
712 black.assert_stable(source, actual, mode)
714 @patch("black.dump_to_file", dump_to_stderr)
715 def test_numeric_literals_ignoring_underscores(self) -> None:
716 source, expected = read_data("numeric_literals_skip_underscores")
717 mode = replace(DEFAULT_MODE, target_versions=black.PY36_VERSIONS)
718 actual = fs(source, mode=mode)
719 self.assertFormatEqual(expected, actual)
720 black.assert_equivalent(source, actual)
721 black.assert_stable(source, actual, mode)
723 @patch("black.dump_to_file", dump_to_stderr)
724 def test_numeric_literals_py2(self) -> None:
725 source, expected = read_data("numeric_literals_py2")
727 self.assertFormatEqual(expected, actual)
728 black.assert_stable(source, actual, DEFAULT_MODE)
730 @patch("black.dump_to_file", dump_to_stderr)
731 def test_python2(self) -> None:
732 source, expected = read_data("python2")
734 self.assertFormatEqual(expected, actual)
735 black.assert_equivalent(source, actual)
736 black.assert_stable(source, actual, DEFAULT_MODE)
738 @patch("black.dump_to_file", dump_to_stderr)
739 def test_python2_print_function(self) -> None:
740 source, expected = read_data("python2_print_function")
741 mode = replace(DEFAULT_MODE, target_versions={TargetVersion.PY27})
742 actual = fs(source, mode=mode)
743 self.assertFormatEqual(expected, actual)
744 black.assert_equivalent(source, actual)
745 black.assert_stable(source, actual, mode)
747 @patch("black.dump_to_file", dump_to_stderr)
748 def test_python2_unicode_literals(self) -> None:
749 source, expected = read_data("python2_unicode_literals")
751 self.assertFormatEqual(expected, actual)
752 black.assert_equivalent(source, actual)
753 black.assert_stable(source, actual, DEFAULT_MODE)
755 @patch("black.dump_to_file", dump_to_stderr)
756 def test_stub(self) -> None:
757 mode = replace(DEFAULT_MODE, is_pyi=True)
758 source, expected = read_data("stub.pyi")
759 actual = fs(source, mode=mode)
760 self.assertFormatEqual(expected, actual)
761 black.assert_stable(source, actual, mode)
763 @patch("black.dump_to_file", dump_to_stderr)
764 def test_async_as_identifier(self) -> None:
765 source_path = (THIS_DIR / "data" / "async_as_identifier.py").resolve()
766 source, expected = read_data("async_as_identifier")
768 self.assertFormatEqual(expected, actual)
769 major, minor = sys.version_info[:2]
770 if major < 3 or (major <= 3 and minor < 7):
771 black.assert_equivalent(source, actual)
772 black.assert_stable(source, actual, DEFAULT_MODE)
773 # ensure black can parse this when the target is 3.6
774 self.invokeBlack([str(source_path), "--target-version", "py36"])
775 # but not on 3.7, because async/await is no longer an identifier
776 self.invokeBlack([str(source_path), "--target-version", "py37"], exit_code=123)
778 @patch("black.dump_to_file", dump_to_stderr)
779 def test_python37(self) -> None:
780 source_path = (THIS_DIR / "data" / "python37.py").resolve()
781 source, expected = read_data("python37")
783 self.assertFormatEqual(expected, actual)
784 major, minor = sys.version_info[:2]
785 if major > 3 or (major == 3 and minor >= 7):
786 black.assert_equivalent(source, actual)
787 black.assert_stable(source, actual, DEFAULT_MODE)
788 # ensure black can parse this when the target is 3.7
789 self.invokeBlack([str(source_path), "--target-version", "py37"])
790 # but not on 3.6, because we use async as a reserved keyword
791 self.invokeBlack([str(source_path), "--target-version", "py36"], exit_code=123)
793 @patch("black.dump_to_file", dump_to_stderr)
794 def test_python38(self) -> None:
795 source, expected = read_data("python38")
797 self.assertFormatEqual(expected, actual)
798 major, minor = sys.version_info[:2]
799 if major > 3 or (major == 3 and minor >= 8):
800 black.assert_equivalent(source, actual)
801 black.assert_stable(source, actual, DEFAULT_MODE)
803 @patch("black.dump_to_file", dump_to_stderr)
804 def test_fmtonoff(self) -> None:
805 source, expected = read_data("fmtonoff")
807 self.assertFormatEqual(expected, actual)
808 black.assert_equivalent(source, actual)
809 black.assert_stable(source, actual, DEFAULT_MODE)
811 @patch("black.dump_to_file", dump_to_stderr)
812 def test_fmtonoff2(self) -> None:
813 source, expected = read_data("fmtonoff2")
815 self.assertFormatEqual(expected, actual)
816 black.assert_equivalent(source, actual)
817 black.assert_stable(source, actual, DEFAULT_MODE)
819 @patch("black.dump_to_file", dump_to_stderr)
820 def test_fmtonoff3(self) -> None:
821 source, expected = read_data("fmtonoff3")
823 self.assertFormatEqual(expected, actual)
824 black.assert_equivalent(source, actual)
825 black.assert_stable(source, actual, DEFAULT_MODE)
827 @patch("black.dump_to_file", dump_to_stderr)
828 def test_fmtonoff4(self) -> None:
829 source, expected = read_data("fmtonoff4")
831 self.assertFormatEqual(expected, actual)
832 black.assert_equivalent(source, actual)
833 black.assert_stable(source, actual, DEFAULT_MODE)
835 @patch("black.dump_to_file", dump_to_stderr)
836 def test_remove_empty_parentheses_after_class(self) -> None:
837 source, expected = read_data("class_blank_parentheses")
839 self.assertFormatEqual(expected, actual)
840 black.assert_equivalent(source, actual)
841 black.assert_stable(source, actual, DEFAULT_MODE)
843 @patch("black.dump_to_file", dump_to_stderr)
844 def test_new_line_between_class_and_code(self) -> None:
845 source, expected = read_data("class_methods_new_line")
847 self.assertFormatEqual(expected, actual)
848 black.assert_equivalent(source, actual)
849 black.assert_stable(source, actual, DEFAULT_MODE)
851 @patch("black.dump_to_file", dump_to_stderr)
852 def test_bracket_match(self) -> None:
853 source, expected = read_data("bracketmatch")
855 self.assertFormatEqual(expected, actual)
856 black.assert_equivalent(source, actual)
857 black.assert_stable(source, actual, DEFAULT_MODE)
859 @patch("black.dump_to_file", dump_to_stderr)
860 def test_tuple_assign(self) -> None:
861 source, expected = read_data("tupleassign")
863 self.assertFormatEqual(expected, actual)
864 black.assert_equivalent(source, actual)
865 black.assert_stable(source, actual, DEFAULT_MODE)
867 @patch("black.dump_to_file", dump_to_stderr)
868 def test_beginning_backslash(self) -> None:
869 source, expected = read_data("beginning_backslash")
871 self.assertFormatEqual(expected, actual)
872 black.assert_equivalent(source, actual)
873 black.assert_stable(source, actual, DEFAULT_MODE)
875 def test_tab_comment_indentation(self) -> None:
876 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
877 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
878 self.assertFormatEqual(contents_spc, fs(contents_spc))
879 self.assertFormatEqual(contents_spc, fs(contents_tab))
881 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
882 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
883 self.assertFormatEqual(contents_spc, fs(contents_spc))
884 self.assertFormatEqual(contents_spc, fs(contents_tab))
886 # mixed tabs and spaces (valid Python 2 code)
887 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t# comment\n pass\n"
888 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
889 self.assertFormatEqual(contents_spc, fs(contents_spc))
890 self.assertFormatEqual(contents_spc, fs(contents_tab))
892 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t\t# comment\n pass\n"
893 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
894 self.assertFormatEqual(contents_spc, fs(contents_spc))
895 self.assertFormatEqual(contents_spc, fs(contents_tab))
897 def test_report_verbose(self) -> None:
898 report = black.Report(verbose=True)
902 def out(msg: str, **kwargs: Any) -> None:
903 out_lines.append(msg)
905 def err(msg: str, **kwargs: Any) -> None:
906 err_lines.append(msg)
908 with patch("black.out", out), patch("black.err", err):
909 report.done(Path("f1"), black.Changed.NO)
910 self.assertEqual(len(out_lines), 1)
911 self.assertEqual(len(err_lines), 0)
912 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
913 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
914 self.assertEqual(report.return_code, 0)
915 report.done(Path("f2"), black.Changed.YES)
916 self.assertEqual(len(out_lines), 2)
917 self.assertEqual(len(err_lines), 0)
918 self.assertEqual(out_lines[-1], "reformatted f2")
920 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
922 report.done(Path("f3"), black.Changed.CACHED)
923 self.assertEqual(len(out_lines), 3)
924 self.assertEqual(len(err_lines), 0)
926 out_lines[-1], "f3 wasn't modified on disk since last run."
929 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
931 self.assertEqual(report.return_code, 0)
933 self.assertEqual(report.return_code, 1)
935 report.failed(Path("e1"), "boom")
936 self.assertEqual(len(out_lines), 3)
937 self.assertEqual(len(err_lines), 1)
938 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
940 unstyle(str(report)),
941 "1 file reformatted, 2 files left unchanged, 1 file failed to"
944 self.assertEqual(report.return_code, 123)
945 report.done(Path("f3"), black.Changed.YES)
946 self.assertEqual(len(out_lines), 4)
947 self.assertEqual(len(err_lines), 1)
948 self.assertEqual(out_lines[-1], "reformatted f3")
950 unstyle(str(report)),
951 "2 files reformatted, 2 files left unchanged, 1 file failed to"
954 self.assertEqual(report.return_code, 123)
955 report.failed(Path("e2"), "boom")
956 self.assertEqual(len(out_lines), 4)
957 self.assertEqual(len(err_lines), 2)
958 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
960 unstyle(str(report)),
961 "2 files reformatted, 2 files left unchanged, 2 files failed to"
964 self.assertEqual(report.return_code, 123)
965 report.path_ignored(Path("wat"), "no match")
966 self.assertEqual(len(out_lines), 5)
967 self.assertEqual(len(err_lines), 2)
968 self.assertEqual(out_lines[-1], "wat ignored: no match")
970 unstyle(str(report)),
971 "2 files reformatted, 2 files left unchanged, 2 files failed to"
974 self.assertEqual(report.return_code, 123)
975 report.done(Path("f4"), black.Changed.NO)
976 self.assertEqual(len(out_lines), 6)
977 self.assertEqual(len(err_lines), 2)
978 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
980 unstyle(str(report)),
981 "2 files reformatted, 3 files left unchanged, 2 files failed to"
984 self.assertEqual(report.return_code, 123)
987 unstyle(str(report)),
988 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
989 " would fail to reformat.",
994 unstyle(str(report)),
995 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
996 " would fail to reformat.",
999 def test_report_quiet(self) -> None:
1000 report = black.Report(quiet=True)
1004 def out(msg: str, **kwargs: Any) -> None:
1005 out_lines.append(msg)
1007 def err(msg: str, **kwargs: Any) -> None:
1008 err_lines.append(msg)
1010 with patch("black.out", out), patch("black.err", err):
1011 report.done(Path("f1"), black.Changed.NO)
1012 self.assertEqual(len(out_lines), 0)
1013 self.assertEqual(len(err_lines), 0)
1014 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
1015 self.assertEqual(report.return_code, 0)
1016 report.done(Path("f2"), black.Changed.YES)
1017 self.assertEqual(len(out_lines), 0)
1018 self.assertEqual(len(err_lines), 0)
1020 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
1022 report.done(Path("f3"), black.Changed.CACHED)
1023 self.assertEqual(len(out_lines), 0)
1024 self.assertEqual(len(err_lines), 0)
1026 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
1028 self.assertEqual(report.return_code, 0)
1030 self.assertEqual(report.return_code, 1)
1031 report.check = False
1032 report.failed(Path("e1"), "boom")
1033 self.assertEqual(len(out_lines), 0)
1034 self.assertEqual(len(err_lines), 1)
1035 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
1037 unstyle(str(report)),
1038 "1 file reformatted, 2 files left unchanged, 1 file failed to"
1041 self.assertEqual(report.return_code, 123)
1042 report.done(Path("f3"), black.Changed.YES)
1043 self.assertEqual(len(out_lines), 0)
1044 self.assertEqual(len(err_lines), 1)
1046 unstyle(str(report)),
1047 "2 files reformatted, 2 files left unchanged, 1 file failed to"
1050 self.assertEqual(report.return_code, 123)
1051 report.failed(Path("e2"), "boom")
1052 self.assertEqual(len(out_lines), 0)
1053 self.assertEqual(len(err_lines), 2)
1054 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
1056 unstyle(str(report)),
1057 "2 files reformatted, 2 files left unchanged, 2 files failed to"
1060 self.assertEqual(report.return_code, 123)
1061 report.path_ignored(Path("wat"), "no match")
1062 self.assertEqual(len(out_lines), 0)
1063 self.assertEqual(len(err_lines), 2)
1065 unstyle(str(report)),
1066 "2 files reformatted, 2 files left unchanged, 2 files failed to"
1069 self.assertEqual(report.return_code, 123)
1070 report.done(Path("f4"), black.Changed.NO)
1071 self.assertEqual(len(out_lines), 0)
1072 self.assertEqual(len(err_lines), 2)
1074 unstyle(str(report)),
1075 "2 files reformatted, 3 files left unchanged, 2 files failed to"
1078 self.assertEqual(report.return_code, 123)
1081 unstyle(str(report)),
1082 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1083 " would fail to reformat.",
1085 report.check = False
1088 unstyle(str(report)),
1089 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1090 " would fail to reformat.",
1093 def test_report_normal(self) -> None:
1094 report = black.Report()
1098 def out(msg: str, **kwargs: Any) -> None:
1099 out_lines.append(msg)
1101 def err(msg: str, **kwargs: Any) -> None:
1102 err_lines.append(msg)
1104 with patch("black.out", out), patch("black.err", err):
1105 report.done(Path("f1"), black.Changed.NO)
1106 self.assertEqual(len(out_lines), 0)
1107 self.assertEqual(len(err_lines), 0)
1108 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
1109 self.assertEqual(report.return_code, 0)
1110 report.done(Path("f2"), black.Changed.YES)
1111 self.assertEqual(len(out_lines), 1)
1112 self.assertEqual(len(err_lines), 0)
1113 self.assertEqual(out_lines[-1], "reformatted f2")
1115 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
1117 report.done(Path("f3"), black.Changed.CACHED)
1118 self.assertEqual(len(out_lines), 1)
1119 self.assertEqual(len(err_lines), 0)
1120 self.assertEqual(out_lines[-1], "reformatted f2")
1122 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
1124 self.assertEqual(report.return_code, 0)
1126 self.assertEqual(report.return_code, 1)
1127 report.check = False
1128 report.failed(Path("e1"), "boom")
1129 self.assertEqual(len(out_lines), 1)
1130 self.assertEqual(len(err_lines), 1)
1131 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
1133 unstyle(str(report)),
1134 "1 file reformatted, 2 files left unchanged, 1 file failed to"
1137 self.assertEqual(report.return_code, 123)
1138 report.done(Path("f3"), black.Changed.YES)
1139 self.assertEqual(len(out_lines), 2)
1140 self.assertEqual(len(err_lines), 1)
1141 self.assertEqual(out_lines[-1], "reformatted f3")
1143 unstyle(str(report)),
1144 "2 files reformatted, 2 files left unchanged, 1 file failed to"
1147 self.assertEqual(report.return_code, 123)
1148 report.failed(Path("e2"), "boom")
1149 self.assertEqual(len(out_lines), 2)
1150 self.assertEqual(len(err_lines), 2)
1151 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
1153 unstyle(str(report)),
1154 "2 files reformatted, 2 files left unchanged, 2 files failed to"
1157 self.assertEqual(report.return_code, 123)
1158 report.path_ignored(Path("wat"), "no match")
1159 self.assertEqual(len(out_lines), 2)
1160 self.assertEqual(len(err_lines), 2)
1162 unstyle(str(report)),
1163 "2 files reformatted, 2 files left unchanged, 2 files failed to"
1166 self.assertEqual(report.return_code, 123)
1167 report.done(Path("f4"), black.Changed.NO)
1168 self.assertEqual(len(out_lines), 2)
1169 self.assertEqual(len(err_lines), 2)
1171 unstyle(str(report)),
1172 "2 files reformatted, 3 files left unchanged, 2 files failed to"
1175 self.assertEqual(report.return_code, 123)
1178 unstyle(str(report)),
1179 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1180 " would fail to reformat.",
1182 report.check = False
1185 unstyle(str(report)),
1186 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1187 " would fail to reformat.",
1190 def test_lib2to3_parse(self) -> None:
1191 with self.assertRaises(black.InvalidInput):
1192 black.lib2to3_parse("invalid syntax")
1194 straddling = "x + y"
1195 black.lib2to3_parse(straddling)
1196 black.lib2to3_parse(straddling, {TargetVersion.PY27})
1197 black.lib2to3_parse(straddling, {TargetVersion.PY36})
1198 black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
1200 py2_only = "print x"
1201 black.lib2to3_parse(py2_only)
1202 black.lib2to3_parse(py2_only, {TargetVersion.PY27})
1203 with self.assertRaises(black.InvalidInput):
1204 black.lib2to3_parse(py2_only, {TargetVersion.PY36})
1205 with self.assertRaises(black.InvalidInput):
1206 black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
1208 py3_only = "exec(x, end=y)"
1209 black.lib2to3_parse(py3_only)
1210 with self.assertRaises(black.InvalidInput):
1211 black.lib2to3_parse(py3_only, {TargetVersion.PY27})
1212 black.lib2to3_parse(py3_only, {TargetVersion.PY36})
1213 black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
1215 def test_get_features_used(self) -> None:
1216 node = black.lib2to3_parse("def f(*, arg): ...\n")
1217 self.assertEqual(black.get_features_used(node), set())
1218 node = black.lib2to3_parse("def f(*, arg,): ...\n")
1219 self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
1220 node = black.lib2to3_parse("f(*arg,)\n")
1222 black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
1224 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
1225 self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
1226 node = black.lib2to3_parse("123_456\n")
1227 self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
1228 node = black.lib2to3_parse("123456\n")
1229 self.assertEqual(black.get_features_used(node), set())
1230 source, expected = read_data("function")
1231 node = black.lib2to3_parse(source)
1232 expected_features = {
1233 Feature.TRAILING_COMMA_IN_CALL,
1234 Feature.TRAILING_COMMA_IN_DEF,
1237 self.assertEqual(black.get_features_used(node), expected_features)
1238 node = black.lib2to3_parse(expected)
1239 self.assertEqual(black.get_features_used(node), expected_features)
1240 source, expected = read_data("expression")
1241 node = black.lib2to3_parse(source)
1242 self.assertEqual(black.get_features_used(node), set())
1243 node = black.lib2to3_parse(expected)
1244 self.assertEqual(black.get_features_used(node), set())
1246 def test_get_future_imports(self) -> None:
1247 node = black.lib2to3_parse("\n")
1248 self.assertEqual(set(), black.get_future_imports(node))
1249 node = black.lib2to3_parse("from __future__ import black\n")
1250 self.assertEqual({"black"}, black.get_future_imports(node))
1251 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
1252 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1253 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
1254 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
1255 node = black.lib2to3_parse(
1256 "from __future__ import multiple\nfrom __future__ import imports\n"
1258 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1259 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
1260 self.assertEqual({"black"}, black.get_future_imports(node))
1261 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
1262 self.assertEqual({"black"}, black.get_future_imports(node))
1263 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
1264 self.assertEqual(set(), black.get_future_imports(node))
1265 node = black.lib2to3_parse("from some.module import black\n")
1266 self.assertEqual(set(), black.get_future_imports(node))
1267 node = black.lib2to3_parse(
1268 "from __future__ import unicode_literals as _unicode_literals"
1270 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
1271 node = black.lib2to3_parse(
1272 "from __future__ import unicode_literals as _lol, print"
1274 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
1276 def test_debug_visitor(self) -> None:
1277 source, _ = read_data("debug_visitor.py")
1278 expected, _ = read_data("debug_visitor.out")
1282 def out(msg: str, **kwargs: Any) -> None:
1283 out_lines.append(msg)
1285 def err(msg: str, **kwargs: Any) -> None:
1286 err_lines.append(msg)
1288 with patch("black.out", out), patch("black.err", err):
1289 black.DebugVisitor.show(source)
1290 actual = "\n".join(out_lines) + "\n"
1292 if expected != actual:
1293 log_name = black.dump_to_file(*out_lines)
1297 f"AST print out is different. Actual version dumped to {log_name}",
1300 def test_format_file_contents(self) -> None:
1303 with self.assertRaises(black.NothingChanged):
1304 black.format_file_contents(empty, mode=mode, fast=False)
1306 with self.assertRaises(black.NothingChanged):
1307 black.format_file_contents(just_nl, mode=mode, fast=False)
1308 same = "j = [1, 2, 3]\n"
1309 with self.assertRaises(black.NothingChanged):
1310 black.format_file_contents(same, mode=mode, fast=False)
1311 different = "j = [1,2,3]"
1313 actual = black.format_file_contents(different, mode=mode, fast=False)
1314 self.assertEqual(expected, actual)
1315 invalid = "return if you can"
1316 with self.assertRaises(black.InvalidInput) as e:
1317 black.format_file_contents(invalid, mode=mode, fast=False)
1318 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
1320 def test_endmarker(self) -> None:
1321 n = black.lib2to3_parse("\n")
1322 self.assertEqual(n.type, black.syms.file_input)
1323 self.assertEqual(len(n.children), 1)
1324 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
1326 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
1327 def test_assertFormatEqual(self) -> None:
1331 def out(msg: str, **kwargs: Any) -> None:
1332 out_lines.append(msg)
1334 def err(msg: str, **kwargs: Any) -> None:
1335 err_lines.append(msg)
1337 with patch("black.out", out), patch("black.err", err):
1338 with self.assertRaises(AssertionError):
1339 self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")
1341 out_str = "".join(out_lines)
1342 self.assertTrue("Expected tree:" in out_str)
1343 self.assertTrue("Actual tree:" in out_str)
1344 self.assertEqual("".join(err_lines), "")
1346 def test_cache_broken_file(self) -> None:
1348 with cache_dir() as workspace:
1349 cache_file = black.get_cache_file(mode)
1350 with cache_file.open("w") as fobj:
1351 fobj.write("this is not a pickle")
1352 self.assertEqual(black.read_cache(mode), {})
1353 src = (workspace / "test.py").resolve()
1354 with src.open("w") as fobj:
1355 fobj.write("print('hello')")
1356 self.invokeBlack([str(src)])
1357 cache = black.read_cache(mode)
1358 self.assertIn(src, cache)
1360 def test_cache_single_file_already_cached(self) -> None:
1362 with cache_dir() as workspace:
1363 src = (workspace / "test.py").resolve()
1364 with src.open("w") as fobj:
1365 fobj.write("print('hello')")
1366 black.write_cache({}, [src], mode)
1367 self.invokeBlack([str(src)])
1368 with src.open("r") as fobj:
1369 self.assertEqual(fobj.read(), "print('hello')")
1372 def test_cache_multiple_files(self) -> None:
1374 with cache_dir() as workspace, patch(
1375 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1377 one = (workspace / "one.py").resolve()
1378 with one.open("w") as fobj:
1379 fobj.write("print('hello')")
1380 two = (workspace / "two.py").resolve()
1381 with two.open("w") as fobj:
1382 fobj.write("print('hello')")
1383 black.write_cache({}, [one], mode)
1384 self.invokeBlack([str(workspace)])
1385 with one.open("r") as fobj:
1386 self.assertEqual(fobj.read(), "print('hello')")
1387 with two.open("r") as fobj:
1388 self.assertEqual(fobj.read(), 'print("hello")\n')
1389 cache = black.read_cache(mode)
1390 self.assertIn(one, cache)
1391 self.assertIn(two, cache)
1393 def test_no_cache_when_writeback_diff(self) -> None:
1395 with cache_dir() as workspace:
1396 src = (workspace / "test.py").resolve()
1397 with src.open("w") as fobj:
1398 fobj.write("print('hello')")
1399 with patch("black.read_cache") as read_cache, patch(
1402 self.invokeBlack([str(src), "--diff"])
1403 cache_file = black.get_cache_file(mode)
1404 self.assertFalse(cache_file.exists())
1405 write_cache.assert_not_called()
1406 read_cache.assert_not_called()
1408 def test_no_cache_when_writeback_color_diff(self) -> None:
1410 with cache_dir() as workspace:
1411 src = (workspace / "test.py").resolve()
1412 with src.open("w") as fobj:
1413 fobj.write("print('hello')")
1414 with patch("black.read_cache") as read_cache, patch(
1417 self.invokeBlack([str(src), "--diff", "--color"])
1418 cache_file = black.get_cache_file(mode)
1419 self.assertFalse(cache_file.exists())
1420 write_cache.assert_not_called()
1421 read_cache.assert_not_called()
1424 def test_output_locking_when_writeback_diff(self) -> None:
1425 with cache_dir() as workspace:
1426 for tag in range(0, 4):
1427 src = (workspace / f"test{tag}.py").resolve()
1428 with src.open("w") as fobj:
1429 fobj.write("print('hello')")
1430 with patch("black.Manager", wraps=multiprocessing.Manager) as mgr:
1431 self.invokeBlack(["--diff", str(workspace)], exit_code=0)
1432 # this isn't quite doing what we want, but if it _isn't_
1433 # called then we cannot be using the lock it provides
1437 def test_output_locking_when_writeback_color_diff(self) -> None:
1438 with cache_dir() as workspace:
1439 for tag in range(0, 4):
1440 src = (workspace / f"test{tag}.py").resolve()
1441 with src.open("w") as fobj:
1442 fobj.write("print('hello')")
1443 with patch("black.Manager", wraps=multiprocessing.Manager) as mgr:
1444 self.invokeBlack(["--diff", "--color", str(workspace)], exit_code=0)
1445 # this isn't quite doing what we want, but if it _isn't_
1446 # called then we cannot be using the lock it provides
1449 def test_no_cache_when_stdin(self) -> None:
1452 result = CliRunner().invoke(
1453 black.main, ["-"], input=BytesIO(b"print('hello')")
1455 self.assertEqual(result.exit_code, 0)
1456 cache_file = black.get_cache_file(mode)
1457 self.assertFalse(cache_file.exists())
1459 def test_read_cache_no_cachefile(self) -> None:
1462 self.assertEqual(black.read_cache(mode), {})
1464 def test_write_cache_read_cache(self) -> None:
1466 with cache_dir() as workspace:
1467 src = (workspace / "test.py").resolve()
1469 black.write_cache({}, [src], mode)
1470 cache = black.read_cache(mode)
1471 self.assertIn(src, cache)
1472 self.assertEqual(cache[src], black.get_cache_info(src))
1474 def test_filter_cached(self) -> None:
1475 with TemporaryDirectory() as workspace:
1476 path = Path(workspace)
1477 uncached = (path / "uncached").resolve()
1478 cached = (path / "cached").resolve()
1479 cached_but_changed = (path / "changed").resolve()
1482 cached_but_changed.touch()
1483 cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
1484 todo, done = black.filter_cached(
1485 cache, {uncached, cached, cached_but_changed}
1487 self.assertEqual(todo, {uncached, cached_but_changed})
1488 self.assertEqual(done, {cached})
1490 def test_write_cache_creates_directory_if_needed(self) -> None:
1492 with cache_dir(exists=False) as workspace:
1493 self.assertFalse(workspace.exists())
1494 black.write_cache({}, [], mode)
1495 self.assertTrue(workspace.exists())
1498 def test_failed_formatting_does_not_get_cached(self) -> None:
1500 with cache_dir() as workspace, patch(
1501 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1503 failing = (workspace / "failing.py").resolve()
1504 with failing.open("w") as fobj:
1505 fobj.write("not actually python")
1506 clean = (workspace / "clean.py").resolve()
1507 with clean.open("w") as fobj:
1508 fobj.write('print("hello")\n')
1509 self.invokeBlack([str(workspace)], exit_code=123)
1510 cache = black.read_cache(mode)
1511 self.assertNotIn(failing, cache)
1512 self.assertIn(clean, cache)
1514 def test_write_cache_write_fail(self) -> None:
1516 with cache_dir(), patch.object(Path, "open") as mock:
1517 mock.side_effect = OSError
1518 black.write_cache({}, [], mode)
1521 @patch("black.ProcessPoolExecutor", MagicMock(side_effect=OSError))
1522 def test_works_in_mono_process_only_environment(self) -> None:
1523 with cache_dir() as workspace:
1525 (workspace / "one.py").resolve(),
1526 (workspace / "two.py").resolve(),
1528 f.write_text('print("hello")\n')
1529 self.invokeBlack([str(workspace)])
1532 def test_check_diff_use_together(self) -> None:
1534 # Files which will be reformatted.
1535 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1536 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1537 # Files which will not be reformatted.
1538 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1539 self.invokeBlack([str(src2), "--diff", "--check"])
1540 # Multi file command.
1541 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1543 def test_no_files(self) -> None:
1545 # Without an argument, black exits with error code 0.
1546 self.invokeBlack([])
1548 def test_broken_symlink(self) -> None:
1549 with cache_dir() as workspace:
1550 symlink = workspace / "broken_link.py"
1552 symlink.symlink_to("nonexistent.py")
1553 except OSError as e:
1554 self.skipTest(f"Can't create symlinks: {e}")
1555 self.invokeBlack([str(workspace.resolve())])
1557 def test_read_cache_line_lengths(self) -> None:
1559 short_mode = replace(DEFAULT_MODE, line_length=1)
1560 with cache_dir() as workspace:
1561 path = (workspace / "file.py").resolve()
1563 black.write_cache({}, [path], mode)
1564 one = black.read_cache(mode)
1565 self.assertIn(path, one)
1566 two = black.read_cache(short_mode)
1567 self.assertNotIn(path, two)
1569 def test_tricky_unicode_symbols(self) -> None:
1570 source, expected = read_data("tricky_unicode_symbols")
1572 self.assertFormatEqual(expected, actual)
1573 black.assert_equivalent(source, actual)
1574 black.assert_stable(source, actual, DEFAULT_MODE)
1576 def test_single_file_force_pyi(self) -> None:
1577 reg_mode = DEFAULT_MODE
1578 pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
1579 contents, expected = read_data("force_pyi")
1580 with cache_dir() as workspace:
1581 path = (workspace / "file.py").resolve()
1582 with open(path, "w") as fh:
1584 self.invokeBlack([str(path), "--pyi"])
1585 with open(path, "r") as fh:
1587 # verify cache with --pyi is separate
1588 pyi_cache = black.read_cache(pyi_mode)
1589 self.assertIn(path, pyi_cache)
1590 normal_cache = black.read_cache(reg_mode)
1591 self.assertNotIn(path, normal_cache)
1592 self.assertEqual(actual, expected)
1595 def test_multi_file_force_pyi(self) -> None:
1596 reg_mode = DEFAULT_MODE
1597 pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
1598 contents, expected = read_data("force_pyi")
1599 with cache_dir() as workspace:
1601 (workspace / "file1.py").resolve(),
1602 (workspace / "file2.py").resolve(),
1605 with open(path, "w") as fh:
1607 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1609 with open(path, "r") as fh:
1611 self.assertEqual(actual, expected)
1612 # verify cache with --pyi is separate
1613 pyi_cache = black.read_cache(pyi_mode)
1614 normal_cache = black.read_cache(reg_mode)
1616 self.assertIn(path, pyi_cache)
1617 self.assertNotIn(path, normal_cache)
1619 def test_pipe_force_pyi(self) -> None:
1620 source, expected = read_data("force_pyi")
1621 result = CliRunner().invoke(
1622 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1624 self.assertEqual(result.exit_code, 0)
1625 actual = result.output
1626 self.assertFormatEqual(actual, expected)
1628 def test_single_file_force_py36(self) -> None:
1629 reg_mode = DEFAULT_MODE
1630 py36_mode = replace(DEFAULT_MODE, target_versions=black.PY36_VERSIONS)
1631 source, expected = read_data("force_py36")
1632 with cache_dir() as workspace:
1633 path = (workspace / "file.py").resolve()
1634 with open(path, "w") as fh:
1636 self.invokeBlack([str(path), *PY36_ARGS])
1637 with open(path, "r") as fh:
1639 # verify cache with --target-version is separate
1640 py36_cache = black.read_cache(py36_mode)
1641 self.assertIn(path, py36_cache)
1642 normal_cache = black.read_cache(reg_mode)
1643 self.assertNotIn(path, normal_cache)
1644 self.assertEqual(actual, expected)
1647 def test_multi_file_force_py36(self) -> None:
1648 reg_mode = DEFAULT_MODE
1649 py36_mode = replace(DEFAULT_MODE, target_versions=black.PY36_VERSIONS)
1650 source, expected = read_data("force_py36")
1651 with cache_dir() as workspace:
1653 (workspace / "file1.py").resolve(),
1654 (workspace / "file2.py").resolve(),
1657 with open(path, "w") as fh:
1659 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1661 with open(path, "r") as fh:
1663 self.assertEqual(actual, expected)
1664 # verify cache with --target-version is separate
1665 pyi_cache = black.read_cache(py36_mode)
1666 normal_cache = black.read_cache(reg_mode)
1668 self.assertIn(path, pyi_cache)
1669 self.assertNotIn(path, normal_cache)
1671 def test_collections(self) -> None:
1672 source, expected = read_data("collections")
1674 self.assertFormatEqual(expected, actual)
1675 black.assert_equivalent(source, actual)
1676 black.assert_stable(source, actual, DEFAULT_MODE)
1678 def test_pipe_force_py36(self) -> None:
1679 source, expected = read_data("force_py36")
1680 result = CliRunner().invoke(
1682 ["-", "-q", "--target-version=py36"],
1683 input=BytesIO(source.encode("utf8")),
1685 self.assertEqual(result.exit_code, 0)
1686 actual = result.output
1687 self.assertFormatEqual(actual, expected)
1689 def test_include_exclude(self) -> None:
1690 path = THIS_DIR / "data" / "include_exclude_tests"
1691 include = re.compile(r"\.pyi?$")
1692 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1693 report = black.Report()
1694 gitignore = PathSpec.from_lines("gitwildmatch", [])
1695 sources: List[Path] = []
1697 Path(path / "b/dont_exclude/a.py"),
1698 Path(path / "b/dont_exclude/a.pyi"),
1700 this_abs = THIS_DIR.resolve()
1702 black.gen_python_files(
1703 path.iterdir(), this_abs, include, exclude, None, report, gitignore
1706 self.assertEqual(sorted(expected), sorted(sources))
1708 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1709 def test_exclude_for_issue_1572(self) -> None:
1710 # Exclude shouldn't touch files that were explicitly given to Black through the
1711 # CLI. Exclude is supposed to only apply to the recursive discovery of files.
1712 # https://github.com/psf/black/issues/1572
1713 path = THIS_DIR / "data" / "include_exclude_tests"
1715 exclude = r"/exclude/|a\.py"
1716 src = str(path / "b/exclude/a.py")
1717 report = black.Report()
1718 expected = [Path(path / "b/exclude/a.py")]
1731 self.assertEqual(sorted(expected), sorted(sources))
1733 def test_gitignore_exclude(self) -> None:
1734 path = THIS_DIR / "data" / "include_exclude_tests"
1735 include = re.compile(r"\.pyi?$")
1736 exclude = re.compile(r"")
1737 report = black.Report()
1738 gitignore = PathSpec.from_lines(
1739 "gitwildmatch", ["exclude/", ".definitely_exclude"]
1741 sources: List[Path] = []
1743 Path(path / "b/dont_exclude/a.py"),
1744 Path(path / "b/dont_exclude/a.pyi"),
1746 this_abs = THIS_DIR.resolve()
1748 black.gen_python_files(
1749 path.iterdir(), this_abs, include, exclude, None, report, gitignore
1752 self.assertEqual(sorted(expected), sorted(sources))
1754 def test_empty_include(self) -> None:
1755 path = THIS_DIR / "data" / "include_exclude_tests"
1756 report = black.Report()
1757 gitignore = PathSpec.from_lines("gitwildmatch", [])
1758 empty = re.compile(r"")
1759 sources: List[Path] = []
1761 Path(path / "b/exclude/a.pie"),
1762 Path(path / "b/exclude/a.py"),
1763 Path(path / "b/exclude/a.pyi"),
1764 Path(path / "b/dont_exclude/a.pie"),
1765 Path(path / "b/dont_exclude/a.py"),
1766 Path(path / "b/dont_exclude/a.pyi"),
1767 Path(path / "b/.definitely_exclude/a.pie"),
1768 Path(path / "b/.definitely_exclude/a.py"),
1769 Path(path / "b/.definitely_exclude/a.pyi"),
1771 this_abs = THIS_DIR.resolve()
1773 black.gen_python_files(
1777 re.compile(black.DEFAULT_EXCLUDES),
1783 self.assertEqual(sorted(expected), sorted(sources))
1785 def test_empty_exclude(self) -> None:
1786 path = THIS_DIR / "data" / "include_exclude_tests"
1787 report = black.Report()
1788 gitignore = PathSpec.from_lines("gitwildmatch", [])
1789 empty = re.compile(r"")
1790 sources: List[Path] = []
1792 Path(path / "b/dont_exclude/a.py"),
1793 Path(path / "b/dont_exclude/a.pyi"),
1794 Path(path / "b/exclude/a.py"),
1795 Path(path / "b/exclude/a.pyi"),
1796 Path(path / "b/.definitely_exclude/a.py"),
1797 Path(path / "b/.definitely_exclude/a.pyi"),
1799 this_abs = THIS_DIR.resolve()
1801 black.gen_python_files(
1804 re.compile(black.DEFAULT_INCLUDES),
1811 self.assertEqual(sorted(expected), sorted(sources))
1813 def test_invalid_include_exclude(self) -> None:
1814 for option in ["--include", "--exclude"]:
1815 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1817 def test_preserves_line_endings(self) -> None:
1818 with TemporaryDirectory() as workspace:
1819 test_file = Path(workspace) / "test.py"
1820 for nl in ["\n", "\r\n"]:
1821 contents = nl.join(["def f( ):", " pass"])
1822 test_file.write_bytes(contents.encode())
1823 ff(test_file, write_back=black.WriteBack.YES)
1824 updated_contents: bytes = test_file.read_bytes()
1825 self.assertIn(nl.encode(), updated_contents)
1827 self.assertNotIn(b"\r\n", updated_contents)
1829 def test_preserves_line_endings_via_stdin(self) -> None:
1830 for nl in ["\n", "\r\n"]:
1831 contents = nl.join(["def f( ):", " pass"])
1832 runner = BlackRunner()
1833 result = runner.invoke(
1834 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1836 self.assertEqual(result.exit_code, 0)
1837 output = runner.stdout_bytes
1838 self.assertIn(nl.encode("utf8"), output)
1840 self.assertNotIn(b"\r\n", output)
1842 def test_assert_equivalent_different_asts(self) -> None:
1843 with self.assertRaises(AssertionError):
1844 black.assert_equivalent("{}", "None")
1846 def test_symlink_out_of_root_directory(self) -> None:
1848 root = THIS_DIR.resolve()
1850 include = re.compile(black.DEFAULT_INCLUDES)
1851 exclude = re.compile(black.DEFAULT_EXCLUDES)
1852 report = black.Report()
1853 gitignore = PathSpec.from_lines("gitwildmatch", [])
1854 # `child` should behave like a symlink which resolved path is clearly
1855 # outside of the `root` directory.
1856 path.iterdir.return_value = [child]
1857 child.resolve.return_value = Path("/a/b/c")
1858 child.as_posix.return_value = "/a/b/c"
1859 child.is_symlink.return_value = True
1862 black.gen_python_files(
1863 path.iterdir(), root, include, exclude, None, report, gitignore
1866 except ValueError as ve:
1867 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1868 path.iterdir.assert_called_once()
1869 child.resolve.assert_called_once()
1870 child.is_symlink.assert_called_once()
1871 # `child` should behave like a strange file which resolved path is clearly
1872 # outside of the `root` directory.
1873 child.is_symlink.return_value = False
1874 with self.assertRaises(ValueError):
1876 black.gen_python_files(
1877 path.iterdir(), root, include, exclude, None, report, gitignore
1880 path.iterdir.assert_called()
1881 self.assertEqual(path.iterdir.call_count, 2)
1882 child.resolve.assert_called()
1883 self.assertEqual(child.resolve.call_count, 2)
1884 child.is_symlink.assert_called()
1885 self.assertEqual(child.is_symlink.call_count, 2)
1887 def test_shhh_click(self) -> None:
1889 from click import _unicodefun # type: ignore
1890 except ModuleNotFoundError:
1891 self.skipTest("Incompatible Click version")
1892 if not hasattr(_unicodefun, "_verify_python3_env"):
1893 self.skipTest("Incompatible Click version")
1894 # First, let's see if Click is crashing with a preferred ASCII charset.
1895 with patch("locale.getpreferredencoding") as gpe:
1896 gpe.return_value = "ASCII"
1897 with self.assertRaises(RuntimeError):
1898 _unicodefun._verify_python3_env()
1899 # Now, let's silence Click...
1901 # ...and confirm it's silent.
1902 with patch("locale.getpreferredencoding") as gpe:
1903 gpe.return_value = "ASCII"
1905 _unicodefun._verify_python3_env()
1906 except RuntimeError as re:
1907 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1909 def test_root_logger_not_used_directly(self) -> None:
1910 def fail(*args: Any, **kwargs: Any) -> None:
1911 self.fail("Record created with root logger")
1913 with patch.multiple(
1924 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1925 def test_blackd_main(self) -> None:
1926 with patch("blackd.web.run_app"):
1927 result = CliRunner().invoke(blackd.main, [])
1928 if result.exception is not None:
1929 raise result.exception
1930 self.assertEqual(result.exit_code, 0)
1932 def test_invalid_config_return_code(self) -> None:
1933 tmp_file = Path(black.dump_to_file())
1935 tmp_config = Path(black.dump_to_file())
1937 args = ["--config", str(tmp_config), str(tmp_file)]
1938 self.invokeBlack(args, exit_code=2, ignore_config=False)
1942 def test_parse_pyproject_toml(self) -> None:
1943 test_toml_file = THIS_DIR / "test.toml"
1944 config = black.parse_pyproject_toml(str(test_toml_file))
1945 self.assertEqual(config["verbose"], 1)
1946 self.assertEqual(config["check"], "no")
1947 self.assertEqual(config["diff"], "y")
1948 self.assertEqual(config["color"], True)
1949 self.assertEqual(config["line_length"], 79)
1950 self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
1951 self.assertEqual(config["exclude"], r"\.pyi?$")
1952 self.assertEqual(config["include"], r"\.py?$")
1954 def test_read_pyproject_toml(self) -> None:
1955 test_toml_file = THIS_DIR / "test.toml"
1956 fake_ctx = FakeContext()
1957 black.read_pyproject_toml(fake_ctx, FakeParameter(), str(test_toml_file))
1958 config = fake_ctx.default_map
1959 self.assertEqual(config["verbose"], "1")
1960 self.assertEqual(config["check"], "no")
1961 self.assertEqual(config["diff"], "y")
1962 self.assertEqual(config["color"], "True")
1963 self.assertEqual(config["line_length"], "79")
1964 self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
1965 self.assertEqual(config["exclude"], r"\.pyi?$")
1966 self.assertEqual(config["include"], r"\.py?$")
1968 def test_find_project_root(self) -> None:
1969 with TemporaryDirectory() as workspace:
1970 root = Path(workspace)
1971 test_dir = root / "test"
1974 src_dir = root / "src"
1977 root_pyproject = root / "pyproject.toml"
1978 root_pyproject.touch()
1979 src_pyproject = src_dir / "pyproject.toml"
1980 src_pyproject.touch()
1981 src_python = src_dir / "foo.py"
1985 black.find_project_root((src_dir, test_dir)), root.resolve()
1987 self.assertEqual(black.find_project_root((src_dir,)), src_dir.resolve())
1988 self.assertEqual(black.find_project_root((src_python,)), src_dir.resolve())
1990 def test_bpo_33660_workaround(self) -> None:
1991 if system() == "Windows":
1994 # https://bugs.python.org/issue33660
1996 old_cwd = Path.cwd()
2000 path = Path("workspace") / "project"
2001 report = black.Report(verbose=True)
2002 normalized_path = black.normalize_path_maybe_ignore(path, root, report)
2003 self.assertEqual(normalized_path, "workspace/project")
2005 os.chdir(str(old_cwd))
2008 class BlackDTestCase(AioHTTPTestCase):
2009 async def get_application(self) -> web.Application:
2010 return blackd.make_app()
2012 # TODO: remove these decorators once the below is released
2013 # https://github.com/aio-libs/aiohttp/pull/3727
2014 @skip_if_exception("ClientOSError")
2015 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
2017 async def test_blackd_request_needs_formatting(self) -> None:
2018 response = await self.client.post("/", data=b"print('hello world')")
2019 self.assertEqual(response.status, 200)
2020 self.assertEqual(response.charset, "utf8")
2021 self.assertEqual(await response.read(), b'print("hello world")\n')
2023 @skip_if_exception("ClientOSError")
2024 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
2026 async def test_blackd_request_no_change(self) -> None:
2027 response = await self.client.post("/", data=b'print("hello world")\n')
2028 self.assertEqual(response.status, 204)
2029 self.assertEqual(await response.read(), b"")
2031 @skip_if_exception("ClientOSError")
2032 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
2034 async def test_blackd_request_syntax_error(self) -> None:
2035 response = await self.client.post("/", data=b"what even ( is")
2036 self.assertEqual(response.status, 400)
2037 content = await response.text()
2039 content.startswith("Cannot parse"),
2040 msg=f"Expected error to start with 'Cannot parse', got {repr(content)}",
2043 @skip_if_exception("ClientOSError")
2044 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
2046 async def test_blackd_unsupported_version(self) -> None:
2047 response = await self.client.post(
2048 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "2"}
2050 self.assertEqual(response.status, 501)
2052 @skip_if_exception("ClientOSError")
2053 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
2055 async def test_blackd_supported_version(self) -> None:
2056 response = await self.client.post(
2057 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "1"}
2059 self.assertEqual(response.status, 200)
2061 @skip_if_exception("ClientOSError")
2062 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
2064 async def test_blackd_invalid_python_variant(self) -> None:
2065 async def check(header_value: str, expected_status: int = 400) -> None:
2066 response = await self.client.post(
2067 "/", data=b"what", headers={blackd.PYTHON_VARIANT_HEADER: header_value}
2069 self.assertEqual(response.status, expected_status)
2072 await check("ruby3.5")
2073 await check("pyi3.6")
2074 await check("py1.5")
2076 await check("py2.8")
2078 await check("pypy3.0")
2079 await check("jython3.4")
2081 @skip_if_exception("ClientOSError")
2082 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
2084 async def test_blackd_pyi(self) -> None:
2085 source, expected = read_data("stub.pyi")
2086 response = await self.client.post(
2087 "/", data=source, headers={blackd.PYTHON_VARIANT_HEADER: "pyi"}
2089 self.assertEqual(response.status, 200)
2090 self.assertEqual(await response.text(), expected)
2092 @skip_if_exception("ClientOSError")
2093 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
2095 async def test_blackd_diff(self) -> None:
2096 diff_header = re.compile(
2097 r"(In|Out)\t\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
2100 source, _ = read_data("blackd_diff.py")
2101 expected, _ = read_data("blackd_diff.diff")
2103 response = await self.client.post(
2104 "/", data=source, headers={blackd.DIFF_HEADER: "true"}
2106 self.assertEqual(response.status, 200)
2108 actual = await response.text()
2109 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
2110 self.assertEqual(actual, expected)
2112 @skip_if_exception("ClientOSError")
2113 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
2115 async def test_blackd_python_variant(self) -> None:
2118 " and_has_a_bunch_of,\n"
2119 " very_long_arguments_too,\n"
2120 " and_lots_of_them_as_well_lol,\n"
2121 " **and_very_long_keyword_arguments\n"
2126 async def check(header_value: str, expected_status: int) -> None:
2127 response = await self.client.post(
2128 "/", data=code, headers={blackd.PYTHON_VARIANT_HEADER: header_value}
2131 response.status, expected_status, msg=await response.text()
2134 await check("3.6", 200)
2135 await check("py3.6", 200)
2136 await check("3.6,3.7", 200)
2137 await check("3.6,py3.7", 200)
2138 await check("py36,py37", 200)
2139 await check("36", 200)
2140 await check("3.6.4", 200)
2142 await check("2", 204)
2143 await check("2.7", 204)
2144 await check("py2.7", 204)
2145 await check("3.4", 204)
2146 await check("py3.4", 204)
2147 await check("py34,py36", 204)
2148 await check("34", 204)
2150 @skip_if_exception("ClientOSError")
2151 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
2153 async def test_blackd_line_length(self) -> None:
2154 response = await self.client.post(
2155 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "7"}
2157 self.assertEqual(response.status, 200)
2159 @skip_if_exception("ClientOSError")
2160 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
2162 async def test_blackd_invalid_line_length(self) -> None:
2163 response = await self.client.post(
2164 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "NaN"}
2166 self.assertEqual(response.status, 400)
2168 @skip_if_exception("ClientOSError")
2169 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
2171 async def test_blackd_response_black_version_header(self) -> None:
2172 response = await self.client.post("/")
2173 self.assertIsNotNone(response.headers.get(blackd.BLACK_VERSION_HEADER))
2176 with open(black.__file__, "r", encoding="utf-8") as _bf:
2177 black_source_lines = _bf.readlines()
2180 def tracefunc(frame: types.FrameType, event: str, arg: Any) -> Callable:
2181 """Show function calls `from black/__init__.py` as they happen.
2183 Register this with `sys.settrace()` in a test you're debugging.
2188 stack = len(inspect.stack()) - 19
2190 filename = frame.f_code.co_filename
2191 lineno = frame.f_lineno
2192 func_sig_lineno = lineno - 1
2193 funcname = black_source_lines[func_sig_lineno].strip()
2194 while funcname.startswith("@"):
2195 func_sig_lineno += 1
2196 funcname = black_source_lines[func_sig_lineno].strip()
2197 if "black/__init__.py" in filename:
2198 print(f"{' ' * stack}{lineno}:{funcname}")
2202 if __name__ == "__main__":
2203 unittest.main(module="test_black")