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, redirect_stderr
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
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_expression(self) -> None:
269 source, expected = read_data("expression")
271 self.assertFormatEqual(expected, actual)
272 black.assert_equivalent(source, actual)
273 black.assert_stable(source, actual, black.FileMode())
275 def test_expression_ff(self) -> None:
276 source, expected = read_data("expression")
277 tmp_file = Path(black.dump_to_file(source))
279 self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
280 with open(tmp_file, encoding="utf8") as f:
284 self.assertFormatEqual(expected, actual)
285 with patch("black.dump_to_file", dump_to_stderr):
286 black.assert_equivalent(source, actual)
287 black.assert_stable(source, actual, black.FileMode())
289 def test_expression_diff(self) -> None:
290 source, _ = read_data("expression.py")
291 expected, _ = read_data("expression.diff")
292 tmp_file = Path(black.dump_to_file(source))
293 diff_header = re.compile(
294 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
295 rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
298 result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
299 self.assertEqual(result.exit_code, 0)
302 actual = result.output
303 actual = diff_header.sub("[Deterministic header]", actual)
304 actual = actual.rstrip() + "\n" # the diff output has a trailing space
305 if expected != actual:
306 dump = black.dump_to_file(actual)
308 f"Expected diff isn't equal to the actual. If you made changes "
309 f"to expression.py and this is an anticipated difference, "
310 f"overwrite tests/data/expression.diff with {dump}"
312 self.assertEqual(expected, actual, msg)
314 @patch("black.dump_to_file", dump_to_stderr)
315 def test_fstring(self) -> None:
316 source, expected = read_data("fstring")
318 self.assertFormatEqual(expected, actual)
319 black.assert_equivalent(source, actual)
320 black.assert_stable(source, actual, black.FileMode())
322 @patch("black.dump_to_file", dump_to_stderr)
323 def test_string_quotes(self) -> None:
324 source, expected = read_data("string_quotes")
326 self.assertFormatEqual(expected, actual)
327 black.assert_equivalent(source, actual)
328 black.assert_stable(source, actual, black.FileMode())
329 mode = black.FileMode(string_normalization=False)
330 not_normalized = fs(source, mode=mode)
331 self.assertFormatEqual(source, not_normalized)
332 black.assert_equivalent(source, not_normalized)
333 black.assert_stable(source, not_normalized, mode=mode)
335 @patch("black.dump_to_file", dump_to_stderr)
336 def test_slices(self) -> None:
337 source, expected = read_data("slices")
339 self.assertFormatEqual(expected, actual)
340 black.assert_equivalent(source, actual)
341 black.assert_stable(source, actual, black.FileMode())
343 @patch("black.dump_to_file", dump_to_stderr)
344 def test_comments(self) -> None:
345 source, expected = read_data("comments")
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_comments2(self) -> None:
353 source, expected = read_data("comments2")
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_comments3(self) -> None:
361 source, expected = read_data("comments3")
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_comments4(self) -> None:
369 source, expected = read_data("comments4")
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_comments5(self) -> None:
377 source, expected = read_data("comments5")
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_comments6(self) -> None:
385 source, expected = read_data("comments6")
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_cantfit(self) -> None:
393 source, expected = read_data("cantfit")
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_import_spacing(self) -> None:
401 source, expected = read_data("import_spacing")
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_composition(self) -> None:
409 source, expected = read_data("composition")
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_empty_lines(self) -> None:
417 source, expected = read_data("empty_lines")
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_string_prefixes(self) -> None:
425 source, expected = read_data("string_prefixes")
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_numeric_literals(self) -> None:
433 source, expected = read_data("numeric_literals")
434 mode = black.FileMode(target_versions=black.PY36_VERSIONS)
435 actual = fs(source, mode=mode)
436 self.assertFormatEqual(expected, actual)
437 black.assert_equivalent(source, actual)
438 black.assert_stable(source, actual, mode)
440 @patch("black.dump_to_file", dump_to_stderr)
441 def test_numeric_literals_ignoring_underscores(self) -> None:
442 source, expected = read_data("numeric_literals_skip_underscores")
443 mode = black.FileMode(target_versions=black.PY36_VERSIONS)
444 actual = fs(source, mode=mode)
445 self.assertFormatEqual(expected, actual)
446 black.assert_equivalent(source, actual)
447 black.assert_stable(source, actual, mode)
449 @patch("black.dump_to_file", dump_to_stderr)
450 def test_numeric_literals_py2(self) -> None:
451 source, expected = read_data("numeric_literals_py2")
453 self.assertFormatEqual(expected, actual)
454 black.assert_stable(source, actual, black.FileMode())
456 @patch("black.dump_to_file", dump_to_stderr)
457 def test_python2(self) -> None:
458 source, expected = read_data("python2")
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_python2_unicode_literals(self) -> None:
466 source, expected = read_data("python2_unicode_literals")
468 self.assertFormatEqual(expected, actual)
469 black.assert_stable(source, actual, black.FileMode())
471 @patch("black.dump_to_file", dump_to_stderr)
472 def test_stub(self) -> None:
473 mode = black.FileMode(is_pyi=True)
474 source, expected = read_data("stub.pyi")
475 actual = fs(source, mode=mode)
476 self.assertFormatEqual(expected, actual)
477 black.assert_stable(source, actual, mode)
479 @patch("black.dump_to_file", dump_to_stderr)
480 def test_python37(self) -> None:
481 source, expected = read_data("python37")
483 self.assertFormatEqual(expected, actual)
484 major, minor = sys.version_info[:2]
485 if major > 3 or (major == 3 and minor >= 7):
486 black.assert_equivalent(source, actual)
487 black.assert_stable(source, actual, black.FileMode())
489 @patch("black.dump_to_file", dump_to_stderr)
490 def test_fmtonoff(self) -> None:
491 source, expected = read_data("fmtonoff")
493 self.assertFormatEqual(expected, actual)
494 black.assert_equivalent(source, actual)
495 black.assert_stable(source, actual, black.FileMode())
497 @patch("black.dump_to_file", dump_to_stderr)
498 def test_fmtonoff2(self) -> None:
499 source, expected = read_data("fmtonoff2")
501 self.assertFormatEqual(expected, actual)
502 black.assert_equivalent(source, actual)
503 black.assert_stable(source, actual, black.FileMode())
505 @patch("black.dump_to_file", dump_to_stderr)
506 def test_remove_empty_parentheses_after_class(self) -> None:
507 source, expected = read_data("class_blank_parentheses")
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_new_line_between_class_and_code(self) -> None:
515 source, expected = read_data("class_methods_new_line")
517 self.assertFormatEqual(expected, actual)
518 black.assert_equivalent(source, actual)
519 black.assert_stable(source, actual, black.FileMode())
521 @patch("black.dump_to_file", dump_to_stderr)
522 def test_bracket_match(self) -> None:
523 source, expected = read_data("bracketmatch")
525 self.assertFormatEqual(expected, actual)
526 black.assert_equivalent(source, actual)
527 black.assert_stable(source, actual, black.FileMode())
529 def test_tab_comment_indentation(self) -> None:
530 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
531 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
532 self.assertFormatEqual(contents_spc, fs(contents_spc))
533 self.assertFormatEqual(contents_spc, fs(contents_tab))
535 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
536 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
537 self.assertFormatEqual(contents_spc, fs(contents_spc))
538 self.assertFormatEqual(contents_spc, fs(contents_tab))
540 # mixed tabs and spaces (valid Python 2 code)
541 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t# comment\n pass\n"
542 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
543 self.assertFormatEqual(contents_spc, fs(contents_spc))
544 self.assertFormatEqual(contents_spc, fs(contents_tab))
546 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t\t# comment\n pass\n"
547 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
548 self.assertFormatEqual(contents_spc, fs(contents_spc))
549 self.assertFormatEqual(contents_spc, fs(contents_tab))
551 def test_report_verbose(self) -> None:
552 report = black.Report(verbose=True)
556 def out(msg: str, **kwargs: Any) -> None:
557 out_lines.append(msg)
559 def err(msg: str, **kwargs: Any) -> None:
560 err_lines.append(msg)
562 with patch("black.out", out), patch("black.err", err):
563 report.done(Path("f1"), black.Changed.NO)
564 self.assertEqual(len(out_lines), 1)
565 self.assertEqual(len(err_lines), 0)
566 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
567 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
568 self.assertEqual(report.return_code, 0)
569 report.done(Path("f2"), black.Changed.YES)
570 self.assertEqual(len(out_lines), 2)
571 self.assertEqual(len(err_lines), 0)
572 self.assertEqual(out_lines[-1], "reformatted f2")
574 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
576 report.done(Path("f3"), black.Changed.CACHED)
577 self.assertEqual(len(out_lines), 3)
578 self.assertEqual(len(err_lines), 0)
580 out_lines[-1], "f3 wasn't modified on disk since last run."
583 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
585 self.assertEqual(report.return_code, 0)
587 self.assertEqual(report.return_code, 1)
589 report.failed(Path("e1"), "boom")
590 self.assertEqual(len(out_lines), 3)
591 self.assertEqual(len(err_lines), 1)
592 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
594 unstyle(str(report)),
595 "1 file reformatted, 2 files left unchanged, "
596 "1 file failed to reformat.",
598 self.assertEqual(report.return_code, 123)
599 report.done(Path("f3"), black.Changed.YES)
600 self.assertEqual(len(out_lines), 4)
601 self.assertEqual(len(err_lines), 1)
602 self.assertEqual(out_lines[-1], "reformatted f3")
604 unstyle(str(report)),
605 "2 files reformatted, 2 files left unchanged, "
606 "1 file failed to reformat.",
608 self.assertEqual(report.return_code, 123)
609 report.failed(Path("e2"), "boom")
610 self.assertEqual(len(out_lines), 4)
611 self.assertEqual(len(err_lines), 2)
612 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
614 unstyle(str(report)),
615 "2 files reformatted, 2 files left unchanged, "
616 "2 files failed to reformat.",
618 self.assertEqual(report.return_code, 123)
619 report.path_ignored(Path("wat"), "no match")
620 self.assertEqual(len(out_lines), 5)
621 self.assertEqual(len(err_lines), 2)
622 self.assertEqual(out_lines[-1], "wat ignored: no match")
624 unstyle(str(report)),
625 "2 files reformatted, 2 files left unchanged, "
626 "2 files failed to reformat.",
628 self.assertEqual(report.return_code, 123)
629 report.done(Path("f4"), black.Changed.NO)
630 self.assertEqual(len(out_lines), 6)
631 self.assertEqual(len(err_lines), 2)
632 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
634 unstyle(str(report)),
635 "2 files reformatted, 3 files left unchanged, "
636 "2 files failed to reformat.",
638 self.assertEqual(report.return_code, 123)
641 unstyle(str(report)),
642 "2 files would be reformatted, 3 files would be left unchanged, "
643 "2 files would fail to reformat.",
646 def test_report_quiet(self) -> None:
647 report = black.Report(quiet=True)
651 def out(msg: str, **kwargs: Any) -> None:
652 out_lines.append(msg)
654 def err(msg: str, **kwargs: Any) -> None:
655 err_lines.append(msg)
657 with patch("black.out", out), patch("black.err", err):
658 report.done(Path("f1"), black.Changed.NO)
659 self.assertEqual(len(out_lines), 0)
660 self.assertEqual(len(err_lines), 0)
661 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
662 self.assertEqual(report.return_code, 0)
663 report.done(Path("f2"), black.Changed.YES)
664 self.assertEqual(len(out_lines), 0)
665 self.assertEqual(len(err_lines), 0)
667 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
669 report.done(Path("f3"), black.Changed.CACHED)
670 self.assertEqual(len(out_lines), 0)
671 self.assertEqual(len(err_lines), 0)
673 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
675 self.assertEqual(report.return_code, 0)
677 self.assertEqual(report.return_code, 1)
679 report.failed(Path("e1"), "boom")
680 self.assertEqual(len(out_lines), 0)
681 self.assertEqual(len(err_lines), 1)
682 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
684 unstyle(str(report)),
685 "1 file reformatted, 2 files left unchanged, "
686 "1 file failed to reformat.",
688 self.assertEqual(report.return_code, 123)
689 report.done(Path("f3"), black.Changed.YES)
690 self.assertEqual(len(out_lines), 0)
691 self.assertEqual(len(err_lines), 1)
693 unstyle(str(report)),
694 "2 files reformatted, 2 files left unchanged, "
695 "1 file failed to reformat.",
697 self.assertEqual(report.return_code, 123)
698 report.failed(Path("e2"), "boom")
699 self.assertEqual(len(out_lines), 0)
700 self.assertEqual(len(err_lines), 2)
701 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
703 unstyle(str(report)),
704 "2 files reformatted, 2 files left unchanged, "
705 "2 files failed to reformat.",
707 self.assertEqual(report.return_code, 123)
708 report.path_ignored(Path("wat"), "no match")
709 self.assertEqual(len(out_lines), 0)
710 self.assertEqual(len(err_lines), 2)
712 unstyle(str(report)),
713 "2 files reformatted, 2 files left unchanged, "
714 "2 files failed to reformat.",
716 self.assertEqual(report.return_code, 123)
717 report.done(Path("f4"), black.Changed.NO)
718 self.assertEqual(len(out_lines), 0)
719 self.assertEqual(len(err_lines), 2)
721 unstyle(str(report)),
722 "2 files reformatted, 3 files left unchanged, "
723 "2 files failed to reformat.",
725 self.assertEqual(report.return_code, 123)
728 unstyle(str(report)),
729 "2 files would be reformatted, 3 files would be left unchanged, "
730 "2 files would fail to reformat.",
733 def test_report_normal(self) -> None:
734 report = black.Report()
738 def out(msg: str, **kwargs: Any) -> None:
739 out_lines.append(msg)
741 def err(msg: str, **kwargs: Any) -> None:
742 err_lines.append(msg)
744 with patch("black.out", out), patch("black.err", err):
745 report.done(Path("f1"), black.Changed.NO)
746 self.assertEqual(len(out_lines), 0)
747 self.assertEqual(len(err_lines), 0)
748 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
749 self.assertEqual(report.return_code, 0)
750 report.done(Path("f2"), black.Changed.YES)
751 self.assertEqual(len(out_lines), 1)
752 self.assertEqual(len(err_lines), 0)
753 self.assertEqual(out_lines[-1], "reformatted f2")
755 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
757 report.done(Path("f3"), black.Changed.CACHED)
758 self.assertEqual(len(out_lines), 1)
759 self.assertEqual(len(err_lines), 0)
760 self.assertEqual(out_lines[-1], "reformatted f2")
762 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
764 self.assertEqual(report.return_code, 0)
766 self.assertEqual(report.return_code, 1)
768 report.failed(Path("e1"), "boom")
769 self.assertEqual(len(out_lines), 1)
770 self.assertEqual(len(err_lines), 1)
771 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
773 unstyle(str(report)),
774 "1 file reformatted, 2 files left unchanged, "
775 "1 file failed to reformat.",
777 self.assertEqual(report.return_code, 123)
778 report.done(Path("f3"), black.Changed.YES)
779 self.assertEqual(len(out_lines), 2)
780 self.assertEqual(len(err_lines), 1)
781 self.assertEqual(out_lines[-1], "reformatted f3")
783 unstyle(str(report)),
784 "2 files reformatted, 2 files left unchanged, "
785 "1 file failed to reformat.",
787 self.assertEqual(report.return_code, 123)
788 report.failed(Path("e2"), "boom")
789 self.assertEqual(len(out_lines), 2)
790 self.assertEqual(len(err_lines), 2)
791 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
793 unstyle(str(report)),
794 "2 files reformatted, 2 files left unchanged, "
795 "2 files failed to reformat.",
797 self.assertEqual(report.return_code, 123)
798 report.path_ignored(Path("wat"), "no match")
799 self.assertEqual(len(out_lines), 2)
800 self.assertEqual(len(err_lines), 2)
802 unstyle(str(report)),
803 "2 files reformatted, 2 files left unchanged, "
804 "2 files failed to reformat.",
806 self.assertEqual(report.return_code, 123)
807 report.done(Path("f4"), black.Changed.NO)
808 self.assertEqual(len(out_lines), 2)
809 self.assertEqual(len(err_lines), 2)
811 unstyle(str(report)),
812 "2 files reformatted, 3 files left unchanged, "
813 "2 files failed to reformat.",
815 self.assertEqual(report.return_code, 123)
818 unstyle(str(report)),
819 "2 files would be reformatted, 3 files would be left unchanged, "
820 "2 files would fail to reformat.",
823 def test_get_features_used(self) -> None:
824 node = black.lib2to3_parse("def f(*, arg): ...\n")
825 self.assertEqual(black.get_features_used(node), set())
826 node = black.lib2to3_parse("def f(*, arg,): ...\n")
827 self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA})
828 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
829 self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
830 node = black.lib2to3_parse("123_456\n")
831 self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
832 node = black.lib2to3_parse("123456\n")
833 self.assertEqual(black.get_features_used(node), set())
834 source, expected = read_data("function")
835 node = black.lib2to3_parse(source)
837 black.get_features_used(node), {Feature.TRAILING_COMMA, Feature.F_STRINGS}
839 node = black.lib2to3_parse(expected)
841 black.get_features_used(node), {Feature.TRAILING_COMMA, Feature.F_STRINGS}
843 source, expected = read_data("expression")
844 node = black.lib2to3_parse(source)
845 self.assertEqual(black.get_features_used(node), set())
846 node = black.lib2to3_parse(expected)
847 self.assertEqual(black.get_features_used(node), set())
849 def test_get_future_imports(self) -> None:
850 node = black.lib2to3_parse("\n")
851 self.assertEqual(set(), black.get_future_imports(node))
852 node = black.lib2to3_parse("from __future__ import black\n")
853 self.assertEqual({"black"}, black.get_future_imports(node))
854 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
855 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
856 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
857 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
858 node = black.lib2to3_parse(
859 "from __future__ import multiple\nfrom __future__ import imports\n"
861 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
862 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
863 self.assertEqual({"black"}, black.get_future_imports(node))
864 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
865 self.assertEqual({"black"}, black.get_future_imports(node))
866 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
867 self.assertEqual(set(), black.get_future_imports(node))
868 node = black.lib2to3_parse("from some.module import black\n")
869 self.assertEqual(set(), black.get_future_imports(node))
870 node = black.lib2to3_parse(
871 "from __future__ import unicode_literals as _unicode_literals"
873 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
874 node = black.lib2to3_parse(
875 "from __future__ import unicode_literals as _lol, print"
877 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
879 def test_debug_visitor(self) -> None:
880 source, _ = read_data("debug_visitor.py")
881 expected, _ = read_data("debug_visitor.out")
885 def out(msg: str, **kwargs: Any) -> None:
886 out_lines.append(msg)
888 def err(msg: str, **kwargs: Any) -> None:
889 err_lines.append(msg)
891 with patch("black.out", out), patch("black.err", err):
892 black.DebugVisitor.show(source)
893 actual = "\n".join(out_lines) + "\n"
895 if expected != actual:
896 log_name = black.dump_to_file(*out_lines)
900 f"AST print out is different. Actual version dumped to {log_name}",
903 def test_format_file_contents(self) -> None:
905 mode = black.FileMode()
906 with self.assertRaises(black.NothingChanged):
907 black.format_file_contents(empty, mode=mode, fast=False)
909 with self.assertRaises(black.NothingChanged):
910 black.format_file_contents(just_nl, mode=mode, fast=False)
911 same = "l = [1, 2, 3]\n"
912 with self.assertRaises(black.NothingChanged):
913 black.format_file_contents(same, mode=mode, fast=False)
914 different = "l = [1,2,3]"
916 actual = black.format_file_contents(different, mode=mode, fast=False)
917 self.assertEqual(expected, actual)
918 invalid = "return if you can"
919 with self.assertRaises(black.InvalidInput) as e:
920 black.format_file_contents(invalid, mode=mode, fast=False)
921 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
923 def test_endmarker(self) -> None:
924 n = black.lib2to3_parse("\n")
925 self.assertEqual(n.type, black.syms.file_input)
926 self.assertEqual(len(n.children), 1)
927 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
929 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
930 def test_assertFormatEqual(self) -> None:
934 def out(msg: str, **kwargs: Any) -> None:
935 out_lines.append(msg)
937 def err(msg: str, **kwargs: Any) -> None:
938 err_lines.append(msg)
940 with patch("black.out", out), patch("black.err", err):
941 with self.assertRaises(AssertionError):
942 self.assertFormatEqual("l = [1, 2, 3]", "l = [1, 2, 3,]")
944 out_str = "".join(out_lines)
945 self.assertTrue("Expected tree:" in out_str)
946 self.assertTrue("Actual tree:" in out_str)
947 self.assertEqual("".join(err_lines), "")
949 def test_cache_broken_file(self) -> None:
950 mode = black.FileMode()
951 with cache_dir() as workspace:
952 cache_file = black.get_cache_file(mode)
953 with cache_file.open("w") as fobj:
954 fobj.write("this is not a pickle")
955 self.assertEqual(black.read_cache(mode), {})
956 src = (workspace / "test.py").resolve()
957 with src.open("w") as fobj:
958 fobj.write("print('hello')")
959 self.invokeBlack([str(src)])
960 cache = black.read_cache(mode)
961 self.assertIn(src, cache)
963 def test_cache_single_file_already_cached(self) -> None:
964 mode = black.FileMode()
965 with cache_dir() as workspace:
966 src = (workspace / "test.py").resolve()
967 with src.open("w") as fobj:
968 fobj.write("print('hello')")
969 black.write_cache({}, [src], mode)
970 self.invokeBlack([str(src)])
971 with src.open("r") as fobj:
972 self.assertEqual(fobj.read(), "print('hello')")
974 @event_loop(close=False)
975 def test_cache_multiple_files(self) -> None:
976 mode = black.FileMode()
977 with cache_dir() as workspace, patch(
978 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
980 one = (workspace / "one.py").resolve()
981 with one.open("w") as fobj:
982 fobj.write("print('hello')")
983 two = (workspace / "two.py").resolve()
984 with two.open("w") as fobj:
985 fobj.write("print('hello')")
986 black.write_cache({}, [one], mode)
987 self.invokeBlack([str(workspace)])
988 with one.open("r") as fobj:
989 self.assertEqual(fobj.read(), "print('hello')")
990 with two.open("r") as fobj:
991 self.assertEqual(fobj.read(), 'print("hello")\n')
992 cache = black.read_cache(mode)
993 self.assertIn(one, cache)
994 self.assertIn(two, cache)
996 def test_no_cache_when_writeback_diff(self) -> None:
997 mode = black.FileMode()
998 with cache_dir() as workspace:
999 src = (workspace / "test.py").resolve()
1000 with src.open("w") as fobj:
1001 fobj.write("print('hello')")
1002 self.invokeBlack([str(src), "--diff"])
1003 cache_file = black.get_cache_file(mode)
1004 self.assertFalse(cache_file.exists())
1006 def test_no_cache_when_stdin(self) -> None:
1007 mode = black.FileMode()
1009 result = CliRunner().invoke(
1010 black.main, ["-"], input=BytesIO(b"print('hello')")
1012 self.assertEqual(result.exit_code, 0)
1013 cache_file = black.get_cache_file(mode)
1014 self.assertFalse(cache_file.exists())
1016 def test_read_cache_no_cachefile(self) -> None:
1017 mode = black.FileMode()
1019 self.assertEqual(black.read_cache(mode), {})
1021 def test_write_cache_read_cache(self) -> None:
1022 mode = black.FileMode()
1023 with cache_dir() as workspace:
1024 src = (workspace / "test.py").resolve()
1026 black.write_cache({}, [src], mode)
1027 cache = black.read_cache(mode)
1028 self.assertIn(src, cache)
1029 self.assertEqual(cache[src], black.get_cache_info(src))
1031 def test_filter_cached(self) -> None:
1032 with TemporaryDirectory() as workspace:
1033 path = Path(workspace)
1034 uncached = (path / "uncached").resolve()
1035 cached = (path / "cached").resolve()
1036 cached_but_changed = (path / "changed").resolve()
1039 cached_but_changed.touch()
1040 cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
1041 todo, done = black.filter_cached(
1042 cache, {uncached, cached, cached_but_changed}
1044 self.assertEqual(todo, {uncached, cached_but_changed})
1045 self.assertEqual(done, {cached})
1047 def test_write_cache_creates_directory_if_needed(self) -> None:
1048 mode = black.FileMode()
1049 with cache_dir(exists=False) as workspace:
1050 self.assertFalse(workspace.exists())
1051 black.write_cache({}, [], mode)
1052 self.assertTrue(workspace.exists())
1054 @event_loop(close=False)
1055 def test_failed_formatting_does_not_get_cached(self) -> None:
1056 mode = black.FileMode()
1057 with cache_dir() as workspace, patch(
1058 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1060 failing = (workspace / "failing.py").resolve()
1061 with failing.open("w") as fobj:
1062 fobj.write("not actually python")
1063 clean = (workspace / "clean.py").resolve()
1064 with clean.open("w") as fobj:
1065 fobj.write('print("hello")\n')
1066 self.invokeBlack([str(workspace)], exit_code=123)
1067 cache = black.read_cache(mode)
1068 self.assertNotIn(failing, cache)
1069 self.assertIn(clean, cache)
1071 def test_write_cache_write_fail(self) -> None:
1072 mode = black.FileMode()
1073 with cache_dir(), patch.object(Path, "open") as mock:
1074 mock.side_effect = OSError
1075 black.write_cache({}, [], mode)
1077 @event_loop(close=False)
1078 def test_check_diff_use_together(self) -> None:
1080 # Files which will be reformatted.
1081 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1082 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1083 # Files which will not be reformatted.
1084 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1085 self.invokeBlack([str(src2), "--diff", "--check"])
1086 # Multi file command.
1087 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1089 def test_no_files(self) -> None:
1091 # Without an argument, black exits with error code 0.
1092 self.invokeBlack([])
1094 def test_broken_symlink(self) -> None:
1095 with cache_dir() as workspace:
1096 symlink = workspace / "broken_link.py"
1098 symlink.symlink_to("nonexistent.py")
1099 except OSError as e:
1100 self.skipTest(f"Can't create symlinks: {e}")
1101 self.invokeBlack([str(workspace.resolve())])
1103 def test_read_cache_line_lengths(self) -> None:
1104 mode = black.FileMode()
1105 short_mode = black.FileMode(line_length=1)
1106 with cache_dir() as workspace:
1107 path = (workspace / "file.py").resolve()
1109 black.write_cache({}, [path], mode)
1110 one = black.read_cache(mode)
1111 self.assertIn(path, one)
1112 two = black.read_cache(short_mode)
1113 self.assertNotIn(path, two)
1115 def test_single_file_force_pyi(self) -> None:
1116 reg_mode = black.FileMode()
1117 pyi_mode = black.FileMode(is_pyi=True)
1118 contents, expected = read_data("force_pyi")
1119 with cache_dir() as workspace:
1120 path = (workspace / "file.py").resolve()
1121 with open(path, "w") as fh:
1123 self.invokeBlack([str(path), "--pyi"])
1124 with open(path, "r") as fh:
1126 # verify cache with --pyi is separate
1127 pyi_cache = black.read_cache(pyi_mode)
1128 self.assertIn(path, pyi_cache)
1129 normal_cache = black.read_cache(reg_mode)
1130 self.assertNotIn(path, normal_cache)
1131 self.assertEqual(actual, expected)
1133 @event_loop(close=False)
1134 def test_multi_file_force_pyi(self) -> None:
1135 reg_mode = black.FileMode()
1136 pyi_mode = black.FileMode(is_pyi=True)
1137 contents, expected = read_data("force_pyi")
1138 with cache_dir() as workspace:
1140 (workspace / "file1.py").resolve(),
1141 (workspace / "file2.py").resolve(),
1144 with open(path, "w") as fh:
1146 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1148 with open(path, "r") as fh:
1150 self.assertEqual(actual, expected)
1151 # verify cache with --pyi is separate
1152 pyi_cache = black.read_cache(pyi_mode)
1153 normal_cache = black.read_cache(reg_mode)
1155 self.assertIn(path, pyi_cache)
1156 self.assertNotIn(path, normal_cache)
1158 def test_pipe_force_pyi(self) -> None:
1159 source, expected = read_data("force_pyi")
1160 result = CliRunner().invoke(
1161 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1163 self.assertEqual(result.exit_code, 0)
1164 actual = result.output
1165 self.assertFormatEqual(actual, expected)
1167 def test_single_file_force_py36(self) -> None:
1168 reg_mode = black.FileMode()
1169 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1170 source, expected = read_data("force_py36")
1171 with cache_dir() as workspace:
1172 path = (workspace / "file.py").resolve()
1173 with open(path, "w") as fh:
1175 self.invokeBlack([str(path), *PY36_ARGS])
1176 with open(path, "r") as fh:
1178 # verify cache with --target-version is separate
1179 py36_cache = black.read_cache(py36_mode)
1180 self.assertIn(path, py36_cache)
1181 normal_cache = black.read_cache(reg_mode)
1182 self.assertNotIn(path, normal_cache)
1183 self.assertEqual(actual, expected)
1185 @event_loop(close=False)
1186 def test_multi_file_force_py36(self) -> None:
1187 reg_mode = black.FileMode()
1188 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1189 source, expected = read_data("force_py36")
1190 with cache_dir() as workspace:
1192 (workspace / "file1.py").resolve(),
1193 (workspace / "file2.py").resolve(),
1196 with open(path, "w") as fh:
1198 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1200 with open(path, "r") as fh:
1202 self.assertEqual(actual, expected)
1203 # verify cache with --target-version is separate
1204 pyi_cache = black.read_cache(py36_mode)
1205 normal_cache = black.read_cache(reg_mode)
1207 self.assertIn(path, pyi_cache)
1208 self.assertNotIn(path, normal_cache)
1210 def test_pipe_force_py36(self) -> None:
1211 source, expected = read_data("force_py36")
1212 result = CliRunner().invoke(
1214 ["-", "-q", "--target-version=py36"],
1215 input=BytesIO(source.encode("utf8")),
1217 self.assertEqual(result.exit_code, 0)
1218 actual = result.output
1219 self.assertFormatEqual(actual, expected)
1221 def test_include_exclude(self) -> None:
1222 path = THIS_DIR / "data" / "include_exclude_tests"
1223 include = re.compile(r"\.pyi?$")
1224 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1225 report = black.Report()
1226 sources: List[Path] = []
1228 Path(path / "b/dont_exclude/a.py"),
1229 Path(path / "b/dont_exclude/a.pyi"),
1231 this_abs = THIS_DIR.resolve()
1233 black.gen_python_files_in_dir(path, this_abs, include, exclude, report)
1235 self.assertEqual(sorted(expected), sorted(sources))
1237 def test_empty_include(self) -> None:
1238 path = THIS_DIR / "data" / "include_exclude_tests"
1239 report = black.Report()
1240 empty = re.compile(r"")
1241 sources: List[Path] = []
1243 Path(path / "b/exclude/a.pie"),
1244 Path(path / "b/exclude/a.py"),
1245 Path(path / "b/exclude/a.pyi"),
1246 Path(path / "b/dont_exclude/a.pie"),
1247 Path(path / "b/dont_exclude/a.py"),
1248 Path(path / "b/dont_exclude/a.pyi"),
1249 Path(path / "b/.definitely_exclude/a.pie"),
1250 Path(path / "b/.definitely_exclude/a.py"),
1251 Path(path / "b/.definitely_exclude/a.pyi"),
1253 this_abs = THIS_DIR.resolve()
1255 black.gen_python_files_in_dir(
1256 path, this_abs, empty, re.compile(black.DEFAULT_EXCLUDES), report
1259 self.assertEqual(sorted(expected), sorted(sources))
1261 def test_empty_exclude(self) -> None:
1262 path = THIS_DIR / "data" / "include_exclude_tests"
1263 report = black.Report()
1264 empty = re.compile(r"")
1265 sources: List[Path] = []
1267 Path(path / "b/dont_exclude/a.py"),
1268 Path(path / "b/dont_exclude/a.pyi"),
1269 Path(path / "b/exclude/a.py"),
1270 Path(path / "b/exclude/a.pyi"),
1271 Path(path / "b/.definitely_exclude/a.py"),
1272 Path(path / "b/.definitely_exclude/a.pyi"),
1274 this_abs = THIS_DIR.resolve()
1276 black.gen_python_files_in_dir(
1277 path, this_abs, re.compile(black.DEFAULT_INCLUDES), empty, report
1280 self.assertEqual(sorted(expected), sorted(sources))
1282 def test_invalid_include_exclude(self) -> None:
1283 for option in ["--include", "--exclude"]:
1284 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1286 def test_preserves_line_endings(self) -> None:
1287 with TemporaryDirectory() as workspace:
1288 test_file = Path(workspace) / "test.py"
1289 for nl in ["\n", "\r\n"]:
1290 contents = nl.join(["def f( ):", " pass"])
1291 test_file.write_bytes(contents.encode())
1292 ff(test_file, write_back=black.WriteBack.YES)
1293 updated_contents: bytes = test_file.read_bytes()
1294 self.assertIn(nl.encode(), updated_contents)
1296 self.assertNotIn(b"\r\n", updated_contents)
1298 def test_preserves_line_endings_via_stdin(self) -> None:
1299 for nl in ["\n", "\r\n"]:
1300 contents = nl.join(["def f( ):", " pass"])
1301 runner = BlackRunner()
1302 result = runner.invoke(
1303 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1305 self.assertEqual(result.exit_code, 0)
1306 output = runner.stdout_bytes
1307 self.assertIn(nl.encode("utf8"), output)
1309 self.assertNotIn(b"\r\n", output)
1311 def test_assert_equivalent_different_asts(self) -> None:
1312 with self.assertRaises(AssertionError):
1313 black.assert_equivalent("{}", "None")
1315 def test_symlink_out_of_root_directory(self) -> None:
1319 include = re.compile(black.DEFAULT_INCLUDES)
1320 exclude = re.compile(black.DEFAULT_EXCLUDES)
1321 report = black.Report()
1322 # `child` should behave like a symlink which resolved path is clearly
1323 # outside of the `root` directory.
1324 path.iterdir.return_value = [child]
1325 child.resolve.return_value = Path("/a/b/c")
1326 child.is_symlink.return_value = True
1328 list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1329 except ValueError as ve:
1330 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1331 path.iterdir.assert_called_once()
1332 child.resolve.assert_called_once()
1333 child.is_symlink.assert_called_once()
1334 # `child` should behave like a strange file which resolved path is clearly
1335 # outside of the `root` directory.
1336 child.is_symlink.return_value = False
1337 with self.assertRaises(ValueError):
1338 list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1339 path.iterdir.assert_called()
1340 self.assertEqual(path.iterdir.call_count, 2)
1341 child.resolve.assert_called()
1342 self.assertEqual(child.resolve.call_count, 2)
1343 child.is_symlink.assert_called()
1344 self.assertEqual(child.is_symlink.call_count, 2)
1346 def test_shhh_click(self) -> None:
1348 from click import _unicodefun # type: ignore
1349 except ModuleNotFoundError:
1350 self.skipTest("Incompatible Click version")
1351 if not hasattr(_unicodefun, "_verify_python3_env"):
1352 self.skipTest("Incompatible Click version")
1353 # First, let's see if Click is crashing with a preferred ASCII charset.
1354 with patch("locale.getpreferredencoding") as gpe:
1355 gpe.return_value = "ASCII"
1356 with self.assertRaises(RuntimeError):
1357 _unicodefun._verify_python3_env()
1358 # Now, let's silence Click...
1360 # ...and confirm it's silent.
1361 with patch("locale.getpreferredencoding") as gpe:
1362 gpe.return_value = "ASCII"
1364 _unicodefun._verify_python3_env()
1365 except RuntimeError as re:
1366 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1368 def test_root_logger_not_used_directly(self) -> None:
1369 def fail(*args: Any, **kwargs: Any) -> None:
1370 self.fail("Record created with root logger")
1372 with patch.multiple(
1383 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1385 async def test_blackd_request_needs_formatting(self) -> None:
1386 app = blackd.make_app()
1387 async with TestClient(TestServer(app)) as client:
1388 response = await client.post("/", data=b"print('hello world')")
1389 self.assertEqual(response.status, 200)
1390 self.assertEqual(response.charset, "utf8")
1391 self.assertEqual(await response.read(), b'print("hello world")\n')
1393 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1395 async def test_blackd_request_no_change(self) -> None:
1396 app = blackd.make_app()
1397 async with TestClient(TestServer(app)) as client:
1398 response = await client.post("/", data=b'print("hello world")\n')
1399 self.assertEqual(response.status, 204)
1400 self.assertEqual(await response.read(), b"")
1402 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1404 async def test_blackd_request_syntax_error(self) -> None:
1405 app = blackd.make_app()
1406 async with TestClient(TestServer(app)) as client:
1407 response = await client.post("/", data=b"what even ( is")
1408 self.assertEqual(response.status, 400)
1409 content = await response.text()
1411 content.startswith("Cannot parse"),
1412 msg=f"Expected error to start with 'Cannot parse', got {repr(content)}",
1415 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1417 async def test_blackd_unsupported_version(self) -> None:
1418 app = blackd.make_app()
1419 async with TestClient(TestServer(app)) as client:
1420 response = await client.post(
1421 "/", data=b"what", headers={blackd.VERSION_HEADER: "2"}
1423 self.assertEqual(response.status, 501)
1425 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1427 async def test_blackd_supported_version(self) -> None:
1428 app = blackd.make_app()
1429 async with TestClient(TestServer(app)) as client:
1430 response = await client.post(
1431 "/", data=b"what", headers={blackd.VERSION_HEADER: "1"}
1433 self.assertEqual(response.status, 200)
1435 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1437 async def test_blackd_invalid_python_variant(self) -> None:
1438 app = blackd.make_app()
1439 async with TestClient(TestServer(app)) as client:
1441 async def check(header_value: str, expected_status: int = 400) -> None:
1442 response = await client.post(
1445 headers={blackd.PYTHON_VARIANT_HEADER: header_value},
1447 self.assertEqual(response.status, expected_status)
1450 await check("ruby3.5")
1451 await check("pyi3.6")
1452 await check("py1.5")
1454 await check("py2.8")
1456 await check("pypy3.0")
1457 await check("jython3.4")
1459 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1461 async def test_blackd_pyi(self) -> None:
1462 app = blackd.make_app()
1463 async with TestClient(TestServer(app)) as client:
1464 source, expected = read_data("stub.pyi")
1465 response = await client.post(
1466 "/", data=source, headers={blackd.PYTHON_VARIANT_HEADER: "pyi"}
1468 self.assertEqual(response.status, 200)
1469 self.assertEqual(await response.text(), expected)
1471 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1473 async def test_blackd_python_variant(self) -> None:
1474 app = blackd.make_app()
1477 " and_has_a_bunch_of,\n"
1478 " very_long_arguments_too,\n"
1479 " and_lots_of_them_as_well_lol,\n"
1480 " **and_very_long_keyword_arguments\n"
1484 async with TestClient(TestServer(app)) as client:
1486 async def check(header_value: str, expected_status: int) -> None:
1487 response = await client.post(
1488 "/", data=code, headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1490 self.assertEqual(response.status, expected_status)
1492 await check("3.6", 200)
1493 await check("py3.6", 200)
1494 await check("3.5,3.7", 200)
1495 await check("3.5,py3.7", 200)
1497 await check("2", 204)
1498 await check("2.7", 204)
1499 await check("py2.7", 204)
1500 await check("3.4", 204)
1501 await check("py3.4", 204)
1503 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1505 async def test_blackd_fast(self) -> None:
1506 with open(os.devnull, "w") as dn, redirect_stderr(dn):
1507 app = blackd.make_app()
1508 async with TestClient(TestServer(app)) as client:
1509 response = await client.post("/", data=b"ur'hello'")
1510 self.assertEqual(response.status, 500)
1511 self.assertIn("failed to parse source file", await response.text())
1512 response = await client.post(
1513 "/", data=b"ur'hello'", headers={blackd.FAST_OR_SAFE_HEADER: "fast"}
1515 self.assertEqual(response.status, 200)
1517 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1519 async def test_blackd_line_length(self) -> None:
1520 app = blackd.make_app()
1521 async with TestClient(TestServer(app)) as client:
1522 response = await client.post(
1523 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "7"}
1525 self.assertEqual(response.status, 200)
1527 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1529 async def test_blackd_invalid_line_length(self) -> None:
1530 app = blackd.make_app()
1531 async with TestClient(TestServer(app)) as client:
1532 response = await client.post(
1534 data=b'print("hello")\n',
1535 headers={blackd.LINE_LENGTH_HEADER: "NaN"},
1537 self.assertEqual(response.status, 400)
1539 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1540 def test_blackd_main(self) -> None:
1541 with patch("blackd.web.run_app"):
1542 result = CliRunner().invoke(blackd.main, [])
1543 if result.exception is not None:
1544 raise result.exception
1545 self.assertEqual(result.exit_code, 0)
1548 if __name__ == "__main__":
1549 unittest.main(module="test_black")