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 dataclasses import replace
7 from functools import partial
9 from io import BytesIO, TextIOWrapper
11 from pathlib import Path
12 from platform import system
15 from tempfile import TemporaryDirectory
29 from unittest.mock import patch, MagicMock
32 from click import unstyle
33 from click.testing import CliRunner
36 from black import Feature, TargetVersion
40 from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
41 from aiohttp import web
43 has_blackd_deps = False
45 has_blackd_deps = True
47 from pathspec import PathSpec
49 # Import other test classes
50 from .test_primer import PrimerCLITests # noqa: F401
53 DEFAULT_MODE = black.FileMode(experimental_string_processing=True)
54 ff = partial(black.format_file_in_place, mode=DEFAULT_MODE, fast=True)
55 fs = partial(black.format_str, mode=DEFAULT_MODE)
56 THIS_FILE = Path(__file__)
57 THIS_DIR = THIS_FILE.parent
58 PROJECT_ROOT = THIS_DIR.parent
59 DETERMINISTIC_HEADER = "[Deterministic header]"
60 EMPTY_LINE = "# EMPTY LINE WITH WHITESPACE" + " (this comment will be removed)"
62 f"--target-version={version.name.lower()}" for version in black.PY36_VERSIONS
68 def dump_to_stderr(*output: str) -> str:
69 return "\n" + "\n".join(output) + "\n"
72 def read_data(name: str, data: bool = True) -> Tuple[str, str]:
73 """read_data('test_name') -> 'input', 'output'"""
74 if not name.endswith((".py", ".pyi", ".out", ".diff")):
76 _input: List[str] = []
77 _output: List[str] = []
78 base_dir = THIS_DIR / "data" if data else PROJECT_ROOT
79 with open(base_dir / name, "r", encoding="utf8") as test:
80 lines = test.readlines()
83 line = line.replace(EMPTY_LINE, "")
84 if line.rstrip() == "# output":
89 if _input and not _output:
90 # If there's no output marker, treat the entire file as already pre-formatted.
92 return "".join(_input).strip() + "\n", "".join(_output).strip() + "\n"
96 def cache_dir(exists: bool = True) -> Iterator[Path]:
97 with TemporaryDirectory() as workspace:
98 cache_dir = Path(workspace)
100 cache_dir = cache_dir / "new"
101 with patch("black.CACHE_DIR", cache_dir):
106 def event_loop() -> Iterator[None]:
107 policy = asyncio.get_event_loop_policy()
108 loop = policy.new_event_loop()
109 asyncio.set_event_loop(loop)
118 def skip_if_exception(e: str) -> Iterator[None]:
121 except Exception as exc:
122 if exc.__class__.__name__ == e:
123 unittest.skip(f"Encountered expected exception {exc}, skipping")
128 class FakeContext(click.Context):
129 """A fake click Context for when calling functions that need it."""
131 def __init__(self) -> None:
132 self.default_map: Dict[str, Any] = {}
135 class FakeParameter(click.Parameter):
136 """A fake click Parameter for when calling functions that need it."""
138 def __init__(self) -> None:
142 class BlackRunner(CliRunner):
143 """Modify CliRunner so that stderr is not merged with stdout.
145 This is a hack that can be removed once we depend on Click 7.x"""
147 def __init__(self) -> None:
148 self.stderrbuf = BytesIO()
149 self.stdoutbuf = BytesIO()
150 self.stdout_bytes = b""
151 self.stderr_bytes = b""
155 def isolation(self, *args: Any, **kwargs: Any) -> Generator[BinaryIO, None, None]:
156 with super().isolation(*args, **kwargs) as output:
158 hold_stderr = sys.stderr
159 sys.stderr = TextIOWrapper(self.stderrbuf, encoding=self.charset)
162 self.stdout_bytes = sys.stdout.buffer.getvalue() # type: ignore
163 self.stderr_bytes = sys.stderr.buffer.getvalue() # type: ignore
164 sys.stderr = hold_stderr
167 class BlackTestCase(unittest.TestCase):
169 _diffThreshold = 2 ** 20
171 def assertFormatEqual(self, expected: str, actual: str) -> None:
172 if actual != expected and not os.environ.get("SKIP_AST_PRINT"):
173 bdv: black.DebugVisitor[Any]
174 black.out("Expected tree:", fg="green")
176 exp_node = black.lib2to3_parse(expected)
177 bdv = black.DebugVisitor()
178 list(bdv.visit(exp_node))
179 except Exception as ve:
181 black.out("Actual tree:", fg="red")
183 exp_node = black.lib2to3_parse(actual)
184 bdv = black.DebugVisitor()
185 list(bdv.visit(exp_node))
186 except Exception as ve:
188 self.assertMultiLineEqual(expected, actual)
191 self, args: List[str], exit_code: int = 0, ignore_config: bool = True
193 runner = BlackRunner()
195 args = ["--verbose", "--config", str(THIS_DIR / "empty.toml"), *args]
196 result = runner.invoke(black.main, args)
201 f"Failed with args: {args}\n"
202 f"stdout: {runner.stdout_bytes.decode()!r}\n"
203 f"stderr: {runner.stderr_bytes.decode()!r}\n"
204 f"exception: {result.exception}"
208 @patch("black.dump_to_file", dump_to_stderr)
209 def checkSourceFile(self, name: str, mode: black.FileMode = DEFAULT_MODE) -> None:
210 path = THIS_DIR.parent / name
211 source, expected = read_data(str(path), data=False)
212 actual = fs(source, mode=mode)
213 self.assertFormatEqual(expected, actual)
214 black.assert_equivalent(source, actual)
215 black.assert_stable(source, actual, mode)
216 self.assertFalse(ff(path))
218 @patch("black.dump_to_file", dump_to_stderr)
219 def test_empty(self) -> None:
220 source = expected = ""
222 self.assertFormatEqual(expected, actual)
223 black.assert_equivalent(source, actual)
224 black.assert_stable(source, actual, DEFAULT_MODE)
226 def test_empty_ff(self) -> None:
228 tmp_file = Path(black.dump_to_file())
230 self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES))
231 with open(tmp_file, encoding="utf8") as f:
235 self.assertFormatEqual(expected, actual)
237 def test_self(self) -> None:
238 self.checkSourceFile("tests/test_black.py")
240 def test_black(self) -> None:
241 self.checkSourceFile("src/black/__init__.py")
243 def test_pygram(self) -> None:
244 self.checkSourceFile("src/blib2to3/pygram.py")
246 def test_pytree(self) -> None:
247 self.checkSourceFile("src/blib2to3/pytree.py")
249 def test_conv(self) -> None:
250 self.checkSourceFile("src/blib2to3/pgen2/conv.py")
252 def test_driver(self) -> None:
253 self.checkSourceFile("src/blib2to3/pgen2/driver.py")
255 def test_grammar(self) -> None:
256 self.checkSourceFile("src/blib2to3/pgen2/grammar.py")
258 def test_literals(self) -> None:
259 self.checkSourceFile("src/blib2to3/pgen2/literals.py")
261 def test_parse(self) -> None:
262 self.checkSourceFile("src/blib2to3/pgen2/parse.py")
264 def test_pgen(self) -> None:
265 self.checkSourceFile("src/blib2to3/pgen2/pgen.py")
267 def test_tokenize(self) -> None:
268 self.checkSourceFile("src/blib2to3/pgen2/tokenize.py")
270 def test_token(self) -> None:
271 self.checkSourceFile("src/blib2to3/pgen2/token.py")
273 def test_setup(self) -> None:
274 self.checkSourceFile("setup.py")
276 def test_piping(self) -> None:
277 source, expected = read_data("src/black/__init__", data=False)
278 result = BlackRunner().invoke(
280 ["-", "--fast", f"--line-length={black.DEFAULT_LINE_LENGTH}"],
281 input=BytesIO(source.encode("utf8")),
283 self.assertEqual(result.exit_code, 0)
284 self.assertFormatEqual(expected, result.output)
285 black.assert_equivalent(source, result.output)
286 black.assert_stable(source, result.output, DEFAULT_MODE)
288 def test_piping_diff(self) -> None:
289 diff_header = re.compile(
290 r"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d\d\d\d "
293 source, _ = read_data("expression.py")
294 expected, _ = read_data("expression.diff")
295 config = THIS_DIR / "data" / "empty_pyproject.toml"
299 f"--line-length={black.DEFAULT_LINE_LENGTH}",
301 f"--config={config}",
303 result = BlackRunner().invoke(
304 black.main, args, input=BytesIO(source.encode("utf8"))
306 self.assertEqual(result.exit_code, 0)
307 actual = diff_header.sub(DETERMINISTIC_HEADER, result.output)
308 actual = actual.rstrip() + "\n" # the diff output has a trailing space
309 self.assertEqual(expected, actual)
311 def test_piping_diff_with_color(self) -> None:
312 source, _ = read_data("expression.py")
313 config = THIS_DIR / "data" / "empty_pyproject.toml"
317 f"--line-length={black.DEFAULT_LINE_LENGTH}",
320 f"--config={config}",
322 result = BlackRunner().invoke(
323 black.main, args, input=BytesIO(source.encode("utf8"))
325 actual = result.output
326 # Again, the contents are checked in a different test, so only look for colors.
327 self.assertIn("\033[1;37m", actual)
328 self.assertIn("\033[36m", actual)
329 self.assertIn("\033[32m", actual)
330 self.assertIn("\033[31m", actual)
331 self.assertIn("\033[0m", actual)
333 @patch("black.dump_to_file", dump_to_stderr)
334 def test_function(self) -> None:
335 source, expected = read_data("function")
337 self.assertFormatEqual(expected, actual)
338 black.assert_equivalent(source, actual)
339 black.assert_stable(source, actual, DEFAULT_MODE)
341 @patch("black.dump_to_file", dump_to_stderr)
342 def test_function2(self) -> None:
343 source, expected = read_data("function2")
345 self.assertFormatEqual(expected, actual)
346 black.assert_equivalent(source, actual)
347 black.assert_stable(source, actual, DEFAULT_MODE)
349 @patch("black.dump_to_file", dump_to_stderr)
350 def _test_wip(self) -> None:
351 source, expected = read_data("wip")
352 sys.settrace(tracefunc)
355 experimental_string_processing=False,
356 target_versions={black.TargetVersion.PY38},
358 actual = fs(source, mode=mode)
360 self.assertFormatEqual(expected, actual)
361 black.assert_equivalent(source, actual)
362 black.assert_stable(source, actual, black.FileMode())
364 @patch("black.dump_to_file", dump_to_stderr)
365 def test_function_trailing_comma(self) -> None:
366 source, expected = read_data("function_trailing_comma")
368 self.assertFormatEqual(expected, actual)
369 black.assert_equivalent(source, actual)
370 black.assert_stable(source, actual, DEFAULT_MODE)
372 @unittest.expectedFailure
373 @patch("black.dump_to_file", dump_to_stderr)
374 def test_trailing_comma_optional_parens_stability1(self) -> None:
375 source, _expected = read_data("trailing_comma_optional_parens1")
377 black.assert_stable(source, actual, DEFAULT_MODE)
379 @unittest.expectedFailure
380 @patch("black.dump_to_file", dump_to_stderr)
381 def test_trailing_comma_optional_parens_stability2(self) -> None:
382 source, _expected = read_data("trailing_comma_optional_parens2")
384 black.assert_stable(source, actual, DEFAULT_MODE)
386 @unittest.expectedFailure
387 @patch("black.dump_to_file", dump_to_stderr)
388 def test_trailing_comma_optional_parens_stability3(self) -> None:
389 source, _expected = read_data("trailing_comma_optional_parens3")
391 black.assert_stable(source, actual, DEFAULT_MODE)
393 @patch("black.dump_to_file", dump_to_stderr)
394 def test_expression(self) -> None:
395 source, expected = read_data("expression")
397 self.assertFormatEqual(expected, actual)
398 black.assert_equivalent(source, actual)
399 black.assert_stable(source, actual, DEFAULT_MODE)
401 @patch("black.dump_to_file", dump_to_stderr)
402 def test_pep_572(self) -> None:
403 source, expected = read_data("pep_572")
405 self.assertFormatEqual(expected, actual)
406 black.assert_stable(source, actual, DEFAULT_MODE)
407 if sys.version_info >= (3, 8):
408 black.assert_equivalent(source, actual)
410 def test_pep_572_version_detection(self) -> None:
411 source, _ = read_data("pep_572")
412 root = black.lib2to3_parse(source)
413 features = black.get_features_used(root)
414 self.assertIn(black.Feature.ASSIGNMENT_EXPRESSIONS, features)
415 versions = black.detect_target_versions(root)
416 self.assertIn(black.TargetVersion.PY38, versions)
418 def test_expression_ff(self) -> None:
419 source, expected = read_data("expression")
420 tmp_file = Path(black.dump_to_file(source))
422 self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
423 with open(tmp_file, encoding="utf8") as f:
427 self.assertFormatEqual(expected, actual)
428 with patch("black.dump_to_file", dump_to_stderr):
429 black.assert_equivalent(source, actual)
430 black.assert_stable(source, actual, DEFAULT_MODE)
432 def test_expression_diff(self) -> None:
433 source, _ = read_data("expression.py")
434 expected, _ = read_data("expression.diff")
435 tmp_file = Path(black.dump_to_file(source))
436 diff_header = re.compile(
437 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
438 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
441 result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
442 self.assertEqual(result.exit_code, 0)
445 actual = result.output
446 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
447 actual = actual.rstrip() + "\n" # the diff output has a trailing space
448 if expected != actual:
449 dump = black.dump_to_file(actual)
451 "Expected diff isn't equal to the actual. If you made changes to"
452 " expression.py and this is an anticipated difference, overwrite"
453 f" tests/data/expression.diff with {dump}"
455 self.assertEqual(expected, actual, msg)
457 def test_expression_diff_with_color(self) -> None:
458 source, _ = read_data("expression.py")
459 expected, _ = read_data("expression.diff")
460 tmp_file = Path(black.dump_to_file(source))
462 result = BlackRunner().invoke(
463 black.main, ["--diff", "--color", str(tmp_file)]
467 actual = result.output
468 # We check the contents of the diff in `test_expression_diff`. All
469 # we need to check here is that color codes exist in the result.
470 self.assertIn("\033[1;37m", actual)
471 self.assertIn("\033[36m", actual)
472 self.assertIn("\033[32m", actual)
473 self.assertIn("\033[31m", actual)
474 self.assertIn("\033[0m", actual)
476 @patch("black.dump_to_file", dump_to_stderr)
477 def test_fstring(self) -> None:
478 source, expected = read_data("fstring")
480 self.assertFormatEqual(expected, actual)
481 black.assert_equivalent(source, actual)
482 black.assert_stable(source, actual, DEFAULT_MODE)
484 @patch("black.dump_to_file", dump_to_stderr)
485 def test_pep_570(self) -> None:
486 source, expected = read_data("pep_570")
488 self.assertFormatEqual(expected, actual)
489 black.assert_stable(source, actual, DEFAULT_MODE)
490 if sys.version_info >= (3, 8):
491 black.assert_equivalent(source, actual)
493 def test_detect_pos_only_arguments(self) -> None:
494 source, _ = read_data("pep_570")
495 root = black.lib2to3_parse(source)
496 features = black.get_features_used(root)
497 self.assertIn(black.Feature.POS_ONLY_ARGUMENTS, features)
498 versions = black.detect_target_versions(root)
499 self.assertIn(black.TargetVersion.PY38, versions)
501 @patch("black.dump_to_file", dump_to_stderr)
502 def test_string_quotes(self) -> None:
503 source, expected = read_data("string_quotes")
505 self.assertFormatEqual(expected, actual)
506 black.assert_equivalent(source, actual)
507 black.assert_stable(source, actual, DEFAULT_MODE)
508 mode = replace(DEFAULT_MODE, string_normalization=False)
509 not_normalized = fs(source, mode=mode)
510 self.assertFormatEqual(source.replace("\\\n", ""), not_normalized)
511 black.assert_equivalent(source, not_normalized)
512 black.assert_stable(source, not_normalized, mode=mode)
514 @patch("black.dump_to_file", dump_to_stderr)
515 def test_docstring(self) -> None:
516 source, expected = read_data("docstring")
518 self.assertFormatEqual(expected, actual)
519 black.assert_equivalent(source, actual)
520 black.assert_stable(source, actual, DEFAULT_MODE)
522 @patch("black.dump_to_file", dump_to_stderr)
523 def test_docstring_no_string_normalization(self) -> None:
524 """Like test_docstring but with string normalization off."""
525 source, expected = read_data("docstring_no_string_normalization")
526 mode = replace(DEFAULT_MODE, string_normalization=False)
527 actual = fs(source, mode=mode)
528 self.assertFormatEqual(expected, actual)
529 black.assert_equivalent(source, actual)
530 black.assert_stable(source, actual, mode)
532 def test_long_strings(self) -> None:
533 """Tests for splitting long strings."""
534 source, expected = read_data("long_strings")
536 self.assertFormatEqual(expected, actual)
537 black.assert_equivalent(source, actual)
538 black.assert_stable(source, actual, DEFAULT_MODE)
540 def test_long_strings_flag_disabled(self) -> None:
541 """Tests for turning off the string processing logic."""
542 source, expected = read_data("long_strings_flag_disabled")
543 mode = replace(DEFAULT_MODE, experimental_string_processing=False)
544 actual = fs(source, mode=mode)
545 self.assertFormatEqual(expected, actual)
546 black.assert_stable(expected, actual, mode)
548 @patch("black.dump_to_file", dump_to_stderr)
549 def test_long_strings__edge_case(self) -> None:
550 """Edge-case tests for splitting long strings."""
551 source, expected = read_data("long_strings__edge_case")
553 self.assertFormatEqual(expected, actual)
554 black.assert_equivalent(source, actual)
555 black.assert_stable(source, actual, DEFAULT_MODE)
557 @patch("black.dump_to_file", dump_to_stderr)
558 def test_long_strings__regression(self) -> None:
559 """Regression tests for splitting long strings."""
560 source, expected = read_data("long_strings__regression")
562 self.assertFormatEqual(expected, actual)
563 black.assert_equivalent(source, actual)
564 black.assert_stable(source, actual, DEFAULT_MODE)
566 @patch("black.dump_to_file", dump_to_stderr)
567 def test_slices(self) -> None:
568 source, expected = read_data("slices")
570 self.assertFormatEqual(expected, actual)
571 black.assert_equivalent(source, actual)
572 black.assert_stable(source, actual, DEFAULT_MODE)
574 @patch("black.dump_to_file", dump_to_stderr)
575 def test_percent_precedence(self) -> None:
576 source, expected = read_data("percent_precedence")
578 self.assertFormatEqual(expected, actual)
579 black.assert_equivalent(source, actual)
580 black.assert_stable(source, actual, DEFAULT_MODE)
582 @patch("black.dump_to_file", dump_to_stderr)
583 def test_comments(self) -> None:
584 source, expected = read_data("comments")
586 self.assertFormatEqual(expected, actual)
587 black.assert_equivalent(source, actual)
588 black.assert_stable(source, actual, DEFAULT_MODE)
590 @patch("black.dump_to_file", dump_to_stderr)
591 def test_comments2(self) -> None:
592 source, expected = read_data("comments2")
594 self.assertFormatEqual(expected, actual)
595 black.assert_equivalent(source, actual)
596 black.assert_stable(source, actual, DEFAULT_MODE)
598 @patch("black.dump_to_file", dump_to_stderr)
599 def test_comments3(self) -> None:
600 source, expected = read_data("comments3")
602 self.assertFormatEqual(expected, actual)
603 black.assert_equivalent(source, actual)
604 black.assert_stable(source, actual, DEFAULT_MODE)
606 @patch("black.dump_to_file", dump_to_stderr)
607 def test_comments4(self) -> None:
608 source, expected = read_data("comments4")
610 self.assertFormatEqual(expected, actual)
611 black.assert_equivalent(source, actual)
612 black.assert_stable(source, actual, DEFAULT_MODE)
614 @patch("black.dump_to_file", dump_to_stderr)
615 def test_comments5(self) -> None:
616 source, expected = read_data("comments5")
618 self.assertFormatEqual(expected, actual)
619 black.assert_equivalent(source, actual)
620 black.assert_stable(source, actual, DEFAULT_MODE)
622 @patch("black.dump_to_file", dump_to_stderr)
623 def test_comments6(self) -> None:
624 source, expected = read_data("comments6")
626 self.assertFormatEqual(expected, actual)
627 black.assert_equivalent(source, actual)
628 black.assert_stable(source, actual, DEFAULT_MODE)
630 @patch("black.dump_to_file", dump_to_stderr)
631 def test_comments7(self) -> None:
632 source, expected = read_data("comments7")
633 mode = replace(DEFAULT_MODE, target_versions={black.TargetVersion.PY38})
634 actual = fs(source, mode=mode)
635 self.assertFormatEqual(expected, actual)
636 black.assert_equivalent(source, actual)
637 black.assert_stable(source, actual, DEFAULT_MODE)
639 @patch("black.dump_to_file", dump_to_stderr)
640 def test_comment_after_escaped_newline(self) -> None:
641 source, expected = read_data("comment_after_escaped_newline")
643 self.assertFormatEqual(expected, actual)
644 black.assert_equivalent(source, actual)
645 black.assert_stable(source, actual, DEFAULT_MODE)
647 @patch("black.dump_to_file", dump_to_stderr)
648 def test_cantfit(self) -> None:
649 source, expected = read_data("cantfit")
651 self.assertFormatEqual(expected, actual)
652 black.assert_equivalent(source, actual)
653 black.assert_stable(source, actual, DEFAULT_MODE)
655 @patch("black.dump_to_file", dump_to_stderr)
656 def test_import_spacing(self) -> None:
657 source, expected = read_data("import_spacing")
659 self.assertFormatEqual(expected, actual)
660 black.assert_equivalent(source, actual)
661 black.assert_stable(source, actual, DEFAULT_MODE)
663 @patch("black.dump_to_file", dump_to_stderr)
664 def test_composition(self) -> None:
665 source, expected = read_data("composition")
667 self.assertFormatEqual(expected, actual)
668 black.assert_equivalent(source, actual)
669 black.assert_stable(source, actual, DEFAULT_MODE)
671 @patch("black.dump_to_file", dump_to_stderr)
672 def test_composition_no_trailing_comma(self) -> None:
673 source, expected = read_data("composition_no_trailing_comma")
674 mode = replace(DEFAULT_MODE, target_versions={black.TargetVersion.PY38})
675 actual = fs(source, mode=mode)
676 self.assertFormatEqual(expected, actual)
677 black.assert_equivalent(source, actual)
678 black.assert_stable(source, actual, DEFAULT_MODE)
680 @patch("black.dump_to_file", dump_to_stderr)
681 def test_empty_lines(self) -> None:
682 source, expected = read_data("empty_lines")
684 self.assertFormatEqual(expected, actual)
685 black.assert_equivalent(source, actual)
686 black.assert_stable(source, actual, DEFAULT_MODE)
688 @patch("black.dump_to_file", dump_to_stderr)
689 def test_remove_parens(self) -> None:
690 source, expected = read_data("remove_parens")
692 self.assertFormatEqual(expected, actual)
693 black.assert_equivalent(source, actual)
694 black.assert_stable(source, actual, DEFAULT_MODE)
696 @patch("black.dump_to_file", dump_to_stderr)
697 def test_string_prefixes(self) -> None:
698 source, expected = read_data("string_prefixes")
700 self.assertFormatEqual(expected, actual)
701 black.assert_equivalent(source, actual)
702 black.assert_stable(source, actual, DEFAULT_MODE)
704 @patch("black.dump_to_file", dump_to_stderr)
705 def test_numeric_literals(self) -> None:
706 source, expected = read_data("numeric_literals")
707 mode = replace(DEFAULT_MODE, target_versions=black.PY36_VERSIONS)
708 actual = fs(source, mode=mode)
709 self.assertFormatEqual(expected, actual)
710 black.assert_equivalent(source, actual)
711 black.assert_stable(source, actual, mode)
713 @patch("black.dump_to_file", dump_to_stderr)
714 def test_numeric_literals_ignoring_underscores(self) -> None:
715 source, expected = read_data("numeric_literals_skip_underscores")
716 mode = replace(DEFAULT_MODE, target_versions=black.PY36_VERSIONS)
717 actual = fs(source, mode=mode)
718 self.assertFormatEqual(expected, actual)
719 black.assert_equivalent(source, actual)
720 black.assert_stable(source, actual, mode)
722 @patch("black.dump_to_file", dump_to_stderr)
723 def test_numeric_literals_py2(self) -> None:
724 source, expected = read_data("numeric_literals_py2")
726 self.assertFormatEqual(expected, actual)
727 black.assert_stable(source, actual, DEFAULT_MODE)
729 @patch("black.dump_to_file", dump_to_stderr)
730 def test_python2(self) -> None:
731 source, expected = read_data("python2")
733 self.assertFormatEqual(expected, actual)
734 black.assert_equivalent(source, actual)
735 black.assert_stable(source, actual, DEFAULT_MODE)
737 @patch("black.dump_to_file", dump_to_stderr)
738 def test_python2_print_function(self) -> None:
739 source, expected = read_data("python2_print_function")
740 mode = replace(DEFAULT_MODE, target_versions={TargetVersion.PY27})
741 actual = fs(source, mode=mode)
742 self.assertFormatEqual(expected, actual)
743 black.assert_equivalent(source, actual)
744 black.assert_stable(source, actual, mode)
746 @patch("black.dump_to_file", dump_to_stderr)
747 def test_python2_unicode_literals(self) -> None:
748 source, expected = read_data("python2_unicode_literals")
750 self.assertFormatEqual(expected, actual)
751 black.assert_equivalent(source, actual)
752 black.assert_stable(source, actual, DEFAULT_MODE)
754 @patch("black.dump_to_file", dump_to_stderr)
755 def test_stub(self) -> None:
756 mode = replace(DEFAULT_MODE, is_pyi=True)
757 source, expected = read_data("stub.pyi")
758 actual = fs(source, mode=mode)
759 self.assertFormatEqual(expected, actual)
760 black.assert_stable(source, actual, mode)
762 @patch("black.dump_to_file", dump_to_stderr)
763 def test_async_as_identifier(self) -> None:
764 source_path = (THIS_DIR / "data" / "async_as_identifier.py").resolve()
765 source, expected = read_data("async_as_identifier")
767 self.assertFormatEqual(expected, actual)
768 major, minor = sys.version_info[:2]
769 if major < 3 or (major <= 3 and minor < 7):
770 black.assert_equivalent(source, actual)
771 black.assert_stable(source, actual, DEFAULT_MODE)
772 # ensure black can parse this when the target is 3.6
773 self.invokeBlack([str(source_path), "--target-version", "py36"])
774 # but not on 3.7, because async/await is no longer an identifier
775 self.invokeBlack([str(source_path), "--target-version", "py37"], exit_code=123)
777 @patch("black.dump_to_file", dump_to_stderr)
778 def test_python37(self) -> None:
779 source_path = (THIS_DIR / "data" / "python37.py").resolve()
780 source, expected = read_data("python37")
782 self.assertFormatEqual(expected, actual)
783 major, minor = sys.version_info[:2]
784 if major > 3 or (major == 3 and minor >= 7):
785 black.assert_equivalent(source, actual)
786 black.assert_stable(source, actual, DEFAULT_MODE)
787 # ensure black can parse this when the target is 3.7
788 self.invokeBlack([str(source_path), "--target-version", "py37"])
789 # but not on 3.6, because we use async as a reserved keyword
790 self.invokeBlack([str(source_path), "--target-version", "py36"], exit_code=123)
792 @patch("black.dump_to_file", dump_to_stderr)
793 def test_python38(self) -> None:
794 source, expected = read_data("python38")
796 self.assertFormatEqual(expected, actual)
797 major, minor = sys.version_info[:2]
798 if major > 3 or (major == 3 and minor >= 8):
799 black.assert_equivalent(source, actual)
800 black.assert_stable(source, actual, DEFAULT_MODE)
802 @patch("black.dump_to_file", dump_to_stderr)
803 def test_fmtonoff(self) -> None:
804 source, expected = read_data("fmtonoff")
806 self.assertFormatEqual(expected, actual)
807 black.assert_equivalent(source, actual)
808 black.assert_stable(source, actual, DEFAULT_MODE)
810 @patch("black.dump_to_file", dump_to_stderr)
811 def test_fmtonoff2(self) -> None:
812 source, expected = read_data("fmtonoff2")
814 self.assertFormatEqual(expected, actual)
815 black.assert_equivalent(source, actual)
816 black.assert_stable(source, actual, DEFAULT_MODE)
818 @patch("black.dump_to_file", dump_to_stderr)
819 def test_fmtonoff3(self) -> None:
820 source, expected = read_data("fmtonoff3")
822 self.assertFormatEqual(expected, actual)
823 black.assert_equivalent(source, actual)
824 black.assert_stable(source, actual, DEFAULT_MODE)
826 @patch("black.dump_to_file", dump_to_stderr)
827 def test_fmtonoff4(self) -> None:
828 source, expected = read_data("fmtonoff4")
830 self.assertFormatEqual(expected, actual)
831 black.assert_equivalent(source, actual)
832 black.assert_stable(source, actual, DEFAULT_MODE)
834 @patch("black.dump_to_file", dump_to_stderr)
835 def test_remove_empty_parentheses_after_class(self) -> None:
836 source, expected = read_data("class_blank_parentheses")
838 self.assertFormatEqual(expected, actual)
839 black.assert_equivalent(source, actual)
840 black.assert_stable(source, actual, DEFAULT_MODE)
842 @patch("black.dump_to_file", dump_to_stderr)
843 def test_new_line_between_class_and_code(self) -> None:
844 source, expected = read_data("class_methods_new_line")
846 self.assertFormatEqual(expected, actual)
847 black.assert_equivalent(source, actual)
848 black.assert_stable(source, actual, DEFAULT_MODE)
850 @patch("black.dump_to_file", dump_to_stderr)
851 def test_bracket_match(self) -> None:
852 source, expected = read_data("bracketmatch")
854 self.assertFormatEqual(expected, actual)
855 black.assert_equivalent(source, actual)
856 black.assert_stable(source, actual, DEFAULT_MODE)
858 @patch("black.dump_to_file", dump_to_stderr)
859 def test_tuple_assign(self) -> None:
860 source, expected = read_data("tupleassign")
862 self.assertFormatEqual(expected, actual)
863 black.assert_equivalent(source, actual)
864 black.assert_stable(source, actual, DEFAULT_MODE)
866 @patch("black.dump_to_file", dump_to_stderr)
867 def test_beginning_backslash(self) -> None:
868 source, expected = read_data("beginning_backslash")
870 self.assertFormatEqual(expected, actual)
871 black.assert_equivalent(source, actual)
872 black.assert_stable(source, actual, DEFAULT_MODE)
874 def test_tab_comment_indentation(self) -> None:
875 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
876 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
877 self.assertFormatEqual(contents_spc, fs(contents_spc))
878 self.assertFormatEqual(contents_spc, fs(contents_tab))
880 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
881 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
882 self.assertFormatEqual(contents_spc, fs(contents_spc))
883 self.assertFormatEqual(contents_spc, fs(contents_tab))
885 # mixed tabs and spaces (valid Python 2 code)
886 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t# comment\n pass\n"
887 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
888 self.assertFormatEqual(contents_spc, fs(contents_spc))
889 self.assertFormatEqual(contents_spc, fs(contents_tab))
891 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t\t# comment\n pass\n"
892 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
893 self.assertFormatEqual(contents_spc, fs(contents_spc))
894 self.assertFormatEqual(contents_spc, fs(contents_tab))
896 def test_report_verbose(self) -> None:
897 report = black.Report(verbose=True)
901 def out(msg: str, **kwargs: Any) -> None:
902 out_lines.append(msg)
904 def err(msg: str, **kwargs: Any) -> None:
905 err_lines.append(msg)
907 with patch("black.out", out), patch("black.err", err):
908 report.done(Path("f1"), black.Changed.NO)
909 self.assertEqual(len(out_lines), 1)
910 self.assertEqual(len(err_lines), 0)
911 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
912 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
913 self.assertEqual(report.return_code, 0)
914 report.done(Path("f2"), black.Changed.YES)
915 self.assertEqual(len(out_lines), 2)
916 self.assertEqual(len(err_lines), 0)
917 self.assertEqual(out_lines[-1], "reformatted f2")
919 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
921 report.done(Path("f3"), black.Changed.CACHED)
922 self.assertEqual(len(out_lines), 3)
923 self.assertEqual(len(err_lines), 0)
925 out_lines[-1], "f3 wasn't modified on disk since last run."
928 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
930 self.assertEqual(report.return_code, 0)
932 self.assertEqual(report.return_code, 1)
934 report.failed(Path("e1"), "boom")
935 self.assertEqual(len(out_lines), 3)
936 self.assertEqual(len(err_lines), 1)
937 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
939 unstyle(str(report)),
940 "1 file reformatted, 2 files left unchanged, 1 file failed to"
943 self.assertEqual(report.return_code, 123)
944 report.done(Path("f3"), black.Changed.YES)
945 self.assertEqual(len(out_lines), 4)
946 self.assertEqual(len(err_lines), 1)
947 self.assertEqual(out_lines[-1], "reformatted f3")
949 unstyle(str(report)),
950 "2 files reformatted, 2 files left unchanged, 1 file failed to"
953 self.assertEqual(report.return_code, 123)
954 report.failed(Path("e2"), "boom")
955 self.assertEqual(len(out_lines), 4)
956 self.assertEqual(len(err_lines), 2)
957 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
959 unstyle(str(report)),
960 "2 files reformatted, 2 files left unchanged, 2 files failed to"
963 self.assertEqual(report.return_code, 123)
964 report.path_ignored(Path("wat"), "no match")
965 self.assertEqual(len(out_lines), 5)
966 self.assertEqual(len(err_lines), 2)
967 self.assertEqual(out_lines[-1], "wat ignored: no match")
969 unstyle(str(report)),
970 "2 files reformatted, 2 files left unchanged, 2 files failed to"
973 self.assertEqual(report.return_code, 123)
974 report.done(Path("f4"), black.Changed.NO)
975 self.assertEqual(len(out_lines), 6)
976 self.assertEqual(len(err_lines), 2)
977 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
979 unstyle(str(report)),
980 "2 files reformatted, 3 files left unchanged, 2 files failed to"
983 self.assertEqual(report.return_code, 123)
986 unstyle(str(report)),
987 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
988 " would fail to reformat.",
993 unstyle(str(report)),
994 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
995 " would fail to reformat.",
998 def test_report_quiet(self) -> None:
999 report = black.Report(quiet=True)
1003 def out(msg: str, **kwargs: Any) -> None:
1004 out_lines.append(msg)
1006 def err(msg: str, **kwargs: Any) -> None:
1007 err_lines.append(msg)
1009 with patch("black.out", out), patch("black.err", err):
1010 report.done(Path("f1"), black.Changed.NO)
1011 self.assertEqual(len(out_lines), 0)
1012 self.assertEqual(len(err_lines), 0)
1013 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
1014 self.assertEqual(report.return_code, 0)
1015 report.done(Path("f2"), black.Changed.YES)
1016 self.assertEqual(len(out_lines), 0)
1017 self.assertEqual(len(err_lines), 0)
1019 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
1021 report.done(Path("f3"), black.Changed.CACHED)
1022 self.assertEqual(len(out_lines), 0)
1023 self.assertEqual(len(err_lines), 0)
1025 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
1027 self.assertEqual(report.return_code, 0)
1029 self.assertEqual(report.return_code, 1)
1030 report.check = False
1031 report.failed(Path("e1"), "boom")
1032 self.assertEqual(len(out_lines), 0)
1033 self.assertEqual(len(err_lines), 1)
1034 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
1036 unstyle(str(report)),
1037 "1 file reformatted, 2 files left unchanged, 1 file failed to"
1040 self.assertEqual(report.return_code, 123)
1041 report.done(Path("f3"), black.Changed.YES)
1042 self.assertEqual(len(out_lines), 0)
1043 self.assertEqual(len(err_lines), 1)
1045 unstyle(str(report)),
1046 "2 files reformatted, 2 files left unchanged, 1 file failed to"
1049 self.assertEqual(report.return_code, 123)
1050 report.failed(Path("e2"), "boom")
1051 self.assertEqual(len(out_lines), 0)
1052 self.assertEqual(len(err_lines), 2)
1053 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
1055 unstyle(str(report)),
1056 "2 files reformatted, 2 files left unchanged, 2 files failed to"
1059 self.assertEqual(report.return_code, 123)
1060 report.path_ignored(Path("wat"), "no match")
1061 self.assertEqual(len(out_lines), 0)
1062 self.assertEqual(len(err_lines), 2)
1064 unstyle(str(report)),
1065 "2 files reformatted, 2 files left unchanged, 2 files failed to"
1068 self.assertEqual(report.return_code, 123)
1069 report.done(Path("f4"), black.Changed.NO)
1070 self.assertEqual(len(out_lines), 0)
1071 self.assertEqual(len(err_lines), 2)
1073 unstyle(str(report)),
1074 "2 files reformatted, 3 files left unchanged, 2 files failed to"
1077 self.assertEqual(report.return_code, 123)
1080 unstyle(str(report)),
1081 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1082 " would fail to reformat.",
1084 report.check = False
1087 unstyle(str(report)),
1088 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1089 " would fail to reformat.",
1092 def test_report_normal(self) -> None:
1093 report = black.Report()
1097 def out(msg: str, **kwargs: Any) -> None:
1098 out_lines.append(msg)
1100 def err(msg: str, **kwargs: Any) -> None:
1101 err_lines.append(msg)
1103 with patch("black.out", out), patch("black.err", err):
1104 report.done(Path("f1"), black.Changed.NO)
1105 self.assertEqual(len(out_lines), 0)
1106 self.assertEqual(len(err_lines), 0)
1107 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
1108 self.assertEqual(report.return_code, 0)
1109 report.done(Path("f2"), black.Changed.YES)
1110 self.assertEqual(len(out_lines), 1)
1111 self.assertEqual(len(err_lines), 0)
1112 self.assertEqual(out_lines[-1], "reformatted f2")
1114 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
1116 report.done(Path("f3"), black.Changed.CACHED)
1117 self.assertEqual(len(out_lines), 1)
1118 self.assertEqual(len(err_lines), 0)
1119 self.assertEqual(out_lines[-1], "reformatted f2")
1121 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
1123 self.assertEqual(report.return_code, 0)
1125 self.assertEqual(report.return_code, 1)
1126 report.check = False
1127 report.failed(Path("e1"), "boom")
1128 self.assertEqual(len(out_lines), 1)
1129 self.assertEqual(len(err_lines), 1)
1130 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
1132 unstyle(str(report)),
1133 "1 file reformatted, 2 files left unchanged, 1 file failed to"
1136 self.assertEqual(report.return_code, 123)
1137 report.done(Path("f3"), black.Changed.YES)
1138 self.assertEqual(len(out_lines), 2)
1139 self.assertEqual(len(err_lines), 1)
1140 self.assertEqual(out_lines[-1], "reformatted f3")
1142 unstyle(str(report)),
1143 "2 files reformatted, 2 files left unchanged, 1 file failed to"
1146 self.assertEqual(report.return_code, 123)
1147 report.failed(Path("e2"), "boom")
1148 self.assertEqual(len(out_lines), 2)
1149 self.assertEqual(len(err_lines), 2)
1150 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
1152 unstyle(str(report)),
1153 "2 files reformatted, 2 files left unchanged, 2 files failed to"
1156 self.assertEqual(report.return_code, 123)
1157 report.path_ignored(Path("wat"), "no match")
1158 self.assertEqual(len(out_lines), 2)
1159 self.assertEqual(len(err_lines), 2)
1161 unstyle(str(report)),
1162 "2 files reformatted, 2 files left unchanged, 2 files failed to"
1165 self.assertEqual(report.return_code, 123)
1166 report.done(Path("f4"), black.Changed.NO)
1167 self.assertEqual(len(out_lines), 2)
1168 self.assertEqual(len(err_lines), 2)
1170 unstyle(str(report)),
1171 "2 files reformatted, 3 files left unchanged, 2 files failed to"
1174 self.assertEqual(report.return_code, 123)
1177 unstyle(str(report)),
1178 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1179 " would fail to reformat.",
1181 report.check = False
1184 unstyle(str(report)),
1185 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1186 " would fail to reformat.",
1189 def test_lib2to3_parse(self) -> None:
1190 with self.assertRaises(black.InvalidInput):
1191 black.lib2to3_parse("invalid syntax")
1193 straddling = "x + y"
1194 black.lib2to3_parse(straddling)
1195 black.lib2to3_parse(straddling, {TargetVersion.PY27})
1196 black.lib2to3_parse(straddling, {TargetVersion.PY36})
1197 black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
1199 py2_only = "print x"
1200 black.lib2to3_parse(py2_only)
1201 black.lib2to3_parse(py2_only, {TargetVersion.PY27})
1202 with self.assertRaises(black.InvalidInput):
1203 black.lib2to3_parse(py2_only, {TargetVersion.PY36})
1204 with self.assertRaises(black.InvalidInput):
1205 black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
1207 py3_only = "exec(x, end=y)"
1208 black.lib2to3_parse(py3_only)
1209 with self.assertRaises(black.InvalidInput):
1210 black.lib2to3_parse(py3_only, {TargetVersion.PY27})
1211 black.lib2to3_parse(py3_only, {TargetVersion.PY36})
1212 black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
1214 def test_get_features_used(self) -> None:
1215 node = black.lib2to3_parse("def f(*, arg): ...\n")
1216 self.assertEqual(black.get_features_used(node), set())
1217 node = black.lib2to3_parse("def f(*, arg,): ...\n")
1218 self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
1219 node = black.lib2to3_parse("f(*arg,)\n")
1221 black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
1223 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
1224 self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
1225 node = black.lib2to3_parse("123_456\n")
1226 self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
1227 node = black.lib2to3_parse("123456\n")
1228 self.assertEqual(black.get_features_used(node), set())
1229 source, expected = read_data("function")
1230 node = black.lib2to3_parse(source)
1231 expected_features = {
1232 Feature.TRAILING_COMMA_IN_CALL,
1233 Feature.TRAILING_COMMA_IN_DEF,
1236 self.assertEqual(black.get_features_used(node), expected_features)
1237 node = black.lib2to3_parse(expected)
1238 self.assertEqual(black.get_features_used(node), expected_features)
1239 source, expected = read_data("expression")
1240 node = black.lib2to3_parse(source)
1241 self.assertEqual(black.get_features_used(node), set())
1242 node = black.lib2to3_parse(expected)
1243 self.assertEqual(black.get_features_used(node), set())
1245 def test_get_future_imports(self) -> None:
1246 node = black.lib2to3_parse("\n")
1247 self.assertEqual(set(), black.get_future_imports(node))
1248 node = black.lib2to3_parse("from __future__ import black\n")
1249 self.assertEqual({"black"}, black.get_future_imports(node))
1250 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
1251 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1252 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
1253 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
1254 node = black.lib2to3_parse(
1255 "from __future__ import multiple\nfrom __future__ import imports\n"
1257 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1258 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
1259 self.assertEqual({"black"}, black.get_future_imports(node))
1260 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
1261 self.assertEqual({"black"}, black.get_future_imports(node))
1262 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
1263 self.assertEqual(set(), black.get_future_imports(node))
1264 node = black.lib2to3_parse("from some.module import black\n")
1265 self.assertEqual(set(), black.get_future_imports(node))
1266 node = black.lib2to3_parse(
1267 "from __future__ import unicode_literals as _unicode_literals"
1269 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
1270 node = black.lib2to3_parse(
1271 "from __future__ import unicode_literals as _lol, print"
1273 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
1275 def test_debug_visitor(self) -> None:
1276 source, _ = read_data("debug_visitor.py")
1277 expected, _ = read_data("debug_visitor.out")
1281 def out(msg: str, **kwargs: Any) -> None:
1282 out_lines.append(msg)
1284 def err(msg: str, **kwargs: Any) -> None:
1285 err_lines.append(msg)
1287 with patch("black.out", out), patch("black.err", err):
1288 black.DebugVisitor.show(source)
1289 actual = "\n".join(out_lines) + "\n"
1291 if expected != actual:
1292 log_name = black.dump_to_file(*out_lines)
1296 f"AST print out is different. Actual version dumped to {log_name}",
1299 def test_format_file_contents(self) -> None:
1302 with self.assertRaises(black.NothingChanged):
1303 black.format_file_contents(empty, mode=mode, fast=False)
1305 with self.assertRaises(black.NothingChanged):
1306 black.format_file_contents(just_nl, mode=mode, fast=False)
1307 same = "j = [1, 2, 3]\n"
1308 with self.assertRaises(black.NothingChanged):
1309 black.format_file_contents(same, mode=mode, fast=False)
1310 different = "j = [1,2,3]"
1312 actual = black.format_file_contents(different, mode=mode, fast=False)
1313 self.assertEqual(expected, actual)
1314 invalid = "return if you can"
1315 with self.assertRaises(black.InvalidInput) as e:
1316 black.format_file_contents(invalid, mode=mode, fast=False)
1317 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
1319 def test_endmarker(self) -> None:
1320 n = black.lib2to3_parse("\n")
1321 self.assertEqual(n.type, black.syms.file_input)
1322 self.assertEqual(len(n.children), 1)
1323 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
1325 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
1326 def test_assertFormatEqual(self) -> None:
1330 def out(msg: str, **kwargs: Any) -> None:
1331 out_lines.append(msg)
1333 def err(msg: str, **kwargs: Any) -> None:
1334 err_lines.append(msg)
1336 with patch("black.out", out), patch("black.err", err):
1337 with self.assertRaises(AssertionError):
1338 self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")
1340 out_str = "".join(out_lines)
1341 self.assertTrue("Expected tree:" in out_str)
1342 self.assertTrue("Actual tree:" in out_str)
1343 self.assertEqual("".join(err_lines), "")
1345 def test_cache_broken_file(self) -> None:
1347 with cache_dir() as workspace:
1348 cache_file = black.get_cache_file(mode)
1349 with cache_file.open("w") as fobj:
1350 fobj.write("this is not a pickle")
1351 self.assertEqual(black.read_cache(mode), {})
1352 src = (workspace / "test.py").resolve()
1353 with src.open("w") as fobj:
1354 fobj.write("print('hello')")
1355 self.invokeBlack([str(src)])
1356 cache = black.read_cache(mode)
1357 self.assertIn(src, cache)
1359 def test_cache_single_file_already_cached(self) -> None:
1361 with cache_dir() as workspace:
1362 src = (workspace / "test.py").resolve()
1363 with src.open("w") as fobj:
1364 fobj.write("print('hello')")
1365 black.write_cache({}, [src], mode)
1366 self.invokeBlack([str(src)])
1367 with src.open("r") as fobj:
1368 self.assertEqual(fobj.read(), "print('hello')")
1371 def test_cache_multiple_files(self) -> None:
1373 with cache_dir() as workspace, patch(
1374 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1376 one = (workspace / "one.py").resolve()
1377 with one.open("w") as fobj:
1378 fobj.write("print('hello')")
1379 two = (workspace / "two.py").resolve()
1380 with two.open("w") as fobj:
1381 fobj.write("print('hello')")
1382 black.write_cache({}, [one], mode)
1383 self.invokeBlack([str(workspace)])
1384 with one.open("r") as fobj:
1385 self.assertEqual(fobj.read(), "print('hello')")
1386 with two.open("r") as fobj:
1387 self.assertEqual(fobj.read(), 'print("hello")\n')
1388 cache = black.read_cache(mode)
1389 self.assertIn(one, cache)
1390 self.assertIn(two, cache)
1392 def test_no_cache_when_writeback_diff(self) -> None:
1394 with cache_dir() as workspace:
1395 src = (workspace / "test.py").resolve()
1396 with src.open("w") as fobj:
1397 fobj.write("print('hello')")
1398 self.invokeBlack([str(src), "--diff"])
1399 cache_file = black.get_cache_file(mode)
1400 self.assertFalse(cache_file.exists())
1402 def test_no_cache_when_stdin(self) -> None:
1405 result = CliRunner().invoke(
1406 black.main, ["-"], input=BytesIO(b"print('hello')")
1408 self.assertEqual(result.exit_code, 0)
1409 cache_file = black.get_cache_file(mode)
1410 self.assertFalse(cache_file.exists())
1412 def test_read_cache_no_cachefile(self) -> None:
1415 self.assertEqual(black.read_cache(mode), {})
1417 def test_write_cache_read_cache(self) -> None:
1419 with cache_dir() as workspace:
1420 src = (workspace / "test.py").resolve()
1422 black.write_cache({}, [src], mode)
1423 cache = black.read_cache(mode)
1424 self.assertIn(src, cache)
1425 self.assertEqual(cache[src], black.get_cache_info(src))
1427 def test_filter_cached(self) -> None:
1428 with TemporaryDirectory() as workspace:
1429 path = Path(workspace)
1430 uncached = (path / "uncached").resolve()
1431 cached = (path / "cached").resolve()
1432 cached_but_changed = (path / "changed").resolve()
1435 cached_but_changed.touch()
1436 cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
1437 todo, done = black.filter_cached(
1438 cache, {uncached, cached, cached_but_changed}
1440 self.assertEqual(todo, {uncached, cached_but_changed})
1441 self.assertEqual(done, {cached})
1443 def test_write_cache_creates_directory_if_needed(self) -> None:
1445 with cache_dir(exists=False) as workspace:
1446 self.assertFalse(workspace.exists())
1447 black.write_cache({}, [], mode)
1448 self.assertTrue(workspace.exists())
1451 def test_failed_formatting_does_not_get_cached(self) -> None:
1453 with cache_dir() as workspace, patch(
1454 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1456 failing = (workspace / "failing.py").resolve()
1457 with failing.open("w") as fobj:
1458 fobj.write("not actually python")
1459 clean = (workspace / "clean.py").resolve()
1460 with clean.open("w") as fobj:
1461 fobj.write('print("hello")\n')
1462 self.invokeBlack([str(workspace)], exit_code=123)
1463 cache = black.read_cache(mode)
1464 self.assertNotIn(failing, cache)
1465 self.assertIn(clean, cache)
1467 def test_write_cache_write_fail(self) -> None:
1469 with cache_dir(), patch.object(Path, "open") as mock:
1470 mock.side_effect = OSError
1471 black.write_cache({}, [], mode)
1474 @patch("black.ProcessPoolExecutor", MagicMock(side_effect=OSError))
1475 def test_works_in_mono_process_only_environment(self) -> None:
1476 with cache_dir() as workspace:
1478 (workspace / "one.py").resolve(),
1479 (workspace / "two.py").resolve(),
1481 f.write_text('print("hello")\n')
1482 self.invokeBlack([str(workspace)])
1485 def test_check_diff_use_together(self) -> None:
1487 # Files which will be reformatted.
1488 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1489 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1490 # Files which will not be reformatted.
1491 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1492 self.invokeBlack([str(src2), "--diff", "--check"])
1493 # Multi file command.
1494 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1496 def test_no_files(self) -> None:
1498 # Without an argument, black exits with error code 0.
1499 self.invokeBlack([])
1501 def test_broken_symlink(self) -> None:
1502 with cache_dir() as workspace:
1503 symlink = workspace / "broken_link.py"
1505 symlink.symlink_to("nonexistent.py")
1506 except OSError as e:
1507 self.skipTest(f"Can't create symlinks: {e}")
1508 self.invokeBlack([str(workspace.resolve())])
1510 def test_read_cache_line_lengths(self) -> None:
1512 short_mode = replace(DEFAULT_MODE, line_length=1)
1513 with cache_dir() as workspace:
1514 path = (workspace / "file.py").resolve()
1516 black.write_cache({}, [path], mode)
1517 one = black.read_cache(mode)
1518 self.assertIn(path, one)
1519 two = black.read_cache(short_mode)
1520 self.assertNotIn(path, two)
1522 def test_tricky_unicode_symbols(self) -> None:
1523 source, expected = read_data("tricky_unicode_symbols")
1525 self.assertFormatEqual(expected, actual)
1526 black.assert_equivalent(source, actual)
1527 black.assert_stable(source, actual, DEFAULT_MODE)
1529 def test_single_file_force_pyi(self) -> None:
1530 reg_mode = DEFAULT_MODE
1531 pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
1532 contents, expected = read_data("force_pyi")
1533 with cache_dir() as workspace:
1534 path = (workspace / "file.py").resolve()
1535 with open(path, "w") as fh:
1537 self.invokeBlack([str(path), "--pyi"])
1538 with open(path, "r") as fh:
1540 # verify cache with --pyi is separate
1541 pyi_cache = black.read_cache(pyi_mode)
1542 self.assertIn(path, pyi_cache)
1543 normal_cache = black.read_cache(reg_mode)
1544 self.assertNotIn(path, normal_cache)
1545 self.assertEqual(actual, expected)
1548 def test_multi_file_force_pyi(self) -> None:
1549 reg_mode = DEFAULT_MODE
1550 pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
1551 contents, expected = read_data("force_pyi")
1552 with cache_dir() as workspace:
1554 (workspace / "file1.py").resolve(),
1555 (workspace / "file2.py").resolve(),
1558 with open(path, "w") as fh:
1560 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1562 with open(path, "r") as fh:
1564 self.assertEqual(actual, expected)
1565 # verify cache with --pyi is separate
1566 pyi_cache = black.read_cache(pyi_mode)
1567 normal_cache = black.read_cache(reg_mode)
1569 self.assertIn(path, pyi_cache)
1570 self.assertNotIn(path, normal_cache)
1572 def test_pipe_force_pyi(self) -> None:
1573 source, expected = read_data("force_pyi")
1574 result = CliRunner().invoke(
1575 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1577 self.assertEqual(result.exit_code, 0)
1578 actual = result.output
1579 self.assertFormatEqual(actual, expected)
1581 def test_single_file_force_py36(self) -> None:
1582 reg_mode = DEFAULT_MODE
1583 py36_mode = replace(DEFAULT_MODE, target_versions=black.PY36_VERSIONS)
1584 source, expected = read_data("force_py36")
1585 with cache_dir() as workspace:
1586 path = (workspace / "file.py").resolve()
1587 with open(path, "w") as fh:
1589 self.invokeBlack([str(path), *PY36_ARGS])
1590 with open(path, "r") as fh:
1592 # verify cache with --target-version is separate
1593 py36_cache = black.read_cache(py36_mode)
1594 self.assertIn(path, py36_cache)
1595 normal_cache = black.read_cache(reg_mode)
1596 self.assertNotIn(path, normal_cache)
1597 self.assertEqual(actual, expected)
1600 def test_multi_file_force_py36(self) -> None:
1601 reg_mode = DEFAULT_MODE
1602 py36_mode = replace(DEFAULT_MODE, target_versions=black.PY36_VERSIONS)
1603 source, expected = read_data("force_py36")
1604 with cache_dir() as workspace:
1606 (workspace / "file1.py").resolve(),
1607 (workspace / "file2.py").resolve(),
1610 with open(path, "w") as fh:
1612 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1614 with open(path, "r") as fh:
1616 self.assertEqual(actual, expected)
1617 # verify cache with --target-version is separate
1618 pyi_cache = black.read_cache(py36_mode)
1619 normal_cache = black.read_cache(reg_mode)
1621 self.assertIn(path, pyi_cache)
1622 self.assertNotIn(path, normal_cache)
1624 def test_collections(self) -> None:
1625 source, expected = read_data("collections")
1627 self.assertFormatEqual(expected, actual)
1628 black.assert_equivalent(source, actual)
1629 black.assert_stable(source, actual, DEFAULT_MODE)
1631 def test_pipe_force_py36(self) -> None:
1632 source, expected = read_data("force_py36")
1633 result = CliRunner().invoke(
1635 ["-", "-q", "--target-version=py36"],
1636 input=BytesIO(source.encode("utf8")),
1638 self.assertEqual(result.exit_code, 0)
1639 actual = result.output
1640 self.assertFormatEqual(actual, expected)
1642 def test_include_exclude(self) -> None:
1643 path = THIS_DIR / "data" / "include_exclude_tests"
1644 include = re.compile(r"\.pyi?$")
1645 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1646 report = black.Report()
1647 gitignore = PathSpec.from_lines("gitwildmatch", [])
1648 sources: List[Path] = []
1650 Path(path / "b/dont_exclude/a.py"),
1651 Path(path / "b/dont_exclude/a.pyi"),
1653 this_abs = THIS_DIR.resolve()
1655 black.gen_python_files(
1656 path.iterdir(), this_abs, include, exclude, None, report, gitignore
1659 self.assertEqual(sorted(expected), sorted(sources))
1661 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1662 def test_exclude_for_issue_1572(self) -> None:
1663 # Exclude shouldn't touch files that were explicitly given to Black through the
1664 # CLI. Exclude is supposed to only apply to the recursive discovery of files.
1665 # https://github.com/psf/black/issues/1572
1666 path = THIS_DIR / "data" / "include_exclude_tests"
1668 exclude = r"/exclude/|a\.py"
1669 src = str(path / "b/exclude/a.py")
1670 report = black.Report()
1671 expected = [Path(path / "b/exclude/a.py")]
1684 self.assertEqual(sorted(expected), sorted(sources))
1686 def test_gitignore_exclude(self) -> None:
1687 path = THIS_DIR / "data" / "include_exclude_tests"
1688 include = re.compile(r"\.pyi?$")
1689 exclude = re.compile(r"")
1690 report = black.Report()
1691 gitignore = PathSpec.from_lines(
1692 "gitwildmatch", ["exclude/", ".definitely_exclude"]
1694 sources: List[Path] = []
1696 Path(path / "b/dont_exclude/a.py"),
1697 Path(path / "b/dont_exclude/a.pyi"),
1699 this_abs = THIS_DIR.resolve()
1701 black.gen_python_files(
1702 path.iterdir(), this_abs, include, exclude, None, report, gitignore
1705 self.assertEqual(sorted(expected), sorted(sources))
1707 def test_empty_include(self) -> None:
1708 path = THIS_DIR / "data" / "include_exclude_tests"
1709 report = black.Report()
1710 gitignore = PathSpec.from_lines("gitwildmatch", [])
1711 empty = re.compile(r"")
1712 sources: List[Path] = []
1714 Path(path / "b/exclude/a.pie"),
1715 Path(path / "b/exclude/a.py"),
1716 Path(path / "b/exclude/a.pyi"),
1717 Path(path / "b/dont_exclude/a.pie"),
1718 Path(path / "b/dont_exclude/a.py"),
1719 Path(path / "b/dont_exclude/a.pyi"),
1720 Path(path / "b/.definitely_exclude/a.pie"),
1721 Path(path / "b/.definitely_exclude/a.py"),
1722 Path(path / "b/.definitely_exclude/a.pyi"),
1724 this_abs = THIS_DIR.resolve()
1726 black.gen_python_files(
1730 re.compile(black.DEFAULT_EXCLUDES),
1736 self.assertEqual(sorted(expected), sorted(sources))
1738 def test_empty_exclude(self) -> None:
1739 path = THIS_DIR / "data" / "include_exclude_tests"
1740 report = black.Report()
1741 gitignore = PathSpec.from_lines("gitwildmatch", [])
1742 empty = re.compile(r"")
1743 sources: List[Path] = []
1745 Path(path / "b/dont_exclude/a.py"),
1746 Path(path / "b/dont_exclude/a.pyi"),
1747 Path(path / "b/exclude/a.py"),
1748 Path(path / "b/exclude/a.pyi"),
1749 Path(path / "b/.definitely_exclude/a.py"),
1750 Path(path / "b/.definitely_exclude/a.pyi"),
1752 this_abs = THIS_DIR.resolve()
1754 black.gen_python_files(
1757 re.compile(black.DEFAULT_INCLUDES),
1764 self.assertEqual(sorted(expected), sorted(sources))
1766 def test_invalid_include_exclude(self) -> None:
1767 for option in ["--include", "--exclude"]:
1768 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1770 def test_preserves_line_endings(self) -> None:
1771 with TemporaryDirectory() as workspace:
1772 test_file = Path(workspace) / "test.py"
1773 for nl in ["\n", "\r\n"]:
1774 contents = nl.join(["def f( ):", " pass"])
1775 test_file.write_bytes(contents.encode())
1776 ff(test_file, write_back=black.WriteBack.YES)
1777 updated_contents: bytes = test_file.read_bytes()
1778 self.assertIn(nl.encode(), updated_contents)
1780 self.assertNotIn(b"\r\n", updated_contents)
1782 def test_preserves_line_endings_via_stdin(self) -> None:
1783 for nl in ["\n", "\r\n"]:
1784 contents = nl.join(["def f( ):", " pass"])
1785 runner = BlackRunner()
1786 result = runner.invoke(
1787 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1789 self.assertEqual(result.exit_code, 0)
1790 output = runner.stdout_bytes
1791 self.assertIn(nl.encode("utf8"), output)
1793 self.assertNotIn(b"\r\n", output)
1795 def test_assert_equivalent_different_asts(self) -> None:
1796 with self.assertRaises(AssertionError):
1797 black.assert_equivalent("{}", "None")
1799 def test_symlink_out_of_root_directory(self) -> None:
1801 root = THIS_DIR.resolve()
1803 include = re.compile(black.DEFAULT_INCLUDES)
1804 exclude = re.compile(black.DEFAULT_EXCLUDES)
1805 report = black.Report()
1806 gitignore = PathSpec.from_lines("gitwildmatch", [])
1807 # `child` should behave like a symlink which resolved path is clearly
1808 # outside of the `root` directory.
1809 path.iterdir.return_value = [child]
1810 child.resolve.return_value = Path("/a/b/c")
1811 child.as_posix.return_value = "/a/b/c"
1812 child.is_symlink.return_value = True
1815 black.gen_python_files(
1816 path.iterdir(), root, include, exclude, None, report, gitignore
1819 except ValueError as ve:
1820 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1821 path.iterdir.assert_called_once()
1822 child.resolve.assert_called_once()
1823 child.is_symlink.assert_called_once()
1824 # `child` should behave like a strange file which resolved path is clearly
1825 # outside of the `root` directory.
1826 child.is_symlink.return_value = False
1827 with self.assertRaises(ValueError):
1829 black.gen_python_files(
1830 path.iterdir(), root, include, exclude, None, report, gitignore
1833 path.iterdir.assert_called()
1834 self.assertEqual(path.iterdir.call_count, 2)
1835 child.resolve.assert_called()
1836 self.assertEqual(child.resolve.call_count, 2)
1837 child.is_symlink.assert_called()
1838 self.assertEqual(child.is_symlink.call_count, 2)
1840 def test_shhh_click(self) -> None:
1842 from click import _unicodefun # type: ignore
1843 except ModuleNotFoundError:
1844 self.skipTest("Incompatible Click version")
1845 if not hasattr(_unicodefun, "_verify_python3_env"):
1846 self.skipTest("Incompatible Click version")
1847 # First, let's see if Click is crashing with a preferred ASCII charset.
1848 with patch("locale.getpreferredencoding") as gpe:
1849 gpe.return_value = "ASCII"
1850 with self.assertRaises(RuntimeError):
1851 _unicodefun._verify_python3_env()
1852 # Now, let's silence Click...
1854 # ...and confirm it's silent.
1855 with patch("locale.getpreferredencoding") as gpe:
1856 gpe.return_value = "ASCII"
1858 _unicodefun._verify_python3_env()
1859 except RuntimeError as re:
1860 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1862 def test_root_logger_not_used_directly(self) -> None:
1863 def fail(*args: Any, **kwargs: Any) -> None:
1864 self.fail("Record created with root logger")
1866 with patch.multiple(
1877 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1878 def test_blackd_main(self) -> None:
1879 with patch("blackd.web.run_app"):
1880 result = CliRunner().invoke(blackd.main, [])
1881 if result.exception is not None:
1882 raise result.exception
1883 self.assertEqual(result.exit_code, 0)
1885 def test_invalid_config_return_code(self) -> None:
1886 tmp_file = Path(black.dump_to_file())
1888 tmp_config = Path(black.dump_to_file())
1890 args = ["--config", str(tmp_config), str(tmp_file)]
1891 self.invokeBlack(args, exit_code=2, ignore_config=False)
1895 def test_parse_pyproject_toml(self) -> None:
1896 test_toml_file = THIS_DIR / "test.toml"
1897 config = black.parse_pyproject_toml(str(test_toml_file))
1898 self.assertEqual(config["verbose"], 1)
1899 self.assertEqual(config["check"], "no")
1900 self.assertEqual(config["diff"], "y")
1901 self.assertEqual(config["color"], True)
1902 self.assertEqual(config["line_length"], 79)
1903 self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
1904 self.assertEqual(config["exclude"], r"\.pyi?$")
1905 self.assertEqual(config["include"], r"\.py?$")
1907 def test_read_pyproject_toml(self) -> None:
1908 test_toml_file = THIS_DIR / "test.toml"
1909 fake_ctx = FakeContext()
1910 black.read_pyproject_toml(fake_ctx, FakeParameter(), str(test_toml_file))
1911 config = fake_ctx.default_map
1912 self.assertEqual(config["verbose"], "1")
1913 self.assertEqual(config["check"], "no")
1914 self.assertEqual(config["diff"], "y")
1915 self.assertEqual(config["color"], "True")
1916 self.assertEqual(config["line_length"], "79")
1917 self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
1918 self.assertEqual(config["exclude"], r"\.pyi?$")
1919 self.assertEqual(config["include"], r"\.py?$")
1921 def test_find_project_root(self) -> None:
1922 with TemporaryDirectory() as workspace:
1923 root = Path(workspace)
1924 test_dir = root / "test"
1927 src_dir = root / "src"
1930 root_pyproject = root / "pyproject.toml"
1931 root_pyproject.touch()
1932 src_pyproject = src_dir / "pyproject.toml"
1933 src_pyproject.touch()
1934 src_python = src_dir / "foo.py"
1938 black.find_project_root((src_dir, test_dir)), root.resolve()
1940 self.assertEqual(black.find_project_root((src_dir,)), src_dir.resolve())
1941 self.assertEqual(black.find_project_root((src_python,)), src_dir.resolve())
1943 def test_bpo_33660_workaround(self) -> None:
1944 if system() == "Windows":
1947 # https://bugs.python.org/issue33660
1949 old_cwd = Path.cwd()
1953 path = Path("workspace") / "project"
1954 report = black.Report(verbose=True)
1955 normalized_path = black.normalize_path_maybe_ignore(path, root, report)
1956 self.assertEqual(normalized_path, "workspace/project")
1958 os.chdir(str(old_cwd))
1961 class BlackDTestCase(AioHTTPTestCase):
1962 async def get_application(self) -> web.Application:
1963 return blackd.make_app()
1965 # TODO: remove these decorators once the below is released
1966 # https://github.com/aio-libs/aiohttp/pull/3727
1967 @skip_if_exception("ClientOSError")
1968 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1970 async def test_blackd_request_needs_formatting(self) -> None:
1971 response = await self.client.post("/", data=b"print('hello world')")
1972 self.assertEqual(response.status, 200)
1973 self.assertEqual(response.charset, "utf8")
1974 self.assertEqual(await response.read(), b'print("hello world")\n')
1976 @skip_if_exception("ClientOSError")
1977 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1979 async def test_blackd_request_no_change(self) -> None:
1980 response = await self.client.post("/", data=b'print("hello world")\n')
1981 self.assertEqual(response.status, 204)
1982 self.assertEqual(await response.read(), b"")
1984 @skip_if_exception("ClientOSError")
1985 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1987 async def test_blackd_request_syntax_error(self) -> None:
1988 response = await self.client.post("/", data=b"what even ( is")
1989 self.assertEqual(response.status, 400)
1990 content = await response.text()
1992 content.startswith("Cannot parse"),
1993 msg=f"Expected error to start with 'Cannot parse', got {repr(content)}",
1996 @skip_if_exception("ClientOSError")
1997 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1999 async def test_blackd_unsupported_version(self) -> None:
2000 response = await self.client.post(
2001 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "2"}
2003 self.assertEqual(response.status, 501)
2005 @skip_if_exception("ClientOSError")
2006 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
2008 async def test_blackd_supported_version(self) -> None:
2009 response = await self.client.post(
2010 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "1"}
2012 self.assertEqual(response.status, 200)
2014 @skip_if_exception("ClientOSError")
2015 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
2017 async def test_blackd_invalid_python_variant(self) -> None:
2018 async def check(header_value: str, expected_status: int = 400) -> None:
2019 response = await self.client.post(
2020 "/", data=b"what", headers={blackd.PYTHON_VARIANT_HEADER: header_value}
2022 self.assertEqual(response.status, expected_status)
2025 await check("ruby3.5")
2026 await check("pyi3.6")
2027 await check("py1.5")
2029 await check("py2.8")
2031 await check("pypy3.0")
2032 await check("jython3.4")
2034 @skip_if_exception("ClientOSError")
2035 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
2037 async def test_blackd_pyi(self) -> None:
2038 source, expected = read_data("stub.pyi")
2039 response = await self.client.post(
2040 "/", data=source, headers={blackd.PYTHON_VARIANT_HEADER: "pyi"}
2042 self.assertEqual(response.status, 200)
2043 self.assertEqual(await response.text(), expected)
2045 @skip_if_exception("ClientOSError")
2046 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
2048 async def test_blackd_diff(self) -> None:
2049 diff_header = re.compile(
2050 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"
2053 source, _ = read_data("blackd_diff.py")
2054 expected, _ = read_data("blackd_diff.diff")
2056 response = await self.client.post(
2057 "/", data=source, headers={blackd.DIFF_HEADER: "true"}
2059 self.assertEqual(response.status, 200)
2061 actual = await response.text()
2062 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
2063 self.assertEqual(actual, expected)
2065 @skip_if_exception("ClientOSError")
2066 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
2068 async def test_blackd_python_variant(self) -> None:
2071 " and_has_a_bunch_of,\n"
2072 " very_long_arguments_too,\n"
2073 " and_lots_of_them_as_well_lol,\n"
2074 " **and_very_long_keyword_arguments\n"
2079 async def check(header_value: str, expected_status: int) -> None:
2080 response = await self.client.post(
2081 "/", data=code, headers={blackd.PYTHON_VARIANT_HEADER: header_value}
2084 response.status, expected_status, msg=await response.text()
2087 await check("3.6", 200)
2088 await check("py3.6", 200)
2089 await check("3.6,3.7", 200)
2090 await check("3.6,py3.7", 200)
2091 await check("py36,py37", 200)
2092 await check("36", 200)
2093 await check("3.6.4", 200)
2095 await check("2", 204)
2096 await check("2.7", 204)
2097 await check("py2.7", 204)
2098 await check("3.4", 204)
2099 await check("py3.4", 204)
2100 await check("py34,py36", 204)
2101 await check("34", 204)
2103 @skip_if_exception("ClientOSError")
2104 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
2106 async def test_blackd_line_length(self) -> None:
2107 response = await self.client.post(
2108 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "7"}
2110 self.assertEqual(response.status, 200)
2112 @skip_if_exception("ClientOSError")
2113 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
2115 async def test_blackd_invalid_line_length(self) -> None:
2116 response = await self.client.post(
2117 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "NaN"}
2119 self.assertEqual(response.status, 400)
2121 @skip_if_exception("ClientOSError")
2122 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
2124 async def test_blackd_response_black_version_header(self) -> None:
2125 response = await self.client.post("/")
2126 self.assertIsNotNone(response.headers.get(blackd.BLACK_VERSION_HEADER))
2129 with open(black.__file__, "r", encoding="utf-8") as _bf:
2130 black_source_lines = _bf.readlines()
2133 def tracefunc(frame: types.FrameType, event: str, arg: Any) -> Callable:
2134 """Show function calls `from black/__init__.py` as they happen.
2136 Register this with `sys.settrace()` in a test you're debugging.
2141 stack = len(inspect.stack()) - 19
2143 filename = frame.f_code.co_filename
2144 lineno = frame.f_lineno
2145 func_sig_lineno = lineno - 1
2146 funcname = black_source_lines[func_sig_lineno].strip()
2147 while funcname.startswith("@"):
2148 func_sig_lineno += 1
2149 funcname = black_source_lines[func_sig_lineno].strip()
2150 if "black/__init__.py" in filename:
2151 print(f"{' ' * stack}{lineno}:{funcname}")
2155 if __name__ == "__main__":
2156 unittest.main(module="test_black")