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_print_function(self) -> None:
466 source, expected = read_data("python2_print_function")
467 mode = black.FileMode(target_versions={black.TargetVersion.PY27})
468 actual = fs(source, mode=mode)
469 self.assertFormatEqual(expected, actual)
470 black.assert_stable(source, actual, mode)
472 @patch("black.dump_to_file", dump_to_stderr)
473 def test_python2_unicode_literals(self) -> None:
474 source, expected = read_data("python2_unicode_literals")
476 self.assertFormatEqual(expected, actual)
477 black.assert_stable(source, actual, black.FileMode())
479 @patch("black.dump_to_file", dump_to_stderr)
480 def test_stub(self) -> None:
481 mode = black.FileMode(is_pyi=True)
482 source, expected = read_data("stub.pyi")
483 actual = fs(source, mode=mode)
484 self.assertFormatEqual(expected, actual)
485 black.assert_stable(source, actual, mode)
487 @patch("black.dump_to_file", dump_to_stderr)
488 def test_python37(self) -> None:
489 source, expected = read_data("python37")
491 self.assertFormatEqual(expected, actual)
492 major, minor = sys.version_info[:2]
493 if major > 3 or (major == 3 and minor >= 7):
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_fmtonoff(self) -> None:
499 source, expected = read_data("fmtonoff")
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_fmtonoff2(self) -> None:
507 source, expected = read_data("fmtonoff2")
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_remove_empty_parentheses_after_class(self) -> None:
515 source, expected = read_data("class_blank_parentheses")
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_new_line_between_class_and_code(self) -> None:
523 source, expected = read_data("class_methods_new_line")
525 self.assertFormatEqual(expected, actual)
526 black.assert_equivalent(source, actual)
527 black.assert_stable(source, actual, black.FileMode())
529 @patch("black.dump_to_file", dump_to_stderr)
530 def test_bracket_match(self) -> None:
531 source, expected = read_data("bracketmatch")
533 self.assertFormatEqual(expected, actual)
534 black.assert_equivalent(source, actual)
535 black.assert_stable(source, actual, black.FileMode())
537 def test_tab_comment_indentation(self) -> None:
538 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
539 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
540 self.assertFormatEqual(contents_spc, fs(contents_spc))
541 self.assertFormatEqual(contents_spc, fs(contents_tab))
543 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
544 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
545 self.assertFormatEqual(contents_spc, fs(contents_spc))
546 self.assertFormatEqual(contents_spc, fs(contents_tab))
548 # mixed tabs and spaces (valid Python 2 code)
549 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t# comment\n pass\n"
550 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
551 self.assertFormatEqual(contents_spc, fs(contents_spc))
552 self.assertFormatEqual(contents_spc, fs(contents_tab))
554 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t\t# comment\n pass\n"
555 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
556 self.assertFormatEqual(contents_spc, fs(contents_spc))
557 self.assertFormatEqual(contents_spc, fs(contents_tab))
559 def test_report_verbose(self) -> None:
560 report = black.Report(verbose=True)
564 def out(msg: str, **kwargs: Any) -> None:
565 out_lines.append(msg)
567 def err(msg: str, **kwargs: Any) -> None:
568 err_lines.append(msg)
570 with patch("black.out", out), patch("black.err", err):
571 report.done(Path("f1"), black.Changed.NO)
572 self.assertEqual(len(out_lines), 1)
573 self.assertEqual(len(err_lines), 0)
574 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
575 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
576 self.assertEqual(report.return_code, 0)
577 report.done(Path("f2"), black.Changed.YES)
578 self.assertEqual(len(out_lines), 2)
579 self.assertEqual(len(err_lines), 0)
580 self.assertEqual(out_lines[-1], "reformatted f2")
582 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
584 report.done(Path("f3"), black.Changed.CACHED)
585 self.assertEqual(len(out_lines), 3)
586 self.assertEqual(len(err_lines), 0)
588 out_lines[-1], "f3 wasn't modified on disk since last run."
591 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
593 self.assertEqual(report.return_code, 0)
595 self.assertEqual(report.return_code, 1)
597 report.failed(Path("e1"), "boom")
598 self.assertEqual(len(out_lines), 3)
599 self.assertEqual(len(err_lines), 1)
600 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
602 unstyle(str(report)),
603 "1 file reformatted, 2 files left unchanged, "
604 "1 file failed to reformat.",
606 self.assertEqual(report.return_code, 123)
607 report.done(Path("f3"), black.Changed.YES)
608 self.assertEqual(len(out_lines), 4)
609 self.assertEqual(len(err_lines), 1)
610 self.assertEqual(out_lines[-1], "reformatted f3")
612 unstyle(str(report)),
613 "2 files reformatted, 2 files left unchanged, "
614 "1 file failed to reformat.",
616 self.assertEqual(report.return_code, 123)
617 report.failed(Path("e2"), "boom")
618 self.assertEqual(len(out_lines), 4)
619 self.assertEqual(len(err_lines), 2)
620 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
622 unstyle(str(report)),
623 "2 files reformatted, 2 files left unchanged, "
624 "2 files failed to reformat.",
626 self.assertEqual(report.return_code, 123)
627 report.path_ignored(Path("wat"), "no match")
628 self.assertEqual(len(out_lines), 5)
629 self.assertEqual(len(err_lines), 2)
630 self.assertEqual(out_lines[-1], "wat ignored: no match")
632 unstyle(str(report)),
633 "2 files reformatted, 2 files left unchanged, "
634 "2 files failed to reformat.",
636 self.assertEqual(report.return_code, 123)
637 report.done(Path("f4"), black.Changed.NO)
638 self.assertEqual(len(out_lines), 6)
639 self.assertEqual(len(err_lines), 2)
640 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
642 unstyle(str(report)),
643 "2 files reformatted, 3 files left unchanged, "
644 "2 files failed to reformat.",
646 self.assertEqual(report.return_code, 123)
649 unstyle(str(report)),
650 "2 files would be reformatted, 3 files would be left unchanged, "
651 "2 files would fail to reformat.",
654 def test_report_quiet(self) -> None:
655 report = black.Report(quiet=True)
659 def out(msg: str, **kwargs: Any) -> None:
660 out_lines.append(msg)
662 def err(msg: str, **kwargs: Any) -> None:
663 err_lines.append(msg)
665 with patch("black.out", out), patch("black.err", err):
666 report.done(Path("f1"), black.Changed.NO)
667 self.assertEqual(len(out_lines), 0)
668 self.assertEqual(len(err_lines), 0)
669 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
670 self.assertEqual(report.return_code, 0)
671 report.done(Path("f2"), black.Changed.YES)
672 self.assertEqual(len(out_lines), 0)
673 self.assertEqual(len(err_lines), 0)
675 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
677 report.done(Path("f3"), black.Changed.CACHED)
678 self.assertEqual(len(out_lines), 0)
679 self.assertEqual(len(err_lines), 0)
681 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
683 self.assertEqual(report.return_code, 0)
685 self.assertEqual(report.return_code, 1)
687 report.failed(Path("e1"), "boom")
688 self.assertEqual(len(out_lines), 0)
689 self.assertEqual(len(err_lines), 1)
690 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
692 unstyle(str(report)),
693 "1 file reformatted, 2 files left unchanged, "
694 "1 file failed to reformat.",
696 self.assertEqual(report.return_code, 123)
697 report.done(Path("f3"), black.Changed.YES)
698 self.assertEqual(len(out_lines), 0)
699 self.assertEqual(len(err_lines), 1)
701 unstyle(str(report)),
702 "2 files reformatted, 2 files left unchanged, "
703 "1 file failed to reformat.",
705 self.assertEqual(report.return_code, 123)
706 report.failed(Path("e2"), "boom")
707 self.assertEqual(len(out_lines), 0)
708 self.assertEqual(len(err_lines), 2)
709 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
711 unstyle(str(report)),
712 "2 files reformatted, 2 files left unchanged, "
713 "2 files failed to reformat.",
715 self.assertEqual(report.return_code, 123)
716 report.path_ignored(Path("wat"), "no match")
717 self.assertEqual(len(out_lines), 0)
718 self.assertEqual(len(err_lines), 2)
720 unstyle(str(report)),
721 "2 files reformatted, 2 files left unchanged, "
722 "2 files failed to reformat.",
724 self.assertEqual(report.return_code, 123)
725 report.done(Path("f4"), black.Changed.NO)
726 self.assertEqual(len(out_lines), 0)
727 self.assertEqual(len(err_lines), 2)
729 unstyle(str(report)),
730 "2 files reformatted, 3 files left unchanged, "
731 "2 files failed to reformat.",
733 self.assertEqual(report.return_code, 123)
736 unstyle(str(report)),
737 "2 files would be reformatted, 3 files would be left unchanged, "
738 "2 files would fail to reformat.",
741 def test_report_normal(self) -> None:
742 report = black.Report()
746 def out(msg: str, **kwargs: Any) -> None:
747 out_lines.append(msg)
749 def err(msg: str, **kwargs: Any) -> None:
750 err_lines.append(msg)
752 with patch("black.out", out), patch("black.err", err):
753 report.done(Path("f1"), black.Changed.NO)
754 self.assertEqual(len(out_lines), 0)
755 self.assertEqual(len(err_lines), 0)
756 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
757 self.assertEqual(report.return_code, 0)
758 report.done(Path("f2"), black.Changed.YES)
759 self.assertEqual(len(out_lines), 1)
760 self.assertEqual(len(err_lines), 0)
761 self.assertEqual(out_lines[-1], "reformatted f2")
763 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
765 report.done(Path("f3"), black.Changed.CACHED)
766 self.assertEqual(len(out_lines), 1)
767 self.assertEqual(len(err_lines), 0)
768 self.assertEqual(out_lines[-1], "reformatted f2")
770 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
772 self.assertEqual(report.return_code, 0)
774 self.assertEqual(report.return_code, 1)
776 report.failed(Path("e1"), "boom")
777 self.assertEqual(len(out_lines), 1)
778 self.assertEqual(len(err_lines), 1)
779 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
781 unstyle(str(report)),
782 "1 file reformatted, 2 files left unchanged, "
783 "1 file failed to reformat.",
785 self.assertEqual(report.return_code, 123)
786 report.done(Path("f3"), black.Changed.YES)
787 self.assertEqual(len(out_lines), 2)
788 self.assertEqual(len(err_lines), 1)
789 self.assertEqual(out_lines[-1], "reformatted f3")
791 unstyle(str(report)),
792 "2 files reformatted, 2 files left unchanged, "
793 "1 file failed to reformat.",
795 self.assertEqual(report.return_code, 123)
796 report.failed(Path("e2"), "boom")
797 self.assertEqual(len(out_lines), 2)
798 self.assertEqual(len(err_lines), 2)
799 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
801 unstyle(str(report)),
802 "2 files reformatted, 2 files left unchanged, "
803 "2 files failed to reformat.",
805 self.assertEqual(report.return_code, 123)
806 report.path_ignored(Path("wat"), "no match")
807 self.assertEqual(len(out_lines), 2)
808 self.assertEqual(len(err_lines), 2)
810 unstyle(str(report)),
811 "2 files reformatted, 2 files left unchanged, "
812 "2 files failed to reformat.",
814 self.assertEqual(report.return_code, 123)
815 report.done(Path("f4"), black.Changed.NO)
816 self.assertEqual(len(out_lines), 2)
817 self.assertEqual(len(err_lines), 2)
819 unstyle(str(report)),
820 "2 files reformatted, 3 files left unchanged, "
821 "2 files failed to reformat.",
823 self.assertEqual(report.return_code, 123)
826 unstyle(str(report)),
827 "2 files would be reformatted, 3 files would be left unchanged, "
828 "2 files would fail to reformat.",
831 def test_get_features_used(self) -> None:
832 node = black.lib2to3_parse("def f(*, arg): ...\n")
833 self.assertEqual(black.get_features_used(node), set())
834 node = black.lib2to3_parse("def f(*, arg,): ...\n")
835 self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA})
836 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
837 self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
838 node = black.lib2to3_parse("123_456\n")
839 self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
840 node = black.lib2to3_parse("123456\n")
841 self.assertEqual(black.get_features_used(node), set())
842 source, expected = read_data("function")
843 node = black.lib2to3_parse(source)
845 black.get_features_used(node), {Feature.TRAILING_COMMA, Feature.F_STRINGS}
847 node = black.lib2to3_parse(expected)
849 black.get_features_used(node), {Feature.TRAILING_COMMA, Feature.F_STRINGS}
851 source, expected = read_data("expression")
852 node = black.lib2to3_parse(source)
853 self.assertEqual(black.get_features_used(node), set())
854 node = black.lib2to3_parse(expected)
855 self.assertEqual(black.get_features_used(node), set())
857 def test_get_future_imports(self) -> None:
858 node = black.lib2to3_parse("\n")
859 self.assertEqual(set(), black.get_future_imports(node))
860 node = black.lib2to3_parse("from __future__ import black\n")
861 self.assertEqual({"black"}, black.get_future_imports(node))
862 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
863 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
864 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
865 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
866 node = black.lib2to3_parse(
867 "from __future__ import multiple\nfrom __future__ import imports\n"
869 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
870 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
871 self.assertEqual({"black"}, black.get_future_imports(node))
872 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
873 self.assertEqual({"black"}, black.get_future_imports(node))
874 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
875 self.assertEqual(set(), black.get_future_imports(node))
876 node = black.lib2to3_parse("from some.module import black\n")
877 self.assertEqual(set(), black.get_future_imports(node))
878 node = black.lib2to3_parse(
879 "from __future__ import unicode_literals as _unicode_literals"
881 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
882 node = black.lib2to3_parse(
883 "from __future__ import unicode_literals as _lol, print"
885 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
887 def test_debug_visitor(self) -> None:
888 source, _ = read_data("debug_visitor.py")
889 expected, _ = read_data("debug_visitor.out")
893 def out(msg: str, **kwargs: Any) -> None:
894 out_lines.append(msg)
896 def err(msg: str, **kwargs: Any) -> None:
897 err_lines.append(msg)
899 with patch("black.out", out), patch("black.err", err):
900 black.DebugVisitor.show(source)
901 actual = "\n".join(out_lines) + "\n"
903 if expected != actual:
904 log_name = black.dump_to_file(*out_lines)
908 f"AST print out is different. Actual version dumped to {log_name}",
911 def test_format_file_contents(self) -> None:
913 mode = black.FileMode()
914 with self.assertRaises(black.NothingChanged):
915 black.format_file_contents(empty, mode=mode, fast=False)
917 with self.assertRaises(black.NothingChanged):
918 black.format_file_contents(just_nl, mode=mode, fast=False)
919 same = "l = [1, 2, 3]\n"
920 with self.assertRaises(black.NothingChanged):
921 black.format_file_contents(same, mode=mode, fast=False)
922 different = "l = [1,2,3]"
924 actual = black.format_file_contents(different, mode=mode, fast=False)
925 self.assertEqual(expected, actual)
926 invalid = "return if you can"
927 with self.assertRaises(black.InvalidInput) as e:
928 black.format_file_contents(invalid, mode=mode, fast=False)
929 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
931 def test_endmarker(self) -> None:
932 n = black.lib2to3_parse("\n")
933 self.assertEqual(n.type, black.syms.file_input)
934 self.assertEqual(len(n.children), 1)
935 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
937 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
938 def test_assertFormatEqual(self) -> None:
942 def out(msg: str, **kwargs: Any) -> None:
943 out_lines.append(msg)
945 def err(msg: str, **kwargs: Any) -> None:
946 err_lines.append(msg)
948 with patch("black.out", out), patch("black.err", err):
949 with self.assertRaises(AssertionError):
950 self.assertFormatEqual("l = [1, 2, 3]", "l = [1, 2, 3,]")
952 out_str = "".join(out_lines)
953 self.assertTrue("Expected tree:" in out_str)
954 self.assertTrue("Actual tree:" in out_str)
955 self.assertEqual("".join(err_lines), "")
957 def test_cache_broken_file(self) -> None:
958 mode = black.FileMode()
959 with cache_dir() as workspace:
960 cache_file = black.get_cache_file(mode)
961 with cache_file.open("w") as fobj:
962 fobj.write("this is not a pickle")
963 self.assertEqual(black.read_cache(mode), {})
964 src = (workspace / "test.py").resolve()
965 with src.open("w") as fobj:
966 fobj.write("print('hello')")
967 self.invokeBlack([str(src)])
968 cache = black.read_cache(mode)
969 self.assertIn(src, cache)
971 def test_cache_single_file_already_cached(self) -> None:
972 mode = black.FileMode()
973 with cache_dir() as workspace:
974 src = (workspace / "test.py").resolve()
975 with src.open("w") as fobj:
976 fobj.write("print('hello')")
977 black.write_cache({}, [src], mode)
978 self.invokeBlack([str(src)])
979 with src.open("r") as fobj:
980 self.assertEqual(fobj.read(), "print('hello')")
982 @event_loop(close=False)
983 def test_cache_multiple_files(self) -> None:
984 mode = black.FileMode()
985 with cache_dir() as workspace, patch(
986 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
988 one = (workspace / "one.py").resolve()
989 with one.open("w") as fobj:
990 fobj.write("print('hello')")
991 two = (workspace / "two.py").resolve()
992 with two.open("w") as fobj:
993 fobj.write("print('hello')")
994 black.write_cache({}, [one], mode)
995 self.invokeBlack([str(workspace)])
996 with one.open("r") as fobj:
997 self.assertEqual(fobj.read(), "print('hello')")
998 with two.open("r") as fobj:
999 self.assertEqual(fobj.read(), 'print("hello")\n')
1000 cache = black.read_cache(mode)
1001 self.assertIn(one, cache)
1002 self.assertIn(two, cache)
1004 def test_no_cache_when_writeback_diff(self) -> None:
1005 mode = black.FileMode()
1006 with cache_dir() as workspace:
1007 src = (workspace / "test.py").resolve()
1008 with src.open("w") as fobj:
1009 fobj.write("print('hello')")
1010 self.invokeBlack([str(src), "--diff"])
1011 cache_file = black.get_cache_file(mode)
1012 self.assertFalse(cache_file.exists())
1014 def test_no_cache_when_stdin(self) -> None:
1015 mode = black.FileMode()
1017 result = CliRunner().invoke(
1018 black.main, ["-"], input=BytesIO(b"print('hello')")
1020 self.assertEqual(result.exit_code, 0)
1021 cache_file = black.get_cache_file(mode)
1022 self.assertFalse(cache_file.exists())
1024 def test_read_cache_no_cachefile(self) -> None:
1025 mode = black.FileMode()
1027 self.assertEqual(black.read_cache(mode), {})
1029 def test_write_cache_read_cache(self) -> None:
1030 mode = black.FileMode()
1031 with cache_dir() as workspace:
1032 src = (workspace / "test.py").resolve()
1034 black.write_cache({}, [src], mode)
1035 cache = black.read_cache(mode)
1036 self.assertIn(src, cache)
1037 self.assertEqual(cache[src], black.get_cache_info(src))
1039 def test_filter_cached(self) -> None:
1040 with TemporaryDirectory() as workspace:
1041 path = Path(workspace)
1042 uncached = (path / "uncached").resolve()
1043 cached = (path / "cached").resolve()
1044 cached_but_changed = (path / "changed").resolve()
1047 cached_but_changed.touch()
1048 cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
1049 todo, done = black.filter_cached(
1050 cache, {uncached, cached, cached_but_changed}
1052 self.assertEqual(todo, {uncached, cached_but_changed})
1053 self.assertEqual(done, {cached})
1055 def test_write_cache_creates_directory_if_needed(self) -> None:
1056 mode = black.FileMode()
1057 with cache_dir(exists=False) as workspace:
1058 self.assertFalse(workspace.exists())
1059 black.write_cache({}, [], mode)
1060 self.assertTrue(workspace.exists())
1062 @event_loop(close=False)
1063 def test_failed_formatting_does_not_get_cached(self) -> None:
1064 mode = black.FileMode()
1065 with cache_dir() as workspace, patch(
1066 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1068 failing = (workspace / "failing.py").resolve()
1069 with failing.open("w") as fobj:
1070 fobj.write("not actually python")
1071 clean = (workspace / "clean.py").resolve()
1072 with clean.open("w") as fobj:
1073 fobj.write('print("hello")\n')
1074 self.invokeBlack([str(workspace)], exit_code=123)
1075 cache = black.read_cache(mode)
1076 self.assertNotIn(failing, cache)
1077 self.assertIn(clean, cache)
1079 def test_write_cache_write_fail(self) -> None:
1080 mode = black.FileMode()
1081 with cache_dir(), patch.object(Path, "open") as mock:
1082 mock.side_effect = OSError
1083 black.write_cache({}, [], mode)
1085 @event_loop(close=False)
1086 def test_check_diff_use_together(self) -> None:
1088 # Files which will be reformatted.
1089 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1090 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1091 # Files which will not be reformatted.
1092 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1093 self.invokeBlack([str(src2), "--diff", "--check"])
1094 # Multi file command.
1095 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1097 def test_no_files(self) -> None:
1099 # Without an argument, black exits with error code 0.
1100 self.invokeBlack([])
1102 def test_broken_symlink(self) -> None:
1103 with cache_dir() as workspace:
1104 symlink = workspace / "broken_link.py"
1106 symlink.symlink_to("nonexistent.py")
1107 except OSError as e:
1108 self.skipTest(f"Can't create symlinks: {e}")
1109 self.invokeBlack([str(workspace.resolve())])
1111 def test_read_cache_line_lengths(self) -> None:
1112 mode = black.FileMode()
1113 short_mode = black.FileMode(line_length=1)
1114 with cache_dir() as workspace:
1115 path = (workspace / "file.py").resolve()
1117 black.write_cache({}, [path], mode)
1118 one = black.read_cache(mode)
1119 self.assertIn(path, one)
1120 two = black.read_cache(short_mode)
1121 self.assertNotIn(path, two)
1123 def test_single_file_force_pyi(self) -> None:
1124 reg_mode = black.FileMode()
1125 pyi_mode = black.FileMode(is_pyi=True)
1126 contents, expected = read_data("force_pyi")
1127 with cache_dir() as workspace:
1128 path = (workspace / "file.py").resolve()
1129 with open(path, "w") as fh:
1131 self.invokeBlack([str(path), "--pyi"])
1132 with open(path, "r") as fh:
1134 # verify cache with --pyi is separate
1135 pyi_cache = black.read_cache(pyi_mode)
1136 self.assertIn(path, pyi_cache)
1137 normal_cache = black.read_cache(reg_mode)
1138 self.assertNotIn(path, normal_cache)
1139 self.assertEqual(actual, expected)
1141 @event_loop(close=False)
1142 def test_multi_file_force_pyi(self) -> None:
1143 reg_mode = black.FileMode()
1144 pyi_mode = black.FileMode(is_pyi=True)
1145 contents, expected = read_data("force_pyi")
1146 with cache_dir() as workspace:
1148 (workspace / "file1.py").resolve(),
1149 (workspace / "file2.py").resolve(),
1152 with open(path, "w") as fh:
1154 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1156 with open(path, "r") as fh:
1158 self.assertEqual(actual, expected)
1159 # verify cache with --pyi is separate
1160 pyi_cache = black.read_cache(pyi_mode)
1161 normal_cache = black.read_cache(reg_mode)
1163 self.assertIn(path, pyi_cache)
1164 self.assertNotIn(path, normal_cache)
1166 def test_pipe_force_pyi(self) -> None:
1167 source, expected = read_data("force_pyi")
1168 result = CliRunner().invoke(
1169 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1171 self.assertEqual(result.exit_code, 0)
1172 actual = result.output
1173 self.assertFormatEqual(actual, expected)
1175 def test_single_file_force_py36(self) -> None:
1176 reg_mode = black.FileMode()
1177 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1178 source, expected = read_data("force_py36")
1179 with cache_dir() as workspace:
1180 path = (workspace / "file.py").resolve()
1181 with open(path, "w") as fh:
1183 self.invokeBlack([str(path), *PY36_ARGS])
1184 with open(path, "r") as fh:
1186 # verify cache with --target-version is separate
1187 py36_cache = black.read_cache(py36_mode)
1188 self.assertIn(path, py36_cache)
1189 normal_cache = black.read_cache(reg_mode)
1190 self.assertNotIn(path, normal_cache)
1191 self.assertEqual(actual, expected)
1193 @event_loop(close=False)
1194 def test_multi_file_force_py36(self) -> None:
1195 reg_mode = black.FileMode()
1196 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1197 source, expected = read_data("force_py36")
1198 with cache_dir() as workspace:
1200 (workspace / "file1.py").resolve(),
1201 (workspace / "file2.py").resolve(),
1204 with open(path, "w") as fh:
1206 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1208 with open(path, "r") as fh:
1210 self.assertEqual(actual, expected)
1211 # verify cache with --target-version is separate
1212 pyi_cache = black.read_cache(py36_mode)
1213 normal_cache = black.read_cache(reg_mode)
1215 self.assertIn(path, pyi_cache)
1216 self.assertNotIn(path, normal_cache)
1218 def test_pipe_force_py36(self) -> None:
1219 source, expected = read_data("force_py36")
1220 result = CliRunner().invoke(
1222 ["-", "-q", "--target-version=py36"],
1223 input=BytesIO(source.encode("utf8")),
1225 self.assertEqual(result.exit_code, 0)
1226 actual = result.output
1227 self.assertFormatEqual(actual, expected)
1229 def test_include_exclude(self) -> None:
1230 path = THIS_DIR / "data" / "include_exclude_tests"
1231 include = re.compile(r"\.pyi?$")
1232 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1233 report = black.Report()
1234 sources: List[Path] = []
1236 Path(path / "b/dont_exclude/a.py"),
1237 Path(path / "b/dont_exclude/a.pyi"),
1239 this_abs = THIS_DIR.resolve()
1241 black.gen_python_files_in_dir(path, this_abs, include, exclude, report)
1243 self.assertEqual(sorted(expected), sorted(sources))
1245 def test_empty_include(self) -> None:
1246 path = THIS_DIR / "data" / "include_exclude_tests"
1247 report = black.Report()
1248 empty = re.compile(r"")
1249 sources: List[Path] = []
1251 Path(path / "b/exclude/a.pie"),
1252 Path(path / "b/exclude/a.py"),
1253 Path(path / "b/exclude/a.pyi"),
1254 Path(path / "b/dont_exclude/a.pie"),
1255 Path(path / "b/dont_exclude/a.py"),
1256 Path(path / "b/dont_exclude/a.pyi"),
1257 Path(path / "b/.definitely_exclude/a.pie"),
1258 Path(path / "b/.definitely_exclude/a.py"),
1259 Path(path / "b/.definitely_exclude/a.pyi"),
1261 this_abs = THIS_DIR.resolve()
1263 black.gen_python_files_in_dir(
1264 path, this_abs, empty, re.compile(black.DEFAULT_EXCLUDES), report
1267 self.assertEqual(sorted(expected), sorted(sources))
1269 def test_empty_exclude(self) -> None:
1270 path = THIS_DIR / "data" / "include_exclude_tests"
1271 report = black.Report()
1272 empty = re.compile(r"")
1273 sources: List[Path] = []
1275 Path(path / "b/dont_exclude/a.py"),
1276 Path(path / "b/dont_exclude/a.pyi"),
1277 Path(path / "b/exclude/a.py"),
1278 Path(path / "b/exclude/a.pyi"),
1279 Path(path / "b/.definitely_exclude/a.py"),
1280 Path(path / "b/.definitely_exclude/a.pyi"),
1282 this_abs = THIS_DIR.resolve()
1284 black.gen_python_files_in_dir(
1285 path, this_abs, re.compile(black.DEFAULT_INCLUDES), empty, report
1288 self.assertEqual(sorted(expected), sorted(sources))
1290 def test_invalid_include_exclude(self) -> None:
1291 for option in ["--include", "--exclude"]:
1292 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1294 def test_preserves_line_endings(self) -> None:
1295 with TemporaryDirectory() as workspace:
1296 test_file = Path(workspace) / "test.py"
1297 for nl in ["\n", "\r\n"]:
1298 contents = nl.join(["def f( ):", " pass"])
1299 test_file.write_bytes(contents.encode())
1300 ff(test_file, write_back=black.WriteBack.YES)
1301 updated_contents: bytes = test_file.read_bytes()
1302 self.assertIn(nl.encode(), updated_contents)
1304 self.assertNotIn(b"\r\n", updated_contents)
1306 def test_preserves_line_endings_via_stdin(self) -> None:
1307 for nl in ["\n", "\r\n"]:
1308 contents = nl.join(["def f( ):", " pass"])
1309 runner = BlackRunner()
1310 result = runner.invoke(
1311 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1313 self.assertEqual(result.exit_code, 0)
1314 output = runner.stdout_bytes
1315 self.assertIn(nl.encode("utf8"), output)
1317 self.assertNotIn(b"\r\n", output)
1319 def test_assert_equivalent_different_asts(self) -> None:
1320 with self.assertRaises(AssertionError):
1321 black.assert_equivalent("{}", "None")
1323 def test_symlink_out_of_root_directory(self) -> None:
1327 include = re.compile(black.DEFAULT_INCLUDES)
1328 exclude = re.compile(black.DEFAULT_EXCLUDES)
1329 report = black.Report()
1330 # `child` should behave like a symlink which resolved path is clearly
1331 # outside of the `root` directory.
1332 path.iterdir.return_value = [child]
1333 child.resolve.return_value = Path("/a/b/c")
1334 child.is_symlink.return_value = True
1336 list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1337 except ValueError as ve:
1338 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1339 path.iterdir.assert_called_once()
1340 child.resolve.assert_called_once()
1341 child.is_symlink.assert_called_once()
1342 # `child` should behave like a strange file which resolved path is clearly
1343 # outside of the `root` directory.
1344 child.is_symlink.return_value = False
1345 with self.assertRaises(ValueError):
1346 list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1347 path.iterdir.assert_called()
1348 self.assertEqual(path.iterdir.call_count, 2)
1349 child.resolve.assert_called()
1350 self.assertEqual(child.resolve.call_count, 2)
1351 child.is_symlink.assert_called()
1352 self.assertEqual(child.is_symlink.call_count, 2)
1354 def test_shhh_click(self) -> None:
1356 from click import _unicodefun # type: ignore
1357 except ModuleNotFoundError:
1358 self.skipTest("Incompatible Click version")
1359 if not hasattr(_unicodefun, "_verify_python3_env"):
1360 self.skipTest("Incompatible Click version")
1361 # First, let's see if Click is crashing with a preferred ASCII charset.
1362 with patch("locale.getpreferredencoding") as gpe:
1363 gpe.return_value = "ASCII"
1364 with self.assertRaises(RuntimeError):
1365 _unicodefun._verify_python3_env()
1366 # Now, let's silence Click...
1368 # ...and confirm it's silent.
1369 with patch("locale.getpreferredencoding") as gpe:
1370 gpe.return_value = "ASCII"
1372 _unicodefun._verify_python3_env()
1373 except RuntimeError as re:
1374 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1376 def test_root_logger_not_used_directly(self) -> None:
1377 def fail(*args: Any, **kwargs: Any) -> None:
1378 self.fail("Record created with root logger")
1380 with patch.multiple(
1391 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1393 async def test_blackd_request_needs_formatting(self) -> None:
1394 app = blackd.make_app()
1395 async with TestClient(TestServer(app)) as client:
1396 response = await client.post("/", data=b"print('hello world')")
1397 self.assertEqual(response.status, 200)
1398 self.assertEqual(response.charset, "utf8")
1399 self.assertEqual(await response.read(), b'print("hello world")\n')
1401 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1403 async def test_blackd_request_no_change(self) -> None:
1404 app = blackd.make_app()
1405 async with TestClient(TestServer(app)) as client:
1406 response = await client.post("/", data=b'print("hello world")\n')
1407 self.assertEqual(response.status, 204)
1408 self.assertEqual(await response.read(), b"")
1410 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1412 async def test_blackd_request_syntax_error(self) -> None:
1413 app = blackd.make_app()
1414 async with TestClient(TestServer(app)) as client:
1415 response = await client.post("/", data=b"what even ( is")
1416 self.assertEqual(response.status, 400)
1417 content = await response.text()
1419 content.startswith("Cannot parse"),
1420 msg=f"Expected error to start with 'Cannot parse', got {repr(content)}",
1423 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1425 async def test_blackd_unsupported_version(self) -> None:
1426 app = blackd.make_app()
1427 async with TestClient(TestServer(app)) as client:
1428 response = await client.post(
1429 "/", data=b"what", headers={blackd.VERSION_HEADER: "2"}
1431 self.assertEqual(response.status, 501)
1433 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1435 async def test_blackd_supported_version(self) -> None:
1436 app = blackd.make_app()
1437 async with TestClient(TestServer(app)) as client:
1438 response = await client.post(
1439 "/", data=b"what", headers={blackd.VERSION_HEADER: "1"}
1441 self.assertEqual(response.status, 200)
1443 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1445 async def test_blackd_invalid_python_variant(self) -> None:
1446 app = blackd.make_app()
1447 async with TestClient(TestServer(app)) as client:
1449 async def check(header_value: str, expected_status: int = 400) -> None:
1450 response = await client.post(
1453 headers={blackd.PYTHON_VARIANT_HEADER: header_value},
1455 self.assertEqual(response.status, expected_status)
1458 await check("ruby3.5")
1459 await check("pyi3.6")
1460 await check("py1.5")
1462 await check("py2.8")
1464 await check("pypy3.0")
1465 await check("jython3.4")
1467 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1469 async def test_blackd_pyi(self) -> None:
1470 app = blackd.make_app()
1471 async with TestClient(TestServer(app)) as client:
1472 source, expected = read_data("stub.pyi")
1473 response = await client.post(
1474 "/", data=source, headers={blackd.PYTHON_VARIANT_HEADER: "pyi"}
1476 self.assertEqual(response.status, 200)
1477 self.assertEqual(await response.text(), expected)
1479 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1481 async def test_blackd_python_variant(self) -> None:
1482 app = blackd.make_app()
1485 " and_has_a_bunch_of,\n"
1486 " very_long_arguments_too,\n"
1487 " and_lots_of_them_as_well_lol,\n"
1488 " **and_very_long_keyword_arguments\n"
1492 async with TestClient(TestServer(app)) as client:
1494 async def check(header_value: str, expected_status: int) -> None:
1495 response = await client.post(
1496 "/", data=code, headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1498 self.assertEqual(response.status, expected_status)
1500 await check("3.6", 200)
1501 await check("py3.6", 200)
1502 await check("3.5,3.7", 200)
1503 await check("3.5,py3.7", 200)
1505 await check("2", 204)
1506 await check("2.7", 204)
1507 await check("py2.7", 204)
1508 await check("3.4", 204)
1509 await check("py3.4", 204)
1511 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1513 async def test_blackd_fast(self) -> None:
1514 with open(os.devnull, "w") as dn, redirect_stderr(dn):
1515 app = blackd.make_app()
1516 async with TestClient(TestServer(app)) as client:
1517 response = await client.post("/", data=b"ur'hello'")
1518 self.assertEqual(response.status, 500)
1519 self.assertIn("failed to parse source file", await response.text())
1520 response = await client.post(
1521 "/", data=b"ur'hello'", headers={blackd.FAST_OR_SAFE_HEADER: "fast"}
1523 self.assertEqual(response.status, 200)
1525 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1527 async def test_blackd_line_length(self) -> None:
1528 app = blackd.make_app()
1529 async with TestClient(TestServer(app)) as client:
1530 response = await client.post(
1531 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "7"}
1533 self.assertEqual(response.status, 200)
1535 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1537 async def test_blackd_invalid_line_length(self) -> None:
1538 app = blackd.make_app()
1539 async with TestClient(TestServer(app)) as client:
1540 response = await client.post(
1542 data=b'print("hello")\n',
1543 headers={blackd.LINE_LENGTH_HEADER: "NaN"},
1545 self.assertEqual(response.status, 400)
1547 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1548 def test_blackd_main(self) -> None:
1549 with patch("blackd.web.run_app"):
1550 result = CliRunner().invoke(blackd.main, [])
1551 if result.exception is not None:
1552 raise result.exception
1553 self.assertEqual(result.exit_code, 0)
1556 if __name__ == "__main__":
1557 unittest.main(module="test_black")