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, TargetVersion
35 from aiohttp.test_utils import TestClient, TestServer
37 has_blackd_deps = False
39 has_blackd_deps = True
41 ff = partial(black.format_file_in_place, mode=black.FileMode(), fast=True)
42 fs = partial(black.format_str, mode=black.FileMode())
43 THIS_FILE = Path(__file__)
44 THIS_DIR = THIS_FILE.parent
45 EMPTY_LINE = "# EMPTY LINE WITH WHITESPACE" + " (this comment will be removed)"
47 f"--target-version={version.name.lower()}" for version in black.PY36_VERSIONS
53 def dump_to_stderr(*output: str) -> str:
54 return "\n" + "\n".join(output) + "\n"
57 def read_data(name: str, data: bool = True) -> Tuple[str, str]:
58 """read_data('test_name') -> 'input', 'output'"""
59 if not name.endswith((".py", ".pyi", ".out", ".diff")):
61 _input: List[str] = []
62 _output: List[str] = []
63 base_dir = THIS_DIR / "data" if data else THIS_DIR
64 with open(base_dir / name, "r", encoding="utf8") as test:
65 lines = test.readlines()
68 line = line.replace(EMPTY_LINE, "")
69 if line.rstrip() == "# output":
74 if _input and not _output:
75 # If there's no output marker, treat the entire file as already pre-formatted.
77 return "".join(_input).strip() + "\n", "".join(_output).strip() + "\n"
81 def cache_dir(exists: bool = True) -> Iterator[Path]:
82 with TemporaryDirectory() as workspace:
83 cache_dir = Path(workspace)
85 cache_dir = cache_dir / "new"
86 with patch("black.CACHE_DIR", cache_dir):
91 def event_loop(close: bool) -> Iterator[None]:
92 policy = asyncio.get_event_loop_policy()
93 old_loop = policy.get_event_loop()
94 loop = policy.new_event_loop()
95 asyncio.set_event_loop(loop)
100 policy.set_event_loop(old_loop)
105 def async_test(f: Callable[..., Coroutine[Any, None, R]]) -> Callable[..., None]:
106 @event_loop(close=True)
108 def wrapper(*args: Any, **kwargs: Any) -> None:
109 asyncio.get_event_loop().run_until_complete(f(*args, **kwargs))
114 class BlackRunner(CliRunner):
115 """Modify CliRunner so that stderr is not merged with stdout.
117 This is a hack that can be removed once we depend on Click 7.x"""
119 def __init__(self) -> None:
120 self.stderrbuf = BytesIO()
121 self.stdoutbuf = BytesIO()
122 self.stdout_bytes = b""
123 self.stderr_bytes = b""
127 def isolation(self, *args: Any, **kwargs: Any) -> Generator[BinaryIO, None, None]:
128 with super().isolation(*args, **kwargs) as output:
130 hold_stderr = sys.stderr
131 sys.stderr = TextIOWrapper(self.stderrbuf, encoding=self.charset)
134 self.stdout_bytes = sys.stdout.buffer.getvalue() # type: ignore
135 self.stderr_bytes = sys.stderr.buffer.getvalue() # type: ignore
136 sys.stderr = hold_stderr
139 class BlackTestCase(unittest.TestCase):
142 def assertFormatEqual(self, expected: str, actual: str) -> None:
143 if actual != expected and not os.environ.get("SKIP_AST_PRINT"):
144 bdv: black.DebugVisitor[Any]
145 black.out("Expected tree:", fg="green")
147 exp_node = black.lib2to3_parse(expected)
148 bdv = black.DebugVisitor()
149 list(bdv.visit(exp_node))
150 except Exception as ve:
152 black.out("Actual tree:", fg="red")
154 exp_node = black.lib2to3_parse(actual)
155 bdv = black.DebugVisitor()
156 list(bdv.visit(exp_node))
157 except Exception as ve:
159 self.assertEqual(expected, actual)
162 self, args: List[str], exit_code: int = 0, ignore_config: bool = True
164 runner = BlackRunner()
166 args = ["--config", str(THIS_DIR / "empty.toml"), *args]
167 result = runner.invoke(black.main, args)
168 self.assertEqual(result.exit_code, exit_code, msg=runner.stderr_bytes.decode())
170 @patch("black.dump_to_file", dump_to_stderr)
171 def test_empty(self) -> None:
172 source = expected = ""
174 self.assertFormatEqual(expected, actual)
175 black.assert_equivalent(source, actual)
176 black.assert_stable(source, actual, black.FileMode())
178 def test_empty_ff(self) -> None:
180 tmp_file = Path(black.dump_to_file())
182 self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES))
183 with open(tmp_file, encoding="utf8") as f:
187 self.assertFormatEqual(expected, actual)
189 @patch("black.dump_to_file", dump_to_stderr)
190 def test_self(self) -> None:
191 source, expected = read_data("test_black", data=False)
193 self.assertFormatEqual(expected, actual)
194 black.assert_equivalent(source, actual)
195 black.assert_stable(source, actual, black.FileMode())
196 self.assertFalse(ff(THIS_FILE))
198 @patch("black.dump_to_file", dump_to_stderr)
199 def test_black(self) -> None:
200 source, expected = read_data("../black", data=False)
202 self.assertFormatEqual(expected, actual)
203 black.assert_equivalent(source, actual)
204 black.assert_stable(source, actual, black.FileMode())
205 self.assertFalse(ff(THIS_DIR / ".." / "black.py"))
207 def test_piping(self) -> None:
208 source, expected = read_data("../black", data=False)
209 result = BlackRunner().invoke(
211 ["-", "--fast", f"--line-length={black.DEFAULT_LINE_LENGTH}"],
212 input=BytesIO(source.encode("utf8")),
214 self.assertEqual(result.exit_code, 0)
215 self.assertFormatEqual(expected, result.output)
216 black.assert_equivalent(source, result.output)
217 black.assert_stable(source, result.output, black.FileMode())
219 def test_piping_diff(self) -> None:
220 diff_header = re.compile(
221 rf"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d "
222 rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
224 source, _ = read_data("expression.py")
225 expected, _ = read_data("expression.diff")
226 config = THIS_DIR / "data" / "empty_pyproject.toml"
230 f"--line-length={black.DEFAULT_LINE_LENGTH}",
232 f"--config={config}",
234 result = BlackRunner().invoke(
235 black.main, args, input=BytesIO(source.encode("utf8"))
237 self.assertEqual(result.exit_code, 0)
238 actual = diff_header.sub("[Deterministic header]", result.output)
239 actual = actual.rstrip() + "\n" # the diff output has a trailing space
240 self.assertEqual(expected, actual)
242 @patch("black.dump_to_file", dump_to_stderr)
243 def test_setup(self) -> None:
244 source, expected = read_data("../setup", data=False)
246 self.assertFormatEqual(expected, actual)
247 black.assert_equivalent(source, actual)
248 black.assert_stable(source, actual, black.FileMode())
249 self.assertFalse(ff(THIS_DIR / ".." / "setup.py"))
251 @patch("black.dump_to_file", dump_to_stderr)
252 def test_function(self) -> None:
253 source, expected = read_data("function")
255 self.assertFormatEqual(expected, actual)
256 black.assert_equivalent(source, actual)
257 black.assert_stable(source, actual, black.FileMode())
259 @patch("black.dump_to_file", dump_to_stderr)
260 def test_function2(self) -> None:
261 source, expected = read_data("function2")
263 self.assertFormatEqual(expected, actual)
264 black.assert_equivalent(source, actual)
265 black.assert_stable(source, actual, black.FileMode())
267 @patch("black.dump_to_file", dump_to_stderr)
268 def test_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_comments7(self) -> None:
393 source, expected = read_data("comments7")
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_cantfit(self) -> None:
401 source, expected = read_data("cantfit")
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_import_spacing(self) -> None:
409 source, expected = read_data("import_spacing")
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_composition(self) -> None:
417 source, expected = read_data("composition")
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_empty_lines(self) -> None:
425 source, expected = read_data("empty_lines")
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_string_prefixes(self) -> None:
433 source, expected = read_data("string_prefixes")
435 self.assertFormatEqual(expected, actual)
436 black.assert_equivalent(source, actual)
437 black.assert_stable(source, actual, black.FileMode())
439 @patch("black.dump_to_file", dump_to_stderr)
440 def test_numeric_literals(self) -> None:
441 source, expected = read_data("numeric_literals")
442 mode = black.FileMode(target_versions=black.PY36_VERSIONS)
443 actual = fs(source, mode=mode)
444 self.assertFormatEqual(expected, actual)
445 black.assert_equivalent(source, actual)
446 black.assert_stable(source, actual, mode)
448 @patch("black.dump_to_file", dump_to_stderr)
449 def test_numeric_literals_ignoring_underscores(self) -> None:
450 source, expected = read_data("numeric_literals_skip_underscores")
451 mode = black.FileMode(target_versions=black.PY36_VERSIONS)
452 actual = fs(source, mode=mode)
453 self.assertFormatEqual(expected, actual)
454 black.assert_equivalent(source, actual)
455 black.assert_stable(source, actual, mode)
457 @patch("black.dump_to_file", dump_to_stderr)
458 def test_numeric_literals_py2(self) -> None:
459 source, expected = read_data("numeric_literals_py2")
461 self.assertFormatEqual(expected, actual)
462 black.assert_stable(source, actual, black.FileMode())
464 @patch("black.dump_to_file", dump_to_stderr)
465 def test_python2(self) -> None:
466 source, expected = read_data("python2")
468 self.assertFormatEqual(expected, actual)
469 # black.assert_equivalent(source, actual)
470 black.assert_stable(source, actual, black.FileMode())
472 @patch("black.dump_to_file", dump_to_stderr)
473 def test_python2_print_function(self) -> None:
474 source, expected = read_data("python2_print_function")
475 mode = black.FileMode(target_versions={TargetVersion.PY27})
476 actual = fs(source, mode=mode)
477 self.assertFormatEqual(expected, actual)
478 black.assert_stable(source, actual, mode)
480 @patch("black.dump_to_file", dump_to_stderr)
481 def test_python2_unicode_literals(self) -> None:
482 source, expected = read_data("python2_unicode_literals")
484 self.assertFormatEqual(expected, actual)
485 black.assert_stable(source, actual, black.FileMode())
487 @patch("black.dump_to_file", dump_to_stderr)
488 def test_stub(self) -> None:
489 mode = black.FileMode(is_pyi=True)
490 source, expected = read_data("stub.pyi")
491 actual = fs(source, mode=mode)
492 self.assertFormatEqual(expected, actual)
493 black.assert_stable(source, actual, mode)
495 @patch("black.dump_to_file", dump_to_stderr)
496 def test_python37(self) -> None:
497 source, expected = read_data("python37")
499 self.assertFormatEqual(expected, actual)
500 major, minor = sys.version_info[:2]
501 if major > 3 or (major == 3 and minor >= 7):
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_fmtonoff(self) -> None:
507 source, expected = read_data("fmtonoff")
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_fmtonoff2(self) -> None:
515 source, expected = read_data("fmtonoff2")
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_remove_empty_parentheses_after_class(self) -> None:
523 source, expected = read_data("class_blank_parentheses")
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_new_line_between_class_and_code(self) -> None:
531 source, expected = read_data("class_methods_new_line")
533 self.assertFormatEqual(expected, actual)
534 black.assert_equivalent(source, actual)
535 black.assert_stable(source, actual, black.FileMode())
537 @patch("black.dump_to_file", dump_to_stderr)
538 def test_bracket_match(self) -> None:
539 source, expected = read_data("bracketmatch")
541 self.assertFormatEqual(expected, actual)
542 black.assert_equivalent(source, actual)
543 black.assert_stable(source, actual, black.FileMode())
545 @patch("black.dump_to_file", dump_to_stderr)
546 def test_tuple_assign(self) -> None:
547 source, expected = read_data("tupleassign")
549 self.assertFormatEqual(expected, actual)
550 black.assert_equivalent(source, actual)
551 black.assert_stable(source, actual, black.FileMode())
553 def test_tab_comment_indentation(self) -> None:
554 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\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 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
560 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
561 self.assertFormatEqual(contents_spc, fs(contents_spc))
562 self.assertFormatEqual(contents_spc, fs(contents_tab))
564 # mixed tabs and spaces (valid Python 2 code)
565 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t# comment\n pass\n"
566 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
567 self.assertFormatEqual(contents_spc, fs(contents_spc))
568 self.assertFormatEqual(contents_spc, fs(contents_tab))
570 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t\t# comment\n pass\n"
571 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
572 self.assertFormatEqual(contents_spc, fs(contents_spc))
573 self.assertFormatEqual(contents_spc, fs(contents_tab))
575 def test_report_verbose(self) -> None:
576 report = black.Report(verbose=True)
580 def out(msg: str, **kwargs: Any) -> None:
581 out_lines.append(msg)
583 def err(msg: str, **kwargs: Any) -> None:
584 err_lines.append(msg)
586 with patch("black.out", out), patch("black.err", err):
587 report.done(Path("f1"), black.Changed.NO)
588 self.assertEqual(len(out_lines), 1)
589 self.assertEqual(len(err_lines), 0)
590 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
591 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
592 self.assertEqual(report.return_code, 0)
593 report.done(Path("f2"), black.Changed.YES)
594 self.assertEqual(len(out_lines), 2)
595 self.assertEqual(len(err_lines), 0)
596 self.assertEqual(out_lines[-1], "reformatted f2")
598 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
600 report.done(Path("f3"), black.Changed.CACHED)
601 self.assertEqual(len(out_lines), 3)
602 self.assertEqual(len(err_lines), 0)
604 out_lines[-1], "f3 wasn't modified on disk since last run."
607 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
609 self.assertEqual(report.return_code, 0)
611 self.assertEqual(report.return_code, 1)
613 report.failed(Path("e1"), "boom")
614 self.assertEqual(len(out_lines), 3)
615 self.assertEqual(len(err_lines), 1)
616 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
618 unstyle(str(report)),
619 "1 file reformatted, 2 files left unchanged, "
620 "1 file failed to reformat.",
622 self.assertEqual(report.return_code, 123)
623 report.done(Path("f3"), black.Changed.YES)
624 self.assertEqual(len(out_lines), 4)
625 self.assertEqual(len(err_lines), 1)
626 self.assertEqual(out_lines[-1], "reformatted f3")
628 unstyle(str(report)),
629 "2 files reformatted, 2 files left unchanged, "
630 "1 file failed to reformat.",
632 self.assertEqual(report.return_code, 123)
633 report.failed(Path("e2"), "boom")
634 self.assertEqual(len(out_lines), 4)
635 self.assertEqual(len(err_lines), 2)
636 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
638 unstyle(str(report)),
639 "2 files reformatted, 2 files left unchanged, "
640 "2 files failed to reformat.",
642 self.assertEqual(report.return_code, 123)
643 report.path_ignored(Path("wat"), "no match")
644 self.assertEqual(len(out_lines), 5)
645 self.assertEqual(len(err_lines), 2)
646 self.assertEqual(out_lines[-1], "wat ignored: no match")
648 unstyle(str(report)),
649 "2 files reformatted, 2 files left unchanged, "
650 "2 files failed to reformat.",
652 self.assertEqual(report.return_code, 123)
653 report.done(Path("f4"), black.Changed.NO)
654 self.assertEqual(len(out_lines), 6)
655 self.assertEqual(len(err_lines), 2)
656 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
658 unstyle(str(report)),
659 "2 files reformatted, 3 files left unchanged, "
660 "2 files failed to reformat.",
662 self.assertEqual(report.return_code, 123)
665 unstyle(str(report)),
666 "2 files would be reformatted, 3 files would be left unchanged, "
667 "2 files would fail to reformat.",
670 def test_report_quiet(self) -> None:
671 report = black.Report(quiet=True)
675 def out(msg: str, **kwargs: Any) -> None:
676 out_lines.append(msg)
678 def err(msg: str, **kwargs: Any) -> None:
679 err_lines.append(msg)
681 with patch("black.out", out), patch("black.err", err):
682 report.done(Path("f1"), black.Changed.NO)
683 self.assertEqual(len(out_lines), 0)
684 self.assertEqual(len(err_lines), 0)
685 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
686 self.assertEqual(report.return_code, 0)
687 report.done(Path("f2"), black.Changed.YES)
688 self.assertEqual(len(out_lines), 0)
689 self.assertEqual(len(err_lines), 0)
691 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
693 report.done(Path("f3"), black.Changed.CACHED)
694 self.assertEqual(len(out_lines), 0)
695 self.assertEqual(len(err_lines), 0)
697 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
699 self.assertEqual(report.return_code, 0)
701 self.assertEqual(report.return_code, 1)
703 report.failed(Path("e1"), "boom")
704 self.assertEqual(len(out_lines), 0)
705 self.assertEqual(len(err_lines), 1)
706 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
708 unstyle(str(report)),
709 "1 file reformatted, 2 files left unchanged, "
710 "1 file failed to reformat.",
712 self.assertEqual(report.return_code, 123)
713 report.done(Path("f3"), black.Changed.YES)
714 self.assertEqual(len(out_lines), 0)
715 self.assertEqual(len(err_lines), 1)
717 unstyle(str(report)),
718 "2 files reformatted, 2 files left unchanged, "
719 "1 file failed to reformat.",
721 self.assertEqual(report.return_code, 123)
722 report.failed(Path("e2"), "boom")
723 self.assertEqual(len(out_lines), 0)
724 self.assertEqual(len(err_lines), 2)
725 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
727 unstyle(str(report)),
728 "2 files reformatted, 2 files left unchanged, "
729 "2 files failed to reformat.",
731 self.assertEqual(report.return_code, 123)
732 report.path_ignored(Path("wat"), "no match")
733 self.assertEqual(len(out_lines), 0)
734 self.assertEqual(len(err_lines), 2)
736 unstyle(str(report)),
737 "2 files reformatted, 2 files left unchanged, "
738 "2 files failed to reformat.",
740 self.assertEqual(report.return_code, 123)
741 report.done(Path("f4"), black.Changed.NO)
742 self.assertEqual(len(out_lines), 0)
743 self.assertEqual(len(err_lines), 2)
745 unstyle(str(report)),
746 "2 files reformatted, 3 files left unchanged, "
747 "2 files failed to reformat.",
749 self.assertEqual(report.return_code, 123)
752 unstyle(str(report)),
753 "2 files would be reformatted, 3 files would be left unchanged, "
754 "2 files would fail to reformat.",
757 def test_report_normal(self) -> None:
758 report = black.Report()
762 def out(msg: str, **kwargs: Any) -> None:
763 out_lines.append(msg)
765 def err(msg: str, **kwargs: Any) -> None:
766 err_lines.append(msg)
768 with patch("black.out", out), patch("black.err", err):
769 report.done(Path("f1"), black.Changed.NO)
770 self.assertEqual(len(out_lines), 0)
771 self.assertEqual(len(err_lines), 0)
772 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
773 self.assertEqual(report.return_code, 0)
774 report.done(Path("f2"), black.Changed.YES)
775 self.assertEqual(len(out_lines), 1)
776 self.assertEqual(len(err_lines), 0)
777 self.assertEqual(out_lines[-1], "reformatted f2")
779 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
781 report.done(Path("f3"), black.Changed.CACHED)
782 self.assertEqual(len(out_lines), 1)
783 self.assertEqual(len(err_lines), 0)
784 self.assertEqual(out_lines[-1], "reformatted f2")
786 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
788 self.assertEqual(report.return_code, 0)
790 self.assertEqual(report.return_code, 1)
792 report.failed(Path("e1"), "boom")
793 self.assertEqual(len(out_lines), 1)
794 self.assertEqual(len(err_lines), 1)
795 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
797 unstyle(str(report)),
798 "1 file reformatted, 2 files left unchanged, "
799 "1 file failed to reformat.",
801 self.assertEqual(report.return_code, 123)
802 report.done(Path("f3"), black.Changed.YES)
803 self.assertEqual(len(out_lines), 2)
804 self.assertEqual(len(err_lines), 1)
805 self.assertEqual(out_lines[-1], "reformatted f3")
807 unstyle(str(report)),
808 "2 files reformatted, 2 files left unchanged, "
809 "1 file failed to reformat.",
811 self.assertEqual(report.return_code, 123)
812 report.failed(Path("e2"), "boom")
813 self.assertEqual(len(out_lines), 2)
814 self.assertEqual(len(err_lines), 2)
815 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
817 unstyle(str(report)),
818 "2 files reformatted, 2 files left unchanged, "
819 "2 files failed to reformat.",
821 self.assertEqual(report.return_code, 123)
822 report.path_ignored(Path("wat"), "no match")
823 self.assertEqual(len(out_lines), 2)
824 self.assertEqual(len(err_lines), 2)
826 unstyle(str(report)),
827 "2 files reformatted, 2 files left unchanged, "
828 "2 files failed to reformat.",
830 self.assertEqual(report.return_code, 123)
831 report.done(Path("f4"), black.Changed.NO)
832 self.assertEqual(len(out_lines), 2)
833 self.assertEqual(len(err_lines), 2)
835 unstyle(str(report)),
836 "2 files reformatted, 3 files left unchanged, "
837 "2 files failed to reformat.",
839 self.assertEqual(report.return_code, 123)
842 unstyle(str(report)),
843 "2 files would be reformatted, 3 files would be left unchanged, "
844 "2 files would fail to reformat.",
847 def test_lib2to3_parse(self) -> None:
848 with self.assertRaises(black.InvalidInput):
849 black.lib2to3_parse("invalid syntax")
852 black.lib2to3_parse(straddling)
853 black.lib2to3_parse(straddling, {TargetVersion.PY27})
854 black.lib2to3_parse(straddling, {TargetVersion.PY36})
855 black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
858 black.lib2to3_parse(py2_only)
859 black.lib2to3_parse(py2_only, {TargetVersion.PY27})
860 with self.assertRaises(black.InvalidInput):
861 black.lib2to3_parse(py2_only, {TargetVersion.PY36})
862 with self.assertRaises(black.InvalidInput):
863 black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
865 py3_only = "exec(x, end=y)"
866 black.lib2to3_parse(py3_only)
867 with self.assertRaises(black.InvalidInput):
868 black.lib2to3_parse(py3_only, {TargetVersion.PY27})
869 black.lib2to3_parse(py3_only, {TargetVersion.PY36})
870 black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
872 def test_get_features_used(self) -> None:
873 node = black.lib2to3_parse("def f(*, arg): ...\n")
874 self.assertEqual(black.get_features_used(node), set())
875 node = black.lib2to3_parse("def f(*, arg,): ...\n")
876 self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
877 node = black.lib2to3_parse("f(*arg,)\n")
879 black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
881 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
882 self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
883 node = black.lib2to3_parse("123_456\n")
884 self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
885 node = black.lib2to3_parse("123456\n")
886 self.assertEqual(black.get_features_used(node), set())
887 source, expected = read_data("function")
888 node = black.lib2to3_parse(source)
889 expected_features = {
890 Feature.TRAILING_COMMA_IN_CALL,
891 Feature.TRAILING_COMMA_IN_DEF,
894 self.assertEqual(black.get_features_used(node), expected_features)
895 node = black.lib2to3_parse(expected)
896 self.assertEqual(black.get_features_used(node), expected_features)
897 source, expected = read_data("expression")
898 node = black.lib2to3_parse(source)
899 self.assertEqual(black.get_features_used(node), set())
900 node = black.lib2to3_parse(expected)
901 self.assertEqual(black.get_features_used(node), set())
903 def test_get_future_imports(self) -> None:
904 node = black.lib2to3_parse("\n")
905 self.assertEqual(set(), black.get_future_imports(node))
906 node = black.lib2to3_parse("from __future__ import black\n")
907 self.assertEqual({"black"}, black.get_future_imports(node))
908 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
909 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
910 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
911 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
912 node = black.lib2to3_parse(
913 "from __future__ import multiple\nfrom __future__ import imports\n"
915 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
916 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
917 self.assertEqual({"black"}, black.get_future_imports(node))
918 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
919 self.assertEqual({"black"}, black.get_future_imports(node))
920 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
921 self.assertEqual(set(), black.get_future_imports(node))
922 node = black.lib2to3_parse("from some.module import black\n")
923 self.assertEqual(set(), black.get_future_imports(node))
924 node = black.lib2to3_parse(
925 "from __future__ import unicode_literals as _unicode_literals"
927 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
928 node = black.lib2to3_parse(
929 "from __future__ import unicode_literals as _lol, print"
931 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
933 def test_debug_visitor(self) -> None:
934 source, _ = read_data("debug_visitor.py")
935 expected, _ = read_data("debug_visitor.out")
939 def out(msg: str, **kwargs: Any) -> None:
940 out_lines.append(msg)
942 def err(msg: str, **kwargs: Any) -> None:
943 err_lines.append(msg)
945 with patch("black.out", out), patch("black.err", err):
946 black.DebugVisitor.show(source)
947 actual = "\n".join(out_lines) + "\n"
949 if expected != actual:
950 log_name = black.dump_to_file(*out_lines)
954 f"AST print out is different. Actual version dumped to {log_name}",
957 def test_format_file_contents(self) -> None:
959 mode = black.FileMode()
960 with self.assertRaises(black.NothingChanged):
961 black.format_file_contents(empty, mode=mode, fast=False)
963 with self.assertRaises(black.NothingChanged):
964 black.format_file_contents(just_nl, mode=mode, fast=False)
965 same = "l = [1, 2, 3]\n"
966 with self.assertRaises(black.NothingChanged):
967 black.format_file_contents(same, mode=mode, fast=False)
968 different = "l = [1,2,3]"
970 actual = black.format_file_contents(different, mode=mode, fast=False)
971 self.assertEqual(expected, actual)
972 invalid = "return if you can"
973 with self.assertRaises(black.InvalidInput) as e:
974 black.format_file_contents(invalid, mode=mode, fast=False)
975 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
977 def test_endmarker(self) -> None:
978 n = black.lib2to3_parse("\n")
979 self.assertEqual(n.type, black.syms.file_input)
980 self.assertEqual(len(n.children), 1)
981 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
983 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
984 def test_assertFormatEqual(self) -> None:
988 def out(msg: str, **kwargs: Any) -> None:
989 out_lines.append(msg)
991 def err(msg: str, **kwargs: Any) -> None:
992 err_lines.append(msg)
994 with patch("black.out", out), patch("black.err", err):
995 with self.assertRaises(AssertionError):
996 self.assertFormatEqual("l = [1, 2, 3]", "l = [1, 2, 3,]")
998 out_str = "".join(out_lines)
999 self.assertTrue("Expected tree:" in out_str)
1000 self.assertTrue("Actual tree:" in out_str)
1001 self.assertEqual("".join(err_lines), "")
1003 def test_cache_broken_file(self) -> None:
1004 mode = black.FileMode()
1005 with cache_dir() as workspace:
1006 cache_file = black.get_cache_file(mode)
1007 with cache_file.open("w") as fobj:
1008 fobj.write("this is not a pickle")
1009 self.assertEqual(black.read_cache(mode), {})
1010 src = (workspace / "test.py").resolve()
1011 with src.open("w") as fobj:
1012 fobj.write("print('hello')")
1013 self.invokeBlack([str(src)])
1014 cache = black.read_cache(mode)
1015 self.assertIn(src, cache)
1017 def test_cache_single_file_already_cached(self) -> None:
1018 mode = black.FileMode()
1019 with cache_dir() as workspace:
1020 src = (workspace / "test.py").resolve()
1021 with src.open("w") as fobj:
1022 fobj.write("print('hello')")
1023 black.write_cache({}, [src], mode)
1024 self.invokeBlack([str(src)])
1025 with src.open("r") as fobj:
1026 self.assertEqual(fobj.read(), "print('hello')")
1028 @event_loop(close=False)
1029 def test_cache_multiple_files(self) -> None:
1030 mode = black.FileMode()
1031 with cache_dir() as workspace, patch(
1032 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1034 one = (workspace / "one.py").resolve()
1035 with one.open("w") as fobj:
1036 fobj.write("print('hello')")
1037 two = (workspace / "two.py").resolve()
1038 with two.open("w") as fobj:
1039 fobj.write("print('hello')")
1040 black.write_cache({}, [one], mode)
1041 self.invokeBlack([str(workspace)])
1042 with one.open("r") as fobj:
1043 self.assertEqual(fobj.read(), "print('hello')")
1044 with two.open("r") as fobj:
1045 self.assertEqual(fobj.read(), 'print("hello")\n')
1046 cache = black.read_cache(mode)
1047 self.assertIn(one, cache)
1048 self.assertIn(two, cache)
1050 def test_no_cache_when_writeback_diff(self) -> None:
1051 mode = black.FileMode()
1052 with cache_dir() as workspace:
1053 src = (workspace / "test.py").resolve()
1054 with src.open("w") as fobj:
1055 fobj.write("print('hello')")
1056 self.invokeBlack([str(src), "--diff"])
1057 cache_file = black.get_cache_file(mode)
1058 self.assertFalse(cache_file.exists())
1060 def test_no_cache_when_stdin(self) -> None:
1061 mode = black.FileMode()
1063 result = CliRunner().invoke(
1064 black.main, ["-"], input=BytesIO(b"print('hello')")
1066 self.assertEqual(result.exit_code, 0)
1067 cache_file = black.get_cache_file(mode)
1068 self.assertFalse(cache_file.exists())
1070 def test_read_cache_no_cachefile(self) -> None:
1071 mode = black.FileMode()
1073 self.assertEqual(black.read_cache(mode), {})
1075 def test_write_cache_read_cache(self) -> None:
1076 mode = black.FileMode()
1077 with cache_dir() as workspace:
1078 src = (workspace / "test.py").resolve()
1080 black.write_cache({}, [src], mode)
1081 cache = black.read_cache(mode)
1082 self.assertIn(src, cache)
1083 self.assertEqual(cache[src], black.get_cache_info(src))
1085 def test_filter_cached(self) -> None:
1086 with TemporaryDirectory() as workspace:
1087 path = Path(workspace)
1088 uncached = (path / "uncached").resolve()
1089 cached = (path / "cached").resolve()
1090 cached_but_changed = (path / "changed").resolve()
1093 cached_but_changed.touch()
1094 cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
1095 todo, done = black.filter_cached(
1096 cache, {uncached, cached, cached_but_changed}
1098 self.assertEqual(todo, {uncached, cached_but_changed})
1099 self.assertEqual(done, {cached})
1101 def test_write_cache_creates_directory_if_needed(self) -> None:
1102 mode = black.FileMode()
1103 with cache_dir(exists=False) as workspace:
1104 self.assertFalse(workspace.exists())
1105 black.write_cache({}, [], mode)
1106 self.assertTrue(workspace.exists())
1108 @event_loop(close=False)
1109 def test_failed_formatting_does_not_get_cached(self) -> None:
1110 mode = black.FileMode()
1111 with cache_dir() as workspace, patch(
1112 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1114 failing = (workspace / "failing.py").resolve()
1115 with failing.open("w") as fobj:
1116 fobj.write("not actually python")
1117 clean = (workspace / "clean.py").resolve()
1118 with clean.open("w") as fobj:
1119 fobj.write('print("hello")\n')
1120 self.invokeBlack([str(workspace)], exit_code=123)
1121 cache = black.read_cache(mode)
1122 self.assertNotIn(failing, cache)
1123 self.assertIn(clean, cache)
1125 def test_write_cache_write_fail(self) -> None:
1126 mode = black.FileMode()
1127 with cache_dir(), patch.object(Path, "open") as mock:
1128 mock.side_effect = OSError
1129 black.write_cache({}, [], mode)
1131 @event_loop(close=False)
1132 def test_check_diff_use_together(self) -> None:
1134 # Files which will be reformatted.
1135 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1136 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1137 # Files which will not be reformatted.
1138 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1139 self.invokeBlack([str(src2), "--diff", "--check"])
1140 # Multi file command.
1141 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1143 def test_no_files(self) -> None:
1145 # Without an argument, black exits with error code 0.
1146 self.invokeBlack([])
1148 def test_broken_symlink(self) -> None:
1149 with cache_dir() as workspace:
1150 symlink = workspace / "broken_link.py"
1152 symlink.symlink_to("nonexistent.py")
1153 except OSError as e:
1154 self.skipTest(f"Can't create symlinks: {e}")
1155 self.invokeBlack([str(workspace.resolve())])
1157 def test_read_cache_line_lengths(self) -> None:
1158 mode = black.FileMode()
1159 short_mode = black.FileMode(line_length=1)
1160 with cache_dir() as workspace:
1161 path = (workspace / "file.py").resolve()
1163 black.write_cache({}, [path], mode)
1164 one = black.read_cache(mode)
1165 self.assertIn(path, one)
1166 two = black.read_cache(short_mode)
1167 self.assertNotIn(path, two)
1169 def test_single_file_force_pyi(self) -> None:
1170 reg_mode = black.FileMode()
1171 pyi_mode = black.FileMode(is_pyi=True)
1172 contents, expected = read_data("force_pyi")
1173 with cache_dir() as workspace:
1174 path = (workspace / "file.py").resolve()
1175 with open(path, "w") as fh:
1177 self.invokeBlack([str(path), "--pyi"])
1178 with open(path, "r") as fh:
1180 # verify cache with --pyi is separate
1181 pyi_cache = black.read_cache(pyi_mode)
1182 self.assertIn(path, pyi_cache)
1183 normal_cache = black.read_cache(reg_mode)
1184 self.assertNotIn(path, normal_cache)
1185 self.assertEqual(actual, expected)
1187 @event_loop(close=False)
1188 def test_multi_file_force_pyi(self) -> None:
1189 reg_mode = black.FileMode()
1190 pyi_mode = black.FileMode(is_pyi=True)
1191 contents, expected = read_data("force_pyi")
1192 with cache_dir() as workspace:
1194 (workspace / "file1.py").resolve(),
1195 (workspace / "file2.py").resolve(),
1198 with open(path, "w") as fh:
1200 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1202 with open(path, "r") as fh:
1204 self.assertEqual(actual, expected)
1205 # verify cache with --pyi is separate
1206 pyi_cache = black.read_cache(pyi_mode)
1207 normal_cache = black.read_cache(reg_mode)
1209 self.assertIn(path, pyi_cache)
1210 self.assertNotIn(path, normal_cache)
1212 def test_pipe_force_pyi(self) -> None:
1213 source, expected = read_data("force_pyi")
1214 result = CliRunner().invoke(
1215 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1217 self.assertEqual(result.exit_code, 0)
1218 actual = result.output
1219 self.assertFormatEqual(actual, expected)
1221 def test_single_file_force_py36(self) -> None:
1222 reg_mode = black.FileMode()
1223 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1224 source, expected = read_data("force_py36")
1225 with cache_dir() as workspace:
1226 path = (workspace / "file.py").resolve()
1227 with open(path, "w") as fh:
1229 self.invokeBlack([str(path), *PY36_ARGS])
1230 with open(path, "r") as fh:
1232 # verify cache with --target-version is separate
1233 py36_cache = black.read_cache(py36_mode)
1234 self.assertIn(path, py36_cache)
1235 normal_cache = black.read_cache(reg_mode)
1236 self.assertNotIn(path, normal_cache)
1237 self.assertEqual(actual, expected)
1239 @event_loop(close=False)
1240 def test_multi_file_force_py36(self) -> None:
1241 reg_mode = black.FileMode()
1242 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1243 source, expected = read_data("force_py36")
1244 with cache_dir() as workspace:
1246 (workspace / "file1.py").resolve(),
1247 (workspace / "file2.py").resolve(),
1250 with open(path, "w") as fh:
1252 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1254 with open(path, "r") as fh:
1256 self.assertEqual(actual, expected)
1257 # verify cache with --target-version is separate
1258 pyi_cache = black.read_cache(py36_mode)
1259 normal_cache = black.read_cache(reg_mode)
1261 self.assertIn(path, pyi_cache)
1262 self.assertNotIn(path, normal_cache)
1264 def test_pipe_force_py36(self) -> None:
1265 source, expected = read_data("force_py36")
1266 result = CliRunner().invoke(
1268 ["-", "-q", "--target-version=py36"],
1269 input=BytesIO(source.encode("utf8")),
1271 self.assertEqual(result.exit_code, 0)
1272 actual = result.output
1273 self.assertFormatEqual(actual, expected)
1275 def test_include_exclude(self) -> None:
1276 path = THIS_DIR / "data" / "include_exclude_tests"
1277 include = re.compile(r"\.pyi?$")
1278 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1279 report = black.Report()
1280 sources: List[Path] = []
1282 Path(path / "b/dont_exclude/a.py"),
1283 Path(path / "b/dont_exclude/a.pyi"),
1285 this_abs = THIS_DIR.resolve()
1287 black.gen_python_files_in_dir(path, this_abs, include, exclude, report)
1289 self.assertEqual(sorted(expected), sorted(sources))
1291 def test_empty_include(self) -> None:
1292 path = THIS_DIR / "data" / "include_exclude_tests"
1293 report = black.Report()
1294 empty = re.compile(r"")
1295 sources: List[Path] = []
1297 Path(path / "b/exclude/a.pie"),
1298 Path(path / "b/exclude/a.py"),
1299 Path(path / "b/exclude/a.pyi"),
1300 Path(path / "b/dont_exclude/a.pie"),
1301 Path(path / "b/dont_exclude/a.py"),
1302 Path(path / "b/dont_exclude/a.pyi"),
1303 Path(path / "b/.definitely_exclude/a.pie"),
1304 Path(path / "b/.definitely_exclude/a.py"),
1305 Path(path / "b/.definitely_exclude/a.pyi"),
1307 this_abs = THIS_DIR.resolve()
1309 black.gen_python_files_in_dir(
1310 path, this_abs, empty, re.compile(black.DEFAULT_EXCLUDES), report
1313 self.assertEqual(sorted(expected), sorted(sources))
1315 def test_empty_exclude(self) -> None:
1316 path = THIS_DIR / "data" / "include_exclude_tests"
1317 report = black.Report()
1318 empty = re.compile(r"")
1319 sources: List[Path] = []
1321 Path(path / "b/dont_exclude/a.py"),
1322 Path(path / "b/dont_exclude/a.pyi"),
1323 Path(path / "b/exclude/a.py"),
1324 Path(path / "b/exclude/a.pyi"),
1325 Path(path / "b/.definitely_exclude/a.py"),
1326 Path(path / "b/.definitely_exclude/a.pyi"),
1328 this_abs = THIS_DIR.resolve()
1330 black.gen_python_files_in_dir(
1331 path, this_abs, re.compile(black.DEFAULT_INCLUDES), empty, report
1334 self.assertEqual(sorted(expected), sorted(sources))
1336 def test_invalid_include_exclude(self) -> None:
1337 for option in ["--include", "--exclude"]:
1338 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1340 def test_preserves_line_endings(self) -> None:
1341 with TemporaryDirectory() as workspace:
1342 test_file = Path(workspace) / "test.py"
1343 for nl in ["\n", "\r\n"]:
1344 contents = nl.join(["def f( ):", " pass"])
1345 test_file.write_bytes(contents.encode())
1346 ff(test_file, write_back=black.WriteBack.YES)
1347 updated_contents: bytes = test_file.read_bytes()
1348 self.assertIn(nl.encode(), updated_contents)
1350 self.assertNotIn(b"\r\n", updated_contents)
1352 def test_preserves_line_endings_via_stdin(self) -> None:
1353 for nl in ["\n", "\r\n"]:
1354 contents = nl.join(["def f( ):", " pass"])
1355 runner = BlackRunner()
1356 result = runner.invoke(
1357 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1359 self.assertEqual(result.exit_code, 0)
1360 output = runner.stdout_bytes
1361 self.assertIn(nl.encode("utf8"), output)
1363 self.assertNotIn(b"\r\n", output)
1365 def test_assert_equivalent_different_asts(self) -> None:
1366 with self.assertRaises(AssertionError):
1367 black.assert_equivalent("{}", "None")
1369 def test_symlink_out_of_root_directory(self) -> None:
1373 include = re.compile(black.DEFAULT_INCLUDES)
1374 exclude = re.compile(black.DEFAULT_EXCLUDES)
1375 report = black.Report()
1376 # `child` should behave like a symlink which resolved path is clearly
1377 # outside of the `root` directory.
1378 path.iterdir.return_value = [child]
1379 child.resolve.return_value = Path("/a/b/c")
1380 child.is_symlink.return_value = True
1382 list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1383 except ValueError as ve:
1384 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1385 path.iterdir.assert_called_once()
1386 child.resolve.assert_called_once()
1387 child.is_symlink.assert_called_once()
1388 # `child` should behave like a strange file which resolved path is clearly
1389 # outside of the `root` directory.
1390 child.is_symlink.return_value = False
1391 with self.assertRaises(ValueError):
1392 list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1393 path.iterdir.assert_called()
1394 self.assertEqual(path.iterdir.call_count, 2)
1395 child.resolve.assert_called()
1396 self.assertEqual(child.resolve.call_count, 2)
1397 child.is_symlink.assert_called()
1398 self.assertEqual(child.is_symlink.call_count, 2)
1400 def test_shhh_click(self) -> None:
1402 from click import _unicodefun # type: ignore
1403 except ModuleNotFoundError:
1404 self.skipTest("Incompatible Click version")
1405 if not hasattr(_unicodefun, "_verify_python3_env"):
1406 self.skipTest("Incompatible Click version")
1407 # First, let's see if Click is crashing with a preferred ASCII charset.
1408 with patch("locale.getpreferredencoding") as gpe:
1409 gpe.return_value = "ASCII"
1410 with self.assertRaises(RuntimeError):
1411 _unicodefun._verify_python3_env()
1412 # Now, let's silence Click...
1414 # ...and confirm it's silent.
1415 with patch("locale.getpreferredencoding") as gpe:
1416 gpe.return_value = "ASCII"
1418 _unicodefun._verify_python3_env()
1419 except RuntimeError as re:
1420 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1422 def test_root_logger_not_used_directly(self) -> None:
1423 def fail(*args: Any, **kwargs: Any) -> None:
1424 self.fail("Record created with root logger")
1426 with patch.multiple(
1437 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1439 async def test_blackd_request_needs_formatting(self) -> None:
1440 app = blackd.make_app()
1441 async with TestClient(TestServer(app)) as client:
1442 response = await client.post("/", data=b"print('hello world')")
1443 self.assertEqual(response.status, 200)
1444 self.assertEqual(response.charset, "utf8")
1445 self.assertEqual(await response.read(), b'print("hello world")\n')
1447 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1449 async def test_blackd_request_no_change(self) -> None:
1450 app = blackd.make_app()
1451 async with TestClient(TestServer(app)) as client:
1452 response = await client.post("/", data=b'print("hello world")\n')
1453 self.assertEqual(response.status, 204)
1454 self.assertEqual(await response.read(), b"")
1456 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1458 async def test_blackd_request_syntax_error(self) -> None:
1459 app = blackd.make_app()
1460 async with TestClient(TestServer(app)) as client:
1461 response = await client.post("/", data=b"what even ( is")
1462 self.assertEqual(response.status, 400)
1463 content = await response.text()
1465 content.startswith("Cannot parse"),
1466 msg=f"Expected error to start with 'Cannot parse', got {repr(content)}",
1469 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1471 async def test_blackd_unsupported_version(self) -> None:
1472 app = blackd.make_app()
1473 async with TestClient(TestServer(app)) as client:
1474 response = await client.post(
1475 "/", data=b"what", headers={blackd.VERSION_HEADER: "2"}
1477 self.assertEqual(response.status, 501)
1479 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1481 async def test_blackd_supported_version(self) -> None:
1482 app = blackd.make_app()
1483 async with TestClient(TestServer(app)) as client:
1484 response = await client.post(
1485 "/", data=b"what", headers={blackd.VERSION_HEADER: "1"}
1487 self.assertEqual(response.status, 200)
1489 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1491 async def test_blackd_invalid_python_variant(self) -> None:
1492 app = blackd.make_app()
1493 async with TestClient(TestServer(app)) as client:
1495 async def check(header_value: str, expected_status: int = 400) -> None:
1496 response = await client.post(
1499 headers={blackd.PYTHON_VARIANT_HEADER: header_value},
1501 self.assertEqual(response.status, expected_status)
1504 await check("ruby3.5")
1505 await check("pyi3.6")
1506 await check("py1.5")
1508 await check("py2.8")
1510 await check("pypy3.0")
1511 await check("jython3.4")
1513 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1515 async def test_blackd_pyi(self) -> None:
1516 app = blackd.make_app()
1517 async with TestClient(TestServer(app)) as client:
1518 source, expected = read_data("stub.pyi")
1519 response = await client.post(
1520 "/", data=source, headers={blackd.PYTHON_VARIANT_HEADER: "pyi"}
1522 self.assertEqual(response.status, 200)
1523 self.assertEqual(await response.text(), expected)
1525 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1527 async def test_blackd_python_variant(self) -> None:
1528 app = blackd.make_app()
1531 " and_has_a_bunch_of,\n"
1532 " very_long_arguments_too,\n"
1533 " and_lots_of_them_as_well_lol,\n"
1534 " **and_very_long_keyword_arguments\n"
1538 async with TestClient(TestServer(app)) as client:
1540 async def check(header_value: str, expected_status: int) -> None:
1541 response = await client.post(
1542 "/", data=code, headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1544 self.assertEqual(response.status, expected_status)
1546 await check("3.6", 200)
1547 await check("py3.6", 200)
1548 await check("3.6,3.7", 200)
1549 await check("3.6,py3.7", 200)
1551 await check("2", 204)
1552 await check("2.7", 204)
1553 await check("py2.7", 204)
1554 await check("3.4", 204)
1555 await check("py3.4", 204)
1557 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1559 async def test_blackd_fast(self) -> None:
1560 with open(os.devnull, "w") as dn, redirect_stderr(dn):
1561 app = blackd.make_app()
1562 async with TestClient(TestServer(app)) as client:
1563 response = await client.post("/", data=b"ur'hello'")
1564 self.assertEqual(response.status, 500)
1565 self.assertIn("failed to parse source file", await response.text())
1566 response = await client.post(
1567 "/", data=b"ur'hello'", headers={blackd.FAST_OR_SAFE_HEADER: "fast"}
1569 self.assertEqual(response.status, 200)
1571 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1573 async def test_blackd_line_length(self) -> None:
1574 app = blackd.make_app()
1575 async with TestClient(TestServer(app)) as client:
1576 response = await client.post(
1577 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "7"}
1579 self.assertEqual(response.status, 200)
1581 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1583 async def test_blackd_invalid_line_length(self) -> None:
1584 app = blackd.make_app()
1585 async with TestClient(TestServer(app)) as client:
1586 response = await client.post(
1588 data=b'print("hello")\n',
1589 headers={blackd.LINE_LENGTH_HEADER: "NaN"},
1591 self.assertEqual(response.status, 400)
1593 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1594 def test_blackd_main(self) -> None:
1595 with patch("blackd.web.run_app"):
1596 result = CliRunner().invoke(blackd.main, [])
1597 if result.exception is not None:
1598 raise result.exception
1599 self.assertEqual(result.exit_code, 0)
1602 if __name__ == "__main__":
1603 unittest.main(module="test_black")