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, wraps
7 from io import BytesIO, TextIOWrapper
9 from pathlib import Path
12 from tempfile import TemporaryDirectory
25 from unittest.mock import patch, MagicMock
27 from click import unstyle
28 from click.testing import CliRunner
31 from black import Feature, TargetVersion
35 from aiohttp.test_utils import TestClient, TestServer
37 has_blackd_deps = False
39 has_blackd_deps = True
41 ff = partial(black.format_file_in_place, mode=black.FileMode(), fast=True)
42 fs = partial(black.format_str, mode=black.FileMode())
43 THIS_FILE = Path(__file__)
44 THIS_DIR = THIS_FILE.parent
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 THIS_DIR
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(close: bool) -> Iterator[None]:
92 policy = asyncio.get_event_loop_policy()
93 old_loop = policy.get_event_loop()
94 loop = policy.new_event_loop()
95 asyncio.set_event_loop(loop)
100 policy.set_event_loop(old_loop)
105 def async_test(f: Callable[..., Coroutine[Any, None, R]]) -> Callable[..., None]:
106 @event_loop(close=True)
108 def wrapper(*args: Any, **kwargs: Any) -> None:
109 asyncio.get_event_loop().run_until_complete(f(*args, **kwargs))
114 class BlackRunner(CliRunner):
115 """Modify CliRunner so that stderr is not merged with stdout.
117 This is a hack that can be removed once we depend on Click 7.x"""
119 def __init__(self) -> None:
120 self.stderrbuf = BytesIO()
121 self.stdoutbuf = BytesIO()
122 self.stdout_bytes = b""
123 self.stderr_bytes = b""
127 def isolation(self, *args: Any, **kwargs: Any) -> Generator[BinaryIO, None, None]:
128 with super().isolation(*args, **kwargs) as output:
130 hold_stderr = sys.stderr
131 sys.stderr = TextIOWrapper(self.stderrbuf, encoding=self.charset)
134 self.stdout_bytes = sys.stdout.buffer.getvalue() # type: ignore
135 self.stderr_bytes = sys.stderr.buffer.getvalue() # type: ignore
136 sys.stderr = hold_stderr
139 class BlackTestCase(unittest.TestCase):
142 def assertFormatEqual(self, expected: str, actual: str) -> None:
143 if actual != expected and not os.environ.get("SKIP_AST_PRINT"):
144 bdv: black.DebugVisitor[Any]
145 black.out("Expected tree:", fg="green")
147 exp_node = black.lib2to3_parse(expected)
148 bdv = black.DebugVisitor()
149 list(bdv.visit(exp_node))
150 except Exception as ve:
152 black.out("Actual tree:", fg="red")
154 exp_node = black.lib2to3_parse(actual)
155 bdv = black.DebugVisitor()
156 list(bdv.visit(exp_node))
157 except Exception as ve:
159 self.assertEqual(expected, actual)
162 self, args: List[str], exit_code: int = 0, ignore_config: bool = True
164 runner = BlackRunner()
166 args = ["--config", str(THIS_DIR / "empty.toml"), *args]
167 result = runner.invoke(black.main, args)
168 self.assertEqual(result.exit_code, exit_code, msg=runner.stderr_bytes.decode())
170 @patch("black.dump_to_file", dump_to_stderr)
171 def test_empty(self) -> None:
172 source = expected = ""
174 self.assertFormatEqual(expected, actual)
175 black.assert_equivalent(source, actual)
176 black.assert_stable(source, actual, black.FileMode())
178 def test_empty_ff(self) -> None:
180 tmp_file = Path(black.dump_to_file())
182 self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES))
183 with open(tmp_file, encoding="utf8") as f:
187 self.assertFormatEqual(expected, actual)
189 @patch("black.dump_to_file", dump_to_stderr)
190 def test_self(self) -> None:
191 source, expected = read_data("test_black", data=False)
193 self.assertFormatEqual(expected, actual)
194 black.assert_equivalent(source, actual)
195 black.assert_stable(source, actual, black.FileMode())
196 self.assertFalse(ff(THIS_FILE))
198 @patch("black.dump_to_file", dump_to_stderr)
199 def test_black(self) -> None:
200 source, expected = read_data("../black", data=False)
202 self.assertFormatEqual(expected, actual)
203 black.assert_equivalent(source, actual)
204 black.assert_stable(source, actual, black.FileMode())
205 self.assertFalse(ff(THIS_DIR / ".." / "black.py"))
207 def test_piping(self) -> None:
208 source, expected = read_data("../black", data=False)
209 result = BlackRunner().invoke(
211 ["-", "--fast", f"--line-length={black.DEFAULT_LINE_LENGTH}"],
212 input=BytesIO(source.encode("utf8")),
214 self.assertEqual(result.exit_code, 0)
215 self.assertFormatEqual(expected, result.output)
216 black.assert_equivalent(source, result.output)
217 black.assert_stable(source, result.output, black.FileMode())
219 def test_piping_diff(self) -> None:
220 diff_header = re.compile(
221 rf"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d "
222 rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
224 source, _ = read_data("expression.py")
225 expected, _ = read_data("expression.diff")
226 config = THIS_DIR / "data" / "empty_pyproject.toml"
230 f"--line-length={black.DEFAULT_LINE_LENGTH}",
232 f"--config={config}",
234 result = BlackRunner().invoke(
235 black.main, args, input=BytesIO(source.encode("utf8"))
237 self.assertEqual(result.exit_code, 0)
238 actual = diff_header.sub("[Deterministic header]", result.output)
239 actual = actual.rstrip() + "\n" # the diff output has a trailing space
240 self.assertEqual(expected, actual)
242 @patch("black.dump_to_file", dump_to_stderr)
243 def test_setup(self) -> None:
244 source, expected = read_data("../setup", data=False)
246 self.assertFormatEqual(expected, actual)
247 black.assert_equivalent(source, actual)
248 black.assert_stable(source, actual, black.FileMode())
249 self.assertFalse(ff(THIS_DIR / ".." / "setup.py"))
251 @patch("black.dump_to_file", dump_to_stderr)
252 def test_function(self) -> None:
253 source, expected = read_data("function")
255 self.assertFormatEqual(expected, actual)
256 black.assert_equivalent(source, actual)
257 black.assert_stable(source, actual, black.FileMode())
259 @patch("black.dump_to_file", dump_to_stderr)
260 def test_function2(self) -> None:
261 source, expected = read_data("function2")
263 self.assertFormatEqual(expected, actual)
264 black.assert_equivalent(source, actual)
265 black.assert_stable(source, actual, black.FileMode())
267 @patch("black.dump_to_file", dump_to_stderr)
268 def test_function_trailing_comma(self) -> None:
269 source, expected = read_data("function_trailing_comma")
271 self.assertFormatEqual(expected, actual)
272 black.assert_equivalent(source, actual)
273 black.assert_stable(source, actual, black.FileMode())
275 @patch("black.dump_to_file", dump_to_stderr)
276 def test_expression(self) -> None:
277 source, expected = read_data("expression")
279 self.assertFormatEqual(expected, actual)
280 black.assert_equivalent(source, actual)
281 black.assert_stable(source, actual, black.FileMode())
283 def test_expression_ff(self) -> None:
284 source, expected = read_data("expression")
285 tmp_file = Path(black.dump_to_file(source))
287 self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
288 with open(tmp_file, encoding="utf8") as f:
292 self.assertFormatEqual(expected, actual)
293 with patch("black.dump_to_file", dump_to_stderr):
294 black.assert_equivalent(source, actual)
295 black.assert_stable(source, actual, black.FileMode())
297 def test_expression_diff(self) -> None:
298 source, _ = read_data("expression.py")
299 expected, _ = read_data("expression.diff")
300 tmp_file = Path(black.dump_to_file(source))
301 diff_header = re.compile(
302 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
303 rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
306 result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
307 self.assertEqual(result.exit_code, 0)
310 actual = result.output
311 actual = diff_header.sub("[Deterministic header]", actual)
312 actual = actual.rstrip() + "\n" # the diff output has a trailing space
313 if expected != actual:
314 dump = black.dump_to_file(actual)
316 f"Expected diff isn't equal to the actual. If you made changes "
317 f"to expression.py and this is an anticipated difference, "
318 f"overwrite tests/data/expression.diff with {dump}"
320 self.assertEqual(expected, actual, msg)
322 @patch("black.dump_to_file", dump_to_stderr)
323 def test_fstring(self) -> None:
324 source, expected = read_data("fstring")
326 self.assertFormatEqual(expected, actual)
327 black.assert_equivalent(source, actual)
328 black.assert_stable(source, actual, black.FileMode())
330 @patch("black.dump_to_file", dump_to_stderr)
331 def test_string_quotes(self) -> None:
332 source, expected = read_data("string_quotes")
334 self.assertFormatEqual(expected, actual)
335 black.assert_equivalent(source, actual)
336 black.assert_stable(source, actual, black.FileMode())
337 mode = black.FileMode(string_normalization=False)
338 not_normalized = fs(source, mode=mode)
339 self.assertFormatEqual(source, not_normalized)
340 black.assert_equivalent(source, not_normalized)
341 black.assert_stable(source, not_normalized, mode=mode)
343 @patch("black.dump_to_file", dump_to_stderr)
344 def test_slices(self) -> None:
345 source, expected = read_data("slices")
347 self.assertFormatEqual(expected, actual)
348 black.assert_equivalent(source, actual)
349 black.assert_stable(source, actual, black.FileMode())
351 @patch("black.dump_to_file", dump_to_stderr)
352 def test_comments(self) -> None:
353 source, expected = read_data("comments")
355 self.assertFormatEqual(expected, actual)
356 black.assert_equivalent(source, actual)
357 black.assert_stable(source, actual, black.FileMode())
359 @patch("black.dump_to_file", dump_to_stderr)
360 def test_comments2(self) -> None:
361 source, expected = read_data("comments2")
363 self.assertFormatEqual(expected, actual)
364 black.assert_equivalent(source, actual)
365 black.assert_stable(source, actual, black.FileMode())
367 @patch("black.dump_to_file", dump_to_stderr)
368 def test_comments3(self) -> None:
369 source, expected = read_data("comments3")
371 self.assertFormatEqual(expected, actual)
372 black.assert_equivalent(source, actual)
373 black.assert_stable(source, actual, black.FileMode())
375 @patch("black.dump_to_file", dump_to_stderr)
376 def test_comments4(self) -> None:
377 source, expected = read_data("comments4")
379 self.assertFormatEqual(expected, actual)
380 black.assert_equivalent(source, actual)
381 black.assert_stable(source, actual, black.FileMode())
383 @patch("black.dump_to_file", dump_to_stderr)
384 def test_comments5(self) -> None:
385 source, expected = read_data("comments5")
387 self.assertFormatEqual(expected, actual)
388 black.assert_equivalent(source, actual)
389 black.assert_stable(source, actual, black.FileMode())
391 @patch("black.dump_to_file", dump_to_stderr)
392 def test_comments6(self) -> None:
393 source, expected = read_data("comments6")
395 self.assertFormatEqual(expected, actual)
396 black.assert_equivalent(source, actual)
397 black.assert_stable(source, actual, black.FileMode())
399 @patch("black.dump_to_file", dump_to_stderr)
400 def test_comments7(self) -> None:
401 source, expected = read_data("comments7")
403 self.assertFormatEqual(expected, actual)
404 black.assert_equivalent(source, actual)
405 black.assert_stable(source, actual, black.FileMode())
407 @patch("black.dump_to_file", dump_to_stderr)
408 def test_comment_after_escaped_newline(self) -> None:
409 source, expected = read_data("comment_after_escaped_newline")
411 self.assertFormatEqual(expected, actual)
412 black.assert_equivalent(source, actual)
413 black.assert_stable(source, actual, black.FileMode())
415 @patch("black.dump_to_file", dump_to_stderr)
416 def test_cantfit(self) -> None:
417 source, expected = read_data("cantfit")
419 self.assertFormatEqual(expected, actual)
420 black.assert_equivalent(source, actual)
421 black.assert_stable(source, actual, black.FileMode())
423 @patch("black.dump_to_file", dump_to_stderr)
424 def test_import_spacing(self) -> None:
425 source, expected = read_data("import_spacing")
427 self.assertFormatEqual(expected, actual)
428 black.assert_equivalent(source, actual)
429 black.assert_stable(source, actual, black.FileMode())
431 @patch("black.dump_to_file", dump_to_stderr)
432 def test_composition(self) -> None:
433 source, expected = read_data("composition")
435 self.assertFormatEqual(expected, actual)
436 black.assert_equivalent(source, actual)
437 black.assert_stable(source, actual, black.FileMode())
439 @patch("black.dump_to_file", dump_to_stderr)
440 def test_empty_lines(self) -> None:
441 source, expected = read_data("empty_lines")
443 self.assertFormatEqual(expected, actual)
444 black.assert_equivalent(source, actual)
445 black.assert_stable(source, actual, black.FileMode())
447 @patch("black.dump_to_file", dump_to_stderr)
448 def test_remove_parens(self) -> None:
449 source, expected = read_data("remove_parens")
451 self.assertFormatEqual(expected, actual)
452 black.assert_equivalent(source, actual)
453 black.assert_stable(source, actual, black.FileMode())
455 @patch("black.dump_to_file", dump_to_stderr)
456 def test_string_prefixes(self) -> None:
457 source, expected = read_data("string_prefixes")
459 self.assertFormatEqual(expected, actual)
460 black.assert_equivalent(source, actual)
461 black.assert_stable(source, actual, black.FileMode())
463 @patch("black.dump_to_file", dump_to_stderr)
464 def test_numeric_literals(self) -> None:
465 source, expected = read_data("numeric_literals")
466 mode = black.FileMode(target_versions=black.PY36_VERSIONS)
467 actual = fs(source, mode=mode)
468 self.assertFormatEqual(expected, actual)
469 black.assert_equivalent(source, actual)
470 black.assert_stable(source, actual, mode)
472 @patch("black.dump_to_file", dump_to_stderr)
473 def test_numeric_literals_ignoring_underscores(self) -> None:
474 source, expected = read_data("numeric_literals_skip_underscores")
475 mode = black.FileMode(target_versions=black.PY36_VERSIONS)
476 actual = fs(source, mode=mode)
477 self.assertFormatEqual(expected, actual)
478 black.assert_equivalent(source, actual)
479 black.assert_stable(source, actual, mode)
481 @patch("black.dump_to_file", dump_to_stderr)
482 def test_numeric_literals_py2(self) -> None:
483 source, expected = read_data("numeric_literals_py2")
485 self.assertFormatEqual(expected, actual)
486 black.assert_stable(source, actual, black.FileMode())
488 @patch("black.dump_to_file", dump_to_stderr)
489 def test_python2(self) -> None:
490 source, expected = read_data("python2")
492 self.assertFormatEqual(expected, actual)
493 black.assert_equivalent(source, actual)
494 black.assert_stable(source, actual, black.FileMode())
496 @patch("black.dump_to_file", dump_to_stderr)
497 def test_python2_print_function(self) -> None:
498 source, expected = read_data("python2_print_function")
499 mode = black.FileMode(target_versions={TargetVersion.PY27})
500 actual = fs(source, mode=mode)
501 self.assertFormatEqual(expected, actual)
502 black.assert_equivalent(source, actual)
503 black.assert_stable(source, actual, mode)
505 @patch("black.dump_to_file", dump_to_stderr)
506 def test_python2_unicode_literals(self) -> None:
507 source, expected = read_data("python2_unicode_literals")
509 self.assertFormatEqual(expected, actual)
510 black.assert_equivalent(source, actual)
511 black.assert_stable(source, actual, black.FileMode())
513 @patch("black.dump_to_file", dump_to_stderr)
514 def test_stub(self) -> None:
515 mode = black.FileMode(is_pyi=True)
516 source, expected = read_data("stub.pyi")
517 actual = fs(source, mode=mode)
518 self.assertFormatEqual(expected, actual)
519 black.assert_stable(source, actual, mode)
521 @patch("black.dump_to_file", dump_to_stderr)
522 def test_async_as_identifier(self) -> None:
523 source_path = (THIS_DIR / "data" / "async_as_identifier.py").resolve()
524 source, expected = read_data("async_as_identifier")
526 self.assertFormatEqual(expected, actual)
527 major, minor = sys.version_info[:2]
528 if major < 3 or (major <= 3 and minor < 7):
529 black.assert_equivalent(source, actual)
530 black.assert_stable(source, actual, black.FileMode())
531 # ensure black can parse this when the target is 3.6
532 self.invokeBlack([str(source_path), "--target-version", "py36"])
533 # but not on 3.7, because async/await is no longer an identifier
534 self.invokeBlack([str(source_path), "--target-version", "py37"], exit_code=123)
536 @patch("black.dump_to_file", dump_to_stderr)
537 def test_python37(self) -> None:
538 source_path = (THIS_DIR / "data" / "python37.py").resolve()
539 source, expected = read_data("python37")
541 self.assertFormatEqual(expected, actual)
542 major, minor = sys.version_info[:2]
543 if major > 3 or (major == 3 and minor >= 7):
544 black.assert_equivalent(source, actual)
545 black.assert_stable(source, actual, black.FileMode())
546 # ensure black can parse this when the target is 3.7
547 self.invokeBlack([str(source_path), "--target-version", "py37"])
548 # but not on 3.6, because we use async as a reserved keyword
549 self.invokeBlack([str(source_path), "--target-version", "py36"], exit_code=123)
551 @patch("black.dump_to_file", dump_to_stderr)
552 def test_fmtonoff(self) -> None:
553 source, expected = read_data("fmtonoff")
555 self.assertFormatEqual(expected, actual)
556 black.assert_equivalent(source, actual)
557 black.assert_stable(source, actual, black.FileMode())
559 @patch("black.dump_to_file", dump_to_stderr)
560 def test_fmtonoff2(self) -> None:
561 source, expected = read_data("fmtonoff2")
563 self.assertFormatEqual(expected, actual)
564 black.assert_equivalent(source, actual)
565 black.assert_stable(source, actual, black.FileMode())
567 @patch("black.dump_to_file", dump_to_stderr)
568 def test_remove_empty_parentheses_after_class(self) -> None:
569 source, expected = read_data("class_blank_parentheses")
571 self.assertFormatEqual(expected, actual)
572 black.assert_equivalent(source, actual)
573 black.assert_stable(source, actual, black.FileMode())
575 @patch("black.dump_to_file", dump_to_stderr)
576 def test_new_line_between_class_and_code(self) -> None:
577 source, expected = read_data("class_methods_new_line")
579 self.assertFormatEqual(expected, actual)
580 black.assert_equivalent(source, actual)
581 black.assert_stable(source, actual, black.FileMode())
583 @patch("black.dump_to_file", dump_to_stderr)
584 def test_bracket_match(self) -> None:
585 source, expected = read_data("bracketmatch")
587 self.assertFormatEqual(expected, actual)
588 black.assert_equivalent(source, actual)
589 black.assert_stable(source, actual, black.FileMode())
591 @patch("black.dump_to_file", dump_to_stderr)
592 def test_tuple_assign(self) -> None:
593 source, expected = read_data("tupleassign")
595 self.assertFormatEqual(expected, actual)
596 black.assert_equivalent(source, actual)
597 black.assert_stable(source, actual, black.FileMode())
599 def test_tab_comment_indentation(self) -> None:
600 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
601 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
602 self.assertFormatEqual(contents_spc, fs(contents_spc))
603 self.assertFormatEqual(contents_spc, fs(contents_tab))
605 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
606 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
607 self.assertFormatEqual(contents_spc, fs(contents_spc))
608 self.assertFormatEqual(contents_spc, fs(contents_tab))
610 # mixed tabs and spaces (valid Python 2 code)
611 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t# comment\n pass\n"
612 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
613 self.assertFormatEqual(contents_spc, fs(contents_spc))
614 self.assertFormatEqual(contents_spc, fs(contents_tab))
616 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t\t# comment\n pass\n"
617 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
618 self.assertFormatEqual(contents_spc, fs(contents_spc))
619 self.assertFormatEqual(contents_spc, fs(contents_tab))
621 def test_report_verbose(self) -> None:
622 report = black.Report(verbose=True)
626 def out(msg: str, **kwargs: Any) -> None:
627 out_lines.append(msg)
629 def err(msg: str, **kwargs: Any) -> None:
630 err_lines.append(msg)
632 with patch("black.out", out), patch("black.err", err):
633 report.done(Path("f1"), black.Changed.NO)
634 self.assertEqual(len(out_lines), 1)
635 self.assertEqual(len(err_lines), 0)
636 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
637 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
638 self.assertEqual(report.return_code, 0)
639 report.done(Path("f2"), black.Changed.YES)
640 self.assertEqual(len(out_lines), 2)
641 self.assertEqual(len(err_lines), 0)
642 self.assertEqual(out_lines[-1], "reformatted f2")
644 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
646 report.done(Path("f3"), black.Changed.CACHED)
647 self.assertEqual(len(out_lines), 3)
648 self.assertEqual(len(err_lines), 0)
650 out_lines[-1], "f3 wasn't modified on disk since last run."
653 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
655 self.assertEqual(report.return_code, 0)
657 self.assertEqual(report.return_code, 1)
659 report.failed(Path("e1"), "boom")
660 self.assertEqual(len(out_lines), 3)
661 self.assertEqual(len(err_lines), 1)
662 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
664 unstyle(str(report)),
665 "1 file reformatted, 2 files left unchanged, "
666 "1 file failed to reformat.",
668 self.assertEqual(report.return_code, 123)
669 report.done(Path("f3"), black.Changed.YES)
670 self.assertEqual(len(out_lines), 4)
671 self.assertEqual(len(err_lines), 1)
672 self.assertEqual(out_lines[-1], "reformatted f3")
674 unstyle(str(report)),
675 "2 files reformatted, 2 files left unchanged, "
676 "1 file failed to reformat.",
678 self.assertEqual(report.return_code, 123)
679 report.failed(Path("e2"), "boom")
680 self.assertEqual(len(out_lines), 4)
681 self.assertEqual(len(err_lines), 2)
682 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
684 unstyle(str(report)),
685 "2 files reformatted, 2 files left unchanged, "
686 "2 files failed to reformat.",
688 self.assertEqual(report.return_code, 123)
689 report.path_ignored(Path("wat"), "no match")
690 self.assertEqual(len(out_lines), 5)
691 self.assertEqual(len(err_lines), 2)
692 self.assertEqual(out_lines[-1], "wat ignored: no match")
694 unstyle(str(report)),
695 "2 files reformatted, 2 files left unchanged, "
696 "2 files failed to reformat.",
698 self.assertEqual(report.return_code, 123)
699 report.done(Path("f4"), black.Changed.NO)
700 self.assertEqual(len(out_lines), 6)
701 self.assertEqual(len(err_lines), 2)
702 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
704 unstyle(str(report)),
705 "2 files reformatted, 3 files left unchanged, "
706 "2 files failed to reformat.",
708 self.assertEqual(report.return_code, 123)
711 unstyle(str(report)),
712 "2 files would be reformatted, 3 files would be left unchanged, "
713 "2 files would fail to reformat.",
716 def test_report_quiet(self) -> None:
717 report = black.Report(quiet=True)
721 def out(msg: str, **kwargs: Any) -> None:
722 out_lines.append(msg)
724 def err(msg: str, **kwargs: Any) -> None:
725 err_lines.append(msg)
727 with patch("black.out", out), patch("black.err", err):
728 report.done(Path("f1"), black.Changed.NO)
729 self.assertEqual(len(out_lines), 0)
730 self.assertEqual(len(err_lines), 0)
731 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
732 self.assertEqual(report.return_code, 0)
733 report.done(Path("f2"), black.Changed.YES)
734 self.assertEqual(len(out_lines), 0)
735 self.assertEqual(len(err_lines), 0)
737 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
739 report.done(Path("f3"), black.Changed.CACHED)
740 self.assertEqual(len(out_lines), 0)
741 self.assertEqual(len(err_lines), 0)
743 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
745 self.assertEqual(report.return_code, 0)
747 self.assertEqual(report.return_code, 1)
749 report.failed(Path("e1"), "boom")
750 self.assertEqual(len(out_lines), 0)
751 self.assertEqual(len(err_lines), 1)
752 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
754 unstyle(str(report)),
755 "1 file reformatted, 2 files left unchanged, "
756 "1 file failed to reformat.",
758 self.assertEqual(report.return_code, 123)
759 report.done(Path("f3"), black.Changed.YES)
760 self.assertEqual(len(out_lines), 0)
761 self.assertEqual(len(err_lines), 1)
763 unstyle(str(report)),
764 "2 files reformatted, 2 files left unchanged, "
765 "1 file failed to reformat.",
767 self.assertEqual(report.return_code, 123)
768 report.failed(Path("e2"), "boom")
769 self.assertEqual(len(out_lines), 0)
770 self.assertEqual(len(err_lines), 2)
771 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
773 unstyle(str(report)),
774 "2 files reformatted, 2 files left unchanged, "
775 "2 files failed to reformat.",
777 self.assertEqual(report.return_code, 123)
778 report.path_ignored(Path("wat"), "no match")
779 self.assertEqual(len(out_lines), 0)
780 self.assertEqual(len(err_lines), 2)
782 unstyle(str(report)),
783 "2 files reformatted, 2 files left unchanged, "
784 "2 files failed to reformat.",
786 self.assertEqual(report.return_code, 123)
787 report.done(Path("f4"), black.Changed.NO)
788 self.assertEqual(len(out_lines), 0)
789 self.assertEqual(len(err_lines), 2)
791 unstyle(str(report)),
792 "2 files reformatted, 3 files left unchanged, "
793 "2 files failed to reformat.",
795 self.assertEqual(report.return_code, 123)
798 unstyle(str(report)),
799 "2 files would be reformatted, 3 files would be left unchanged, "
800 "2 files would fail to reformat.",
803 def test_report_normal(self) -> None:
804 report = black.Report()
808 def out(msg: str, **kwargs: Any) -> None:
809 out_lines.append(msg)
811 def err(msg: str, **kwargs: Any) -> None:
812 err_lines.append(msg)
814 with patch("black.out", out), patch("black.err", err):
815 report.done(Path("f1"), black.Changed.NO)
816 self.assertEqual(len(out_lines), 0)
817 self.assertEqual(len(err_lines), 0)
818 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
819 self.assertEqual(report.return_code, 0)
820 report.done(Path("f2"), black.Changed.YES)
821 self.assertEqual(len(out_lines), 1)
822 self.assertEqual(len(err_lines), 0)
823 self.assertEqual(out_lines[-1], "reformatted f2")
825 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
827 report.done(Path("f3"), black.Changed.CACHED)
828 self.assertEqual(len(out_lines), 1)
829 self.assertEqual(len(err_lines), 0)
830 self.assertEqual(out_lines[-1], "reformatted f2")
832 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
834 self.assertEqual(report.return_code, 0)
836 self.assertEqual(report.return_code, 1)
838 report.failed(Path("e1"), "boom")
839 self.assertEqual(len(out_lines), 1)
840 self.assertEqual(len(err_lines), 1)
841 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
843 unstyle(str(report)),
844 "1 file reformatted, 2 files left unchanged, "
845 "1 file failed to reformat.",
847 self.assertEqual(report.return_code, 123)
848 report.done(Path("f3"), black.Changed.YES)
849 self.assertEqual(len(out_lines), 2)
850 self.assertEqual(len(err_lines), 1)
851 self.assertEqual(out_lines[-1], "reformatted f3")
853 unstyle(str(report)),
854 "2 files reformatted, 2 files left unchanged, "
855 "1 file failed to reformat.",
857 self.assertEqual(report.return_code, 123)
858 report.failed(Path("e2"), "boom")
859 self.assertEqual(len(out_lines), 2)
860 self.assertEqual(len(err_lines), 2)
861 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
863 unstyle(str(report)),
864 "2 files reformatted, 2 files left unchanged, "
865 "2 files failed to reformat.",
867 self.assertEqual(report.return_code, 123)
868 report.path_ignored(Path("wat"), "no match")
869 self.assertEqual(len(out_lines), 2)
870 self.assertEqual(len(err_lines), 2)
872 unstyle(str(report)),
873 "2 files reformatted, 2 files left unchanged, "
874 "2 files failed to reformat.",
876 self.assertEqual(report.return_code, 123)
877 report.done(Path("f4"), black.Changed.NO)
878 self.assertEqual(len(out_lines), 2)
879 self.assertEqual(len(err_lines), 2)
881 unstyle(str(report)),
882 "2 files reformatted, 3 files left unchanged, "
883 "2 files failed to reformat.",
885 self.assertEqual(report.return_code, 123)
888 unstyle(str(report)),
889 "2 files would be reformatted, 3 files would be left unchanged, "
890 "2 files would fail to reformat.",
893 def test_lib2to3_parse(self) -> None:
894 with self.assertRaises(black.InvalidInput):
895 black.lib2to3_parse("invalid syntax")
898 black.lib2to3_parse(straddling)
899 black.lib2to3_parse(straddling, {TargetVersion.PY27})
900 black.lib2to3_parse(straddling, {TargetVersion.PY36})
901 black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
904 black.lib2to3_parse(py2_only)
905 black.lib2to3_parse(py2_only, {TargetVersion.PY27})
906 with self.assertRaises(black.InvalidInput):
907 black.lib2to3_parse(py2_only, {TargetVersion.PY36})
908 with self.assertRaises(black.InvalidInput):
909 black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
911 py3_only = "exec(x, end=y)"
912 black.lib2to3_parse(py3_only)
913 with self.assertRaises(black.InvalidInput):
914 black.lib2to3_parse(py3_only, {TargetVersion.PY27})
915 black.lib2to3_parse(py3_only, {TargetVersion.PY36})
916 black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
918 def test_get_features_used(self) -> None:
919 node = black.lib2to3_parse("def f(*, arg): ...\n")
920 self.assertEqual(black.get_features_used(node), set())
921 node = black.lib2to3_parse("def f(*, arg,): ...\n")
922 self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
923 node = black.lib2to3_parse("f(*arg,)\n")
925 black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
927 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
928 self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
929 node = black.lib2to3_parse("123_456\n")
930 self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
931 node = black.lib2to3_parse("123456\n")
932 self.assertEqual(black.get_features_used(node), set())
933 source, expected = read_data("function")
934 node = black.lib2to3_parse(source)
935 expected_features = {
936 Feature.TRAILING_COMMA_IN_CALL,
937 Feature.TRAILING_COMMA_IN_DEF,
940 self.assertEqual(black.get_features_used(node), expected_features)
941 node = black.lib2to3_parse(expected)
942 self.assertEqual(black.get_features_used(node), expected_features)
943 source, expected = read_data("expression")
944 node = black.lib2to3_parse(source)
945 self.assertEqual(black.get_features_used(node), set())
946 node = black.lib2to3_parse(expected)
947 self.assertEqual(black.get_features_used(node), set())
949 def test_get_future_imports(self) -> None:
950 node = black.lib2to3_parse("\n")
951 self.assertEqual(set(), black.get_future_imports(node))
952 node = black.lib2to3_parse("from __future__ import black\n")
953 self.assertEqual({"black"}, black.get_future_imports(node))
954 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
955 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
956 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
957 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
958 node = black.lib2to3_parse(
959 "from __future__ import multiple\nfrom __future__ import imports\n"
961 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
962 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
963 self.assertEqual({"black"}, black.get_future_imports(node))
964 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
965 self.assertEqual({"black"}, black.get_future_imports(node))
966 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
967 self.assertEqual(set(), black.get_future_imports(node))
968 node = black.lib2to3_parse("from some.module import black\n")
969 self.assertEqual(set(), black.get_future_imports(node))
970 node = black.lib2to3_parse(
971 "from __future__ import unicode_literals as _unicode_literals"
973 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
974 node = black.lib2to3_parse(
975 "from __future__ import unicode_literals as _lol, print"
977 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
979 def test_debug_visitor(self) -> None:
980 source, _ = read_data("debug_visitor.py")
981 expected, _ = read_data("debug_visitor.out")
985 def out(msg: str, **kwargs: Any) -> None:
986 out_lines.append(msg)
988 def err(msg: str, **kwargs: Any) -> None:
989 err_lines.append(msg)
991 with patch("black.out", out), patch("black.err", err):
992 black.DebugVisitor.show(source)
993 actual = "\n".join(out_lines) + "\n"
995 if expected != actual:
996 log_name = black.dump_to_file(*out_lines)
1000 f"AST print out is different. Actual version dumped to {log_name}",
1003 def test_format_file_contents(self) -> None:
1005 mode = black.FileMode()
1006 with self.assertRaises(black.NothingChanged):
1007 black.format_file_contents(empty, mode=mode, fast=False)
1009 with self.assertRaises(black.NothingChanged):
1010 black.format_file_contents(just_nl, mode=mode, fast=False)
1011 same = "l = [1, 2, 3]\n"
1012 with self.assertRaises(black.NothingChanged):
1013 black.format_file_contents(same, mode=mode, fast=False)
1014 different = "l = [1,2,3]"
1016 actual = black.format_file_contents(different, mode=mode, fast=False)
1017 self.assertEqual(expected, actual)
1018 invalid = "return if you can"
1019 with self.assertRaises(black.InvalidInput) as e:
1020 black.format_file_contents(invalid, mode=mode, fast=False)
1021 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
1023 def test_endmarker(self) -> None:
1024 n = black.lib2to3_parse("\n")
1025 self.assertEqual(n.type, black.syms.file_input)
1026 self.assertEqual(len(n.children), 1)
1027 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
1029 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
1030 def test_assertFormatEqual(self) -> None:
1034 def out(msg: str, **kwargs: Any) -> None:
1035 out_lines.append(msg)
1037 def err(msg: str, **kwargs: Any) -> None:
1038 err_lines.append(msg)
1040 with patch("black.out", out), patch("black.err", err):
1041 with self.assertRaises(AssertionError):
1042 self.assertFormatEqual("l = [1, 2, 3]", "l = [1, 2, 3,]")
1044 out_str = "".join(out_lines)
1045 self.assertTrue("Expected tree:" in out_str)
1046 self.assertTrue("Actual tree:" in out_str)
1047 self.assertEqual("".join(err_lines), "")
1049 def test_cache_broken_file(self) -> None:
1050 mode = black.FileMode()
1051 with cache_dir() as workspace:
1052 cache_file = black.get_cache_file(mode)
1053 with cache_file.open("w") as fobj:
1054 fobj.write("this is not a pickle")
1055 self.assertEqual(black.read_cache(mode), {})
1056 src = (workspace / "test.py").resolve()
1057 with src.open("w") as fobj:
1058 fobj.write("print('hello')")
1059 self.invokeBlack([str(src)])
1060 cache = black.read_cache(mode)
1061 self.assertIn(src, cache)
1063 def test_cache_single_file_already_cached(self) -> None:
1064 mode = black.FileMode()
1065 with cache_dir() as workspace:
1066 src = (workspace / "test.py").resolve()
1067 with src.open("w") as fobj:
1068 fobj.write("print('hello')")
1069 black.write_cache({}, [src], mode)
1070 self.invokeBlack([str(src)])
1071 with src.open("r") as fobj:
1072 self.assertEqual(fobj.read(), "print('hello')")
1074 @event_loop(close=False)
1075 def test_cache_multiple_files(self) -> None:
1076 mode = black.FileMode()
1077 with cache_dir() as workspace, patch(
1078 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1080 one = (workspace / "one.py").resolve()
1081 with one.open("w") as fobj:
1082 fobj.write("print('hello')")
1083 two = (workspace / "two.py").resolve()
1084 with two.open("w") as fobj:
1085 fobj.write("print('hello')")
1086 black.write_cache({}, [one], mode)
1087 self.invokeBlack([str(workspace)])
1088 with one.open("r") as fobj:
1089 self.assertEqual(fobj.read(), "print('hello')")
1090 with two.open("r") as fobj:
1091 self.assertEqual(fobj.read(), 'print("hello")\n')
1092 cache = black.read_cache(mode)
1093 self.assertIn(one, cache)
1094 self.assertIn(two, cache)
1096 def test_no_cache_when_writeback_diff(self) -> None:
1097 mode = black.FileMode()
1098 with cache_dir() as workspace:
1099 src = (workspace / "test.py").resolve()
1100 with src.open("w") as fobj:
1101 fobj.write("print('hello')")
1102 self.invokeBlack([str(src), "--diff"])
1103 cache_file = black.get_cache_file(mode)
1104 self.assertFalse(cache_file.exists())
1106 def test_no_cache_when_stdin(self) -> None:
1107 mode = black.FileMode()
1109 result = CliRunner().invoke(
1110 black.main, ["-"], input=BytesIO(b"print('hello')")
1112 self.assertEqual(result.exit_code, 0)
1113 cache_file = black.get_cache_file(mode)
1114 self.assertFalse(cache_file.exists())
1116 def test_read_cache_no_cachefile(self) -> None:
1117 mode = black.FileMode()
1119 self.assertEqual(black.read_cache(mode), {})
1121 def test_write_cache_read_cache(self) -> None:
1122 mode = black.FileMode()
1123 with cache_dir() as workspace:
1124 src = (workspace / "test.py").resolve()
1126 black.write_cache({}, [src], mode)
1127 cache = black.read_cache(mode)
1128 self.assertIn(src, cache)
1129 self.assertEqual(cache[src], black.get_cache_info(src))
1131 def test_filter_cached(self) -> None:
1132 with TemporaryDirectory() as workspace:
1133 path = Path(workspace)
1134 uncached = (path / "uncached").resolve()
1135 cached = (path / "cached").resolve()
1136 cached_but_changed = (path / "changed").resolve()
1139 cached_but_changed.touch()
1140 cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
1141 todo, done = black.filter_cached(
1142 cache, {uncached, cached, cached_but_changed}
1144 self.assertEqual(todo, {uncached, cached_but_changed})
1145 self.assertEqual(done, {cached})
1147 def test_write_cache_creates_directory_if_needed(self) -> None:
1148 mode = black.FileMode()
1149 with cache_dir(exists=False) as workspace:
1150 self.assertFalse(workspace.exists())
1151 black.write_cache({}, [], mode)
1152 self.assertTrue(workspace.exists())
1154 @event_loop(close=False)
1155 def test_failed_formatting_does_not_get_cached(self) -> None:
1156 mode = black.FileMode()
1157 with cache_dir() as workspace, patch(
1158 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1160 failing = (workspace / "failing.py").resolve()
1161 with failing.open("w") as fobj:
1162 fobj.write("not actually python")
1163 clean = (workspace / "clean.py").resolve()
1164 with clean.open("w") as fobj:
1165 fobj.write('print("hello")\n')
1166 self.invokeBlack([str(workspace)], exit_code=123)
1167 cache = black.read_cache(mode)
1168 self.assertNotIn(failing, cache)
1169 self.assertIn(clean, cache)
1171 def test_write_cache_write_fail(self) -> None:
1172 mode = black.FileMode()
1173 with cache_dir(), patch.object(Path, "open") as mock:
1174 mock.side_effect = OSError
1175 black.write_cache({}, [], mode)
1177 @event_loop(close=False)
1178 def test_check_diff_use_together(self) -> None:
1180 # Files which will be reformatted.
1181 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1182 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1183 # Files which will not be reformatted.
1184 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1185 self.invokeBlack([str(src2), "--diff", "--check"])
1186 # Multi file command.
1187 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1189 def test_no_files(self) -> None:
1191 # Without an argument, black exits with error code 0.
1192 self.invokeBlack([])
1194 def test_broken_symlink(self) -> None:
1195 with cache_dir() as workspace:
1196 symlink = workspace / "broken_link.py"
1198 symlink.symlink_to("nonexistent.py")
1199 except OSError as e:
1200 self.skipTest(f"Can't create symlinks: {e}")
1201 self.invokeBlack([str(workspace.resolve())])
1203 def test_read_cache_line_lengths(self) -> None:
1204 mode = black.FileMode()
1205 short_mode = black.FileMode(line_length=1)
1206 with cache_dir() as workspace:
1207 path = (workspace / "file.py").resolve()
1209 black.write_cache({}, [path], mode)
1210 one = black.read_cache(mode)
1211 self.assertIn(path, one)
1212 two = black.read_cache(short_mode)
1213 self.assertNotIn(path, two)
1215 def test_single_file_force_pyi(self) -> None:
1216 reg_mode = black.FileMode()
1217 pyi_mode = black.FileMode(is_pyi=True)
1218 contents, expected = read_data("force_pyi")
1219 with cache_dir() as workspace:
1220 path = (workspace / "file.py").resolve()
1221 with open(path, "w") as fh:
1223 self.invokeBlack([str(path), "--pyi"])
1224 with open(path, "r") as fh:
1226 # verify cache with --pyi is separate
1227 pyi_cache = black.read_cache(pyi_mode)
1228 self.assertIn(path, pyi_cache)
1229 normal_cache = black.read_cache(reg_mode)
1230 self.assertNotIn(path, normal_cache)
1231 self.assertEqual(actual, expected)
1233 @event_loop(close=False)
1234 def test_multi_file_force_pyi(self) -> None:
1235 reg_mode = black.FileMode()
1236 pyi_mode = black.FileMode(is_pyi=True)
1237 contents, expected = read_data("force_pyi")
1238 with cache_dir() as workspace:
1240 (workspace / "file1.py").resolve(),
1241 (workspace / "file2.py").resolve(),
1244 with open(path, "w") as fh:
1246 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1248 with open(path, "r") as fh:
1250 self.assertEqual(actual, expected)
1251 # verify cache with --pyi is separate
1252 pyi_cache = black.read_cache(pyi_mode)
1253 normal_cache = black.read_cache(reg_mode)
1255 self.assertIn(path, pyi_cache)
1256 self.assertNotIn(path, normal_cache)
1258 def test_pipe_force_pyi(self) -> None:
1259 source, expected = read_data("force_pyi")
1260 result = CliRunner().invoke(
1261 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1263 self.assertEqual(result.exit_code, 0)
1264 actual = result.output
1265 self.assertFormatEqual(actual, expected)
1267 def test_single_file_force_py36(self) -> None:
1268 reg_mode = black.FileMode()
1269 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1270 source, expected = read_data("force_py36")
1271 with cache_dir() as workspace:
1272 path = (workspace / "file.py").resolve()
1273 with open(path, "w") as fh:
1275 self.invokeBlack([str(path), *PY36_ARGS])
1276 with open(path, "r") as fh:
1278 # verify cache with --target-version is separate
1279 py36_cache = black.read_cache(py36_mode)
1280 self.assertIn(path, py36_cache)
1281 normal_cache = black.read_cache(reg_mode)
1282 self.assertNotIn(path, normal_cache)
1283 self.assertEqual(actual, expected)
1285 @event_loop(close=False)
1286 def test_multi_file_force_py36(self) -> None:
1287 reg_mode = black.FileMode()
1288 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1289 source, expected = read_data("force_py36")
1290 with cache_dir() as workspace:
1292 (workspace / "file1.py").resolve(),
1293 (workspace / "file2.py").resolve(),
1296 with open(path, "w") as fh:
1298 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1300 with open(path, "r") as fh:
1302 self.assertEqual(actual, expected)
1303 # verify cache with --target-version is separate
1304 pyi_cache = black.read_cache(py36_mode)
1305 normal_cache = black.read_cache(reg_mode)
1307 self.assertIn(path, pyi_cache)
1308 self.assertNotIn(path, normal_cache)
1310 def test_pipe_force_py36(self) -> None:
1311 source, expected = read_data("force_py36")
1312 result = CliRunner().invoke(
1314 ["-", "-q", "--target-version=py36"],
1315 input=BytesIO(source.encode("utf8")),
1317 self.assertEqual(result.exit_code, 0)
1318 actual = result.output
1319 self.assertFormatEqual(actual, expected)
1321 def test_include_exclude(self) -> None:
1322 path = THIS_DIR / "data" / "include_exclude_tests"
1323 include = re.compile(r"\.pyi?$")
1324 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1325 report = black.Report()
1326 sources: List[Path] = []
1328 Path(path / "b/dont_exclude/a.py"),
1329 Path(path / "b/dont_exclude/a.pyi"),
1331 this_abs = THIS_DIR.resolve()
1333 black.gen_python_files_in_dir(path, this_abs, include, exclude, report)
1335 self.assertEqual(sorted(expected), sorted(sources))
1337 def test_empty_include(self) -> None:
1338 path = THIS_DIR / "data" / "include_exclude_tests"
1339 report = black.Report()
1340 empty = re.compile(r"")
1341 sources: List[Path] = []
1343 Path(path / "b/exclude/a.pie"),
1344 Path(path / "b/exclude/a.py"),
1345 Path(path / "b/exclude/a.pyi"),
1346 Path(path / "b/dont_exclude/a.pie"),
1347 Path(path / "b/dont_exclude/a.py"),
1348 Path(path / "b/dont_exclude/a.pyi"),
1349 Path(path / "b/.definitely_exclude/a.pie"),
1350 Path(path / "b/.definitely_exclude/a.py"),
1351 Path(path / "b/.definitely_exclude/a.pyi"),
1353 this_abs = THIS_DIR.resolve()
1355 black.gen_python_files_in_dir(
1356 path, this_abs, empty, re.compile(black.DEFAULT_EXCLUDES), report
1359 self.assertEqual(sorted(expected), sorted(sources))
1361 def test_empty_exclude(self) -> None:
1362 path = THIS_DIR / "data" / "include_exclude_tests"
1363 report = black.Report()
1364 empty = re.compile(r"")
1365 sources: List[Path] = []
1367 Path(path / "b/dont_exclude/a.py"),
1368 Path(path / "b/dont_exclude/a.pyi"),
1369 Path(path / "b/exclude/a.py"),
1370 Path(path / "b/exclude/a.pyi"),
1371 Path(path / "b/.definitely_exclude/a.py"),
1372 Path(path / "b/.definitely_exclude/a.pyi"),
1374 this_abs = THIS_DIR.resolve()
1376 black.gen_python_files_in_dir(
1377 path, this_abs, re.compile(black.DEFAULT_INCLUDES), empty, report
1380 self.assertEqual(sorted(expected), sorted(sources))
1382 def test_invalid_include_exclude(self) -> None:
1383 for option in ["--include", "--exclude"]:
1384 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1386 def test_preserves_line_endings(self) -> None:
1387 with TemporaryDirectory() as workspace:
1388 test_file = Path(workspace) / "test.py"
1389 for nl in ["\n", "\r\n"]:
1390 contents = nl.join(["def f( ):", " pass"])
1391 test_file.write_bytes(contents.encode())
1392 ff(test_file, write_back=black.WriteBack.YES)
1393 updated_contents: bytes = test_file.read_bytes()
1394 self.assertIn(nl.encode(), updated_contents)
1396 self.assertNotIn(b"\r\n", updated_contents)
1398 def test_preserves_line_endings_via_stdin(self) -> None:
1399 for nl in ["\n", "\r\n"]:
1400 contents = nl.join(["def f( ):", " pass"])
1401 runner = BlackRunner()
1402 result = runner.invoke(
1403 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1405 self.assertEqual(result.exit_code, 0)
1406 output = runner.stdout_bytes
1407 self.assertIn(nl.encode("utf8"), output)
1409 self.assertNotIn(b"\r\n", output)
1411 def test_assert_equivalent_different_asts(self) -> None:
1412 with self.assertRaises(AssertionError):
1413 black.assert_equivalent("{}", "None")
1415 def test_symlink_out_of_root_directory(self) -> None:
1419 include = re.compile(black.DEFAULT_INCLUDES)
1420 exclude = re.compile(black.DEFAULT_EXCLUDES)
1421 report = black.Report()
1422 # `child` should behave like a symlink which resolved path is clearly
1423 # outside of the `root` directory.
1424 path.iterdir.return_value = [child]
1425 child.resolve.return_value = Path("/a/b/c")
1426 child.is_symlink.return_value = True
1428 list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1429 except ValueError as ve:
1430 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1431 path.iterdir.assert_called_once()
1432 child.resolve.assert_called_once()
1433 child.is_symlink.assert_called_once()
1434 # `child` should behave like a strange file which resolved path is clearly
1435 # outside of the `root` directory.
1436 child.is_symlink.return_value = False
1437 with self.assertRaises(ValueError):
1438 list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1439 path.iterdir.assert_called()
1440 self.assertEqual(path.iterdir.call_count, 2)
1441 child.resolve.assert_called()
1442 self.assertEqual(child.resolve.call_count, 2)
1443 child.is_symlink.assert_called()
1444 self.assertEqual(child.is_symlink.call_count, 2)
1446 def test_shhh_click(self) -> None:
1448 from click import _unicodefun # type: ignore
1449 except ModuleNotFoundError:
1450 self.skipTest("Incompatible Click version")
1451 if not hasattr(_unicodefun, "_verify_python3_env"):
1452 self.skipTest("Incompatible Click version")
1453 # First, let's see if Click is crashing with a preferred ASCII charset.
1454 with patch("locale.getpreferredencoding") as gpe:
1455 gpe.return_value = "ASCII"
1456 with self.assertRaises(RuntimeError):
1457 _unicodefun._verify_python3_env()
1458 # Now, let's silence Click...
1460 # ...and confirm it's silent.
1461 with patch("locale.getpreferredencoding") as gpe:
1462 gpe.return_value = "ASCII"
1464 _unicodefun._verify_python3_env()
1465 except RuntimeError as re:
1466 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1468 def test_root_logger_not_used_directly(self) -> None:
1469 def fail(*args: Any, **kwargs: Any) -> None:
1470 self.fail("Record created with root logger")
1472 with patch.multiple(
1483 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1485 async def test_blackd_request_needs_formatting(self) -> None:
1486 app = blackd.make_app()
1487 async with TestClient(TestServer(app)) as client:
1488 response = await client.post("/", data=b"print('hello world')")
1489 self.assertEqual(response.status, 200)
1490 self.assertEqual(response.charset, "utf8")
1491 self.assertEqual(await response.read(), b'print("hello world")\n')
1493 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1495 async def test_blackd_request_no_change(self) -> None:
1496 app = blackd.make_app()
1497 async with TestClient(TestServer(app)) as client:
1498 response = await client.post("/", data=b'print("hello world")\n')
1499 self.assertEqual(response.status, 204)
1500 self.assertEqual(await response.read(), b"")
1502 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1504 async def test_blackd_request_syntax_error(self) -> None:
1505 app = blackd.make_app()
1506 async with TestClient(TestServer(app)) as client:
1507 response = await client.post("/", data=b"what even ( is")
1508 self.assertEqual(response.status, 400)
1509 content = await response.text()
1511 content.startswith("Cannot parse"),
1512 msg=f"Expected error to start with 'Cannot parse', got {repr(content)}",
1515 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1517 async def test_blackd_unsupported_version(self) -> None:
1518 app = blackd.make_app()
1519 async with TestClient(TestServer(app)) as client:
1520 response = await client.post(
1521 "/", data=b"what", headers={blackd.VERSION_HEADER: "2"}
1523 self.assertEqual(response.status, 501)
1525 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1527 async def test_blackd_supported_version(self) -> None:
1528 app = blackd.make_app()
1529 async with TestClient(TestServer(app)) as client:
1530 response = await client.post(
1531 "/", data=b"what", headers={blackd.VERSION_HEADER: "1"}
1533 self.assertEqual(response.status, 200)
1535 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1537 async def test_blackd_invalid_python_variant(self) -> None:
1538 app = blackd.make_app()
1539 async with TestClient(TestServer(app)) as client:
1541 async def check(header_value: str, expected_status: int = 400) -> None:
1542 response = await client.post(
1545 headers={blackd.PYTHON_VARIANT_HEADER: header_value},
1547 self.assertEqual(response.status, expected_status)
1550 await check("ruby3.5")
1551 await check("pyi3.6")
1552 await check("py1.5")
1554 await check("py2.8")
1556 await check("pypy3.0")
1557 await check("jython3.4")
1559 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1561 async def test_blackd_pyi(self) -> None:
1562 app = blackd.make_app()
1563 async with TestClient(TestServer(app)) as client:
1564 source, expected = read_data("stub.pyi")
1565 response = await client.post(
1566 "/", data=source, headers={blackd.PYTHON_VARIANT_HEADER: "pyi"}
1568 self.assertEqual(response.status, 200)
1569 self.assertEqual(await response.text(), expected)
1571 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1573 async def test_blackd_python_variant(self) -> None:
1574 app = blackd.make_app()
1577 " and_has_a_bunch_of,\n"
1578 " very_long_arguments_too,\n"
1579 " and_lots_of_them_as_well_lol,\n"
1580 " **and_very_long_keyword_arguments\n"
1584 async with TestClient(TestServer(app)) as client:
1586 async def check(header_value: str, expected_status: int) -> None:
1587 response = await client.post(
1588 "/", data=code, headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1590 self.assertEqual(response.status, expected_status)
1592 await check("3.6", 200)
1593 await check("py3.6", 200)
1594 await check("3.6,3.7", 200)
1595 await check("3.6,py3.7", 200)
1597 await check("2", 204)
1598 await check("2.7", 204)
1599 await check("py2.7", 204)
1600 await check("3.4", 204)
1601 await check("py3.4", 204)
1603 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1605 async def test_blackd_line_length(self) -> None:
1606 app = blackd.make_app()
1607 async with TestClient(TestServer(app)) as client:
1608 response = await client.post(
1609 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "7"}
1611 self.assertEqual(response.status, 200)
1613 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1615 async def test_blackd_invalid_line_length(self) -> None:
1616 app = blackd.make_app()
1617 async with TestClient(TestServer(app)) as client:
1618 response = await client.post(
1620 data=b'print("hello")\n',
1621 headers={blackd.LINE_LENGTH_HEADER: "NaN"},
1623 self.assertEqual(response.status, 400)
1625 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1626 def test_blackd_main(self) -> None:
1627 with patch("blackd.web.run_app"):
1628 result = CliRunner().invoke(blackd.main, [])
1629 if result.exception is not None:
1630 raise result.exception
1631 self.assertEqual(result.exit_code, 0)
1634 if __name__ == "__main__":
1635 unittest.main(module="test_black")