All patches and comments are welcome. Please squash your changes to logical
commits before using git-format-patch and git-send-email to
patches@git.madduck.net.
If you'd read over the Git project's submission guidelines and adhered to them,
I'd be especially grateful.
4 from concurrent.futures import ThreadPoolExecutor
5 from contextlib import contextmanager
6 from functools import partial, wraps
7 from io import BytesIO, TextIOWrapper
9 from pathlib import Path
12 from tempfile import TemporaryDirectory
25 from unittest.mock import patch, MagicMock
27 from click import unstyle
28 from click.testing import CliRunner
31 from black import Feature, TargetVersion
35 from aiohttp.test_utils import TestClient, TestServer
37 has_blackd_deps = False
39 has_blackd_deps = True
41 ff = partial(black.format_file_in_place, mode=black.FileMode(), fast=True)
42 fs = partial(black.format_str, mode=black.FileMode())
43 THIS_FILE = Path(__file__)
44 THIS_DIR = THIS_FILE.parent
45 EMPTY_LINE = "# EMPTY LINE WITH WHITESPACE" + " (this comment will be removed)"
47 f"--target-version={version.name.lower()}" for version in black.PY36_VERSIONS
53 def dump_to_stderr(*output: str) -> str:
54 return "\n" + "\n".join(output) + "\n"
57 def read_data(name: str, data: bool = True) -> Tuple[str, str]:
58 """read_data('test_name') -> 'input', 'output'"""
59 if not name.endswith((".py", ".pyi", ".out", ".diff")):
61 _input: List[str] = []
62 _output: List[str] = []
63 base_dir = THIS_DIR / "data" if data else THIS_DIR
64 with open(base_dir / name, "r", encoding="utf8") as test:
65 lines = test.readlines()
68 line = line.replace(EMPTY_LINE, "")
69 if line.rstrip() == "# output":
74 if _input and not _output:
75 # If there's no output marker, treat the entire file as already pre-formatted.
77 return "".join(_input).strip() + "\n", "".join(_output).strip() + "\n"
81 def cache_dir(exists: bool = True) -> Iterator[Path]:
82 with TemporaryDirectory() as workspace:
83 cache_dir = Path(workspace)
85 cache_dir = cache_dir / "new"
86 with patch("black.CACHE_DIR", cache_dir):
91 def event_loop(close: bool) -> Iterator[None]:
92 policy = asyncio.get_event_loop_policy()
93 old_loop = policy.get_event_loop()
94 loop = policy.new_event_loop()
95 asyncio.set_event_loop(loop)
100 policy.set_event_loop(old_loop)
105 def async_test(f: Callable[..., Coroutine[Any, None, R]]) -> Callable[..., None]:
106 @event_loop(close=True)
108 def wrapper(*args: Any, **kwargs: Any) -> None:
109 asyncio.get_event_loop().run_until_complete(f(*args, **kwargs))
114 class BlackRunner(CliRunner):
115 """Modify CliRunner so that stderr is not merged with stdout.
117 This is a hack that can be removed once we depend on Click 7.x"""
119 def __init__(self) -> None:
120 self.stderrbuf = BytesIO()
121 self.stdoutbuf = BytesIO()
122 self.stdout_bytes = b""
123 self.stderr_bytes = b""
127 def isolation(self, *args: Any, **kwargs: Any) -> Generator[BinaryIO, None, None]:
128 with super().isolation(*args, **kwargs) as output:
130 hold_stderr = sys.stderr
131 sys.stderr = TextIOWrapper(self.stderrbuf, encoding=self.charset)
134 self.stdout_bytes = sys.stdout.buffer.getvalue() # type: ignore
135 self.stderr_bytes = sys.stderr.buffer.getvalue() # type: ignore
136 sys.stderr = hold_stderr
139 class BlackTestCase(unittest.TestCase):
142 def assertFormatEqual(self, expected: str, actual: str) -> None:
143 if actual != expected and not os.environ.get("SKIP_AST_PRINT"):
144 bdv: black.DebugVisitor[Any]
145 black.out("Expected tree:", fg="green")
147 exp_node = black.lib2to3_parse(expected)
148 bdv = black.DebugVisitor()
149 list(bdv.visit(exp_node))
150 except Exception as ve:
152 black.out("Actual tree:", fg="red")
154 exp_node = black.lib2to3_parse(actual)
155 bdv = black.DebugVisitor()
156 list(bdv.visit(exp_node))
157 except Exception as ve:
159 self.assertEqual(expected, actual)
162 self, args: List[str], exit_code: int = 0, ignore_config: bool = True
164 runner = BlackRunner()
166 args = ["--config", str(THIS_DIR / "empty.toml"), *args]
167 result = runner.invoke(black.main, args)
168 self.assertEqual(result.exit_code, exit_code, msg=runner.stderr_bytes.decode())
170 @patch("black.dump_to_file", dump_to_stderr)
171 def test_empty(self) -> None:
172 source = expected = ""
174 self.assertFormatEqual(expected, actual)
175 black.assert_equivalent(source, actual)
176 black.assert_stable(source, actual, black.FileMode())
178 def test_empty_ff(self) -> None:
180 tmp_file = Path(black.dump_to_file())
182 self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES))
183 with open(tmp_file, encoding="utf8") as f:
187 self.assertFormatEqual(expected, actual)
189 @patch("black.dump_to_file", dump_to_stderr)
190 def test_self(self) -> None:
191 source, expected = read_data("test_black", data=False)
193 self.assertFormatEqual(expected, actual)
194 black.assert_equivalent(source, actual)
195 black.assert_stable(source, actual, black.FileMode())
196 self.assertFalse(ff(THIS_FILE))
198 @patch("black.dump_to_file", dump_to_stderr)
199 def test_black(self) -> None:
200 source, expected = read_data("../black", data=False)
202 self.assertFormatEqual(expected, actual)
203 black.assert_equivalent(source, actual)
204 black.assert_stable(source, actual, black.FileMode())
205 self.assertFalse(ff(THIS_DIR / ".." / "black.py"))
207 def test_piping(self) -> None:
208 source, expected = read_data("../black", data=False)
209 result = BlackRunner().invoke(
211 ["-", "--fast", f"--line-length={black.DEFAULT_LINE_LENGTH}"],
212 input=BytesIO(source.encode("utf8")),
214 self.assertEqual(result.exit_code, 0)
215 self.assertFormatEqual(expected, result.output)
216 black.assert_equivalent(source, result.output)
217 black.assert_stable(source, result.output, black.FileMode())
219 def test_piping_diff(self) -> None:
220 diff_header = re.compile(
221 rf"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d "
222 rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
224 source, _ = read_data("expression.py")
225 expected, _ = read_data("expression.diff")
226 config = THIS_DIR / "data" / "empty_pyproject.toml"
230 f"--line-length={black.DEFAULT_LINE_LENGTH}",
232 f"--config={config}",
234 result = BlackRunner().invoke(
235 black.main, args, input=BytesIO(source.encode("utf8"))
237 self.assertEqual(result.exit_code, 0)
238 actual = diff_header.sub("[Deterministic header]", result.output)
239 actual = actual.rstrip() + "\n" # the diff output has a trailing space
240 self.assertEqual(expected, actual)
242 @patch("black.dump_to_file", dump_to_stderr)
243 def test_setup(self) -> None:
244 source, expected = read_data("../setup", data=False)
246 self.assertFormatEqual(expected, actual)
247 black.assert_equivalent(source, actual)
248 black.assert_stable(source, actual, black.FileMode())
249 self.assertFalse(ff(THIS_DIR / ".." / "setup.py"))
251 @patch("black.dump_to_file", dump_to_stderr)
252 def test_function(self) -> None:
253 source, expected = read_data("function")
255 self.assertFormatEqual(expected, actual)
256 black.assert_equivalent(source, actual)
257 black.assert_stable(source, actual, black.FileMode())
259 @patch("black.dump_to_file", dump_to_stderr)
260 def test_function2(self) -> None:
261 source, expected = read_data("function2")
263 self.assertFormatEqual(expected, actual)
264 black.assert_equivalent(source, actual)
265 black.assert_stable(source, actual, black.FileMode())
267 @patch("black.dump_to_file", dump_to_stderr)
268 def test_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_comment_after_escaped_newline(self) -> None:
401 source, expected = read_data("comment_after_escaped_newline")
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_cantfit(self) -> None:
409 source, expected = read_data("cantfit")
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_import_spacing(self) -> None:
417 source, expected = read_data("import_spacing")
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_composition(self) -> None:
425 source, expected = read_data("composition")
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_empty_lines(self) -> None:
433 source, expected = read_data("empty_lines")
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_remove_parens(self) -> None:
441 source, expected = read_data("remove_parens")
443 self.assertFormatEqual(expected, actual)
444 black.assert_equivalent(source, actual)
445 black.assert_stable(source, actual, black.FileMode())
447 @patch("black.dump_to_file", dump_to_stderr)
448 def test_string_prefixes(self) -> None:
449 source, expected = read_data("string_prefixes")
451 self.assertFormatEqual(expected, actual)
452 black.assert_equivalent(source, actual)
453 black.assert_stable(source, actual, black.FileMode())
455 @patch("black.dump_to_file", dump_to_stderr)
456 def test_numeric_literals(self) -> None:
457 source, expected = read_data("numeric_literals")
458 mode = black.FileMode(target_versions=black.PY36_VERSIONS)
459 actual = fs(source, mode=mode)
460 self.assertFormatEqual(expected, actual)
461 black.assert_equivalent(source, actual)
462 black.assert_stable(source, actual, mode)
464 @patch("black.dump_to_file", dump_to_stderr)
465 def test_numeric_literals_ignoring_underscores(self) -> None:
466 source, expected = read_data("numeric_literals_skip_underscores")
467 mode = black.FileMode(target_versions=black.PY36_VERSIONS)
468 actual = fs(source, mode=mode)
469 self.assertFormatEqual(expected, actual)
470 black.assert_equivalent(source, actual)
471 black.assert_stable(source, actual, mode)
473 @patch("black.dump_to_file", dump_to_stderr)
474 def test_numeric_literals_py2(self) -> None:
475 source, expected = read_data("numeric_literals_py2")
477 self.assertFormatEqual(expected, actual)
478 black.assert_stable(source, actual, black.FileMode())
480 @patch("black.dump_to_file", dump_to_stderr)
481 def test_python2(self) -> None:
482 source, expected = read_data("python2")
484 self.assertFormatEqual(expected, actual)
485 black.assert_equivalent(source, actual)
486 black.assert_stable(source, actual, black.FileMode())
488 @patch("black.dump_to_file", dump_to_stderr)
489 def test_python2_print_function(self) -> None:
490 source, expected = read_data("python2_print_function")
491 mode = black.FileMode(target_versions={TargetVersion.PY27})
492 actual = fs(source, mode=mode)
493 self.assertFormatEqual(expected, actual)
494 black.assert_equivalent(source, actual)
495 black.assert_stable(source, actual, mode)
497 @patch("black.dump_to_file", dump_to_stderr)
498 def test_python2_unicode_literals(self) -> None:
499 source, expected = read_data("python2_unicode_literals")
501 self.assertFormatEqual(expected, actual)
502 black.assert_equivalent(source, actual)
503 black.assert_stable(source, actual, black.FileMode())
505 @patch("black.dump_to_file", dump_to_stderr)
506 def test_stub(self) -> None:
507 mode = black.FileMode(is_pyi=True)
508 source, expected = read_data("stub.pyi")
509 actual = fs(source, mode=mode)
510 self.assertFormatEqual(expected, actual)
511 black.assert_stable(source, actual, mode)
513 @patch("black.dump_to_file", dump_to_stderr)
514 def test_async_as_identifier(self) -> None:
515 source_path = (THIS_DIR / "data" / "async_as_identifier.py").resolve()
516 source, expected = read_data("async_as_identifier")
518 self.assertFormatEqual(expected, actual)
519 major, minor = sys.version_info[:2]
520 if major < 3 or (major <= 3 and minor < 7):
521 black.assert_equivalent(source, actual)
522 black.assert_stable(source, actual, black.FileMode())
523 # ensure black can parse this when the target is 3.6
524 self.invokeBlack([str(source_path), "--target-version", "py36"])
525 # but not on 3.7, because async/await is no longer an identifier
526 self.invokeBlack([str(source_path), "--target-version", "py37"], exit_code=123)
528 @patch("black.dump_to_file", dump_to_stderr)
529 def test_python37(self) -> None:
530 source_path = (THIS_DIR / "data" / "python37.py").resolve()
531 source, expected = read_data("python37")
533 self.assertFormatEqual(expected, actual)
534 major, minor = sys.version_info[:2]
535 if major > 3 or (major == 3 and minor >= 7):
536 black.assert_equivalent(source, actual)
537 black.assert_stable(source, actual, black.FileMode())
538 # ensure black can parse this when the target is 3.7
539 self.invokeBlack([str(source_path), "--target-version", "py37"])
540 # but not on 3.6, because we use async as a reserved keyword
541 self.invokeBlack([str(source_path), "--target-version", "py36"], exit_code=123)
543 @patch("black.dump_to_file", dump_to_stderr)
544 def test_fmtonoff(self) -> None:
545 source, expected = read_data("fmtonoff")
547 self.assertFormatEqual(expected, actual)
548 black.assert_equivalent(source, actual)
549 black.assert_stable(source, actual, black.FileMode())
551 @patch("black.dump_to_file", dump_to_stderr)
552 def test_fmtonoff2(self) -> None:
553 source, expected = read_data("fmtonoff2")
555 self.assertFormatEqual(expected, actual)
556 black.assert_equivalent(source, actual)
557 black.assert_stable(source, actual, black.FileMode())
559 @patch("black.dump_to_file", dump_to_stderr)
560 def test_remove_empty_parentheses_after_class(self) -> None:
561 source, expected = read_data("class_blank_parentheses")
563 self.assertFormatEqual(expected, actual)
564 black.assert_equivalent(source, actual)
565 black.assert_stable(source, actual, black.FileMode())
567 @patch("black.dump_to_file", dump_to_stderr)
568 def test_new_line_between_class_and_code(self) -> None:
569 source, expected = read_data("class_methods_new_line")
571 self.assertFormatEqual(expected, actual)
572 black.assert_equivalent(source, actual)
573 black.assert_stable(source, actual, black.FileMode())
575 @patch("black.dump_to_file", dump_to_stderr)
576 def test_bracket_match(self) -> None:
577 source, expected = read_data("bracketmatch")
579 self.assertFormatEqual(expected, actual)
580 black.assert_equivalent(source, actual)
581 black.assert_stable(source, actual, black.FileMode())
583 @patch("black.dump_to_file", dump_to_stderr)
584 def test_tuple_assign(self) -> None:
585 source, expected = read_data("tupleassign")
587 self.assertFormatEqual(expected, actual)
588 black.assert_equivalent(source, actual)
589 black.assert_stable(source, actual, black.FileMode())
591 def test_tab_comment_indentation(self) -> None:
592 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
593 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
594 self.assertFormatEqual(contents_spc, fs(contents_spc))
595 self.assertFormatEqual(contents_spc, fs(contents_tab))
597 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
598 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
599 self.assertFormatEqual(contents_spc, fs(contents_spc))
600 self.assertFormatEqual(contents_spc, fs(contents_tab))
602 # mixed tabs and spaces (valid Python 2 code)
603 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t# comment\n pass\n"
604 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
605 self.assertFormatEqual(contents_spc, fs(contents_spc))
606 self.assertFormatEqual(contents_spc, fs(contents_tab))
608 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t\t# comment\n pass\n"
609 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
610 self.assertFormatEqual(contents_spc, fs(contents_spc))
611 self.assertFormatEqual(contents_spc, fs(contents_tab))
613 def test_report_verbose(self) -> None:
614 report = black.Report(verbose=True)
618 def out(msg: str, **kwargs: Any) -> None:
619 out_lines.append(msg)
621 def err(msg: str, **kwargs: Any) -> None:
622 err_lines.append(msg)
624 with patch("black.out", out), patch("black.err", err):
625 report.done(Path("f1"), black.Changed.NO)
626 self.assertEqual(len(out_lines), 1)
627 self.assertEqual(len(err_lines), 0)
628 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
629 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
630 self.assertEqual(report.return_code, 0)
631 report.done(Path("f2"), black.Changed.YES)
632 self.assertEqual(len(out_lines), 2)
633 self.assertEqual(len(err_lines), 0)
634 self.assertEqual(out_lines[-1], "reformatted f2")
636 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
638 report.done(Path("f3"), black.Changed.CACHED)
639 self.assertEqual(len(out_lines), 3)
640 self.assertEqual(len(err_lines), 0)
642 out_lines[-1], "f3 wasn't modified on disk since last run."
645 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
647 self.assertEqual(report.return_code, 0)
649 self.assertEqual(report.return_code, 1)
651 report.failed(Path("e1"), "boom")
652 self.assertEqual(len(out_lines), 3)
653 self.assertEqual(len(err_lines), 1)
654 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
656 unstyle(str(report)),
657 "1 file reformatted, 2 files left unchanged, "
658 "1 file failed to reformat.",
660 self.assertEqual(report.return_code, 123)
661 report.done(Path("f3"), black.Changed.YES)
662 self.assertEqual(len(out_lines), 4)
663 self.assertEqual(len(err_lines), 1)
664 self.assertEqual(out_lines[-1], "reformatted f3")
666 unstyle(str(report)),
667 "2 files reformatted, 2 files left unchanged, "
668 "1 file failed to reformat.",
670 self.assertEqual(report.return_code, 123)
671 report.failed(Path("e2"), "boom")
672 self.assertEqual(len(out_lines), 4)
673 self.assertEqual(len(err_lines), 2)
674 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
676 unstyle(str(report)),
677 "2 files reformatted, 2 files left unchanged, "
678 "2 files failed to reformat.",
680 self.assertEqual(report.return_code, 123)
681 report.path_ignored(Path("wat"), "no match")
682 self.assertEqual(len(out_lines), 5)
683 self.assertEqual(len(err_lines), 2)
684 self.assertEqual(out_lines[-1], "wat ignored: no match")
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), 6)
693 self.assertEqual(len(err_lines), 2)
694 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
696 unstyle(str(report)),
697 "2 files reformatted, 3 files left unchanged, "
698 "2 files failed to reformat.",
700 self.assertEqual(report.return_code, 123)
703 unstyle(str(report)),
704 "2 files would be reformatted, 3 files would be left unchanged, "
705 "2 files would fail to reformat.",
708 def test_report_quiet(self) -> None:
709 report = black.Report(quiet=True)
713 def out(msg: str, **kwargs: Any) -> None:
714 out_lines.append(msg)
716 def err(msg: str, **kwargs: Any) -> None:
717 err_lines.append(msg)
719 with patch("black.out", out), patch("black.err", err):
720 report.done(Path("f1"), black.Changed.NO)
721 self.assertEqual(len(out_lines), 0)
722 self.assertEqual(len(err_lines), 0)
723 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
724 self.assertEqual(report.return_code, 0)
725 report.done(Path("f2"), black.Changed.YES)
726 self.assertEqual(len(out_lines), 0)
727 self.assertEqual(len(err_lines), 0)
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), 0)
733 self.assertEqual(len(err_lines), 0)
735 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
737 self.assertEqual(report.return_code, 0)
739 self.assertEqual(report.return_code, 1)
741 report.failed(Path("e1"), "boom")
742 self.assertEqual(len(out_lines), 0)
743 self.assertEqual(len(err_lines), 1)
744 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
746 unstyle(str(report)),
747 "1 file reformatted, 2 files left unchanged, "
748 "1 file failed to reformat.",
750 self.assertEqual(report.return_code, 123)
751 report.done(Path("f3"), black.Changed.YES)
752 self.assertEqual(len(out_lines), 0)
753 self.assertEqual(len(err_lines), 1)
755 unstyle(str(report)),
756 "2 files reformatted, 2 files left unchanged, "
757 "1 file failed to reformat.",
759 self.assertEqual(report.return_code, 123)
760 report.failed(Path("e2"), "boom")
761 self.assertEqual(len(out_lines), 0)
762 self.assertEqual(len(err_lines), 2)
763 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
765 unstyle(str(report)),
766 "2 files reformatted, 2 files left unchanged, "
767 "2 files failed to reformat.",
769 self.assertEqual(report.return_code, 123)
770 report.path_ignored(Path("wat"), "no match")
771 self.assertEqual(len(out_lines), 0)
772 self.assertEqual(len(err_lines), 2)
774 unstyle(str(report)),
775 "2 files reformatted, 2 files left unchanged, "
776 "2 files failed to reformat.",
778 self.assertEqual(report.return_code, 123)
779 report.done(Path("f4"), black.Changed.NO)
780 self.assertEqual(len(out_lines), 0)
781 self.assertEqual(len(err_lines), 2)
783 unstyle(str(report)),
784 "2 files reformatted, 3 files left unchanged, "
785 "2 files failed to reformat.",
787 self.assertEqual(report.return_code, 123)
790 unstyle(str(report)),
791 "2 files would be reformatted, 3 files would be left unchanged, "
792 "2 files would fail to reformat.",
795 def test_report_normal(self) -> None:
796 report = black.Report()
800 def out(msg: str, **kwargs: Any) -> None:
801 out_lines.append(msg)
803 def err(msg: str, **kwargs: Any) -> None:
804 err_lines.append(msg)
806 with patch("black.out", out), patch("black.err", err):
807 report.done(Path("f1"), black.Changed.NO)
808 self.assertEqual(len(out_lines), 0)
809 self.assertEqual(len(err_lines), 0)
810 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
811 self.assertEqual(report.return_code, 0)
812 report.done(Path("f2"), black.Changed.YES)
813 self.assertEqual(len(out_lines), 1)
814 self.assertEqual(len(err_lines), 0)
815 self.assertEqual(out_lines[-1], "reformatted f2")
817 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
819 report.done(Path("f3"), black.Changed.CACHED)
820 self.assertEqual(len(out_lines), 1)
821 self.assertEqual(len(err_lines), 0)
822 self.assertEqual(out_lines[-1], "reformatted f2")
824 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
826 self.assertEqual(report.return_code, 0)
828 self.assertEqual(report.return_code, 1)
830 report.failed(Path("e1"), "boom")
831 self.assertEqual(len(out_lines), 1)
832 self.assertEqual(len(err_lines), 1)
833 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
835 unstyle(str(report)),
836 "1 file reformatted, 2 files left unchanged, "
837 "1 file failed to reformat.",
839 self.assertEqual(report.return_code, 123)
840 report.done(Path("f3"), black.Changed.YES)
841 self.assertEqual(len(out_lines), 2)
842 self.assertEqual(len(err_lines), 1)
843 self.assertEqual(out_lines[-1], "reformatted f3")
845 unstyle(str(report)),
846 "2 files reformatted, 2 files left unchanged, "
847 "1 file failed to reformat.",
849 self.assertEqual(report.return_code, 123)
850 report.failed(Path("e2"), "boom")
851 self.assertEqual(len(out_lines), 2)
852 self.assertEqual(len(err_lines), 2)
853 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
855 unstyle(str(report)),
856 "2 files reformatted, 2 files left unchanged, "
857 "2 files failed to reformat.",
859 self.assertEqual(report.return_code, 123)
860 report.path_ignored(Path("wat"), "no match")
861 self.assertEqual(len(out_lines), 2)
862 self.assertEqual(len(err_lines), 2)
864 unstyle(str(report)),
865 "2 files reformatted, 2 files left unchanged, "
866 "2 files failed to reformat.",
868 self.assertEqual(report.return_code, 123)
869 report.done(Path("f4"), black.Changed.NO)
870 self.assertEqual(len(out_lines), 2)
871 self.assertEqual(len(err_lines), 2)
873 unstyle(str(report)),
874 "2 files reformatted, 3 files left unchanged, "
875 "2 files failed to reformat.",
877 self.assertEqual(report.return_code, 123)
880 unstyle(str(report)),
881 "2 files would be reformatted, 3 files would be left unchanged, "
882 "2 files would fail to reformat.",
885 def test_lib2to3_parse(self) -> None:
886 with self.assertRaises(black.InvalidInput):
887 black.lib2to3_parse("invalid syntax")
890 black.lib2to3_parse(straddling)
891 black.lib2to3_parse(straddling, {TargetVersion.PY27})
892 black.lib2to3_parse(straddling, {TargetVersion.PY36})
893 black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
896 black.lib2to3_parse(py2_only)
897 black.lib2to3_parse(py2_only, {TargetVersion.PY27})
898 with self.assertRaises(black.InvalidInput):
899 black.lib2to3_parse(py2_only, {TargetVersion.PY36})
900 with self.assertRaises(black.InvalidInput):
901 black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
903 py3_only = "exec(x, end=y)"
904 black.lib2to3_parse(py3_only)
905 with self.assertRaises(black.InvalidInput):
906 black.lib2to3_parse(py3_only, {TargetVersion.PY27})
907 black.lib2to3_parse(py3_only, {TargetVersion.PY36})
908 black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
910 def test_get_features_used(self) -> None:
911 node = black.lib2to3_parse("def f(*, arg): ...\n")
912 self.assertEqual(black.get_features_used(node), set())
913 node = black.lib2to3_parse("def f(*, arg,): ...\n")
914 self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
915 node = black.lib2to3_parse("f(*arg,)\n")
917 black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
919 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
920 self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
921 node = black.lib2to3_parse("123_456\n")
922 self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
923 node = black.lib2to3_parse("123456\n")
924 self.assertEqual(black.get_features_used(node), set())
925 source, expected = read_data("function")
926 node = black.lib2to3_parse(source)
927 expected_features = {
928 Feature.TRAILING_COMMA_IN_CALL,
929 Feature.TRAILING_COMMA_IN_DEF,
932 self.assertEqual(black.get_features_used(node), expected_features)
933 node = black.lib2to3_parse(expected)
934 self.assertEqual(black.get_features_used(node), expected_features)
935 source, expected = read_data("expression")
936 node = black.lib2to3_parse(source)
937 self.assertEqual(black.get_features_used(node), set())
938 node = black.lib2to3_parse(expected)
939 self.assertEqual(black.get_features_used(node), set())
941 def test_get_future_imports(self) -> None:
942 node = black.lib2to3_parse("\n")
943 self.assertEqual(set(), black.get_future_imports(node))
944 node = black.lib2to3_parse("from __future__ import black\n")
945 self.assertEqual({"black"}, black.get_future_imports(node))
946 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
947 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
948 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
949 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
950 node = black.lib2to3_parse(
951 "from __future__ import multiple\nfrom __future__ import imports\n"
953 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
954 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
955 self.assertEqual({"black"}, black.get_future_imports(node))
956 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
957 self.assertEqual({"black"}, black.get_future_imports(node))
958 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
959 self.assertEqual(set(), black.get_future_imports(node))
960 node = black.lib2to3_parse("from some.module import black\n")
961 self.assertEqual(set(), black.get_future_imports(node))
962 node = black.lib2to3_parse(
963 "from __future__ import unicode_literals as _unicode_literals"
965 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
966 node = black.lib2to3_parse(
967 "from __future__ import unicode_literals as _lol, print"
969 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
971 def test_debug_visitor(self) -> None:
972 source, _ = read_data("debug_visitor.py")
973 expected, _ = read_data("debug_visitor.out")
977 def out(msg: str, **kwargs: Any) -> None:
978 out_lines.append(msg)
980 def err(msg: str, **kwargs: Any) -> None:
981 err_lines.append(msg)
983 with patch("black.out", out), patch("black.err", err):
984 black.DebugVisitor.show(source)
985 actual = "\n".join(out_lines) + "\n"
987 if expected != actual:
988 log_name = black.dump_to_file(*out_lines)
992 f"AST print out is different. Actual version dumped to {log_name}",
995 def test_format_file_contents(self) -> None:
997 mode = black.FileMode()
998 with self.assertRaises(black.NothingChanged):
999 black.format_file_contents(empty, mode=mode, fast=False)
1001 with self.assertRaises(black.NothingChanged):
1002 black.format_file_contents(just_nl, mode=mode, fast=False)
1003 same = "l = [1, 2, 3]\n"
1004 with self.assertRaises(black.NothingChanged):
1005 black.format_file_contents(same, mode=mode, fast=False)
1006 different = "l = [1,2,3]"
1008 actual = black.format_file_contents(different, mode=mode, fast=False)
1009 self.assertEqual(expected, actual)
1010 invalid = "return if you can"
1011 with self.assertRaises(black.InvalidInput) as e:
1012 black.format_file_contents(invalid, mode=mode, fast=False)
1013 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
1015 def test_endmarker(self) -> None:
1016 n = black.lib2to3_parse("\n")
1017 self.assertEqual(n.type, black.syms.file_input)
1018 self.assertEqual(len(n.children), 1)
1019 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
1021 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
1022 def test_assertFormatEqual(self) -> None:
1026 def out(msg: str, **kwargs: Any) -> None:
1027 out_lines.append(msg)
1029 def err(msg: str, **kwargs: Any) -> None:
1030 err_lines.append(msg)
1032 with patch("black.out", out), patch("black.err", err):
1033 with self.assertRaises(AssertionError):
1034 self.assertFormatEqual("l = [1, 2, 3]", "l = [1, 2, 3,]")
1036 out_str = "".join(out_lines)
1037 self.assertTrue("Expected tree:" in out_str)
1038 self.assertTrue("Actual tree:" in out_str)
1039 self.assertEqual("".join(err_lines), "")
1041 def test_cache_broken_file(self) -> None:
1042 mode = black.FileMode()
1043 with cache_dir() as workspace:
1044 cache_file = black.get_cache_file(mode)
1045 with cache_file.open("w") as fobj:
1046 fobj.write("this is not a pickle")
1047 self.assertEqual(black.read_cache(mode), {})
1048 src = (workspace / "test.py").resolve()
1049 with src.open("w") as fobj:
1050 fobj.write("print('hello')")
1051 self.invokeBlack([str(src)])
1052 cache = black.read_cache(mode)
1053 self.assertIn(src, cache)
1055 def test_cache_single_file_already_cached(self) -> None:
1056 mode = black.FileMode()
1057 with cache_dir() as workspace:
1058 src = (workspace / "test.py").resolve()
1059 with src.open("w") as fobj:
1060 fobj.write("print('hello')")
1061 black.write_cache({}, [src], mode)
1062 self.invokeBlack([str(src)])
1063 with src.open("r") as fobj:
1064 self.assertEqual(fobj.read(), "print('hello')")
1066 @event_loop(close=False)
1067 def test_cache_multiple_files(self) -> None:
1068 mode = black.FileMode()
1069 with cache_dir() as workspace, patch(
1070 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1072 one = (workspace / "one.py").resolve()
1073 with one.open("w") as fobj:
1074 fobj.write("print('hello')")
1075 two = (workspace / "two.py").resolve()
1076 with two.open("w") as fobj:
1077 fobj.write("print('hello')")
1078 black.write_cache({}, [one], mode)
1079 self.invokeBlack([str(workspace)])
1080 with one.open("r") as fobj:
1081 self.assertEqual(fobj.read(), "print('hello')")
1082 with two.open("r") as fobj:
1083 self.assertEqual(fobj.read(), 'print("hello")\n')
1084 cache = black.read_cache(mode)
1085 self.assertIn(one, cache)
1086 self.assertIn(two, cache)
1088 def test_no_cache_when_writeback_diff(self) -> None:
1089 mode = black.FileMode()
1090 with cache_dir() as workspace:
1091 src = (workspace / "test.py").resolve()
1092 with src.open("w") as fobj:
1093 fobj.write("print('hello')")
1094 self.invokeBlack([str(src), "--diff"])
1095 cache_file = black.get_cache_file(mode)
1096 self.assertFalse(cache_file.exists())
1098 def test_no_cache_when_stdin(self) -> None:
1099 mode = black.FileMode()
1101 result = CliRunner().invoke(
1102 black.main, ["-"], input=BytesIO(b"print('hello')")
1104 self.assertEqual(result.exit_code, 0)
1105 cache_file = black.get_cache_file(mode)
1106 self.assertFalse(cache_file.exists())
1108 def test_read_cache_no_cachefile(self) -> None:
1109 mode = black.FileMode()
1111 self.assertEqual(black.read_cache(mode), {})
1113 def test_write_cache_read_cache(self) -> None:
1114 mode = black.FileMode()
1115 with cache_dir() as workspace:
1116 src = (workspace / "test.py").resolve()
1118 black.write_cache({}, [src], mode)
1119 cache = black.read_cache(mode)
1120 self.assertIn(src, cache)
1121 self.assertEqual(cache[src], black.get_cache_info(src))
1123 def test_filter_cached(self) -> None:
1124 with TemporaryDirectory() as workspace:
1125 path = Path(workspace)
1126 uncached = (path / "uncached").resolve()
1127 cached = (path / "cached").resolve()
1128 cached_but_changed = (path / "changed").resolve()
1131 cached_but_changed.touch()
1132 cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
1133 todo, done = black.filter_cached(
1134 cache, {uncached, cached, cached_but_changed}
1136 self.assertEqual(todo, {uncached, cached_but_changed})
1137 self.assertEqual(done, {cached})
1139 def test_write_cache_creates_directory_if_needed(self) -> None:
1140 mode = black.FileMode()
1141 with cache_dir(exists=False) as workspace:
1142 self.assertFalse(workspace.exists())
1143 black.write_cache({}, [], mode)
1144 self.assertTrue(workspace.exists())
1146 @event_loop(close=False)
1147 def test_failed_formatting_does_not_get_cached(self) -> None:
1148 mode = black.FileMode()
1149 with cache_dir() as workspace, patch(
1150 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1152 failing = (workspace / "failing.py").resolve()
1153 with failing.open("w") as fobj:
1154 fobj.write("not actually python")
1155 clean = (workspace / "clean.py").resolve()
1156 with clean.open("w") as fobj:
1157 fobj.write('print("hello")\n')
1158 self.invokeBlack([str(workspace)], exit_code=123)
1159 cache = black.read_cache(mode)
1160 self.assertNotIn(failing, cache)
1161 self.assertIn(clean, cache)
1163 def test_write_cache_write_fail(self) -> None:
1164 mode = black.FileMode()
1165 with cache_dir(), patch.object(Path, "open") as mock:
1166 mock.side_effect = OSError
1167 black.write_cache({}, [], mode)
1169 @event_loop(close=False)
1170 def test_check_diff_use_together(self) -> None:
1172 # Files which will be reformatted.
1173 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1174 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1175 # Files which will not be reformatted.
1176 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1177 self.invokeBlack([str(src2), "--diff", "--check"])
1178 # Multi file command.
1179 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1181 def test_no_files(self) -> None:
1183 # Without an argument, black exits with error code 0.
1184 self.invokeBlack([])
1186 def test_broken_symlink(self) -> None:
1187 with cache_dir() as workspace:
1188 symlink = workspace / "broken_link.py"
1190 symlink.symlink_to("nonexistent.py")
1191 except OSError as e:
1192 self.skipTest(f"Can't create symlinks: {e}")
1193 self.invokeBlack([str(workspace.resolve())])
1195 def test_read_cache_line_lengths(self) -> None:
1196 mode = black.FileMode()
1197 short_mode = black.FileMode(line_length=1)
1198 with cache_dir() as workspace:
1199 path = (workspace / "file.py").resolve()
1201 black.write_cache({}, [path], mode)
1202 one = black.read_cache(mode)
1203 self.assertIn(path, one)
1204 two = black.read_cache(short_mode)
1205 self.assertNotIn(path, two)
1207 def test_single_file_force_pyi(self) -> None:
1208 reg_mode = black.FileMode()
1209 pyi_mode = black.FileMode(is_pyi=True)
1210 contents, expected = read_data("force_pyi")
1211 with cache_dir() as workspace:
1212 path = (workspace / "file.py").resolve()
1213 with open(path, "w") as fh:
1215 self.invokeBlack([str(path), "--pyi"])
1216 with open(path, "r") as fh:
1218 # verify cache with --pyi is separate
1219 pyi_cache = black.read_cache(pyi_mode)
1220 self.assertIn(path, pyi_cache)
1221 normal_cache = black.read_cache(reg_mode)
1222 self.assertNotIn(path, normal_cache)
1223 self.assertEqual(actual, expected)
1225 @event_loop(close=False)
1226 def test_multi_file_force_pyi(self) -> None:
1227 reg_mode = black.FileMode()
1228 pyi_mode = black.FileMode(is_pyi=True)
1229 contents, expected = read_data("force_pyi")
1230 with cache_dir() as workspace:
1232 (workspace / "file1.py").resolve(),
1233 (workspace / "file2.py").resolve(),
1236 with open(path, "w") as fh:
1238 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1240 with open(path, "r") as fh:
1242 self.assertEqual(actual, expected)
1243 # verify cache with --pyi is separate
1244 pyi_cache = black.read_cache(pyi_mode)
1245 normal_cache = black.read_cache(reg_mode)
1247 self.assertIn(path, pyi_cache)
1248 self.assertNotIn(path, normal_cache)
1250 def test_pipe_force_pyi(self) -> None:
1251 source, expected = read_data("force_pyi")
1252 result = CliRunner().invoke(
1253 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1255 self.assertEqual(result.exit_code, 0)
1256 actual = result.output
1257 self.assertFormatEqual(actual, expected)
1259 def test_single_file_force_py36(self) -> None:
1260 reg_mode = black.FileMode()
1261 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1262 source, expected = read_data("force_py36")
1263 with cache_dir() as workspace:
1264 path = (workspace / "file.py").resolve()
1265 with open(path, "w") as fh:
1267 self.invokeBlack([str(path), *PY36_ARGS])
1268 with open(path, "r") as fh:
1270 # verify cache with --target-version is separate
1271 py36_cache = black.read_cache(py36_mode)
1272 self.assertIn(path, py36_cache)
1273 normal_cache = black.read_cache(reg_mode)
1274 self.assertNotIn(path, normal_cache)
1275 self.assertEqual(actual, expected)
1277 @event_loop(close=False)
1278 def test_multi_file_force_py36(self) -> None:
1279 reg_mode = black.FileMode()
1280 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1281 source, expected = read_data("force_py36")
1282 with cache_dir() as workspace:
1284 (workspace / "file1.py").resolve(),
1285 (workspace / "file2.py").resolve(),
1288 with open(path, "w") as fh:
1290 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1292 with open(path, "r") as fh:
1294 self.assertEqual(actual, expected)
1295 # verify cache with --target-version is separate
1296 pyi_cache = black.read_cache(py36_mode)
1297 normal_cache = black.read_cache(reg_mode)
1299 self.assertIn(path, pyi_cache)
1300 self.assertNotIn(path, normal_cache)
1302 def test_pipe_force_py36(self) -> None:
1303 source, expected = read_data("force_py36")
1304 result = CliRunner().invoke(
1306 ["-", "-q", "--target-version=py36"],
1307 input=BytesIO(source.encode("utf8")),
1309 self.assertEqual(result.exit_code, 0)
1310 actual = result.output
1311 self.assertFormatEqual(actual, expected)
1313 def test_include_exclude(self) -> None:
1314 path = THIS_DIR / "data" / "include_exclude_tests"
1315 include = re.compile(r"\.pyi?$")
1316 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1317 report = black.Report()
1318 sources: List[Path] = []
1320 Path(path / "b/dont_exclude/a.py"),
1321 Path(path / "b/dont_exclude/a.pyi"),
1323 this_abs = THIS_DIR.resolve()
1325 black.gen_python_files_in_dir(path, this_abs, include, exclude, report)
1327 self.assertEqual(sorted(expected), sorted(sources))
1329 def test_empty_include(self) -> None:
1330 path = THIS_DIR / "data" / "include_exclude_tests"
1331 report = black.Report()
1332 empty = re.compile(r"")
1333 sources: List[Path] = []
1335 Path(path / "b/exclude/a.pie"),
1336 Path(path / "b/exclude/a.py"),
1337 Path(path / "b/exclude/a.pyi"),
1338 Path(path / "b/dont_exclude/a.pie"),
1339 Path(path / "b/dont_exclude/a.py"),
1340 Path(path / "b/dont_exclude/a.pyi"),
1341 Path(path / "b/.definitely_exclude/a.pie"),
1342 Path(path / "b/.definitely_exclude/a.py"),
1343 Path(path / "b/.definitely_exclude/a.pyi"),
1345 this_abs = THIS_DIR.resolve()
1347 black.gen_python_files_in_dir(
1348 path, this_abs, empty, re.compile(black.DEFAULT_EXCLUDES), report
1351 self.assertEqual(sorted(expected), sorted(sources))
1353 def test_empty_exclude(self) -> None:
1354 path = THIS_DIR / "data" / "include_exclude_tests"
1355 report = black.Report()
1356 empty = re.compile(r"")
1357 sources: List[Path] = []
1359 Path(path / "b/dont_exclude/a.py"),
1360 Path(path / "b/dont_exclude/a.pyi"),
1361 Path(path / "b/exclude/a.py"),
1362 Path(path / "b/exclude/a.pyi"),
1363 Path(path / "b/.definitely_exclude/a.py"),
1364 Path(path / "b/.definitely_exclude/a.pyi"),
1366 this_abs = THIS_DIR.resolve()
1368 black.gen_python_files_in_dir(
1369 path, this_abs, re.compile(black.DEFAULT_INCLUDES), empty, report
1372 self.assertEqual(sorted(expected), sorted(sources))
1374 def test_invalid_include_exclude(self) -> None:
1375 for option in ["--include", "--exclude"]:
1376 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1378 def test_preserves_line_endings(self) -> None:
1379 with TemporaryDirectory() as workspace:
1380 test_file = Path(workspace) / "test.py"
1381 for nl in ["\n", "\r\n"]:
1382 contents = nl.join(["def f( ):", " pass"])
1383 test_file.write_bytes(contents.encode())
1384 ff(test_file, write_back=black.WriteBack.YES)
1385 updated_contents: bytes = test_file.read_bytes()
1386 self.assertIn(nl.encode(), updated_contents)
1388 self.assertNotIn(b"\r\n", updated_contents)
1390 def test_preserves_line_endings_via_stdin(self) -> None:
1391 for nl in ["\n", "\r\n"]:
1392 contents = nl.join(["def f( ):", " pass"])
1393 runner = BlackRunner()
1394 result = runner.invoke(
1395 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1397 self.assertEqual(result.exit_code, 0)
1398 output = runner.stdout_bytes
1399 self.assertIn(nl.encode("utf8"), output)
1401 self.assertNotIn(b"\r\n", output)
1403 def test_assert_equivalent_different_asts(self) -> None:
1404 with self.assertRaises(AssertionError):
1405 black.assert_equivalent("{}", "None")
1407 def test_symlink_out_of_root_directory(self) -> None:
1411 include = re.compile(black.DEFAULT_INCLUDES)
1412 exclude = re.compile(black.DEFAULT_EXCLUDES)
1413 report = black.Report()
1414 # `child` should behave like a symlink which resolved path is clearly
1415 # outside of the `root` directory.
1416 path.iterdir.return_value = [child]
1417 child.resolve.return_value = Path("/a/b/c")
1418 child.is_symlink.return_value = True
1420 list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1421 except ValueError as ve:
1422 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1423 path.iterdir.assert_called_once()
1424 child.resolve.assert_called_once()
1425 child.is_symlink.assert_called_once()
1426 # `child` should behave like a strange file which resolved path is clearly
1427 # outside of the `root` directory.
1428 child.is_symlink.return_value = False
1429 with self.assertRaises(ValueError):
1430 list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1431 path.iterdir.assert_called()
1432 self.assertEqual(path.iterdir.call_count, 2)
1433 child.resolve.assert_called()
1434 self.assertEqual(child.resolve.call_count, 2)
1435 child.is_symlink.assert_called()
1436 self.assertEqual(child.is_symlink.call_count, 2)
1438 def test_shhh_click(self) -> None:
1440 from click import _unicodefun # type: ignore
1441 except ModuleNotFoundError:
1442 self.skipTest("Incompatible Click version")
1443 if not hasattr(_unicodefun, "_verify_python3_env"):
1444 self.skipTest("Incompatible Click version")
1445 # First, let's see if Click is crashing with a preferred ASCII charset.
1446 with patch("locale.getpreferredencoding") as gpe:
1447 gpe.return_value = "ASCII"
1448 with self.assertRaises(RuntimeError):
1449 _unicodefun._verify_python3_env()
1450 # Now, let's silence Click...
1452 # ...and confirm it's silent.
1453 with patch("locale.getpreferredencoding") as gpe:
1454 gpe.return_value = "ASCII"
1456 _unicodefun._verify_python3_env()
1457 except RuntimeError as re:
1458 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1460 def test_root_logger_not_used_directly(self) -> None:
1461 def fail(*args: Any, **kwargs: Any) -> None:
1462 self.fail("Record created with root logger")
1464 with patch.multiple(
1475 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1477 async def test_blackd_request_needs_formatting(self) -> None:
1478 app = blackd.make_app()
1479 async with TestClient(TestServer(app)) as client:
1480 response = await client.post("/", data=b"print('hello world')")
1481 self.assertEqual(response.status, 200)
1482 self.assertEqual(response.charset, "utf8")
1483 self.assertEqual(await response.read(), b'print("hello world")\n')
1485 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1487 async def test_blackd_request_no_change(self) -> None:
1488 app = blackd.make_app()
1489 async with TestClient(TestServer(app)) as client:
1490 response = await client.post("/", data=b'print("hello world")\n')
1491 self.assertEqual(response.status, 204)
1492 self.assertEqual(await response.read(), b"")
1494 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1496 async def test_blackd_request_syntax_error(self) -> None:
1497 app = blackd.make_app()
1498 async with TestClient(TestServer(app)) as client:
1499 response = await client.post("/", data=b"what even ( is")
1500 self.assertEqual(response.status, 400)
1501 content = await response.text()
1503 content.startswith("Cannot parse"),
1504 msg=f"Expected error to start with 'Cannot parse', got {repr(content)}",
1507 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1509 async def test_blackd_unsupported_version(self) -> None:
1510 app = blackd.make_app()
1511 async with TestClient(TestServer(app)) as client:
1512 response = await client.post(
1513 "/", data=b"what", headers={blackd.VERSION_HEADER: "2"}
1515 self.assertEqual(response.status, 501)
1517 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1519 async def test_blackd_supported_version(self) -> None:
1520 app = blackd.make_app()
1521 async with TestClient(TestServer(app)) as client:
1522 response = await client.post(
1523 "/", data=b"what", headers={blackd.VERSION_HEADER: "1"}
1525 self.assertEqual(response.status, 200)
1527 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1529 async def test_blackd_invalid_python_variant(self) -> None:
1530 app = blackd.make_app()
1531 async with TestClient(TestServer(app)) as client:
1533 async def check(header_value: str, expected_status: int = 400) -> None:
1534 response = await client.post(
1537 headers={blackd.PYTHON_VARIANT_HEADER: header_value},
1539 self.assertEqual(response.status, expected_status)
1542 await check("ruby3.5")
1543 await check("pyi3.6")
1544 await check("py1.5")
1546 await check("py2.8")
1548 await check("pypy3.0")
1549 await check("jython3.4")
1551 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1553 async def test_blackd_pyi(self) -> None:
1554 app = blackd.make_app()
1555 async with TestClient(TestServer(app)) as client:
1556 source, expected = read_data("stub.pyi")
1557 response = await client.post(
1558 "/", data=source, headers={blackd.PYTHON_VARIANT_HEADER: "pyi"}
1560 self.assertEqual(response.status, 200)
1561 self.assertEqual(await response.text(), expected)
1563 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1565 async def test_blackd_python_variant(self) -> None:
1566 app = blackd.make_app()
1569 " and_has_a_bunch_of,\n"
1570 " very_long_arguments_too,\n"
1571 " and_lots_of_them_as_well_lol,\n"
1572 " **and_very_long_keyword_arguments\n"
1576 async with TestClient(TestServer(app)) as client:
1578 async def check(header_value: str, expected_status: int) -> None:
1579 response = await client.post(
1580 "/", data=code, headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1582 self.assertEqual(response.status, expected_status)
1584 await check("3.6", 200)
1585 await check("py3.6", 200)
1586 await check("3.6,3.7", 200)
1587 await check("3.6,py3.7", 200)
1589 await check("2", 204)
1590 await check("2.7", 204)
1591 await check("py2.7", 204)
1592 await check("3.4", 204)
1593 await check("py3.4", 204)
1595 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1597 async def test_blackd_line_length(self) -> None:
1598 app = blackd.make_app()
1599 async with TestClient(TestServer(app)) as client:
1600 response = await client.post(
1601 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "7"}
1603 self.assertEqual(response.status, 200)
1605 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1607 async def test_blackd_invalid_line_length(self) -> None:
1608 app = blackd.make_app()
1609 async with TestClient(TestServer(app)) as client:
1610 response = await client.post(
1612 data=b'print("hello")\n',
1613 headers={blackd.LINE_LENGTH_HEADER: "NaN"},
1615 self.assertEqual(response.status, 400)
1617 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1618 def test_blackd_main(self) -> None:
1619 with patch("blackd.web.run_app"):
1620 result = CliRunner().invoke(blackd.main, [])
1621 if result.exception is not None:
1622 raise result.exception
1623 self.assertEqual(result.exit_code, 0)
1626 if __name__ == "__main__":
1627 unittest.main(module="test_black")