All patches and comments are welcome. Please squash your changes to logical
commits before using git-format-patch and git-send-email to
patches@git.madduck.net.
If you'd read over the Git project's submission guidelines and adhered to them,
I'd be especially grateful.
4 from concurrent.futures import ThreadPoolExecutor
5 from contextlib import contextmanager
6 from functools import partial
7 from io import BytesIO, TextIOWrapper
9 from pathlib import Path
12 from tempfile import TemporaryDirectory
13 from typing import Any, BinaryIO, Dict, Generator, List, Tuple, Iterator, TypeVar
15 from unittest.mock import patch, MagicMock
18 from click import unstyle
19 from click.testing import CliRunner
22 from black import Feature, TargetVersion
26 from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
27 from aiohttp import web
29 has_blackd_deps = False
31 has_blackd_deps = True
33 from pathspec import PathSpec
35 # Import other test classes
36 from .test_primer import PrimerCLITests # noqa: F401
39 ff = partial(black.format_file_in_place, mode=black.FileMode(), fast=True)
40 fs = partial(black.format_str, mode=black.FileMode())
41 THIS_FILE = Path(__file__)
42 THIS_DIR = THIS_FILE.parent
43 PROJECT_ROOT = THIS_DIR.parent
44 DETERMINISTIC_HEADER = "[Deterministic header]"
45 EMPTY_LINE = "# EMPTY LINE WITH WHITESPACE" + " (this comment will be removed)"
47 f"--target-version={version.name.lower()}" for version in black.PY36_VERSIONS
53 def dump_to_stderr(*output: str) -> str:
54 return "\n" + "\n".join(output) + "\n"
57 def read_data(name: str, data: bool = True) -> Tuple[str, str]:
58 """read_data('test_name') -> 'input', 'output'"""
59 if not name.endswith((".py", ".pyi", ".out", ".diff")):
61 _input: List[str] = []
62 _output: List[str] = []
63 base_dir = THIS_DIR / "data" if data else PROJECT_ROOT
64 with open(base_dir / name, "r", encoding="utf8") as test:
65 lines = test.readlines()
68 line = line.replace(EMPTY_LINE, "")
69 if line.rstrip() == "# output":
74 if _input and not _output:
75 # If there's no output marker, treat the entire file as already pre-formatted.
77 return "".join(_input).strip() + "\n", "".join(_output).strip() + "\n"
81 def cache_dir(exists: bool = True) -> Iterator[Path]:
82 with TemporaryDirectory() as workspace:
83 cache_dir = Path(workspace)
85 cache_dir = cache_dir / "new"
86 with patch("black.CACHE_DIR", cache_dir):
91 def event_loop() -> Iterator[None]:
92 policy = asyncio.get_event_loop_policy()
93 loop = policy.new_event_loop()
94 asyncio.set_event_loop(loop)
103 def skip_if_exception(e: str) -> Iterator[None]:
106 except Exception as exc:
107 if exc.__class__.__name__ == e:
108 unittest.skip(f"Encountered expected exception {exc}, skipping")
113 class BlackRunner(CliRunner):
114 """Modify CliRunner so that stderr is not merged with stdout.
116 This is a hack that can be removed once we depend on Click 7.x"""
118 def __init__(self) -> None:
119 self.stderrbuf = BytesIO()
120 self.stdoutbuf = BytesIO()
121 self.stdout_bytes = b""
122 self.stderr_bytes = b""
126 def isolation(self, *args: Any, **kwargs: Any) -> Generator[BinaryIO, None, None]:
127 with super().isolation(*args, **kwargs) as output:
129 hold_stderr = sys.stderr
130 sys.stderr = TextIOWrapper(self.stderrbuf, encoding=self.charset)
133 self.stdout_bytes = sys.stdout.buffer.getvalue() # type: ignore
134 self.stderr_bytes = sys.stderr.buffer.getvalue() # type: ignore
135 sys.stderr = hold_stderr
138 class BlackTestCase(unittest.TestCase):
141 def assertFormatEqual(self, expected: str, actual: str) -> None:
142 if actual != expected and not os.environ.get("SKIP_AST_PRINT"):
143 bdv: black.DebugVisitor[Any]
144 black.out("Expected tree:", fg="green")
146 exp_node = black.lib2to3_parse(expected)
147 bdv = black.DebugVisitor()
148 list(bdv.visit(exp_node))
149 except Exception as ve:
151 black.out("Actual tree:", fg="red")
153 exp_node = black.lib2to3_parse(actual)
154 bdv = black.DebugVisitor()
155 list(bdv.visit(exp_node))
156 except Exception as ve:
158 self.assertEqual(expected, actual)
161 self, args: List[str], exit_code: int = 0, ignore_config: bool = True
163 runner = BlackRunner()
165 args = ["--verbose", "--config", str(THIS_DIR / "empty.toml"), *args]
166 result = runner.invoke(black.main, args)
171 f"Failed with args: {args}\n"
172 f"stdout: {runner.stdout_bytes.decode()!r}\n"
173 f"stderr: {runner.stderr_bytes.decode()!r}\n"
174 f"exception: {result.exception}"
178 @patch("black.dump_to_file", dump_to_stderr)
179 def checkSourceFile(self, name: str) -> None:
180 path = THIS_DIR.parent / name
181 source, expected = read_data(str(path), data=False)
183 self.assertFormatEqual(expected, actual)
184 black.assert_equivalent(source, actual)
185 black.assert_stable(source, actual, black.FileMode())
186 self.assertFalse(ff(path))
188 @patch("black.dump_to_file", dump_to_stderr)
189 def test_empty(self) -> None:
190 source = expected = ""
192 self.assertFormatEqual(expected, actual)
193 black.assert_equivalent(source, actual)
194 black.assert_stable(source, actual, black.FileMode())
196 def test_empty_ff(self) -> None:
198 tmp_file = Path(black.dump_to_file())
200 self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES))
201 with open(tmp_file, encoding="utf8") as f:
205 self.assertFormatEqual(expected, actual)
207 def test_self(self) -> None:
208 self.checkSourceFile("tests/test_black.py")
210 def test_black(self) -> None:
211 self.checkSourceFile("src/black/__init__.py")
213 def test_pygram(self) -> None:
214 self.checkSourceFile("src/blib2to3/pygram.py")
216 def test_pytree(self) -> None:
217 self.checkSourceFile("src/blib2to3/pytree.py")
219 def test_conv(self) -> None:
220 self.checkSourceFile("src/blib2to3/pgen2/conv.py")
222 def test_driver(self) -> None:
223 self.checkSourceFile("src/blib2to3/pgen2/driver.py")
225 def test_grammar(self) -> None:
226 self.checkSourceFile("src/blib2to3/pgen2/grammar.py")
228 def test_literals(self) -> None:
229 self.checkSourceFile("src/blib2to3/pgen2/literals.py")
231 def test_parse(self) -> None:
232 self.checkSourceFile("src/blib2to3/pgen2/parse.py")
234 def test_pgen(self) -> None:
235 self.checkSourceFile("src/blib2to3/pgen2/pgen.py")
237 def test_tokenize(self) -> None:
238 self.checkSourceFile("src/blib2to3/pgen2/tokenize.py")
240 def test_token(self) -> None:
241 self.checkSourceFile("src/blib2to3/pgen2/token.py")
243 def test_setup(self) -> None:
244 self.checkSourceFile("setup.py")
246 def test_piping(self) -> None:
247 source, expected = read_data("src/black/__init__", data=False)
248 result = BlackRunner().invoke(
250 ["-", "--fast", f"--line-length={black.DEFAULT_LINE_LENGTH}"],
251 input=BytesIO(source.encode("utf8")),
253 self.assertEqual(result.exit_code, 0)
254 self.assertFormatEqual(expected, result.output)
255 black.assert_equivalent(source, result.output)
256 black.assert_stable(source, result.output, black.FileMode())
258 def test_piping_diff(self) -> None:
259 diff_header = re.compile(
260 r"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d\d\d\d "
263 source, _ = read_data("expression.py")
264 expected, _ = read_data("expression.diff")
265 config = THIS_DIR / "data" / "empty_pyproject.toml"
269 f"--line-length={black.DEFAULT_LINE_LENGTH}",
271 f"--config={config}",
273 result = BlackRunner().invoke(
274 black.main, args, input=BytesIO(source.encode("utf8"))
276 self.assertEqual(result.exit_code, 0)
277 actual = diff_header.sub(DETERMINISTIC_HEADER, result.output)
278 actual = actual.rstrip() + "\n" # the diff output has a trailing space
279 self.assertEqual(expected, actual)
281 def test_piping_diff_with_color(self) -> None:
282 source, _ = read_data("expression.py")
283 config = THIS_DIR / "data" / "empty_pyproject.toml"
287 f"--line-length={black.DEFAULT_LINE_LENGTH}",
290 f"--config={config}",
292 result = BlackRunner().invoke(
293 black.main, args, input=BytesIO(source.encode("utf8"))
295 actual = result.output
296 # Again, the contents are checked in a different test, so only look for colors.
297 self.assertIn("\033[1;37m", actual)
298 self.assertIn("\033[36m", actual)
299 self.assertIn("\033[32m", actual)
300 self.assertIn("\033[31m", actual)
301 self.assertIn("\033[0m", actual)
303 @patch("black.dump_to_file", dump_to_stderr)
304 def test_function(self) -> None:
305 source, expected = read_data("function")
307 self.assertFormatEqual(expected, actual)
308 black.assert_equivalent(source, actual)
309 black.assert_stable(source, actual, black.FileMode())
311 @patch("black.dump_to_file", dump_to_stderr)
312 def test_function2(self) -> None:
313 source, expected = read_data("function2")
315 self.assertFormatEqual(expected, actual)
316 black.assert_equivalent(source, actual)
317 black.assert_stable(source, actual, black.FileMode())
319 @patch("black.dump_to_file", dump_to_stderr)
320 def test_function_trailing_comma(self) -> None:
321 source, expected = read_data("function_trailing_comma")
323 self.assertFormatEqual(expected, actual)
324 black.assert_equivalent(source, actual)
325 black.assert_stable(source, actual, black.FileMode())
327 @patch("black.dump_to_file", dump_to_stderr)
328 def test_expression(self) -> None:
329 source, expected = read_data("expression")
331 self.assertFormatEqual(expected, actual)
332 black.assert_equivalent(source, actual)
333 black.assert_stable(source, actual, black.FileMode())
335 @patch("black.dump_to_file", dump_to_stderr)
336 def test_pep_572(self) -> None:
337 source, expected = read_data("pep_572")
339 self.assertFormatEqual(expected, actual)
340 black.assert_stable(source, actual, black.FileMode())
341 if sys.version_info >= (3, 8):
342 black.assert_equivalent(source, actual)
344 def test_pep_572_version_detection(self) -> None:
345 source, _ = read_data("pep_572")
346 root = black.lib2to3_parse(source)
347 features = black.get_features_used(root)
348 self.assertIn(black.Feature.ASSIGNMENT_EXPRESSIONS, features)
349 versions = black.detect_target_versions(root)
350 self.assertIn(black.TargetVersion.PY38, versions)
352 def test_expression_ff(self) -> None:
353 source, expected = read_data("expression")
354 tmp_file = Path(black.dump_to_file(source))
356 self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
357 with open(tmp_file, encoding="utf8") as f:
361 self.assertFormatEqual(expected, actual)
362 with patch("black.dump_to_file", dump_to_stderr):
363 black.assert_equivalent(source, actual)
364 black.assert_stable(source, actual, black.FileMode())
366 def test_expression_diff(self) -> None:
367 source, _ = read_data("expression.py")
368 expected, _ = read_data("expression.diff")
369 tmp_file = Path(black.dump_to_file(source))
370 diff_header = re.compile(
371 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
372 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
375 result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
376 self.assertEqual(result.exit_code, 0)
379 actual = result.output
380 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
381 actual = actual.rstrip() + "\n" # the diff output has a trailing space
382 if expected != actual:
383 dump = black.dump_to_file(actual)
385 "Expected diff isn't equal to the actual. If you made changes to"
386 " expression.py and this is an anticipated difference, overwrite"
387 f" tests/data/expression.diff with {dump}"
389 self.assertEqual(expected, actual, msg)
391 def test_expression_diff_with_color(self) -> None:
392 source, _ = read_data("expression.py")
393 expected, _ = read_data("expression.diff")
394 tmp_file = Path(black.dump_to_file(source))
396 result = BlackRunner().invoke(
397 black.main, ["--diff", "--color", str(tmp_file)]
401 actual = result.output
402 # We check the contents of the diff in `test_expression_diff`. All
403 # we need to check here is that color codes exist in the result.
404 self.assertIn("\033[1;37m", actual)
405 self.assertIn("\033[36m", actual)
406 self.assertIn("\033[32m", actual)
407 self.assertIn("\033[31m", actual)
408 self.assertIn("\033[0m", actual)
410 @patch("black.dump_to_file", dump_to_stderr)
411 def test_fstring(self) -> None:
412 source, expected = read_data("fstring")
414 self.assertFormatEqual(expected, actual)
415 black.assert_equivalent(source, actual)
416 black.assert_stable(source, actual, black.FileMode())
418 @patch("black.dump_to_file", dump_to_stderr)
419 def test_pep_570(self) -> None:
420 source, expected = read_data("pep_570")
422 self.assertFormatEqual(expected, actual)
423 black.assert_stable(source, actual, black.FileMode())
424 if sys.version_info >= (3, 8):
425 black.assert_equivalent(source, actual)
427 def test_detect_pos_only_arguments(self) -> None:
428 source, _ = read_data("pep_570")
429 root = black.lib2to3_parse(source)
430 features = black.get_features_used(root)
431 self.assertIn(black.Feature.POS_ONLY_ARGUMENTS, features)
432 versions = black.detect_target_versions(root)
433 self.assertIn(black.TargetVersion.PY38, versions)
435 @patch("black.dump_to_file", dump_to_stderr)
436 def test_string_quotes(self) -> None:
437 source, expected = read_data("string_quotes")
439 self.assertFormatEqual(expected, actual)
440 black.assert_equivalent(source, actual)
441 black.assert_stable(source, actual, black.FileMode())
442 mode = black.FileMode(string_normalization=False)
443 not_normalized = fs(source, mode=mode)
444 self.assertFormatEqual(source.replace("\\\n", ""), not_normalized)
445 black.assert_equivalent(source, not_normalized)
446 black.assert_stable(source, not_normalized, mode=mode)
448 @patch("black.dump_to_file", dump_to_stderr)
449 def test_docstring(self) -> None:
450 source, expected = read_data("docstring")
452 self.assertFormatEqual(expected, actual)
453 black.assert_equivalent(source, actual)
454 black.assert_stable(source, actual, black.FileMode())
456 def test_long_strings(self) -> None:
457 """Tests for splitting long strings."""
458 source, expected = read_data("long_strings")
460 self.assertFormatEqual(expected, actual)
461 black.assert_equivalent(source, actual)
462 black.assert_stable(source, actual, black.FileMode())
464 @patch("black.dump_to_file", dump_to_stderr)
465 def test_long_strings__edge_case(self) -> None:
466 """Edge-case tests for splitting long strings."""
467 source, expected = read_data("long_strings__edge_case")
469 self.assertFormatEqual(expected, actual)
470 black.assert_equivalent(source, actual)
471 black.assert_stable(source, actual, black.FileMode())
473 @patch("black.dump_to_file", dump_to_stderr)
474 def test_long_strings__regression(self) -> None:
475 """Regression tests for splitting long strings."""
476 source, expected = read_data("long_strings__regression")
478 self.assertFormatEqual(expected, actual)
479 black.assert_equivalent(source, actual)
480 black.assert_stable(source, actual, black.FileMode())
482 @patch("black.dump_to_file", dump_to_stderr)
483 def test_slices(self) -> None:
484 source, expected = read_data("slices")
486 self.assertFormatEqual(expected, actual)
487 black.assert_equivalent(source, actual)
488 black.assert_stable(source, actual, black.FileMode())
490 @patch("black.dump_to_file", dump_to_stderr)
491 def test_comments(self) -> None:
492 source, expected = read_data("comments")
494 self.assertFormatEqual(expected, actual)
495 black.assert_equivalent(source, actual)
496 black.assert_stable(source, actual, black.FileMode())
498 @patch("black.dump_to_file", dump_to_stderr)
499 def test_comments2(self) -> None:
500 source, expected = read_data("comments2")
502 self.assertFormatEqual(expected, actual)
503 black.assert_equivalent(source, actual)
504 black.assert_stable(source, actual, black.FileMode())
506 @patch("black.dump_to_file", dump_to_stderr)
507 def test_comments3(self) -> None:
508 source, expected = read_data("comments3")
510 self.assertFormatEqual(expected, actual)
511 black.assert_equivalent(source, actual)
512 black.assert_stable(source, actual, black.FileMode())
514 @patch("black.dump_to_file", dump_to_stderr)
515 def test_comments4(self) -> None:
516 source, expected = read_data("comments4")
518 self.assertFormatEqual(expected, actual)
519 black.assert_equivalent(source, actual)
520 black.assert_stable(source, actual, black.FileMode())
522 @patch("black.dump_to_file", dump_to_stderr)
523 def test_comments5(self) -> None:
524 source, expected = read_data("comments5")
526 self.assertFormatEqual(expected, actual)
527 black.assert_equivalent(source, actual)
528 black.assert_stable(source, actual, black.FileMode())
530 @patch("black.dump_to_file", dump_to_stderr)
531 def test_comments6(self) -> None:
532 source, expected = read_data("comments6")
534 self.assertFormatEqual(expected, actual)
535 black.assert_equivalent(source, actual)
536 black.assert_stable(source, actual, black.FileMode())
538 @patch("black.dump_to_file", dump_to_stderr)
539 def test_comments7(self) -> None:
540 source, expected = read_data("comments7")
542 self.assertFormatEqual(expected, actual)
543 black.assert_equivalent(source, actual)
544 black.assert_stable(source, actual, black.FileMode())
546 @patch("black.dump_to_file", dump_to_stderr)
547 def test_comment_after_escaped_newline(self) -> None:
548 source, expected = read_data("comment_after_escaped_newline")
550 self.assertFormatEqual(expected, actual)
551 black.assert_equivalent(source, actual)
552 black.assert_stable(source, actual, black.FileMode())
554 @patch("black.dump_to_file", dump_to_stderr)
555 def test_cantfit(self) -> None:
556 source, expected = read_data("cantfit")
558 self.assertFormatEqual(expected, actual)
559 black.assert_equivalent(source, actual)
560 black.assert_stable(source, actual, black.FileMode())
562 @patch("black.dump_to_file", dump_to_stderr)
563 def test_import_spacing(self) -> None:
564 source, expected = read_data("import_spacing")
566 self.assertFormatEqual(expected, actual)
567 black.assert_equivalent(source, actual)
568 black.assert_stable(source, actual, black.FileMode())
570 @patch("black.dump_to_file", dump_to_stderr)
571 def test_composition(self) -> None:
572 source, expected = read_data("composition")
574 self.assertFormatEqual(expected, actual)
575 black.assert_equivalent(source, actual)
576 black.assert_stable(source, actual, black.FileMode())
578 @patch("black.dump_to_file", dump_to_stderr)
579 def test_empty_lines(self) -> None:
580 source, expected = read_data("empty_lines")
582 self.assertFormatEqual(expected, actual)
583 black.assert_equivalent(source, actual)
584 black.assert_stable(source, actual, black.FileMode())
586 @patch("black.dump_to_file", dump_to_stderr)
587 def test_remove_parens(self) -> None:
588 source, expected = read_data("remove_parens")
590 self.assertFormatEqual(expected, actual)
591 black.assert_equivalent(source, actual)
592 black.assert_stable(source, actual, black.FileMode())
594 @patch("black.dump_to_file", dump_to_stderr)
595 def test_string_prefixes(self) -> None:
596 source, expected = read_data("string_prefixes")
598 self.assertFormatEqual(expected, actual)
599 black.assert_equivalent(source, actual)
600 black.assert_stable(source, actual, black.FileMode())
602 @patch("black.dump_to_file", dump_to_stderr)
603 def test_numeric_literals(self) -> None:
604 source, expected = read_data("numeric_literals")
605 mode = black.FileMode(target_versions=black.PY36_VERSIONS)
606 actual = fs(source, mode=mode)
607 self.assertFormatEqual(expected, actual)
608 black.assert_equivalent(source, actual)
609 black.assert_stable(source, actual, mode)
611 @patch("black.dump_to_file", dump_to_stderr)
612 def test_numeric_literals_ignoring_underscores(self) -> None:
613 source, expected = read_data("numeric_literals_skip_underscores")
614 mode = black.FileMode(target_versions=black.PY36_VERSIONS)
615 actual = fs(source, mode=mode)
616 self.assertFormatEqual(expected, actual)
617 black.assert_equivalent(source, actual)
618 black.assert_stable(source, actual, mode)
620 @patch("black.dump_to_file", dump_to_stderr)
621 def test_numeric_literals_py2(self) -> None:
622 source, expected = read_data("numeric_literals_py2")
624 self.assertFormatEqual(expected, actual)
625 black.assert_stable(source, actual, black.FileMode())
627 @patch("black.dump_to_file", dump_to_stderr)
628 def test_python2(self) -> None:
629 source, expected = read_data("python2")
631 self.assertFormatEqual(expected, actual)
632 black.assert_equivalent(source, actual)
633 black.assert_stable(source, actual, black.FileMode())
635 @patch("black.dump_to_file", dump_to_stderr)
636 def test_python2_print_function(self) -> None:
637 source, expected = read_data("python2_print_function")
638 mode = black.FileMode(target_versions={TargetVersion.PY27})
639 actual = fs(source, mode=mode)
640 self.assertFormatEqual(expected, actual)
641 black.assert_equivalent(source, actual)
642 black.assert_stable(source, actual, mode)
644 @patch("black.dump_to_file", dump_to_stderr)
645 def test_python2_unicode_literals(self) -> None:
646 source, expected = read_data("python2_unicode_literals")
648 self.assertFormatEqual(expected, actual)
649 black.assert_equivalent(source, actual)
650 black.assert_stable(source, actual, black.FileMode())
652 @patch("black.dump_to_file", dump_to_stderr)
653 def test_stub(self) -> None:
654 mode = black.FileMode(is_pyi=True)
655 source, expected = read_data("stub.pyi")
656 actual = fs(source, mode=mode)
657 self.assertFormatEqual(expected, actual)
658 black.assert_stable(source, actual, mode)
660 @patch("black.dump_to_file", dump_to_stderr)
661 def test_async_as_identifier(self) -> None:
662 source_path = (THIS_DIR / "data" / "async_as_identifier.py").resolve()
663 source, expected = read_data("async_as_identifier")
665 self.assertFormatEqual(expected, actual)
666 major, minor = sys.version_info[:2]
667 if major < 3 or (major <= 3 and minor < 7):
668 black.assert_equivalent(source, actual)
669 black.assert_stable(source, actual, black.FileMode())
670 # ensure black can parse this when the target is 3.6
671 self.invokeBlack([str(source_path), "--target-version", "py36"])
672 # but not on 3.7, because async/await is no longer an identifier
673 self.invokeBlack([str(source_path), "--target-version", "py37"], exit_code=123)
675 @patch("black.dump_to_file", dump_to_stderr)
676 def test_python37(self) -> None:
677 source_path = (THIS_DIR / "data" / "python37.py").resolve()
678 source, expected = read_data("python37")
680 self.assertFormatEqual(expected, actual)
681 major, minor = sys.version_info[:2]
682 if major > 3 or (major == 3 and minor >= 7):
683 black.assert_equivalent(source, actual)
684 black.assert_stable(source, actual, black.FileMode())
685 # ensure black can parse this when the target is 3.7
686 self.invokeBlack([str(source_path), "--target-version", "py37"])
687 # but not on 3.6, because we use async as a reserved keyword
688 self.invokeBlack([str(source_path), "--target-version", "py36"], exit_code=123)
690 @patch("black.dump_to_file", dump_to_stderr)
691 def test_python38(self) -> None:
692 source, expected = read_data("python38")
694 self.assertFormatEqual(expected, actual)
695 major, minor = sys.version_info[:2]
696 if major > 3 or (major == 3 and minor >= 8):
697 black.assert_equivalent(source, actual)
698 black.assert_stable(source, actual, black.FileMode())
700 @patch("black.dump_to_file", dump_to_stderr)
701 def test_fmtonoff(self) -> None:
702 source, expected = read_data("fmtonoff")
704 self.assertFormatEqual(expected, actual)
705 black.assert_equivalent(source, actual)
706 black.assert_stable(source, actual, black.FileMode())
708 @patch("black.dump_to_file", dump_to_stderr)
709 def test_fmtonoff2(self) -> None:
710 source, expected = read_data("fmtonoff2")
712 self.assertFormatEqual(expected, actual)
713 black.assert_equivalent(source, actual)
714 black.assert_stable(source, actual, black.FileMode())
716 @patch("black.dump_to_file", dump_to_stderr)
717 def test_fmtonoff3(self) -> None:
718 source, expected = read_data("fmtonoff3")
720 self.assertFormatEqual(expected, actual)
721 black.assert_equivalent(source, actual)
722 black.assert_stable(source, actual, black.FileMode())
724 @patch("black.dump_to_file", dump_to_stderr)
725 def test_fmtonoff4(self) -> None:
726 source, expected = read_data("fmtonoff4")
728 self.assertFormatEqual(expected, actual)
729 black.assert_equivalent(source, actual)
730 black.assert_stable(source, actual, black.FileMode())
732 @patch("black.dump_to_file", dump_to_stderr)
733 def test_remove_empty_parentheses_after_class(self) -> None:
734 source, expected = read_data("class_blank_parentheses")
736 self.assertFormatEqual(expected, actual)
737 black.assert_equivalent(source, actual)
738 black.assert_stable(source, actual, black.FileMode())
740 @patch("black.dump_to_file", dump_to_stderr)
741 def test_new_line_between_class_and_code(self) -> None:
742 source, expected = read_data("class_methods_new_line")
744 self.assertFormatEqual(expected, actual)
745 black.assert_equivalent(source, actual)
746 black.assert_stable(source, actual, black.FileMode())
748 @patch("black.dump_to_file", dump_to_stderr)
749 def test_bracket_match(self) -> None:
750 source, expected = read_data("bracketmatch")
752 self.assertFormatEqual(expected, actual)
753 black.assert_equivalent(source, actual)
754 black.assert_stable(source, actual, black.FileMode())
756 @patch("black.dump_to_file", dump_to_stderr)
757 def test_tuple_assign(self) -> None:
758 source, expected = read_data("tupleassign")
760 self.assertFormatEqual(expected, actual)
761 black.assert_equivalent(source, actual)
762 black.assert_stable(source, actual, black.FileMode())
764 @patch("black.dump_to_file", dump_to_stderr)
765 def test_beginning_backslash(self) -> None:
766 source, expected = read_data("beginning_backslash")
768 self.assertFormatEqual(expected, actual)
769 black.assert_equivalent(source, actual)
770 black.assert_stable(source, actual, black.FileMode())
772 def test_tab_comment_indentation(self) -> None:
773 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
774 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
775 self.assertFormatEqual(contents_spc, fs(contents_spc))
776 self.assertFormatEqual(contents_spc, fs(contents_tab))
778 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
779 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
780 self.assertFormatEqual(contents_spc, fs(contents_spc))
781 self.assertFormatEqual(contents_spc, fs(contents_tab))
783 # mixed tabs and spaces (valid Python 2 code)
784 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t# comment\n pass\n"
785 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
786 self.assertFormatEqual(contents_spc, fs(contents_spc))
787 self.assertFormatEqual(contents_spc, fs(contents_tab))
789 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t\t# comment\n pass\n"
790 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
791 self.assertFormatEqual(contents_spc, fs(contents_spc))
792 self.assertFormatEqual(contents_spc, fs(contents_tab))
794 def test_report_verbose(self) -> None:
795 report = black.Report(verbose=True)
799 def out(msg: str, **kwargs: Any) -> None:
800 out_lines.append(msg)
802 def err(msg: str, **kwargs: Any) -> None:
803 err_lines.append(msg)
805 with patch("black.out", out), patch("black.err", err):
806 report.done(Path("f1"), black.Changed.NO)
807 self.assertEqual(len(out_lines), 1)
808 self.assertEqual(len(err_lines), 0)
809 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
810 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
811 self.assertEqual(report.return_code, 0)
812 report.done(Path("f2"), black.Changed.YES)
813 self.assertEqual(len(out_lines), 2)
814 self.assertEqual(len(err_lines), 0)
815 self.assertEqual(out_lines[-1], "reformatted f2")
817 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
819 report.done(Path("f3"), black.Changed.CACHED)
820 self.assertEqual(len(out_lines), 3)
821 self.assertEqual(len(err_lines), 0)
823 out_lines[-1], "f3 wasn't modified on disk since last run."
826 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
828 self.assertEqual(report.return_code, 0)
830 self.assertEqual(report.return_code, 1)
832 report.failed(Path("e1"), "boom")
833 self.assertEqual(len(out_lines), 3)
834 self.assertEqual(len(err_lines), 1)
835 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
837 unstyle(str(report)),
838 "1 file reformatted, 2 files left unchanged, 1 file failed to"
841 self.assertEqual(report.return_code, 123)
842 report.done(Path("f3"), black.Changed.YES)
843 self.assertEqual(len(out_lines), 4)
844 self.assertEqual(len(err_lines), 1)
845 self.assertEqual(out_lines[-1], "reformatted f3")
847 unstyle(str(report)),
848 "2 files reformatted, 2 files left unchanged, 1 file failed to"
851 self.assertEqual(report.return_code, 123)
852 report.failed(Path("e2"), "boom")
853 self.assertEqual(len(out_lines), 4)
854 self.assertEqual(len(err_lines), 2)
855 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
857 unstyle(str(report)),
858 "2 files reformatted, 2 files left unchanged, 2 files failed to"
861 self.assertEqual(report.return_code, 123)
862 report.path_ignored(Path("wat"), "no match")
863 self.assertEqual(len(out_lines), 5)
864 self.assertEqual(len(err_lines), 2)
865 self.assertEqual(out_lines[-1], "wat ignored: no match")
867 unstyle(str(report)),
868 "2 files reformatted, 2 files left unchanged, 2 files failed to"
871 self.assertEqual(report.return_code, 123)
872 report.done(Path("f4"), black.Changed.NO)
873 self.assertEqual(len(out_lines), 6)
874 self.assertEqual(len(err_lines), 2)
875 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
877 unstyle(str(report)),
878 "2 files reformatted, 3 files left unchanged, 2 files failed to"
881 self.assertEqual(report.return_code, 123)
884 unstyle(str(report)),
885 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
886 " would fail to reformat.",
891 unstyle(str(report)),
892 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
893 " would fail to reformat.",
896 def test_report_quiet(self) -> None:
897 report = black.Report(quiet=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), 0)
910 self.assertEqual(len(err_lines), 0)
911 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
912 self.assertEqual(report.return_code, 0)
913 report.done(Path("f2"), black.Changed.YES)
914 self.assertEqual(len(out_lines), 0)
915 self.assertEqual(len(err_lines), 0)
917 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
919 report.done(Path("f3"), black.Changed.CACHED)
920 self.assertEqual(len(out_lines), 0)
921 self.assertEqual(len(err_lines), 0)
923 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
925 self.assertEqual(report.return_code, 0)
927 self.assertEqual(report.return_code, 1)
929 report.failed(Path("e1"), "boom")
930 self.assertEqual(len(out_lines), 0)
931 self.assertEqual(len(err_lines), 1)
932 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
934 unstyle(str(report)),
935 "1 file reformatted, 2 files left unchanged, 1 file failed to"
938 self.assertEqual(report.return_code, 123)
939 report.done(Path("f3"), black.Changed.YES)
940 self.assertEqual(len(out_lines), 0)
941 self.assertEqual(len(err_lines), 1)
943 unstyle(str(report)),
944 "2 files reformatted, 2 files left unchanged, 1 file failed to"
947 self.assertEqual(report.return_code, 123)
948 report.failed(Path("e2"), "boom")
949 self.assertEqual(len(out_lines), 0)
950 self.assertEqual(len(err_lines), 2)
951 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
953 unstyle(str(report)),
954 "2 files reformatted, 2 files left unchanged, 2 files failed to"
957 self.assertEqual(report.return_code, 123)
958 report.path_ignored(Path("wat"), "no match")
959 self.assertEqual(len(out_lines), 0)
960 self.assertEqual(len(err_lines), 2)
962 unstyle(str(report)),
963 "2 files reformatted, 2 files left unchanged, 2 files failed to"
966 self.assertEqual(report.return_code, 123)
967 report.done(Path("f4"), black.Changed.NO)
968 self.assertEqual(len(out_lines), 0)
969 self.assertEqual(len(err_lines), 2)
971 unstyle(str(report)),
972 "2 files reformatted, 3 files left unchanged, 2 files failed to"
975 self.assertEqual(report.return_code, 123)
978 unstyle(str(report)),
979 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
980 " would fail to reformat.",
985 unstyle(str(report)),
986 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
987 " would fail to reformat.",
990 def test_report_normal(self) -> None:
991 report = black.Report()
995 def out(msg: str, **kwargs: Any) -> None:
996 out_lines.append(msg)
998 def err(msg: str, **kwargs: Any) -> None:
999 err_lines.append(msg)
1001 with patch("black.out", out), patch("black.err", err):
1002 report.done(Path("f1"), black.Changed.NO)
1003 self.assertEqual(len(out_lines), 0)
1004 self.assertEqual(len(err_lines), 0)
1005 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
1006 self.assertEqual(report.return_code, 0)
1007 report.done(Path("f2"), black.Changed.YES)
1008 self.assertEqual(len(out_lines), 1)
1009 self.assertEqual(len(err_lines), 0)
1010 self.assertEqual(out_lines[-1], "reformatted f2")
1012 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
1014 report.done(Path("f3"), black.Changed.CACHED)
1015 self.assertEqual(len(out_lines), 1)
1016 self.assertEqual(len(err_lines), 0)
1017 self.assertEqual(out_lines[-1], "reformatted f2")
1019 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
1021 self.assertEqual(report.return_code, 0)
1023 self.assertEqual(report.return_code, 1)
1024 report.check = False
1025 report.failed(Path("e1"), "boom")
1026 self.assertEqual(len(out_lines), 1)
1027 self.assertEqual(len(err_lines), 1)
1028 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
1030 unstyle(str(report)),
1031 "1 file reformatted, 2 files left unchanged, 1 file failed to"
1034 self.assertEqual(report.return_code, 123)
1035 report.done(Path("f3"), black.Changed.YES)
1036 self.assertEqual(len(out_lines), 2)
1037 self.assertEqual(len(err_lines), 1)
1038 self.assertEqual(out_lines[-1], "reformatted f3")
1040 unstyle(str(report)),
1041 "2 files reformatted, 2 files left unchanged, 1 file failed to"
1044 self.assertEqual(report.return_code, 123)
1045 report.failed(Path("e2"), "boom")
1046 self.assertEqual(len(out_lines), 2)
1047 self.assertEqual(len(err_lines), 2)
1048 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
1050 unstyle(str(report)),
1051 "2 files reformatted, 2 files left unchanged, 2 files failed to"
1054 self.assertEqual(report.return_code, 123)
1055 report.path_ignored(Path("wat"), "no match")
1056 self.assertEqual(len(out_lines), 2)
1057 self.assertEqual(len(err_lines), 2)
1059 unstyle(str(report)),
1060 "2 files reformatted, 2 files left unchanged, 2 files failed to"
1063 self.assertEqual(report.return_code, 123)
1064 report.done(Path("f4"), black.Changed.NO)
1065 self.assertEqual(len(out_lines), 2)
1066 self.assertEqual(len(err_lines), 2)
1068 unstyle(str(report)),
1069 "2 files reformatted, 3 files left unchanged, 2 files failed to"
1072 self.assertEqual(report.return_code, 123)
1075 unstyle(str(report)),
1076 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1077 " would fail to reformat.",
1079 report.check = False
1082 unstyle(str(report)),
1083 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1084 " would fail to reformat.",
1087 def test_lib2to3_parse(self) -> None:
1088 with self.assertRaises(black.InvalidInput):
1089 black.lib2to3_parse("invalid syntax")
1091 straddling = "x + y"
1092 black.lib2to3_parse(straddling)
1093 black.lib2to3_parse(straddling, {TargetVersion.PY27})
1094 black.lib2to3_parse(straddling, {TargetVersion.PY36})
1095 black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
1097 py2_only = "print x"
1098 black.lib2to3_parse(py2_only)
1099 black.lib2to3_parse(py2_only, {TargetVersion.PY27})
1100 with self.assertRaises(black.InvalidInput):
1101 black.lib2to3_parse(py2_only, {TargetVersion.PY36})
1102 with self.assertRaises(black.InvalidInput):
1103 black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
1105 py3_only = "exec(x, end=y)"
1106 black.lib2to3_parse(py3_only)
1107 with self.assertRaises(black.InvalidInput):
1108 black.lib2to3_parse(py3_only, {TargetVersion.PY27})
1109 black.lib2to3_parse(py3_only, {TargetVersion.PY36})
1110 black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
1112 def test_get_features_used(self) -> None:
1113 node = black.lib2to3_parse("def f(*, arg): ...\n")
1114 self.assertEqual(black.get_features_used(node), set())
1115 node = black.lib2to3_parse("def f(*, arg,): ...\n")
1116 self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
1117 node = black.lib2to3_parse("f(*arg,)\n")
1119 black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
1121 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
1122 self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
1123 node = black.lib2to3_parse("123_456\n")
1124 self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
1125 node = black.lib2to3_parse("123456\n")
1126 self.assertEqual(black.get_features_used(node), set())
1127 source, expected = read_data("function")
1128 node = black.lib2to3_parse(source)
1129 expected_features = {
1130 Feature.TRAILING_COMMA_IN_CALL,
1131 Feature.TRAILING_COMMA_IN_DEF,
1134 self.assertEqual(black.get_features_used(node), expected_features)
1135 node = black.lib2to3_parse(expected)
1136 self.assertEqual(black.get_features_used(node), expected_features)
1137 source, expected = read_data("expression")
1138 node = black.lib2to3_parse(source)
1139 self.assertEqual(black.get_features_used(node), set())
1140 node = black.lib2to3_parse(expected)
1141 self.assertEqual(black.get_features_used(node), set())
1143 def test_get_future_imports(self) -> None:
1144 node = black.lib2to3_parse("\n")
1145 self.assertEqual(set(), black.get_future_imports(node))
1146 node = black.lib2to3_parse("from __future__ import black\n")
1147 self.assertEqual({"black"}, black.get_future_imports(node))
1148 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
1149 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1150 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
1151 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
1152 node = black.lib2to3_parse(
1153 "from __future__ import multiple\nfrom __future__ import imports\n"
1155 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1156 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
1157 self.assertEqual({"black"}, black.get_future_imports(node))
1158 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
1159 self.assertEqual({"black"}, black.get_future_imports(node))
1160 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
1161 self.assertEqual(set(), black.get_future_imports(node))
1162 node = black.lib2to3_parse("from some.module import black\n")
1163 self.assertEqual(set(), black.get_future_imports(node))
1164 node = black.lib2to3_parse(
1165 "from __future__ import unicode_literals as _unicode_literals"
1167 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
1168 node = black.lib2to3_parse(
1169 "from __future__ import unicode_literals as _lol, print"
1171 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
1173 def test_debug_visitor(self) -> None:
1174 source, _ = read_data("debug_visitor.py")
1175 expected, _ = read_data("debug_visitor.out")
1179 def out(msg: str, **kwargs: Any) -> None:
1180 out_lines.append(msg)
1182 def err(msg: str, **kwargs: Any) -> None:
1183 err_lines.append(msg)
1185 with patch("black.out", out), patch("black.err", err):
1186 black.DebugVisitor.show(source)
1187 actual = "\n".join(out_lines) + "\n"
1189 if expected != actual:
1190 log_name = black.dump_to_file(*out_lines)
1194 f"AST print out is different. Actual version dumped to {log_name}",
1197 def test_format_file_contents(self) -> None:
1199 mode = black.FileMode()
1200 with self.assertRaises(black.NothingChanged):
1201 black.format_file_contents(empty, mode=mode, fast=False)
1203 with self.assertRaises(black.NothingChanged):
1204 black.format_file_contents(just_nl, mode=mode, fast=False)
1205 same = "j = [1, 2, 3]\n"
1206 with self.assertRaises(black.NothingChanged):
1207 black.format_file_contents(same, mode=mode, fast=False)
1208 different = "j = [1,2,3]"
1210 actual = black.format_file_contents(different, mode=mode, fast=False)
1211 self.assertEqual(expected, actual)
1212 invalid = "return if you can"
1213 with self.assertRaises(black.InvalidInput) as e:
1214 black.format_file_contents(invalid, mode=mode, fast=False)
1215 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
1217 def test_endmarker(self) -> None:
1218 n = black.lib2to3_parse("\n")
1219 self.assertEqual(n.type, black.syms.file_input)
1220 self.assertEqual(len(n.children), 1)
1221 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
1223 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
1224 def test_assertFormatEqual(self) -> None:
1228 def out(msg: str, **kwargs: Any) -> None:
1229 out_lines.append(msg)
1231 def err(msg: str, **kwargs: Any) -> None:
1232 err_lines.append(msg)
1234 with patch("black.out", out), patch("black.err", err):
1235 with self.assertRaises(AssertionError):
1236 self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")
1238 out_str = "".join(out_lines)
1239 self.assertTrue("Expected tree:" in out_str)
1240 self.assertTrue("Actual tree:" in out_str)
1241 self.assertEqual("".join(err_lines), "")
1243 def test_cache_broken_file(self) -> None:
1244 mode = black.FileMode()
1245 with cache_dir() as workspace:
1246 cache_file = black.get_cache_file(mode)
1247 with cache_file.open("w") as fobj:
1248 fobj.write("this is not a pickle")
1249 self.assertEqual(black.read_cache(mode), {})
1250 src = (workspace / "test.py").resolve()
1251 with src.open("w") as fobj:
1252 fobj.write("print('hello')")
1253 self.invokeBlack([str(src)])
1254 cache = black.read_cache(mode)
1255 self.assertIn(src, cache)
1257 def test_cache_single_file_already_cached(self) -> None:
1258 mode = black.FileMode()
1259 with cache_dir() as workspace:
1260 src = (workspace / "test.py").resolve()
1261 with src.open("w") as fobj:
1262 fobj.write("print('hello')")
1263 black.write_cache({}, [src], mode)
1264 self.invokeBlack([str(src)])
1265 with src.open("r") as fobj:
1266 self.assertEqual(fobj.read(), "print('hello')")
1269 def test_cache_multiple_files(self) -> None:
1270 mode = black.FileMode()
1271 with cache_dir() as workspace, patch(
1272 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1274 one = (workspace / "one.py").resolve()
1275 with one.open("w") as fobj:
1276 fobj.write("print('hello')")
1277 two = (workspace / "two.py").resolve()
1278 with two.open("w") as fobj:
1279 fobj.write("print('hello')")
1280 black.write_cache({}, [one], mode)
1281 self.invokeBlack([str(workspace)])
1282 with one.open("r") as fobj:
1283 self.assertEqual(fobj.read(), "print('hello')")
1284 with two.open("r") as fobj:
1285 self.assertEqual(fobj.read(), 'print("hello")\n')
1286 cache = black.read_cache(mode)
1287 self.assertIn(one, cache)
1288 self.assertIn(two, cache)
1290 def test_no_cache_when_writeback_diff(self) -> None:
1291 mode = black.FileMode()
1292 with cache_dir() as workspace:
1293 src = (workspace / "test.py").resolve()
1294 with src.open("w") as fobj:
1295 fobj.write("print('hello')")
1296 self.invokeBlack([str(src), "--diff"])
1297 cache_file = black.get_cache_file(mode)
1298 self.assertFalse(cache_file.exists())
1300 def test_no_cache_when_stdin(self) -> None:
1301 mode = black.FileMode()
1303 result = CliRunner().invoke(
1304 black.main, ["-"], input=BytesIO(b"print('hello')")
1306 self.assertEqual(result.exit_code, 0)
1307 cache_file = black.get_cache_file(mode)
1308 self.assertFalse(cache_file.exists())
1310 def test_read_cache_no_cachefile(self) -> None:
1311 mode = black.FileMode()
1313 self.assertEqual(black.read_cache(mode), {})
1315 def test_write_cache_read_cache(self) -> None:
1316 mode = black.FileMode()
1317 with cache_dir() as workspace:
1318 src = (workspace / "test.py").resolve()
1320 black.write_cache({}, [src], mode)
1321 cache = black.read_cache(mode)
1322 self.assertIn(src, cache)
1323 self.assertEqual(cache[src], black.get_cache_info(src))
1325 def test_filter_cached(self) -> None:
1326 with TemporaryDirectory() as workspace:
1327 path = Path(workspace)
1328 uncached = (path / "uncached").resolve()
1329 cached = (path / "cached").resolve()
1330 cached_but_changed = (path / "changed").resolve()
1333 cached_but_changed.touch()
1334 cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
1335 todo, done = black.filter_cached(
1336 cache, {uncached, cached, cached_but_changed}
1338 self.assertEqual(todo, {uncached, cached_but_changed})
1339 self.assertEqual(done, {cached})
1341 def test_write_cache_creates_directory_if_needed(self) -> None:
1342 mode = black.FileMode()
1343 with cache_dir(exists=False) as workspace:
1344 self.assertFalse(workspace.exists())
1345 black.write_cache({}, [], mode)
1346 self.assertTrue(workspace.exists())
1349 def test_failed_formatting_does_not_get_cached(self) -> None:
1350 mode = black.FileMode()
1351 with cache_dir() as workspace, patch(
1352 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1354 failing = (workspace / "failing.py").resolve()
1355 with failing.open("w") as fobj:
1356 fobj.write("not actually python")
1357 clean = (workspace / "clean.py").resolve()
1358 with clean.open("w") as fobj:
1359 fobj.write('print("hello")\n')
1360 self.invokeBlack([str(workspace)], exit_code=123)
1361 cache = black.read_cache(mode)
1362 self.assertNotIn(failing, cache)
1363 self.assertIn(clean, cache)
1365 def test_write_cache_write_fail(self) -> None:
1366 mode = black.FileMode()
1367 with cache_dir(), patch.object(Path, "open") as mock:
1368 mock.side_effect = OSError
1369 black.write_cache({}, [], mode)
1372 @patch("black.ProcessPoolExecutor", MagicMock(side_effect=OSError))
1373 def test_works_in_mono_process_only_environment(self) -> None:
1374 with cache_dir() as workspace:
1376 (workspace / "one.py").resolve(),
1377 (workspace / "two.py").resolve(),
1379 f.write_text('print("hello")\n')
1380 self.invokeBlack([str(workspace)])
1383 def test_check_diff_use_together(self) -> None:
1385 # Files which will be reformatted.
1386 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1387 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1388 # Files which will not be reformatted.
1389 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1390 self.invokeBlack([str(src2), "--diff", "--check"])
1391 # Multi file command.
1392 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1394 def test_no_files(self) -> None:
1396 # Without an argument, black exits with error code 0.
1397 self.invokeBlack([])
1399 def test_broken_symlink(self) -> None:
1400 with cache_dir() as workspace:
1401 symlink = workspace / "broken_link.py"
1403 symlink.symlink_to("nonexistent.py")
1404 except OSError as e:
1405 self.skipTest(f"Can't create symlinks: {e}")
1406 self.invokeBlack([str(workspace.resolve())])
1408 def test_read_cache_line_lengths(self) -> None:
1409 mode = black.FileMode()
1410 short_mode = black.FileMode(line_length=1)
1411 with cache_dir() as workspace:
1412 path = (workspace / "file.py").resolve()
1414 black.write_cache({}, [path], mode)
1415 one = black.read_cache(mode)
1416 self.assertIn(path, one)
1417 two = black.read_cache(short_mode)
1418 self.assertNotIn(path, two)
1420 def test_tricky_unicode_symbols(self) -> None:
1421 source, expected = read_data("tricky_unicode_symbols")
1423 self.assertFormatEqual(expected, actual)
1424 black.assert_equivalent(source, actual)
1425 black.assert_stable(source, actual, black.FileMode())
1427 def test_single_file_force_pyi(self) -> None:
1428 reg_mode = black.FileMode()
1429 pyi_mode = black.FileMode(is_pyi=True)
1430 contents, expected = read_data("force_pyi")
1431 with cache_dir() as workspace:
1432 path = (workspace / "file.py").resolve()
1433 with open(path, "w") as fh:
1435 self.invokeBlack([str(path), "--pyi"])
1436 with open(path, "r") as fh:
1438 # verify cache with --pyi is separate
1439 pyi_cache = black.read_cache(pyi_mode)
1440 self.assertIn(path, pyi_cache)
1441 normal_cache = black.read_cache(reg_mode)
1442 self.assertNotIn(path, normal_cache)
1443 self.assertEqual(actual, expected)
1446 def test_multi_file_force_pyi(self) -> None:
1447 reg_mode = black.FileMode()
1448 pyi_mode = black.FileMode(is_pyi=True)
1449 contents, expected = read_data("force_pyi")
1450 with cache_dir() as workspace:
1452 (workspace / "file1.py").resolve(),
1453 (workspace / "file2.py").resolve(),
1456 with open(path, "w") as fh:
1458 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1460 with open(path, "r") as fh:
1462 self.assertEqual(actual, expected)
1463 # verify cache with --pyi is separate
1464 pyi_cache = black.read_cache(pyi_mode)
1465 normal_cache = black.read_cache(reg_mode)
1467 self.assertIn(path, pyi_cache)
1468 self.assertNotIn(path, normal_cache)
1470 def test_pipe_force_pyi(self) -> None:
1471 source, expected = read_data("force_pyi")
1472 result = CliRunner().invoke(
1473 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1475 self.assertEqual(result.exit_code, 0)
1476 actual = result.output
1477 self.assertFormatEqual(actual, expected)
1479 def test_single_file_force_py36(self) -> None:
1480 reg_mode = black.FileMode()
1481 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1482 source, expected = read_data("force_py36")
1483 with cache_dir() as workspace:
1484 path = (workspace / "file.py").resolve()
1485 with open(path, "w") as fh:
1487 self.invokeBlack([str(path), *PY36_ARGS])
1488 with open(path, "r") as fh:
1490 # verify cache with --target-version is separate
1491 py36_cache = black.read_cache(py36_mode)
1492 self.assertIn(path, py36_cache)
1493 normal_cache = black.read_cache(reg_mode)
1494 self.assertNotIn(path, normal_cache)
1495 self.assertEqual(actual, expected)
1498 def test_multi_file_force_py36(self) -> None:
1499 reg_mode = black.FileMode()
1500 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1501 source, expected = read_data("force_py36")
1502 with cache_dir() as workspace:
1504 (workspace / "file1.py").resolve(),
1505 (workspace / "file2.py").resolve(),
1508 with open(path, "w") as fh:
1510 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1512 with open(path, "r") as fh:
1514 self.assertEqual(actual, expected)
1515 # verify cache with --target-version is separate
1516 pyi_cache = black.read_cache(py36_mode)
1517 normal_cache = black.read_cache(reg_mode)
1519 self.assertIn(path, pyi_cache)
1520 self.assertNotIn(path, normal_cache)
1522 def test_collections(self) -> None:
1523 source, expected = read_data("collections")
1525 self.assertFormatEqual(expected, actual)
1526 black.assert_equivalent(source, actual)
1527 black.assert_stable(source, actual, black.FileMode())
1529 def test_pipe_force_py36(self) -> None:
1530 source, expected = read_data("force_py36")
1531 result = CliRunner().invoke(
1533 ["-", "-q", "--target-version=py36"],
1534 input=BytesIO(source.encode("utf8")),
1536 self.assertEqual(result.exit_code, 0)
1537 actual = result.output
1538 self.assertFormatEqual(actual, expected)
1540 def test_include_exclude(self) -> None:
1541 path = THIS_DIR / "data" / "include_exclude_tests"
1542 include = re.compile(r"\.pyi?$")
1543 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1544 report = black.Report()
1545 gitignore = PathSpec.from_lines("gitwildmatch", [])
1546 sources: List[Path] = []
1548 Path(path / "b/dont_exclude/a.py"),
1549 Path(path / "b/dont_exclude/a.pyi"),
1551 this_abs = THIS_DIR.resolve()
1553 black.gen_python_files(
1554 path.iterdir(), this_abs, include, [exclude], report, gitignore
1557 self.assertEqual(sorted(expected), sorted(sources))
1559 def test_gitignore_exclude(self) -> None:
1560 path = THIS_DIR / "data" / "include_exclude_tests"
1561 include = re.compile(r"\.pyi?$")
1562 exclude = re.compile(r"")
1563 report = black.Report()
1564 gitignore = PathSpec.from_lines(
1565 "gitwildmatch", ["exclude/", ".definitely_exclude"]
1567 sources: List[Path] = []
1569 Path(path / "b/dont_exclude/a.py"),
1570 Path(path / "b/dont_exclude/a.pyi"),
1572 this_abs = THIS_DIR.resolve()
1574 black.gen_python_files(
1575 path.iterdir(), this_abs, include, [exclude], report, gitignore
1578 self.assertEqual(sorted(expected), sorted(sources))
1580 def test_empty_include(self) -> None:
1581 path = THIS_DIR / "data" / "include_exclude_tests"
1582 report = black.Report()
1583 gitignore = PathSpec.from_lines("gitwildmatch", [])
1584 empty = re.compile(r"")
1585 sources: List[Path] = []
1587 Path(path / "b/exclude/a.pie"),
1588 Path(path / "b/exclude/a.py"),
1589 Path(path / "b/exclude/a.pyi"),
1590 Path(path / "b/dont_exclude/a.pie"),
1591 Path(path / "b/dont_exclude/a.py"),
1592 Path(path / "b/dont_exclude/a.pyi"),
1593 Path(path / "b/.definitely_exclude/a.pie"),
1594 Path(path / "b/.definitely_exclude/a.py"),
1595 Path(path / "b/.definitely_exclude/a.pyi"),
1597 this_abs = THIS_DIR.resolve()
1599 black.gen_python_files(
1603 [re.compile(black.DEFAULT_EXCLUDES)],
1608 self.assertEqual(sorted(expected), sorted(sources))
1610 def test_empty_exclude(self) -> None:
1611 path = THIS_DIR / "data" / "include_exclude_tests"
1612 report = black.Report()
1613 gitignore = PathSpec.from_lines("gitwildmatch", [])
1614 empty = re.compile(r"")
1615 sources: List[Path] = []
1617 Path(path / "b/dont_exclude/a.py"),
1618 Path(path / "b/dont_exclude/a.pyi"),
1619 Path(path / "b/exclude/a.py"),
1620 Path(path / "b/exclude/a.pyi"),
1621 Path(path / "b/.definitely_exclude/a.py"),
1622 Path(path / "b/.definitely_exclude/a.pyi"),
1624 this_abs = THIS_DIR.resolve()
1626 black.gen_python_files(
1629 re.compile(black.DEFAULT_INCLUDES),
1635 self.assertEqual(sorted(expected), sorted(sources))
1637 def test_invalid_include_exclude(self) -> None:
1638 for option in ["--include", "--exclude"]:
1639 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1641 def test_preserves_line_endings(self) -> None:
1642 with TemporaryDirectory() as workspace:
1643 test_file = Path(workspace) / "test.py"
1644 for nl in ["\n", "\r\n"]:
1645 contents = nl.join(["def f( ):", " pass"])
1646 test_file.write_bytes(contents.encode())
1647 ff(test_file, write_back=black.WriteBack.YES)
1648 updated_contents: bytes = test_file.read_bytes()
1649 self.assertIn(nl.encode(), updated_contents)
1651 self.assertNotIn(b"\r\n", updated_contents)
1653 def test_preserves_line_endings_via_stdin(self) -> None:
1654 for nl in ["\n", "\r\n"]:
1655 contents = nl.join(["def f( ):", " pass"])
1656 runner = BlackRunner()
1657 result = runner.invoke(
1658 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1660 self.assertEqual(result.exit_code, 0)
1661 output = runner.stdout_bytes
1662 self.assertIn(nl.encode("utf8"), output)
1664 self.assertNotIn(b"\r\n", output)
1666 def test_assert_equivalent_different_asts(self) -> None:
1667 with self.assertRaises(AssertionError):
1668 black.assert_equivalent("{}", "None")
1670 def test_symlink_out_of_root_directory(self) -> None:
1672 root = THIS_DIR.resolve()
1674 include = re.compile(black.DEFAULT_INCLUDES)
1675 exclude = re.compile(black.DEFAULT_EXCLUDES)
1676 report = black.Report()
1677 gitignore = PathSpec.from_lines("gitwildmatch", [])
1678 # `child` should behave like a symlink which resolved path is clearly
1679 # outside of the `root` directory.
1680 path.iterdir.return_value = [child]
1681 child.resolve.return_value = Path("/a/b/c")
1682 child.as_posix.return_value = "/a/b/c"
1683 child.is_symlink.return_value = True
1686 black.gen_python_files(
1687 path.iterdir(), root, include, exclude, report, gitignore
1690 except ValueError as ve:
1691 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1692 path.iterdir.assert_called_once()
1693 child.resolve.assert_called_once()
1694 child.is_symlink.assert_called_once()
1695 # `child` should behave like a strange file which resolved path is clearly
1696 # outside of the `root` directory.
1697 child.is_symlink.return_value = False
1698 with self.assertRaises(ValueError):
1700 black.gen_python_files(
1701 path.iterdir(), root, include, exclude, report, gitignore
1704 path.iterdir.assert_called()
1705 self.assertEqual(path.iterdir.call_count, 2)
1706 child.resolve.assert_called()
1707 self.assertEqual(child.resolve.call_count, 2)
1708 child.is_symlink.assert_called()
1709 self.assertEqual(child.is_symlink.call_count, 2)
1711 def test_shhh_click(self) -> None:
1713 from click import _unicodefun # type: ignore
1714 except ModuleNotFoundError:
1715 self.skipTest("Incompatible Click version")
1716 if not hasattr(_unicodefun, "_verify_python3_env"):
1717 self.skipTest("Incompatible Click version")
1718 # First, let's see if Click is crashing with a preferred ASCII charset.
1719 with patch("locale.getpreferredencoding") as gpe:
1720 gpe.return_value = "ASCII"
1721 with self.assertRaises(RuntimeError):
1722 _unicodefun._verify_python3_env()
1723 # Now, let's silence Click...
1725 # ...and confirm it's silent.
1726 with patch("locale.getpreferredencoding") as gpe:
1727 gpe.return_value = "ASCII"
1729 _unicodefun._verify_python3_env()
1730 except RuntimeError as re:
1731 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1733 def test_root_logger_not_used_directly(self) -> None:
1734 def fail(*args: Any, **kwargs: Any) -> None:
1735 self.fail("Record created with root logger")
1737 with patch.multiple(
1748 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1749 def test_blackd_main(self) -> None:
1750 with patch("blackd.web.run_app"):
1751 result = CliRunner().invoke(blackd.main, [])
1752 if result.exception is not None:
1753 raise result.exception
1754 self.assertEqual(result.exit_code, 0)
1756 def test_invalid_config_return_code(self) -> None:
1757 tmp_file = Path(black.dump_to_file())
1759 tmp_config = Path(black.dump_to_file())
1761 args = ["--config", str(tmp_config), str(tmp_file)]
1762 self.invokeBlack(args, exit_code=2, ignore_config=False)
1766 def test_parse_pyproject_toml(self) -> None:
1767 test_toml_file = THIS_DIR / "test.toml"
1768 config = black.parse_pyproject_toml(str(test_toml_file))
1769 self.assertEqual(config["verbose"], 1)
1770 self.assertEqual(config["check"], "no")
1771 self.assertEqual(config["diff"], "y")
1772 self.assertEqual(config["color"], True)
1773 self.assertEqual(config["line_length"], 79)
1774 self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
1775 self.assertEqual(config["exclude"], r"\.pyi?$")
1776 self.assertEqual(config["include"], r"\.py?$")
1778 def test_read_pyproject_toml(self) -> None:
1779 test_toml_file = THIS_DIR / "test.toml"
1781 # Fake a click context and parameter so mypy stays happy
1782 class FakeContext(click.Context):
1783 def __init__(self) -> None:
1784 self.default_map: Dict[str, Any] = {}
1786 class FakeParameter(click.Parameter):
1787 def __init__(self) -> None:
1790 fake_ctx = FakeContext()
1791 black.read_pyproject_toml(
1792 fake_ctx, FakeParameter(), str(test_toml_file),
1794 config = fake_ctx.default_map
1795 self.assertEqual(config["verbose"], "1")
1796 self.assertEqual(config["check"], "no")
1797 self.assertEqual(config["diff"], "y")
1798 self.assertEqual(config["color"], "True")
1799 self.assertEqual(config["line_length"], "79")
1800 self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
1801 self.assertEqual(config["exclude"], r"\.pyi?$")
1802 self.assertEqual(config["include"], r"\.py?$")
1804 def test_find_project_root(self) -> None:
1805 with TemporaryDirectory() as workspace:
1806 root = Path(workspace)
1807 test_dir = root / "test"
1810 src_dir = root / "src"
1813 root_pyproject = root / "pyproject.toml"
1814 root_pyproject.touch()
1815 src_pyproject = src_dir / "pyproject.toml"
1816 src_pyproject.touch()
1817 src_python = src_dir / "foo.py"
1821 black.find_project_root((src_dir, test_dir)), root.resolve()
1823 self.assertEqual(black.find_project_root((src_dir,)), src_dir.resolve())
1824 self.assertEqual(black.find_project_root((src_python,)), src_dir.resolve())
1827 class BlackDTestCase(AioHTTPTestCase):
1828 async def get_application(self) -> web.Application:
1829 return blackd.make_app()
1831 # TODO: remove these decorators once the below is released
1832 # https://github.com/aio-libs/aiohttp/pull/3727
1833 @skip_if_exception("ClientOSError")
1834 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1836 async def test_blackd_request_needs_formatting(self) -> None:
1837 response = await self.client.post("/", data=b"print('hello world')")
1838 self.assertEqual(response.status, 200)
1839 self.assertEqual(response.charset, "utf8")
1840 self.assertEqual(await response.read(), b'print("hello world")\n')
1842 @skip_if_exception("ClientOSError")
1843 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1845 async def test_blackd_request_no_change(self) -> None:
1846 response = await self.client.post("/", data=b'print("hello world")\n')
1847 self.assertEqual(response.status, 204)
1848 self.assertEqual(await response.read(), b"")
1850 @skip_if_exception("ClientOSError")
1851 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1853 async def test_blackd_request_syntax_error(self) -> None:
1854 response = await self.client.post("/", data=b"what even ( is")
1855 self.assertEqual(response.status, 400)
1856 content = await response.text()
1858 content.startswith("Cannot parse"),
1859 msg=f"Expected error to start with 'Cannot parse', got {repr(content)}",
1862 @skip_if_exception("ClientOSError")
1863 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1865 async def test_blackd_unsupported_version(self) -> None:
1866 response = await self.client.post(
1867 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "2"}
1869 self.assertEqual(response.status, 501)
1871 @skip_if_exception("ClientOSError")
1872 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1874 async def test_blackd_supported_version(self) -> None:
1875 response = await self.client.post(
1876 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "1"}
1878 self.assertEqual(response.status, 200)
1880 @skip_if_exception("ClientOSError")
1881 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1883 async def test_blackd_invalid_python_variant(self) -> None:
1884 async def check(header_value: str, expected_status: int = 400) -> None:
1885 response = await self.client.post(
1886 "/", data=b"what", headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1888 self.assertEqual(response.status, expected_status)
1891 await check("ruby3.5")
1892 await check("pyi3.6")
1893 await check("py1.5")
1895 await check("py2.8")
1897 await check("pypy3.0")
1898 await check("jython3.4")
1900 @skip_if_exception("ClientOSError")
1901 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1903 async def test_blackd_pyi(self) -> None:
1904 source, expected = read_data("stub.pyi")
1905 response = await self.client.post(
1906 "/", data=source, headers={blackd.PYTHON_VARIANT_HEADER: "pyi"}
1908 self.assertEqual(response.status, 200)
1909 self.assertEqual(await response.text(), expected)
1911 @skip_if_exception("ClientOSError")
1912 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1914 async def test_blackd_diff(self) -> None:
1915 diff_header = re.compile(
1916 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"
1919 source, _ = read_data("blackd_diff.py")
1920 expected, _ = read_data("blackd_diff.diff")
1922 response = await self.client.post(
1923 "/", data=source, headers={blackd.DIFF_HEADER: "true"}
1925 self.assertEqual(response.status, 200)
1927 actual = await response.text()
1928 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
1929 self.assertEqual(actual, expected)
1931 @skip_if_exception("ClientOSError")
1932 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1934 async def test_blackd_python_variant(self) -> None:
1937 " and_has_a_bunch_of,\n"
1938 " very_long_arguments_too,\n"
1939 " and_lots_of_them_as_well_lol,\n"
1940 " **and_very_long_keyword_arguments\n"
1945 async def check(header_value: str, expected_status: int) -> None:
1946 response = await self.client.post(
1947 "/", data=code, headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1950 response.status, expected_status, msg=await response.text()
1953 await check("3.6", 200)
1954 await check("py3.6", 200)
1955 await check("3.6,3.7", 200)
1956 await check("3.6,py3.7", 200)
1957 await check("py36,py37", 200)
1958 await check("36", 200)
1959 await check("3.6.4", 200)
1961 await check("2", 204)
1962 await check("2.7", 204)
1963 await check("py2.7", 204)
1964 await check("3.4", 204)
1965 await check("py3.4", 204)
1966 await check("py34,py36", 204)
1967 await check("34", 204)
1969 @skip_if_exception("ClientOSError")
1970 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1972 async def test_blackd_line_length(self) -> None:
1973 response = await self.client.post(
1974 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "7"}
1976 self.assertEqual(response.status, 200)
1978 @skip_if_exception("ClientOSError")
1979 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1981 async def test_blackd_invalid_line_length(self) -> None:
1982 response = await self.client.post(
1983 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "NaN"}
1985 self.assertEqual(response.status, 400)
1987 @skip_if_exception("ClientOSError")
1988 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1990 async def test_blackd_response_black_version_header(self) -> None:
1991 response = await self.client.post("/")
1992 self.assertIsNotNone(response.headers.get(blackd.BLACK_VERSION_HEADER))
1995 if __name__ == "__main__":
1996 unittest.main(module="test_black")