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.
3 from concurrent.futures import ThreadPoolExecutor
4 from contextlib import contextmanager, redirect_stderr
5 from functools import partial, wraps
6 from io import BytesIO, TextIOWrapper
8 from pathlib import Path
11 from tempfile import TemporaryDirectory
24 from unittest.mock import patch, MagicMock
26 from click import unstyle
27 from click.testing import CliRunner
33 from aiohttp.test_utils import TestClient, TestServer
35 has_blackd_deps = False
37 has_blackd_deps = True
41 ff = partial(black.format_file_in_place, line_length=ll, fast=True)
42 fs = partial(black.format_str, line_length=ll)
43 THIS_FILE = Path(__file__)
44 THIS_DIR = THIS_FILE.parent
45 EMPTY_LINE = "# EMPTY LINE WITH WHITESPACE" + " (this comment will be removed)"
50 def dump_to_stderr(*output: str) -> str:
51 return "\n" + "\n".join(output) + "\n"
54 def read_data(name: str, data: bool = True) -> Tuple[str, str]:
55 """read_data('test_name') -> 'input', 'output'"""
56 if not name.endswith((".py", ".pyi", ".out", ".diff")):
58 _input: List[str] = []
59 _output: List[str] = []
60 base_dir = THIS_DIR / "data" if data else THIS_DIR
61 with open(base_dir / name, "r", encoding="utf8") as test:
62 lines = test.readlines()
65 line = line.replace(EMPTY_LINE, "")
66 if line.rstrip() == "# output":
71 if _input and not _output:
72 # If there's no output marker, treat the entire file as already pre-formatted.
74 return "".join(_input).strip() + "\n", "".join(_output).strip() + "\n"
78 def cache_dir(exists: bool = True) -> Iterator[Path]:
79 with TemporaryDirectory() as workspace:
80 cache_dir = Path(workspace)
82 cache_dir = cache_dir / "new"
83 with patch("black.CACHE_DIR", cache_dir):
88 def event_loop(close: bool) -> Iterator[None]:
89 policy = asyncio.get_event_loop_policy()
90 old_loop = policy.get_event_loop()
91 loop = policy.new_event_loop()
92 asyncio.set_event_loop(loop)
97 policy.set_event_loop(old_loop)
102 def async_test(f: Callable[..., Coroutine[Any, None, R]]) -> Callable[..., None]:
103 @event_loop(close=True)
105 def wrapper(*args: Any, **kwargs: Any) -> None:
106 asyncio.get_event_loop().run_until_complete(f(*args, **kwargs))
111 class BlackRunner(CliRunner):
112 """Modify CliRunner so that stderr is not merged with stdout.
114 This is a hack that can be removed once we depend on Click 7.x"""
116 def __init__(self) -> None:
117 self.stderrbuf = BytesIO()
118 self.stdoutbuf = BytesIO()
119 self.stdout_bytes = b""
120 self.stderr_bytes = b""
124 def isolation(self, *args: Any, **kwargs: Any) -> Generator[BinaryIO, None, None]:
125 with super().isolation(*args, **kwargs) as output:
127 hold_stderr = sys.stderr
128 sys.stderr = TextIOWrapper(self.stderrbuf, encoding=self.charset)
131 self.stdout_bytes = sys.stdout.buffer.getvalue() # type: ignore
132 self.stderr_bytes = sys.stderr.buffer.getvalue() # type: ignore
133 sys.stderr = hold_stderr
136 class BlackTestCase(unittest.TestCase):
139 def assertFormatEqual(self, expected: str, actual: str) -> None:
140 if actual != expected and not os.environ.get("SKIP_AST_PRINT"):
141 bdv: black.DebugVisitor[Any]
142 black.out("Expected tree:", fg="green")
144 exp_node = black.lib2to3_parse(expected)
145 bdv = black.DebugVisitor()
146 list(bdv.visit(exp_node))
147 except Exception as ve:
149 black.out("Actual tree:", fg="red")
151 exp_node = black.lib2to3_parse(actual)
152 bdv = black.DebugVisitor()
153 list(bdv.visit(exp_node))
154 except Exception as ve:
156 self.assertEqual(expected, actual)
158 @patch("black.dump_to_file", dump_to_stderr)
159 def test_empty(self) -> None:
160 source = expected = ""
162 self.assertFormatEqual(expected, actual)
163 black.assert_equivalent(source, actual)
164 black.assert_stable(source, actual, line_length=ll)
166 def test_empty_ff(self) -> None:
168 tmp_file = Path(black.dump_to_file())
170 self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES))
171 with open(tmp_file, encoding="utf8") as f:
175 self.assertFormatEqual(expected, actual)
177 @patch("black.dump_to_file", dump_to_stderr)
178 def test_self(self) -> None:
179 source, expected = read_data("test_black", data=False)
181 self.assertFormatEqual(expected, actual)
182 black.assert_equivalent(source, actual)
183 black.assert_stable(source, actual, line_length=ll)
184 self.assertFalse(ff(THIS_FILE))
186 @patch("black.dump_to_file", dump_to_stderr)
187 def test_black(self) -> None:
188 source, expected = read_data("../black", data=False)
190 self.assertFormatEqual(expected, actual)
191 black.assert_equivalent(source, actual)
192 black.assert_stable(source, actual, line_length=ll)
193 self.assertFalse(ff(THIS_DIR / ".." / "black.py"))
195 def test_piping(self) -> None:
196 source, expected = read_data("../black", data=False)
197 result = BlackRunner().invoke(
199 ["-", "--fast", f"--line-length={ll}"],
200 input=BytesIO(source.encode("utf8")),
202 self.assertEqual(result.exit_code, 0)
203 self.assertFormatEqual(expected, result.output)
204 black.assert_equivalent(source, result.output)
205 black.assert_stable(source, result.output, line_length=ll)
207 def test_piping_diff(self) -> None:
208 diff_header = re.compile(
209 rf"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d "
210 rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
212 source, _ = read_data("expression.py")
213 expected, _ = read_data("expression.diff")
214 config = THIS_DIR / "data" / "empty_pyproject.toml"
215 args = ["-", "--fast", f"--line-length={ll}", "--diff", f"--config={config}"]
216 result = BlackRunner().invoke(
217 black.main, args, input=BytesIO(source.encode("utf8"))
219 self.assertEqual(result.exit_code, 0)
220 actual = diff_header.sub("[Deterministic header]", result.output)
221 actual = actual.rstrip() + "\n" # the diff output has a trailing space
222 self.assertEqual(expected, actual)
224 @patch("black.dump_to_file", dump_to_stderr)
225 def test_setup(self) -> None:
226 source, expected = read_data("../setup", data=False)
228 self.assertFormatEqual(expected, actual)
229 black.assert_equivalent(source, actual)
230 black.assert_stable(source, actual, line_length=ll)
231 self.assertFalse(ff(THIS_DIR / ".." / "setup.py"))
233 @patch("black.dump_to_file", dump_to_stderr)
234 def test_function(self) -> None:
235 source, expected = read_data("function")
237 self.assertFormatEqual(expected, actual)
238 black.assert_equivalent(source, actual)
239 black.assert_stable(source, actual, line_length=ll)
241 @patch("black.dump_to_file", dump_to_stderr)
242 def test_function2(self) -> None:
243 source, expected = read_data("function2")
245 self.assertFormatEqual(expected, actual)
246 black.assert_equivalent(source, actual)
247 black.assert_stable(source, actual, line_length=ll)
249 @patch("black.dump_to_file", dump_to_stderr)
250 def test_expression(self) -> None:
251 source, expected = read_data("expression")
253 self.assertFormatEqual(expected, actual)
254 black.assert_equivalent(source, actual)
255 black.assert_stable(source, actual, line_length=ll)
257 def test_expression_ff(self) -> None:
258 source, expected = read_data("expression")
259 tmp_file = Path(black.dump_to_file(source))
261 self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
262 with open(tmp_file, encoding="utf8") as f:
266 self.assertFormatEqual(expected, actual)
267 with patch("black.dump_to_file", dump_to_stderr):
268 black.assert_equivalent(source, actual)
269 black.assert_stable(source, actual, line_length=ll)
271 def test_expression_diff(self) -> None:
272 source, _ = read_data("expression.py")
273 expected, _ = read_data("expression.diff")
274 tmp_file = Path(black.dump_to_file(source))
275 diff_header = re.compile(
276 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
277 rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
280 result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
281 self.assertEqual(result.exit_code, 0)
284 actual = result.output
285 actual = diff_header.sub("[Deterministic header]", actual)
286 actual = actual.rstrip() + "\n" # the diff output has a trailing space
287 if expected != actual:
288 dump = black.dump_to_file(actual)
290 f"Expected diff isn't equal to the actual. If you made changes "
291 f"to expression.py and this is an anticipated difference, "
292 f"overwrite tests/data/expression.diff with {dump}"
294 self.assertEqual(expected, actual, msg)
296 @patch("black.dump_to_file", dump_to_stderr)
297 def test_fstring(self) -> None:
298 source, expected = read_data("fstring")
300 self.assertFormatEqual(expected, actual)
301 black.assert_equivalent(source, actual)
302 black.assert_stable(source, actual, line_length=ll)
304 @patch("black.dump_to_file", dump_to_stderr)
305 def test_string_quotes(self) -> None:
306 source, expected = read_data("string_quotes")
308 self.assertFormatEqual(expected, actual)
309 black.assert_equivalent(source, actual)
310 black.assert_stable(source, actual, line_length=ll)
311 mode = black.FileMode.NO_STRING_NORMALIZATION
312 not_normalized = fs(source, mode=mode)
313 self.assertFormatEqual(source, not_normalized)
314 black.assert_equivalent(source, not_normalized)
315 black.assert_stable(source, not_normalized, line_length=ll, mode=mode)
317 @patch("black.dump_to_file", dump_to_stderr)
318 def test_slices(self) -> None:
319 source, expected = read_data("slices")
321 self.assertFormatEqual(expected, actual)
322 black.assert_equivalent(source, actual)
323 black.assert_stable(source, actual, line_length=ll)
325 @patch("black.dump_to_file", dump_to_stderr)
326 def test_comments(self) -> None:
327 source, expected = read_data("comments")
329 self.assertFormatEqual(expected, actual)
330 black.assert_equivalent(source, actual)
331 black.assert_stable(source, actual, line_length=ll)
333 @patch("black.dump_to_file", dump_to_stderr)
334 def test_comments2(self) -> None:
335 source, expected = read_data("comments2")
337 self.assertFormatEqual(expected, actual)
338 black.assert_equivalent(source, actual)
339 black.assert_stable(source, actual, line_length=ll)
341 @patch("black.dump_to_file", dump_to_stderr)
342 def test_comments3(self) -> None:
343 source, expected = read_data("comments3")
345 self.assertFormatEqual(expected, actual)
346 black.assert_equivalent(source, actual)
347 black.assert_stable(source, actual, line_length=ll)
349 @patch("black.dump_to_file", dump_to_stderr)
350 def test_comments4(self) -> None:
351 source, expected = read_data("comments4")
353 self.assertFormatEqual(expected, actual)
354 black.assert_equivalent(source, actual)
355 black.assert_stable(source, actual, line_length=ll)
357 @patch("black.dump_to_file", dump_to_stderr)
358 def test_comments5(self) -> None:
359 source, expected = read_data("comments5")
361 self.assertFormatEqual(expected, actual)
362 black.assert_equivalent(source, actual)
363 black.assert_stable(source, actual, line_length=ll)
365 @patch("black.dump_to_file", dump_to_stderr)
366 def test_comments6(self) -> None:
367 source, expected = read_data("comments6")
369 self.assertFormatEqual(expected, actual)
370 black.assert_equivalent(source, actual)
371 black.assert_stable(source, actual, line_length=ll)
373 @patch("black.dump_to_file", dump_to_stderr)
374 def test_cantfit(self) -> None:
375 source, expected = read_data("cantfit")
377 self.assertFormatEqual(expected, actual)
378 black.assert_equivalent(source, actual)
379 black.assert_stable(source, actual, line_length=ll)
381 @patch("black.dump_to_file", dump_to_stderr)
382 def test_import_spacing(self) -> None:
383 source, expected = read_data("import_spacing")
385 self.assertFormatEqual(expected, actual)
386 black.assert_equivalent(source, actual)
387 black.assert_stable(source, actual, line_length=ll)
389 @patch("black.dump_to_file", dump_to_stderr)
390 def test_composition(self) -> None:
391 source, expected = read_data("composition")
393 self.assertFormatEqual(expected, actual)
394 black.assert_equivalent(source, actual)
395 black.assert_stable(source, actual, line_length=ll)
397 @patch("black.dump_to_file", dump_to_stderr)
398 def test_empty_lines(self) -> None:
399 source, expected = read_data("empty_lines")
401 self.assertFormatEqual(expected, actual)
402 black.assert_equivalent(source, actual)
403 black.assert_stable(source, actual, line_length=ll)
405 @patch("black.dump_to_file", dump_to_stderr)
406 def test_string_prefixes(self) -> None:
407 source, expected = read_data("string_prefixes")
409 self.assertFormatEqual(expected, actual)
410 black.assert_equivalent(source, actual)
411 black.assert_stable(source, actual, line_length=ll)
413 @patch("black.dump_to_file", dump_to_stderr)
414 def test_numeric_literals(self) -> None:
415 source, expected = read_data("numeric_literals")
416 actual = fs(source, mode=black.FileMode.PYTHON36)
417 self.assertFormatEqual(expected, actual)
418 black.assert_equivalent(source, actual)
419 black.assert_stable(source, actual, line_length=ll)
421 @patch("black.dump_to_file", dump_to_stderr)
422 def test_numeric_literals_ignoring_underscores(self) -> None:
423 source, expected = read_data("numeric_literals_skip_underscores")
425 black.FileMode.PYTHON36 | black.FileMode.NO_NUMERIC_UNDERSCORE_NORMALIZATION
427 actual = fs(source, mode=mode)
428 self.assertFormatEqual(expected, actual)
429 black.assert_equivalent(source, actual)
430 black.assert_stable(source, actual, line_length=ll, mode=mode)
432 @patch("black.dump_to_file", dump_to_stderr)
433 def test_numeric_literals_py2(self) -> None:
434 source, expected = read_data("numeric_literals_py2")
436 self.assertFormatEqual(expected, actual)
437 black.assert_stable(source, actual, line_length=ll)
439 @patch("black.dump_to_file", dump_to_stderr)
440 def test_python2(self) -> None:
441 source, expected = read_data("python2")
443 self.assertFormatEqual(expected, actual)
444 # black.assert_equivalent(source, actual)
445 black.assert_stable(source, actual, line_length=ll)
447 @patch("black.dump_to_file", dump_to_stderr)
448 def test_python2_unicode_literals(self) -> None:
449 source, expected = read_data("python2_unicode_literals")
451 self.assertFormatEqual(expected, actual)
452 black.assert_stable(source, actual, line_length=ll)
454 @patch("black.dump_to_file", dump_to_stderr)
455 def test_stub(self) -> None:
456 mode = black.FileMode.PYI
457 source, expected = read_data("stub.pyi")
458 actual = fs(source, mode=mode)
459 self.assertFormatEqual(expected, actual)
460 black.assert_stable(source, actual, line_length=ll, mode=mode)
462 @patch("black.dump_to_file", dump_to_stderr)
463 def test_python37(self) -> None:
464 source, expected = read_data("python37")
466 self.assertFormatEqual(expected, actual)
467 major, minor = sys.version_info[:2]
468 if major > 3 or (major == 3 and minor >= 7):
469 black.assert_equivalent(source, actual)
470 black.assert_stable(source, actual, line_length=ll)
472 @patch("black.dump_to_file", dump_to_stderr)
473 def test_fmtonoff(self) -> None:
474 source, expected = read_data("fmtonoff")
476 self.assertFormatEqual(expected, actual)
477 black.assert_equivalent(source, actual)
478 black.assert_stable(source, actual, line_length=ll)
480 @patch("black.dump_to_file", dump_to_stderr)
481 def test_fmtonoff2(self) -> None:
482 source, expected = read_data("fmtonoff2")
484 self.assertFormatEqual(expected, actual)
485 black.assert_equivalent(source, actual)
486 black.assert_stable(source, actual, line_length=ll)
488 @patch("black.dump_to_file", dump_to_stderr)
489 def test_remove_empty_parentheses_after_class(self) -> None:
490 source, expected = read_data("class_blank_parentheses")
492 self.assertFormatEqual(expected, actual)
493 black.assert_equivalent(source, actual)
494 black.assert_stable(source, actual, line_length=ll)
496 @patch("black.dump_to_file", dump_to_stderr)
497 def test_new_line_between_class_and_code(self) -> None:
498 source, expected = read_data("class_methods_new_line")
500 self.assertFormatEqual(expected, actual)
501 black.assert_equivalent(source, actual)
502 black.assert_stable(source, actual, line_length=ll)
504 @patch("black.dump_to_file", dump_to_stderr)
505 def test_bracket_match(self) -> None:
506 source, expected = read_data("bracketmatch")
508 self.assertFormatEqual(expected, actual)
509 black.assert_equivalent(source, actual)
510 black.assert_stable(source, actual, line_length=ll)
512 def test_comment_indentation(self) -> None:
513 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
514 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
516 self.assertFormatEqual(fs(contents_spc), contents_spc)
517 self.assertFormatEqual(fs(contents_tab), contents_spc)
519 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
520 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
522 self.assertFormatEqual(fs(contents_tab), contents_spc)
523 self.assertFormatEqual(fs(contents_spc), contents_spc)
525 def test_report_verbose(self) -> None:
526 report = black.Report(verbose=True)
530 def out(msg: str, **kwargs: Any) -> None:
531 out_lines.append(msg)
533 def err(msg: str, **kwargs: Any) -> None:
534 err_lines.append(msg)
536 with patch("black.out", out), patch("black.err", err):
537 report.done(Path("f1"), black.Changed.NO)
538 self.assertEqual(len(out_lines), 1)
539 self.assertEqual(len(err_lines), 0)
540 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
541 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
542 self.assertEqual(report.return_code, 0)
543 report.done(Path("f2"), black.Changed.YES)
544 self.assertEqual(len(out_lines), 2)
545 self.assertEqual(len(err_lines), 0)
546 self.assertEqual(out_lines[-1], "reformatted f2")
548 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
550 report.done(Path("f3"), black.Changed.CACHED)
551 self.assertEqual(len(out_lines), 3)
552 self.assertEqual(len(err_lines), 0)
554 out_lines[-1], "f3 wasn't modified on disk since last run."
557 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
559 self.assertEqual(report.return_code, 0)
561 self.assertEqual(report.return_code, 1)
563 report.failed(Path("e1"), "boom")
564 self.assertEqual(len(out_lines), 3)
565 self.assertEqual(len(err_lines), 1)
566 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
568 unstyle(str(report)),
569 "1 file reformatted, 2 files left unchanged, "
570 "1 file failed to reformat.",
572 self.assertEqual(report.return_code, 123)
573 report.done(Path("f3"), black.Changed.YES)
574 self.assertEqual(len(out_lines), 4)
575 self.assertEqual(len(err_lines), 1)
576 self.assertEqual(out_lines[-1], "reformatted f3")
578 unstyle(str(report)),
579 "2 files reformatted, 2 files left unchanged, "
580 "1 file failed to reformat.",
582 self.assertEqual(report.return_code, 123)
583 report.failed(Path("e2"), "boom")
584 self.assertEqual(len(out_lines), 4)
585 self.assertEqual(len(err_lines), 2)
586 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
588 unstyle(str(report)),
589 "2 files reformatted, 2 files left unchanged, "
590 "2 files failed to reformat.",
592 self.assertEqual(report.return_code, 123)
593 report.path_ignored(Path("wat"), "no match")
594 self.assertEqual(len(out_lines), 5)
595 self.assertEqual(len(err_lines), 2)
596 self.assertEqual(out_lines[-1], "wat ignored: no match")
598 unstyle(str(report)),
599 "2 files reformatted, 2 files left unchanged, "
600 "2 files failed to reformat.",
602 self.assertEqual(report.return_code, 123)
603 report.done(Path("f4"), black.Changed.NO)
604 self.assertEqual(len(out_lines), 6)
605 self.assertEqual(len(err_lines), 2)
606 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
608 unstyle(str(report)),
609 "2 files reformatted, 3 files left unchanged, "
610 "2 files failed to reformat.",
612 self.assertEqual(report.return_code, 123)
615 unstyle(str(report)),
616 "2 files would be reformatted, 3 files would be left unchanged, "
617 "2 files would fail to reformat.",
620 def test_report_quiet(self) -> None:
621 report = black.Report(quiet=True)
625 def out(msg: str, **kwargs: Any) -> None:
626 out_lines.append(msg)
628 def err(msg: str, **kwargs: Any) -> None:
629 err_lines.append(msg)
631 with patch("black.out", out), patch("black.err", err):
632 report.done(Path("f1"), black.Changed.NO)
633 self.assertEqual(len(out_lines), 0)
634 self.assertEqual(len(err_lines), 0)
635 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
636 self.assertEqual(report.return_code, 0)
637 report.done(Path("f2"), black.Changed.YES)
638 self.assertEqual(len(out_lines), 0)
639 self.assertEqual(len(err_lines), 0)
641 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
643 report.done(Path("f3"), black.Changed.CACHED)
644 self.assertEqual(len(out_lines), 0)
645 self.assertEqual(len(err_lines), 0)
647 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
649 self.assertEqual(report.return_code, 0)
651 self.assertEqual(report.return_code, 1)
653 report.failed(Path("e1"), "boom")
654 self.assertEqual(len(out_lines), 0)
655 self.assertEqual(len(err_lines), 1)
656 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
658 unstyle(str(report)),
659 "1 file reformatted, 2 files left unchanged, "
660 "1 file failed to reformat.",
662 self.assertEqual(report.return_code, 123)
663 report.done(Path("f3"), black.Changed.YES)
664 self.assertEqual(len(out_lines), 0)
665 self.assertEqual(len(err_lines), 1)
667 unstyle(str(report)),
668 "2 files reformatted, 2 files left unchanged, "
669 "1 file failed to reformat.",
671 self.assertEqual(report.return_code, 123)
672 report.failed(Path("e2"), "boom")
673 self.assertEqual(len(out_lines), 0)
674 self.assertEqual(len(err_lines), 2)
675 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
677 unstyle(str(report)),
678 "2 files reformatted, 2 files left unchanged, "
679 "2 files failed to reformat.",
681 self.assertEqual(report.return_code, 123)
682 report.path_ignored(Path("wat"), "no match")
683 self.assertEqual(len(out_lines), 0)
684 self.assertEqual(len(err_lines), 2)
686 unstyle(str(report)),
687 "2 files reformatted, 2 files left unchanged, "
688 "2 files failed to reformat.",
690 self.assertEqual(report.return_code, 123)
691 report.done(Path("f4"), black.Changed.NO)
692 self.assertEqual(len(out_lines), 0)
693 self.assertEqual(len(err_lines), 2)
695 unstyle(str(report)),
696 "2 files reformatted, 3 files left unchanged, "
697 "2 files failed to reformat.",
699 self.assertEqual(report.return_code, 123)
702 unstyle(str(report)),
703 "2 files would be reformatted, 3 files would be left unchanged, "
704 "2 files would fail to reformat.",
707 def test_report_normal(self) -> None:
708 report = black.Report()
712 def out(msg: str, **kwargs: Any) -> None:
713 out_lines.append(msg)
715 def err(msg: str, **kwargs: Any) -> None:
716 err_lines.append(msg)
718 with patch("black.out", out), patch("black.err", err):
719 report.done(Path("f1"), black.Changed.NO)
720 self.assertEqual(len(out_lines), 0)
721 self.assertEqual(len(err_lines), 0)
722 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
723 self.assertEqual(report.return_code, 0)
724 report.done(Path("f2"), black.Changed.YES)
725 self.assertEqual(len(out_lines), 1)
726 self.assertEqual(len(err_lines), 0)
727 self.assertEqual(out_lines[-1], "reformatted f2")
729 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
731 report.done(Path("f3"), black.Changed.CACHED)
732 self.assertEqual(len(out_lines), 1)
733 self.assertEqual(len(err_lines), 0)
734 self.assertEqual(out_lines[-1], "reformatted f2")
736 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
738 self.assertEqual(report.return_code, 0)
740 self.assertEqual(report.return_code, 1)
742 report.failed(Path("e1"), "boom")
743 self.assertEqual(len(out_lines), 1)
744 self.assertEqual(len(err_lines), 1)
745 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
747 unstyle(str(report)),
748 "1 file reformatted, 2 files left unchanged, "
749 "1 file failed to reformat.",
751 self.assertEqual(report.return_code, 123)
752 report.done(Path("f3"), black.Changed.YES)
753 self.assertEqual(len(out_lines), 2)
754 self.assertEqual(len(err_lines), 1)
755 self.assertEqual(out_lines[-1], "reformatted f3")
757 unstyle(str(report)),
758 "2 files reformatted, 2 files left unchanged, "
759 "1 file failed to reformat.",
761 self.assertEqual(report.return_code, 123)
762 report.failed(Path("e2"), "boom")
763 self.assertEqual(len(out_lines), 2)
764 self.assertEqual(len(err_lines), 2)
765 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
767 unstyle(str(report)),
768 "2 files reformatted, 2 files left unchanged, "
769 "2 files failed to reformat.",
771 self.assertEqual(report.return_code, 123)
772 report.path_ignored(Path("wat"), "no match")
773 self.assertEqual(len(out_lines), 2)
774 self.assertEqual(len(err_lines), 2)
776 unstyle(str(report)),
777 "2 files reformatted, 2 files left unchanged, "
778 "2 files failed to reformat.",
780 self.assertEqual(report.return_code, 123)
781 report.done(Path("f4"), black.Changed.NO)
782 self.assertEqual(len(out_lines), 2)
783 self.assertEqual(len(err_lines), 2)
785 unstyle(str(report)),
786 "2 files reformatted, 3 files left unchanged, "
787 "2 files failed to reformat.",
789 self.assertEqual(report.return_code, 123)
792 unstyle(str(report)),
793 "2 files would be reformatted, 3 files would be left unchanged, "
794 "2 files would fail to reformat.",
797 def test_is_python36(self) -> None:
798 node = black.lib2to3_parse("def f(*, arg): ...\n")
799 self.assertFalse(black.is_python36(node))
800 node = black.lib2to3_parse("def f(*, arg,): ...\n")
801 self.assertTrue(black.is_python36(node))
802 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
803 self.assertTrue(black.is_python36(node))
804 node = black.lib2to3_parse("123_456\n")
805 self.assertTrue(black.is_python36(node))
806 node = black.lib2to3_parse("123456\n")
807 self.assertFalse(black.is_python36(node))
808 source, expected = read_data("function")
809 node = black.lib2to3_parse(source)
810 self.assertTrue(black.is_python36(node))
811 node = black.lib2to3_parse(expected)
812 self.assertTrue(black.is_python36(node))
813 source, expected = read_data("expression")
814 node = black.lib2to3_parse(source)
815 self.assertFalse(black.is_python36(node))
816 node = black.lib2to3_parse(expected)
817 self.assertFalse(black.is_python36(node))
819 def test_get_future_imports(self) -> None:
820 node = black.lib2to3_parse("\n")
821 self.assertEqual(set(), black.get_future_imports(node))
822 node = black.lib2to3_parse("from __future__ import black\n")
823 self.assertEqual({"black"}, black.get_future_imports(node))
824 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
825 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
826 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
827 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
828 node = black.lib2to3_parse(
829 "from __future__ import multiple\nfrom __future__ import imports\n"
831 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
832 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
833 self.assertEqual({"black"}, black.get_future_imports(node))
834 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
835 self.assertEqual({"black"}, black.get_future_imports(node))
836 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
837 self.assertEqual(set(), black.get_future_imports(node))
838 node = black.lib2to3_parse("from some.module import black\n")
839 self.assertEqual(set(), black.get_future_imports(node))
840 node = black.lib2to3_parse(
841 "from __future__ import unicode_literals as _unicode_literals"
843 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
844 node = black.lib2to3_parse(
845 "from __future__ import unicode_literals as _lol, print"
847 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
849 def test_debug_visitor(self) -> None:
850 source, _ = read_data("debug_visitor.py")
851 expected, _ = read_data("debug_visitor.out")
855 def out(msg: str, **kwargs: Any) -> None:
856 out_lines.append(msg)
858 def err(msg: str, **kwargs: Any) -> None:
859 err_lines.append(msg)
861 with patch("black.out", out), patch("black.err", err):
862 black.DebugVisitor.show(source)
863 actual = "\n".join(out_lines) + "\n"
865 if expected != actual:
866 log_name = black.dump_to_file(*out_lines)
870 f"AST print out is different. Actual version dumped to {log_name}",
873 def test_format_file_contents(self) -> None:
875 with self.assertRaises(black.NothingChanged):
876 black.format_file_contents(empty, line_length=ll, fast=False)
878 with self.assertRaises(black.NothingChanged):
879 black.format_file_contents(just_nl, line_length=ll, fast=False)
880 same = "l = [1, 2, 3]\n"
881 with self.assertRaises(black.NothingChanged):
882 black.format_file_contents(same, line_length=ll, fast=False)
883 different = "l = [1,2,3]"
885 actual = black.format_file_contents(different, line_length=ll, fast=False)
886 self.assertEqual(expected, actual)
887 invalid = "return if you can"
888 with self.assertRaises(black.InvalidInput) as e:
889 black.format_file_contents(invalid, line_length=ll, fast=False)
890 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
892 def test_endmarker(self) -> None:
893 n = black.lib2to3_parse("\n")
894 self.assertEqual(n.type, black.syms.file_input)
895 self.assertEqual(len(n.children), 1)
896 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
898 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
899 def test_assertFormatEqual(self) -> None:
903 def out(msg: str, **kwargs: Any) -> None:
904 out_lines.append(msg)
906 def err(msg: str, **kwargs: Any) -> None:
907 err_lines.append(msg)
909 with patch("black.out", out), patch("black.err", err):
910 with self.assertRaises(AssertionError):
911 self.assertFormatEqual("l = [1, 2, 3]", "l = [1, 2, 3,]")
913 out_str = "".join(out_lines)
914 self.assertTrue("Expected tree:" in out_str)
915 self.assertTrue("Actual tree:" in out_str)
916 self.assertEqual("".join(err_lines), "")
918 def test_cache_broken_file(self) -> None:
919 mode = black.FileMode.AUTO_DETECT
920 with cache_dir() as workspace:
921 cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode)
922 with cache_file.open("w") as fobj:
923 fobj.write("this is not a pickle")
924 self.assertEqual(black.read_cache(black.DEFAULT_LINE_LENGTH, mode), {})
925 src = (workspace / "test.py").resolve()
926 with src.open("w") as fobj:
927 fobj.write("print('hello')")
928 result = CliRunner().invoke(black.main, [str(src)])
929 self.assertEqual(result.exit_code, 0)
930 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
931 self.assertIn(src, cache)
933 def test_cache_single_file_already_cached(self) -> None:
934 mode = black.FileMode.AUTO_DETECT
935 with cache_dir() as workspace:
936 src = (workspace / "test.py").resolve()
937 with src.open("w") as fobj:
938 fobj.write("print('hello')")
939 black.write_cache({}, [src], black.DEFAULT_LINE_LENGTH, mode)
940 result = CliRunner().invoke(black.main, [str(src)])
941 self.assertEqual(result.exit_code, 0)
942 with src.open("r") as fobj:
943 self.assertEqual(fobj.read(), "print('hello')")
945 @event_loop(close=False)
946 def test_cache_multiple_files(self) -> None:
947 mode = black.FileMode.AUTO_DETECT
948 with cache_dir() as workspace, patch(
949 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
951 one = (workspace / "one.py").resolve()
952 with one.open("w") as fobj:
953 fobj.write("print('hello')")
954 two = (workspace / "two.py").resolve()
955 with two.open("w") as fobj:
956 fobj.write("print('hello')")
957 black.write_cache({}, [one], black.DEFAULT_LINE_LENGTH, mode)
958 result = CliRunner().invoke(black.main, [str(workspace)])
959 self.assertEqual(result.exit_code, 0)
960 with one.open("r") as fobj:
961 self.assertEqual(fobj.read(), "print('hello')")
962 with two.open("r") as fobj:
963 self.assertEqual(fobj.read(), 'print("hello")\n')
964 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
965 self.assertIn(one, cache)
966 self.assertIn(two, cache)
968 def test_no_cache_when_writeback_diff(self) -> None:
969 mode = black.FileMode.AUTO_DETECT
970 with cache_dir() as workspace:
971 src = (workspace / "test.py").resolve()
972 with src.open("w") as fobj:
973 fobj.write("print('hello')")
974 result = CliRunner().invoke(black.main, [str(src), "--diff"])
975 self.assertEqual(result.exit_code, 0)
976 cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode)
977 self.assertFalse(cache_file.exists())
979 def test_no_cache_when_stdin(self) -> None:
980 mode = black.FileMode.AUTO_DETECT
982 result = CliRunner().invoke(
983 black.main, ["-"], input=BytesIO(b"print('hello')")
985 self.assertEqual(result.exit_code, 0)
986 cache_file = black.get_cache_file(black.DEFAULT_LINE_LENGTH, mode)
987 self.assertFalse(cache_file.exists())
989 def test_read_cache_no_cachefile(self) -> None:
990 mode = black.FileMode.AUTO_DETECT
992 self.assertEqual(black.read_cache(black.DEFAULT_LINE_LENGTH, mode), {})
994 def test_write_cache_read_cache(self) -> None:
995 mode = black.FileMode.AUTO_DETECT
996 with cache_dir() as workspace:
997 src = (workspace / "test.py").resolve()
999 black.write_cache({}, [src], black.DEFAULT_LINE_LENGTH, mode)
1000 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
1001 self.assertIn(src, cache)
1002 self.assertEqual(cache[src], black.get_cache_info(src))
1004 def test_filter_cached(self) -> None:
1005 with TemporaryDirectory() as workspace:
1006 path = Path(workspace)
1007 uncached = (path / "uncached").resolve()
1008 cached = (path / "cached").resolve()
1009 cached_but_changed = (path / "changed").resolve()
1012 cached_but_changed.touch()
1013 cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
1014 todo, done = black.filter_cached(
1015 cache, {uncached, cached, cached_but_changed}
1017 self.assertEqual(todo, {uncached, cached_but_changed})
1018 self.assertEqual(done, {cached})
1020 def test_write_cache_creates_directory_if_needed(self) -> None:
1021 mode = black.FileMode.AUTO_DETECT
1022 with cache_dir(exists=False) as workspace:
1023 self.assertFalse(workspace.exists())
1024 black.write_cache({}, [], black.DEFAULT_LINE_LENGTH, mode)
1025 self.assertTrue(workspace.exists())
1027 @event_loop(close=False)
1028 def test_failed_formatting_does_not_get_cached(self) -> None:
1029 mode = black.FileMode.AUTO_DETECT
1030 with cache_dir() as workspace, patch(
1031 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1033 failing = (workspace / "failing.py").resolve()
1034 with failing.open("w") as fobj:
1035 fobj.write("not actually python")
1036 clean = (workspace / "clean.py").resolve()
1037 with clean.open("w") as fobj:
1038 fobj.write('print("hello")\n')
1039 result = CliRunner().invoke(black.main, [str(workspace)])
1040 self.assertEqual(result.exit_code, 123)
1041 cache = black.read_cache(black.DEFAULT_LINE_LENGTH, mode)
1042 self.assertNotIn(failing, cache)
1043 self.assertIn(clean, cache)
1045 def test_write_cache_write_fail(self) -> None:
1046 mode = black.FileMode.AUTO_DETECT
1047 with cache_dir(), patch.object(Path, "open") as mock:
1048 mock.side_effect = OSError
1049 black.write_cache({}, [], black.DEFAULT_LINE_LENGTH, mode)
1051 @event_loop(close=False)
1052 def test_check_diff_use_together(self) -> None:
1054 # Files which will be reformatted.
1055 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1056 result = CliRunner().invoke(black.main, [str(src1), "--diff", "--check"])
1057 self.assertEqual(result.exit_code, 1, result.output)
1058 # Files which will not be reformatted.
1059 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1060 result = CliRunner().invoke(black.main, [str(src2), "--diff", "--check"])
1061 self.assertEqual(result.exit_code, 0, result.output)
1062 # Multi file command.
1063 result = CliRunner().invoke(
1064 black.main, [str(src1), str(src2), "--diff", "--check"]
1066 self.assertEqual(result.exit_code, 1, result.output)
1068 def test_no_files(self) -> None:
1070 # Without an argument, black exits with error code 0.
1071 result = CliRunner().invoke(black.main, [])
1072 self.assertEqual(result.exit_code, 0)
1074 def test_broken_symlink(self) -> None:
1075 with cache_dir() as workspace:
1076 symlink = workspace / "broken_link.py"
1078 symlink.symlink_to("nonexistent.py")
1079 except OSError as e:
1080 self.skipTest(f"Can't create symlinks: {e}")
1081 result = CliRunner().invoke(black.main, [str(workspace.resolve())])
1082 self.assertEqual(result.exit_code, 0)
1084 def test_read_cache_line_lengths(self) -> None:
1085 mode = black.FileMode.AUTO_DETECT
1086 with cache_dir() as workspace:
1087 path = (workspace / "file.py").resolve()
1089 black.write_cache({}, [path], 1, mode)
1090 one = black.read_cache(1, mode)
1091 self.assertIn(path, one)
1092 two = black.read_cache(2, mode)
1093 self.assertNotIn(path, two)
1095 def test_single_file_force_pyi(self) -> None:
1096 reg_mode = black.FileMode.AUTO_DETECT
1097 pyi_mode = black.FileMode.PYI
1098 contents, expected = read_data("force_pyi")
1099 with cache_dir() as workspace:
1100 path = (workspace / "file.py").resolve()
1101 with open(path, "w") as fh:
1103 result = CliRunner().invoke(black.main, [str(path), "--pyi"])
1104 self.assertEqual(result.exit_code, 0)
1105 with open(path, "r") as fh:
1107 # verify cache with --pyi is separate
1108 pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, pyi_mode)
1109 self.assertIn(path, pyi_cache)
1110 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1111 self.assertNotIn(path, normal_cache)
1112 self.assertEqual(actual, expected)
1114 @event_loop(close=False)
1115 def test_multi_file_force_pyi(self) -> None:
1116 reg_mode = black.FileMode.AUTO_DETECT
1117 pyi_mode = black.FileMode.PYI
1118 contents, expected = read_data("force_pyi")
1119 with cache_dir() as workspace:
1121 (workspace / "file1.py").resolve(),
1122 (workspace / "file2.py").resolve(),
1125 with open(path, "w") as fh:
1127 result = CliRunner().invoke(black.main, [str(p) for p in paths] + ["--pyi"])
1128 self.assertEqual(result.exit_code, 0)
1130 with open(path, "r") as fh:
1132 self.assertEqual(actual, expected)
1133 # verify cache with --pyi is separate
1134 pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, pyi_mode)
1135 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1137 self.assertIn(path, pyi_cache)
1138 self.assertNotIn(path, normal_cache)
1140 def test_pipe_force_pyi(self) -> None:
1141 source, expected = read_data("force_pyi")
1142 result = CliRunner().invoke(
1143 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1145 self.assertEqual(result.exit_code, 0)
1146 actual = result.output
1147 self.assertFormatEqual(actual, expected)
1149 def test_single_file_force_py36(self) -> None:
1150 reg_mode = black.FileMode.AUTO_DETECT
1151 py36_mode = black.FileMode.PYTHON36
1152 source, expected = read_data("force_py36")
1153 with cache_dir() as workspace:
1154 path = (workspace / "file.py").resolve()
1155 with open(path, "w") as fh:
1157 result = CliRunner().invoke(black.main, [str(path), "--py36"])
1158 self.assertEqual(result.exit_code, 0)
1159 with open(path, "r") as fh:
1161 # verify cache with --py36 is separate
1162 py36_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, py36_mode)
1163 self.assertIn(path, py36_cache)
1164 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1165 self.assertNotIn(path, normal_cache)
1166 self.assertEqual(actual, expected)
1168 @event_loop(close=False)
1169 def test_multi_file_force_py36(self) -> None:
1170 reg_mode = black.FileMode.AUTO_DETECT
1171 py36_mode = black.FileMode.PYTHON36
1172 source, expected = read_data("force_py36")
1173 with cache_dir() as workspace:
1175 (workspace / "file1.py").resolve(),
1176 (workspace / "file2.py").resolve(),
1179 with open(path, "w") as fh:
1181 result = CliRunner().invoke(
1182 black.main, [str(p) for p in paths] + ["--py36"]
1184 self.assertEqual(result.exit_code, 0)
1186 with open(path, "r") as fh:
1188 self.assertEqual(actual, expected)
1189 # verify cache with --py36 is separate
1190 pyi_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, py36_mode)
1191 normal_cache = black.read_cache(black.DEFAULT_LINE_LENGTH, reg_mode)
1193 self.assertIn(path, pyi_cache)
1194 self.assertNotIn(path, normal_cache)
1196 def test_pipe_force_py36(self) -> None:
1197 source, expected = read_data("force_py36")
1198 result = CliRunner().invoke(
1199 black.main, ["-", "-q", "--py36"], input=BytesIO(source.encode("utf8"))
1201 self.assertEqual(result.exit_code, 0)
1202 actual = result.output
1203 self.assertFormatEqual(actual, expected)
1205 def test_include_exclude(self) -> None:
1206 path = THIS_DIR / "data" / "include_exclude_tests"
1207 include = re.compile(r"\.pyi?$")
1208 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1209 report = black.Report()
1210 sources: List[Path] = []
1212 Path(path / "b/dont_exclude/a.py"),
1213 Path(path / "b/dont_exclude/a.pyi"),
1215 this_abs = THIS_DIR.resolve()
1217 black.gen_python_files_in_dir(path, this_abs, include, exclude, report)
1219 self.assertEqual(sorted(expected), sorted(sources))
1221 def test_empty_include(self) -> None:
1222 path = THIS_DIR / "data" / "include_exclude_tests"
1223 report = black.Report()
1224 empty = re.compile(r"")
1225 sources: List[Path] = []
1227 Path(path / "b/exclude/a.pie"),
1228 Path(path / "b/exclude/a.py"),
1229 Path(path / "b/exclude/a.pyi"),
1230 Path(path / "b/dont_exclude/a.pie"),
1231 Path(path / "b/dont_exclude/a.py"),
1232 Path(path / "b/dont_exclude/a.pyi"),
1233 Path(path / "b/.definitely_exclude/a.pie"),
1234 Path(path / "b/.definitely_exclude/a.py"),
1235 Path(path / "b/.definitely_exclude/a.pyi"),
1237 this_abs = THIS_DIR.resolve()
1239 black.gen_python_files_in_dir(
1240 path, this_abs, empty, re.compile(black.DEFAULT_EXCLUDES), report
1243 self.assertEqual(sorted(expected), sorted(sources))
1245 def test_empty_exclude(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/dont_exclude/a.py"),
1252 Path(path / "b/dont_exclude/a.pyi"),
1253 Path(path / "b/exclude/a.py"),
1254 Path(path / "b/exclude/a.pyi"),
1255 Path(path / "b/.definitely_exclude/a.py"),
1256 Path(path / "b/.definitely_exclude/a.pyi"),
1258 this_abs = THIS_DIR.resolve()
1260 black.gen_python_files_in_dir(
1261 path, this_abs, re.compile(black.DEFAULT_INCLUDES), empty, report
1264 self.assertEqual(sorted(expected), sorted(sources))
1266 def test_invalid_include_exclude(self) -> None:
1267 for option in ["--include", "--exclude"]:
1268 result = CliRunner().invoke(black.main, ["-", option, "**()(!!*)"])
1269 self.assertEqual(result.exit_code, 2)
1271 def test_preserves_line_endings(self) -> None:
1272 with TemporaryDirectory() as workspace:
1273 test_file = Path(workspace) / "test.py"
1274 for nl in ["\n", "\r\n"]:
1275 contents = nl.join(["def f( ):", " pass"])
1276 test_file.write_bytes(contents.encode())
1277 ff(test_file, write_back=black.WriteBack.YES)
1278 updated_contents: bytes = test_file.read_bytes()
1279 self.assertIn(nl.encode(), updated_contents)
1281 self.assertNotIn(b"\r\n", updated_contents)
1283 def test_preserves_line_endings_via_stdin(self) -> None:
1284 for nl in ["\n", "\r\n"]:
1285 contents = nl.join(["def f( ):", " pass"])
1286 runner = BlackRunner()
1287 result = runner.invoke(
1288 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1290 self.assertEqual(result.exit_code, 0)
1291 output = runner.stdout_bytes
1292 self.assertIn(nl.encode("utf8"), output)
1294 self.assertNotIn(b"\r\n", output)
1296 def test_assert_equivalent_different_asts(self) -> None:
1297 with self.assertRaises(AssertionError):
1298 black.assert_equivalent("{}", "None")
1300 def test_symlink_out_of_root_directory(self) -> None:
1304 include = re.compile(black.DEFAULT_INCLUDES)
1305 exclude = re.compile(black.DEFAULT_EXCLUDES)
1306 report = black.Report()
1307 # `child` should behave like a symlink which resolved path is clearly
1308 # outside of the `root` directory.
1309 path.iterdir.return_value = [child]
1310 child.resolve.return_value = Path("/a/b/c")
1311 child.is_symlink.return_value = True
1313 list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1314 except ValueError as ve:
1315 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1316 path.iterdir.assert_called_once()
1317 child.resolve.assert_called_once()
1318 child.is_symlink.assert_called_once()
1319 # `child` should behave like a strange file which resolved path is clearly
1320 # outside of the `root` directory.
1321 child.is_symlink.return_value = False
1322 with self.assertRaises(ValueError):
1323 list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1324 path.iterdir.assert_called()
1325 self.assertEqual(path.iterdir.call_count, 2)
1326 child.resolve.assert_called()
1327 self.assertEqual(child.resolve.call_count, 2)
1328 child.is_symlink.assert_called()
1329 self.assertEqual(child.is_symlink.call_count, 2)
1331 def test_shhh_click(self) -> None:
1333 from click import _unicodefun # type: ignore
1334 except ModuleNotFoundError:
1335 self.skipTest("Incompatible Click version")
1336 if not hasattr(_unicodefun, "_verify_python3_env"):
1337 self.skipTest("Incompatible Click version")
1338 # First, let's see if Click is crashing with a preferred ASCII charset.
1339 with patch("locale.getpreferredencoding") as gpe:
1340 gpe.return_value = "ASCII"
1341 with self.assertRaises(RuntimeError):
1342 _unicodefun._verify_python3_env()
1343 # Now, let's silence Click...
1345 # ...and confirm it's silent.
1346 with patch("locale.getpreferredencoding") as gpe:
1347 gpe.return_value = "ASCII"
1349 _unicodefun._verify_python3_env()
1350 except RuntimeError as re:
1351 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1353 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1355 async def test_blackd_request_needs_formatting(self) -> None:
1356 app = blackd.make_app()
1357 async with TestClient(TestServer(app)) as client:
1358 response = await client.post("/", data=b"print('hello world')")
1359 self.assertEqual(response.status, 200)
1360 self.assertEqual(response.charset, "utf8")
1361 self.assertEqual(await response.read(), b'print("hello world")\n')
1363 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1365 async def test_blackd_request_no_change(self) -> None:
1366 app = blackd.make_app()
1367 async with TestClient(TestServer(app)) as client:
1368 response = await client.post("/", data=b'print("hello world")\n')
1369 self.assertEqual(response.status, 204)
1370 self.assertEqual(await response.read(), b"")
1372 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1374 async def test_blackd_request_syntax_error(self) -> None:
1375 app = blackd.make_app()
1376 async with TestClient(TestServer(app)) as client:
1377 response = await client.post("/", data=b"what even ( is")
1378 self.assertEqual(response.status, 400)
1379 content = await response.text()
1381 content.startswith("Cannot parse"),
1382 msg=f"Expected error to start with 'Cannot parse', got {repr(content)}",
1385 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1387 async def test_blackd_unsupported_version(self) -> None:
1388 app = blackd.make_app()
1389 async with TestClient(TestServer(app)) as client:
1390 response = await client.post(
1391 "/", data=b"what", headers={blackd.VERSION_HEADER: "2"}
1393 self.assertEqual(response.status, 501)
1395 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1397 async def test_blackd_supported_version(self) -> None:
1398 app = blackd.make_app()
1399 async with TestClient(TestServer(app)) as client:
1400 response = await client.post(
1401 "/", data=b"what", headers={blackd.VERSION_HEADER: "1"}
1403 self.assertEqual(response.status, 200)
1405 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1407 async def test_blackd_invalid_python_variant(self) -> None:
1408 app = blackd.make_app()
1409 async with TestClient(TestServer(app)) as client:
1410 response = await client.post(
1411 "/", data=b"what", headers={blackd.PYTHON_VARIANT_HEADER: "lol"}
1413 self.assertEqual(response.status, 400)
1415 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1417 async def test_blackd_pyi(self) -> None:
1418 app = blackd.make_app()
1419 async with TestClient(TestServer(app)) as client:
1420 source, expected = read_data("stub.pyi")
1421 response = await client.post(
1422 "/", data=source, headers={blackd.PYTHON_VARIANT_HEADER: "pyi"}
1424 self.assertEqual(response.status, 200)
1425 self.assertEqual(await response.text(), expected)
1427 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1429 async def test_blackd_py36(self) -> None:
1430 app = blackd.make_app()
1431 async with TestClient(TestServer(app)) as client:
1432 response = await client.post(
1436 " and_has_a_bunch_of,\n"
1437 " very_long_arguments_too,\n"
1438 " and_lots_of_them_as_well_lol,\n"
1439 " **and_very_long_keyword_arguments\n"
1443 headers={blackd.PYTHON_VARIANT_HEADER: "3.6"},
1445 self.assertEqual(response.status, 200)
1446 response = await client.post(
1450 " and_has_a_bunch_of,\n"
1451 " very_long_arguments_too,\n"
1452 " and_lots_of_them_as_well_lol,\n"
1453 " **and_very_long_keyword_arguments\n"
1457 headers={blackd.PYTHON_VARIANT_HEADER: "3.5"},
1459 self.assertEqual(response.status, 204)
1460 response = await client.post(
1464 " and_has_a_bunch_of,\n"
1465 " very_long_arguments_too,\n"
1466 " and_lots_of_them_as_well_lol,\n"
1467 " **and_very_long_keyword_arguments\n"
1471 headers={blackd.PYTHON_VARIANT_HEADER: "2"},
1473 self.assertEqual(response.status, 204)
1475 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1477 async def test_blackd_fast(self) -> None:
1478 with open(os.devnull, "w") as dn, redirect_stderr(dn):
1479 app = blackd.make_app()
1480 async with TestClient(TestServer(app)) as client:
1481 response = await client.post("/", data=b"ur'hello'")
1482 self.assertEqual(response.status, 500)
1483 self.assertIn("failed to parse source file", await response.text())
1484 response = await client.post(
1485 "/", data=b"ur'hello'", headers={blackd.FAST_OR_SAFE_HEADER: "fast"}
1487 self.assertEqual(response.status, 200)
1489 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1491 async def test_blackd_line_length(self) -> None:
1492 app = blackd.make_app()
1493 async with TestClient(TestServer(app)) as client:
1494 response = await client.post(
1495 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "7"}
1497 self.assertEqual(response.status, 200)
1499 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1501 async def test_blackd_invalid_line_length(self) -> None:
1502 app = blackd.make_app()
1503 async with TestClient(TestServer(app)) as client:
1504 response = await client.post(
1506 data=b'print("hello")\n',
1507 headers={blackd.LINE_LENGTH_HEADER: "NaN"},
1509 self.assertEqual(response.status, 400)
1511 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1512 def test_blackd_main(self) -> None:
1513 with patch("blackd.web.run_app"):
1514 result = CliRunner().invoke(blackd.main, [])
1515 if result.exception is not None:
1516 raise result.exception
1517 self.assertEqual(result.exit_code, 0)
1520 if __name__ == "__main__":
1521 unittest.main(module="test_black")