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.
5 from concurrent.futures import ThreadPoolExecutor
6 from contextlib import contextmanager
7 from dataclasses import replace
9 from io import BytesIO, TextIOWrapper
11 from pathlib import Path
12 from platform import system
15 from tempfile import TemporaryDirectory
28 from unittest.mock import patch, MagicMock
31 from click import unstyle
32 from click.testing import CliRunner
35 from black import Feature, TargetVersion
37 from pathspec import PathSpec
39 # Import other test classes
40 from tests.util import (
50 from .test_primer import PrimerCLITests # noqa: F401
53 THIS_FILE = Path(__file__)
60 PY36_ARGS = [f"--target-version={version.name.lower()}" for version in PY36_VERSIONS]
66 def cache_dir(exists: bool = True) -> Iterator[Path]:
67 with TemporaryDirectory() as workspace:
68 cache_dir = Path(workspace)
70 cache_dir = cache_dir / "new"
71 with patch("black.CACHE_DIR", cache_dir):
76 def event_loop() -> Iterator[None]:
77 policy = asyncio.get_event_loop_policy()
78 loop = policy.new_event_loop()
79 asyncio.set_event_loop(loop)
87 class FakeContext(click.Context):
88 """A fake click Context for when calling functions that need it."""
90 def __init__(self) -> None:
91 self.default_map: Dict[str, Any] = {}
94 class FakeParameter(click.Parameter):
95 """A fake click Parameter for when calling functions that need it."""
97 def __init__(self) -> None:
101 class BlackRunner(CliRunner):
102 """Modify CliRunner so that stderr is not merged with stdout.
104 This is a hack that can be removed once we depend on Click 7.x"""
106 def __init__(self) -> None:
107 self.stderrbuf = BytesIO()
108 self.stdoutbuf = BytesIO()
109 self.stdout_bytes = b""
110 self.stderr_bytes = b""
114 def isolation(self, *args: Any, **kwargs: Any) -> Generator[BinaryIO, None, None]:
115 with super().isolation(*args, **kwargs) as output:
117 hold_stderr = sys.stderr
118 sys.stderr = TextIOWrapper(self.stderrbuf, encoding=self.charset)
121 self.stdout_bytes = sys.stdout.buffer.getvalue() # type: ignore
122 self.stderr_bytes = sys.stderr.buffer.getvalue() # type: ignore
123 sys.stderr = hold_stderr
126 class BlackTestCase(BlackBaseTestCase):
128 self, args: List[str], exit_code: int = 0, ignore_config: bool = True
130 runner = BlackRunner()
132 args = ["--verbose", "--config", str(THIS_DIR / "empty.toml"), *args]
133 result = runner.invoke(black.main, args)
138 f"Failed with args: {args}\n"
139 f"stdout: {runner.stdout_bytes.decode()!r}\n"
140 f"stderr: {runner.stderr_bytes.decode()!r}\n"
141 f"exception: {result.exception}"
145 @patch("black.dump_to_file", dump_to_stderr)
146 def test_empty(self) -> None:
147 source = expected = ""
149 self.assertFormatEqual(expected, actual)
150 black.assert_equivalent(source, actual)
151 black.assert_stable(source, actual, DEFAULT_MODE)
153 def test_empty_ff(self) -> None:
155 tmp_file = Path(black.dump_to_file())
157 self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES))
158 with open(tmp_file, encoding="utf8") as f:
162 self.assertFormatEqual(expected, actual)
164 def test_piping(self) -> None:
165 source, expected = read_data("src/black/__init__", data=False)
166 result = BlackRunner().invoke(
168 ["-", "--fast", f"--line-length={black.DEFAULT_LINE_LENGTH}"],
169 input=BytesIO(source.encode("utf8")),
171 self.assertEqual(result.exit_code, 0)
172 self.assertFormatEqual(expected, result.output)
173 black.assert_equivalent(source, result.output)
174 black.assert_stable(source, result.output, DEFAULT_MODE)
176 def test_piping_diff(self) -> None:
177 diff_header = re.compile(
178 r"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d\d\d\d "
181 source, _ = read_data("expression.py")
182 expected, _ = read_data("expression.diff")
183 config = THIS_DIR / "data" / "empty_pyproject.toml"
187 f"--line-length={black.DEFAULT_LINE_LENGTH}",
189 f"--config={config}",
191 result = BlackRunner().invoke(
192 black.main, args, input=BytesIO(source.encode("utf8"))
194 self.assertEqual(result.exit_code, 0)
195 actual = diff_header.sub(DETERMINISTIC_HEADER, result.output)
196 actual = actual.rstrip() + "\n" # the diff output has a trailing space
197 self.assertEqual(expected, actual)
199 def test_piping_diff_with_color(self) -> None:
200 source, _ = read_data("expression.py")
201 config = THIS_DIR / "data" / "empty_pyproject.toml"
205 f"--line-length={black.DEFAULT_LINE_LENGTH}",
208 f"--config={config}",
210 result = BlackRunner().invoke(
211 black.main, args, input=BytesIO(source.encode("utf8"))
213 actual = result.output
214 # Again, the contents are checked in a different test, so only look for colors.
215 self.assertIn("\033[1;37m", actual)
216 self.assertIn("\033[36m", actual)
217 self.assertIn("\033[32m", actual)
218 self.assertIn("\033[31m", actual)
219 self.assertIn("\033[0m", actual)
221 @patch("black.dump_to_file", dump_to_stderr)
222 def _test_wip(self) -> None:
223 source, expected = read_data("wip")
224 sys.settrace(tracefunc)
227 experimental_string_processing=False,
228 target_versions={black.TargetVersion.PY38},
230 actual = fs(source, mode=mode)
232 self.assertFormatEqual(expected, actual)
233 black.assert_equivalent(source, actual)
234 black.assert_stable(source, actual, black.FileMode())
236 @unittest.expectedFailure
237 @patch("black.dump_to_file", dump_to_stderr)
238 def test_trailing_comma_optional_parens_stability1(self) -> None:
239 source, _expected = read_data("trailing_comma_optional_parens1")
241 black.assert_stable(source, actual, DEFAULT_MODE)
243 @unittest.expectedFailure
244 @patch("black.dump_to_file", dump_to_stderr)
245 def test_trailing_comma_optional_parens_stability2(self) -> None:
246 source, _expected = read_data("trailing_comma_optional_parens2")
248 black.assert_stable(source, actual, DEFAULT_MODE)
250 @unittest.expectedFailure
251 @patch("black.dump_to_file", dump_to_stderr)
252 def test_trailing_comma_optional_parens_stability3(self) -> None:
253 source, _expected = read_data("trailing_comma_optional_parens3")
255 black.assert_stable(source, actual, DEFAULT_MODE)
257 @patch("black.dump_to_file", dump_to_stderr)
258 def test_pep_572(self) -> None:
259 source, expected = read_data("pep_572")
261 self.assertFormatEqual(expected, actual)
262 black.assert_stable(source, actual, DEFAULT_MODE)
263 if sys.version_info >= (3, 8):
264 black.assert_equivalent(source, actual)
266 @patch("black.dump_to_file", dump_to_stderr)
267 def test_pep_572_remove_parens(self) -> None:
268 source, expected = read_data("pep_572_remove_parens")
270 self.assertFormatEqual(expected, actual)
271 black.assert_stable(source, actual, DEFAULT_MODE)
272 if sys.version_info >= (3, 8):
273 black.assert_equivalent(source, actual)
275 def test_pep_572_version_detection(self) -> None:
276 source, _ = read_data("pep_572")
277 root = black.lib2to3_parse(source)
278 features = black.get_features_used(root)
279 self.assertIn(black.Feature.ASSIGNMENT_EXPRESSIONS, features)
280 versions = black.detect_target_versions(root)
281 self.assertIn(black.TargetVersion.PY38, versions)
283 def test_expression_ff(self) -> None:
284 source, expected = read_data("expression")
285 tmp_file = Path(black.dump_to_file(source))
287 self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
288 with open(tmp_file, encoding="utf8") as f:
292 self.assertFormatEqual(expected, actual)
293 with patch("black.dump_to_file", dump_to_stderr):
294 black.assert_equivalent(source, actual)
295 black.assert_stable(source, actual, DEFAULT_MODE)
297 def test_expression_diff(self) -> None:
298 source, _ = read_data("expression.py")
299 expected, _ = read_data("expression.diff")
300 tmp_file = Path(black.dump_to_file(source))
301 diff_header = re.compile(
302 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
303 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
306 result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
307 self.assertEqual(result.exit_code, 0)
310 actual = result.output
311 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
312 if expected != actual:
313 dump = black.dump_to_file(actual)
315 "Expected diff isn't equal to the actual. If you made changes to"
316 " expression.py and this is an anticipated difference, overwrite"
317 f" tests/data/expression.diff with {dump}"
319 self.assertEqual(expected, actual, msg)
321 def test_expression_diff_with_color(self) -> None:
322 source, _ = read_data("expression.py")
323 expected, _ = read_data("expression.diff")
324 tmp_file = Path(black.dump_to_file(source))
326 result = BlackRunner().invoke(
327 black.main, ["--diff", "--color", str(tmp_file)]
331 actual = result.output
332 # We check the contents of the diff in `test_expression_diff`. All
333 # we need to check here is that color codes exist in the result.
334 self.assertIn("\033[1;37m", actual)
335 self.assertIn("\033[36m", actual)
336 self.assertIn("\033[32m", actual)
337 self.assertIn("\033[31m", actual)
338 self.assertIn("\033[0m", actual)
340 @patch("black.dump_to_file", dump_to_stderr)
341 def test_pep_570(self) -> None:
342 source, expected = read_data("pep_570")
344 self.assertFormatEqual(expected, actual)
345 black.assert_stable(source, actual, DEFAULT_MODE)
346 if sys.version_info >= (3, 8):
347 black.assert_equivalent(source, actual)
349 def test_detect_pos_only_arguments(self) -> None:
350 source, _ = read_data("pep_570")
351 root = black.lib2to3_parse(source)
352 features = black.get_features_used(root)
353 self.assertIn(black.Feature.POS_ONLY_ARGUMENTS, features)
354 versions = black.detect_target_versions(root)
355 self.assertIn(black.TargetVersion.PY38, versions)
357 @patch("black.dump_to_file", dump_to_stderr)
358 def test_string_quotes(self) -> None:
359 source, expected = read_data("string_quotes")
361 self.assertFormatEqual(expected, actual)
362 black.assert_equivalent(source, actual)
363 black.assert_stable(source, actual, DEFAULT_MODE)
364 mode = replace(DEFAULT_MODE, string_normalization=False)
365 not_normalized = fs(source, mode=mode)
366 self.assertFormatEqual(source.replace("\\\n", ""), not_normalized)
367 black.assert_equivalent(source, not_normalized)
368 black.assert_stable(source, not_normalized, mode=mode)
370 @patch("black.dump_to_file", dump_to_stderr)
371 def test_docstring_no_string_normalization(self) -> None:
372 """Like test_docstring but with string normalization off."""
373 source, expected = read_data("docstring_no_string_normalization")
374 mode = replace(DEFAULT_MODE, string_normalization=False)
375 actual = fs(source, mode=mode)
376 self.assertFormatEqual(expected, actual)
377 black.assert_equivalent(source, actual)
378 black.assert_stable(source, actual, mode)
380 def test_long_strings_flag_disabled(self) -> None:
381 """Tests for turning off the string processing logic."""
382 source, expected = read_data("long_strings_flag_disabled")
383 mode = replace(DEFAULT_MODE, experimental_string_processing=False)
384 actual = fs(source, mode=mode)
385 self.assertFormatEqual(expected, actual)
386 black.assert_stable(expected, actual, mode)
388 @patch("black.dump_to_file", dump_to_stderr)
389 def test_numeric_literals(self) -> None:
390 source, expected = read_data("numeric_literals")
391 mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
392 actual = fs(source, mode=mode)
393 self.assertFormatEqual(expected, actual)
394 black.assert_equivalent(source, actual)
395 black.assert_stable(source, actual, mode)
397 @patch("black.dump_to_file", dump_to_stderr)
398 def test_numeric_literals_ignoring_underscores(self) -> None:
399 source, expected = read_data("numeric_literals_skip_underscores")
400 mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
401 actual = fs(source, mode=mode)
402 self.assertFormatEqual(expected, actual)
403 black.assert_equivalent(source, actual)
404 black.assert_stable(source, actual, mode)
406 def test_skip_magic_trailing_comma(self) -> None:
407 source, _ = read_data("expression.py")
408 expected, _ = read_data("expression_skip_magic_trailing_comma.diff")
409 tmp_file = Path(black.dump_to_file(source))
410 diff_header = re.compile(
411 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
412 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
415 result = BlackRunner().invoke(black.main, ["-C", "--diff", str(tmp_file)])
416 self.assertEqual(result.exit_code, 0)
419 actual = result.output
420 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
421 actual = actual.rstrip() + "\n" # the diff output has a trailing space
422 if expected != actual:
423 dump = black.dump_to_file(actual)
425 "Expected diff isn't equal to the actual. If you made changes to"
426 " expression.py and this is an anticipated difference, overwrite"
427 f" tests/data/expression_skip_magic_trailing_comma.diff with {dump}"
429 self.assertEqual(expected, actual, msg)
431 @patch("black.dump_to_file", dump_to_stderr)
432 def test_python2_print_function(self) -> None:
433 source, expected = read_data("python2_print_function")
434 mode = replace(DEFAULT_MODE, target_versions={TargetVersion.PY27})
435 actual = fs(source, mode=mode)
436 self.assertFormatEqual(expected, actual)
437 black.assert_equivalent(source, actual)
438 black.assert_stable(source, actual, mode)
440 @patch("black.dump_to_file", dump_to_stderr)
441 def test_stub(self) -> None:
442 mode = replace(DEFAULT_MODE, is_pyi=True)
443 source, expected = read_data("stub.pyi")
444 actual = fs(source, mode=mode)
445 self.assertFormatEqual(expected, actual)
446 black.assert_stable(source, actual, mode)
448 @patch("black.dump_to_file", dump_to_stderr)
449 def test_async_as_identifier(self) -> None:
450 source_path = (THIS_DIR / "data" / "async_as_identifier.py").resolve()
451 source, expected = read_data("async_as_identifier")
453 self.assertFormatEqual(expected, actual)
454 major, minor = sys.version_info[:2]
455 if major < 3 or (major <= 3 and minor < 7):
456 black.assert_equivalent(source, actual)
457 black.assert_stable(source, actual, DEFAULT_MODE)
458 # ensure black can parse this when the target is 3.6
459 self.invokeBlack([str(source_path), "--target-version", "py36"])
460 # but not on 3.7, because async/await is no longer an identifier
461 self.invokeBlack([str(source_path), "--target-version", "py37"], exit_code=123)
463 @patch("black.dump_to_file", dump_to_stderr)
464 def test_python37(self) -> None:
465 source_path = (THIS_DIR / "data" / "python37.py").resolve()
466 source, expected = read_data("python37")
468 self.assertFormatEqual(expected, actual)
469 major, minor = sys.version_info[:2]
470 if major > 3 or (major == 3 and minor >= 7):
471 black.assert_equivalent(source, actual)
472 black.assert_stable(source, actual, DEFAULT_MODE)
473 # ensure black can parse this when the target is 3.7
474 self.invokeBlack([str(source_path), "--target-version", "py37"])
475 # but not on 3.6, because we use async as a reserved keyword
476 self.invokeBlack([str(source_path), "--target-version", "py36"], exit_code=123)
478 @patch("black.dump_to_file", dump_to_stderr)
479 def test_python38(self) -> None:
480 source, expected = read_data("python38")
482 self.assertFormatEqual(expected, actual)
483 major, minor = sys.version_info[:2]
484 if major > 3 or (major == 3 and minor >= 8):
485 black.assert_equivalent(source, actual)
486 black.assert_stable(source, actual, DEFAULT_MODE)
488 @patch("black.dump_to_file", dump_to_stderr)
489 def test_python39(self) -> None:
490 source, expected = read_data("python39")
492 self.assertFormatEqual(expected, actual)
493 major, minor = sys.version_info[:2]
494 if major > 3 or (major == 3 and minor >= 9):
495 black.assert_equivalent(source, actual)
496 black.assert_stable(source, actual, DEFAULT_MODE)
498 def test_tab_comment_indentation(self) -> None:
499 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
500 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
501 self.assertFormatEqual(contents_spc, fs(contents_spc))
502 self.assertFormatEqual(contents_spc, fs(contents_tab))
504 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
505 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
506 self.assertFormatEqual(contents_spc, fs(contents_spc))
507 self.assertFormatEqual(contents_spc, fs(contents_tab))
509 # mixed tabs and spaces (valid Python 2 code)
510 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t# comment\n pass\n"
511 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
512 self.assertFormatEqual(contents_spc, fs(contents_spc))
513 self.assertFormatEqual(contents_spc, fs(contents_tab))
515 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t\t# comment\n pass\n"
516 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
517 self.assertFormatEqual(contents_spc, fs(contents_spc))
518 self.assertFormatEqual(contents_spc, fs(contents_tab))
520 def test_report_verbose(self) -> None:
521 report = black.Report(verbose=True)
525 def out(msg: str, **kwargs: Any) -> None:
526 out_lines.append(msg)
528 def err(msg: str, **kwargs: Any) -> None:
529 err_lines.append(msg)
531 with patch("black.out", out), patch("black.err", err):
532 report.done(Path("f1"), black.Changed.NO)
533 self.assertEqual(len(out_lines), 1)
534 self.assertEqual(len(err_lines), 0)
535 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
536 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
537 self.assertEqual(report.return_code, 0)
538 report.done(Path("f2"), black.Changed.YES)
539 self.assertEqual(len(out_lines), 2)
540 self.assertEqual(len(err_lines), 0)
541 self.assertEqual(out_lines[-1], "reformatted f2")
543 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
545 report.done(Path("f3"), black.Changed.CACHED)
546 self.assertEqual(len(out_lines), 3)
547 self.assertEqual(len(err_lines), 0)
549 out_lines[-1], "f3 wasn't modified on disk since last run."
552 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
554 self.assertEqual(report.return_code, 0)
556 self.assertEqual(report.return_code, 1)
558 report.failed(Path("e1"), "boom")
559 self.assertEqual(len(out_lines), 3)
560 self.assertEqual(len(err_lines), 1)
561 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
563 unstyle(str(report)),
564 "1 file reformatted, 2 files left unchanged, 1 file failed to"
567 self.assertEqual(report.return_code, 123)
568 report.done(Path("f3"), black.Changed.YES)
569 self.assertEqual(len(out_lines), 4)
570 self.assertEqual(len(err_lines), 1)
571 self.assertEqual(out_lines[-1], "reformatted f3")
573 unstyle(str(report)),
574 "2 files reformatted, 2 files left unchanged, 1 file failed to"
577 self.assertEqual(report.return_code, 123)
578 report.failed(Path("e2"), "boom")
579 self.assertEqual(len(out_lines), 4)
580 self.assertEqual(len(err_lines), 2)
581 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
583 unstyle(str(report)),
584 "2 files reformatted, 2 files left unchanged, 2 files failed to"
587 self.assertEqual(report.return_code, 123)
588 report.path_ignored(Path("wat"), "no match")
589 self.assertEqual(len(out_lines), 5)
590 self.assertEqual(len(err_lines), 2)
591 self.assertEqual(out_lines[-1], "wat ignored: no match")
593 unstyle(str(report)),
594 "2 files reformatted, 2 files left unchanged, 2 files failed to"
597 self.assertEqual(report.return_code, 123)
598 report.done(Path("f4"), black.Changed.NO)
599 self.assertEqual(len(out_lines), 6)
600 self.assertEqual(len(err_lines), 2)
601 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
603 unstyle(str(report)),
604 "2 files reformatted, 3 files left unchanged, 2 files failed to"
607 self.assertEqual(report.return_code, 123)
610 unstyle(str(report)),
611 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
612 " would fail to reformat.",
617 unstyle(str(report)),
618 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
619 " would fail to reformat.",
622 def test_report_quiet(self) -> None:
623 report = black.Report(quiet=True)
627 def out(msg: str, **kwargs: Any) -> None:
628 out_lines.append(msg)
630 def err(msg: str, **kwargs: Any) -> None:
631 err_lines.append(msg)
633 with patch("black.out", out), patch("black.err", err):
634 report.done(Path("f1"), black.Changed.NO)
635 self.assertEqual(len(out_lines), 0)
636 self.assertEqual(len(err_lines), 0)
637 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
638 self.assertEqual(report.return_code, 0)
639 report.done(Path("f2"), black.Changed.YES)
640 self.assertEqual(len(out_lines), 0)
641 self.assertEqual(len(err_lines), 0)
643 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
645 report.done(Path("f3"), black.Changed.CACHED)
646 self.assertEqual(len(out_lines), 0)
647 self.assertEqual(len(err_lines), 0)
649 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
651 self.assertEqual(report.return_code, 0)
653 self.assertEqual(report.return_code, 1)
655 report.failed(Path("e1"), "boom")
656 self.assertEqual(len(out_lines), 0)
657 self.assertEqual(len(err_lines), 1)
658 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
660 unstyle(str(report)),
661 "1 file reformatted, 2 files left unchanged, 1 file failed to"
664 self.assertEqual(report.return_code, 123)
665 report.done(Path("f3"), black.Changed.YES)
666 self.assertEqual(len(out_lines), 0)
667 self.assertEqual(len(err_lines), 1)
669 unstyle(str(report)),
670 "2 files reformatted, 2 files left unchanged, 1 file failed to"
673 self.assertEqual(report.return_code, 123)
674 report.failed(Path("e2"), "boom")
675 self.assertEqual(len(out_lines), 0)
676 self.assertEqual(len(err_lines), 2)
677 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
679 unstyle(str(report)),
680 "2 files reformatted, 2 files left unchanged, 2 files failed to"
683 self.assertEqual(report.return_code, 123)
684 report.path_ignored(Path("wat"), "no match")
685 self.assertEqual(len(out_lines), 0)
686 self.assertEqual(len(err_lines), 2)
688 unstyle(str(report)),
689 "2 files reformatted, 2 files left unchanged, 2 files failed to"
692 self.assertEqual(report.return_code, 123)
693 report.done(Path("f4"), black.Changed.NO)
694 self.assertEqual(len(out_lines), 0)
695 self.assertEqual(len(err_lines), 2)
697 unstyle(str(report)),
698 "2 files reformatted, 3 files left unchanged, 2 files failed to"
701 self.assertEqual(report.return_code, 123)
704 unstyle(str(report)),
705 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
706 " would fail to reformat.",
711 unstyle(str(report)),
712 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
713 " would fail to reformat.",
716 def test_report_normal(self) -> None:
717 report = black.Report()
721 def out(msg: str, **kwargs: Any) -> None:
722 out_lines.append(msg)
724 def err(msg: str, **kwargs: Any) -> None:
725 err_lines.append(msg)
727 with patch("black.out", out), patch("black.err", err):
728 report.done(Path("f1"), black.Changed.NO)
729 self.assertEqual(len(out_lines), 0)
730 self.assertEqual(len(err_lines), 0)
731 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
732 self.assertEqual(report.return_code, 0)
733 report.done(Path("f2"), black.Changed.YES)
734 self.assertEqual(len(out_lines), 1)
735 self.assertEqual(len(err_lines), 0)
736 self.assertEqual(out_lines[-1], "reformatted f2")
738 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
740 report.done(Path("f3"), black.Changed.CACHED)
741 self.assertEqual(len(out_lines), 1)
742 self.assertEqual(len(err_lines), 0)
743 self.assertEqual(out_lines[-1], "reformatted f2")
745 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
747 self.assertEqual(report.return_code, 0)
749 self.assertEqual(report.return_code, 1)
751 report.failed(Path("e1"), "boom")
752 self.assertEqual(len(out_lines), 1)
753 self.assertEqual(len(err_lines), 1)
754 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
756 unstyle(str(report)),
757 "1 file reformatted, 2 files left unchanged, 1 file failed to"
760 self.assertEqual(report.return_code, 123)
761 report.done(Path("f3"), black.Changed.YES)
762 self.assertEqual(len(out_lines), 2)
763 self.assertEqual(len(err_lines), 1)
764 self.assertEqual(out_lines[-1], "reformatted f3")
766 unstyle(str(report)),
767 "2 files reformatted, 2 files left unchanged, 1 file failed to"
770 self.assertEqual(report.return_code, 123)
771 report.failed(Path("e2"), "boom")
772 self.assertEqual(len(out_lines), 2)
773 self.assertEqual(len(err_lines), 2)
774 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
776 unstyle(str(report)),
777 "2 files reformatted, 2 files left unchanged, 2 files failed to"
780 self.assertEqual(report.return_code, 123)
781 report.path_ignored(Path("wat"), "no match")
782 self.assertEqual(len(out_lines), 2)
783 self.assertEqual(len(err_lines), 2)
785 unstyle(str(report)),
786 "2 files reformatted, 2 files left unchanged, 2 files failed to"
789 self.assertEqual(report.return_code, 123)
790 report.done(Path("f4"), black.Changed.NO)
791 self.assertEqual(len(out_lines), 2)
792 self.assertEqual(len(err_lines), 2)
794 unstyle(str(report)),
795 "2 files reformatted, 3 files left unchanged, 2 files failed to"
798 self.assertEqual(report.return_code, 123)
801 unstyle(str(report)),
802 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
803 " would fail to reformat.",
808 unstyle(str(report)),
809 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
810 " would fail to reformat.",
813 def test_lib2to3_parse(self) -> None:
814 with self.assertRaises(black.InvalidInput):
815 black.lib2to3_parse("invalid syntax")
818 black.lib2to3_parse(straddling)
819 black.lib2to3_parse(straddling, {TargetVersion.PY27})
820 black.lib2to3_parse(straddling, {TargetVersion.PY36})
821 black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
824 black.lib2to3_parse(py2_only)
825 black.lib2to3_parse(py2_only, {TargetVersion.PY27})
826 with self.assertRaises(black.InvalidInput):
827 black.lib2to3_parse(py2_only, {TargetVersion.PY36})
828 with self.assertRaises(black.InvalidInput):
829 black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
831 py3_only = "exec(x, end=y)"
832 black.lib2to3_parse(py3_only)
833 with self.assertRaises(black.InvalidInput):
834 black.lib2to3_parse(py3_only, {TargetVersion.PY27})
835 black.lib2to3_parse(py3_only, {TargetVersion.PY36})
836 black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
838 def test_get_features_used_decorator(self) -> None:
839 # Test the feature detection of new decorator syntax
840 # since this makes some test cases of test_get_features_used()
841 # fails if it fails, this is tested first so that a useful case
843 simples, relaxed = read_data("decorators")
844 # skip explanation comments at the top of the file
845 for simple_test in simples.split("##")[1:]:
846 node = black.lib2to3_parse(simple_test)
847 decorator = str(node.children[0].children[0]).strip()
849 Feature.RELAXED_DECORATORS,
850 black.get_features_used(node),
852 f"decorator '{decorator}' follows python<=3.8 syntax"
853 "but is detected as 3.9+"
854 # f"The full node is\n{node!r}"
857 # skip the '# output' comment at the top of the output part
858 for relaxed_test in relaxed.split("##")[1:]:
859 node = black.lib2to3_parse(relaxed_test)
860 decorator = str(node.children[0].children[0]).strip()
862 Feature.RELAXED_DECORATORS,
863 black.get_features_used(node),
865 f"decorator '{decorator}' uses python3.9+ syntax"
866 "but is detected as python<=3.8"
867 # f"The full node is\n{node!r}"
871 def test_get_features_used(self) -> None:
872 node = black.lib2to3_parse("def f(*, arg): ...\n")
873 self.assertEqual(black.get_features_used(node), set())
874 node = black.lib2to3_parse("def f(*, arg,): ...\n")
875 self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
876 node = black.lib2to3_parse("f(*arg,)\n")
878 black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
880 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
881 self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
882 node = black.lib2to3_parse("123_456\n")
883 self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
884 node = black.lib2to3_parse("123456\n")
885 self.assertEqual(black.get_features_used(node), set())
886 source, expected = read_data("function")
887 node = black.lib2to3_parse(source)
888 expected_features = {
889 Feature.TRAILING_COMMA_IN_CALL,
890 Feature.TRAILING_COMMA_IN_DEF,
893 self.assertEqual(black.get_features_used(node), expected_features)
894 node = black.lib2to3_parse(expected)
895 self.assertEqual(black.get_features_used(node), expected_features)
896 source, expected = read_data("expression")
897 node = black.lib2to3_parse(source)
898 self.assertEqual(black.get_features_used(node), set())
899 node = black.lib2to3_parse(expected)
900 self.assertEqual(black.get_features_used(node), set())
902 def test_get_future_imports(self) -> None:
903 node = black.lib2to3_parse("\n")
904 self.assertEqual(set(), black.get_future_imports(node))
905 node = black.lib2to3_parse("from __future__ import black\n")
906 self.assertEqual({"black"}, black.get_future_imports(node))
907 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
908 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
909 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
910 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
911 node = black.lib2to3_parse(
912 "from __future__ import multiple\nfrom __future__ import imports\n"
914 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
915 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
916 self.assertEqual({"black"}, black.get_future_imports(node))
917 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
918 self.assertEqual({"black"}, black.get_future_imports(node))
919 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
920 self.assertEqual(set(), black.get_future_imports(node))
921 node = black.lib2to3_parse("from some.module import black\n")
922 self.assertEqual(set(), black.get_future_imports(node))
923 node = black.lib2to3_parse(
924 "from __future__ import unicode_literals as _unicode_literals"
926 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
927 node = black.lib2to3_parse(
928 "from __future__ import unicode_literals as _lol, print"
930 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
932 def test_debug_visitor(self) -> None:
933 source, _ = read_data("debug_visitor.py")
934 expected, _ = read_data("debug_visitor.out")
938 def out(msg: str, **kwargs: Any) -> None:
939 out_lines.append(msg)
941 def err(msg: str, **kwargs: Any) -> None:
942 err_lines.append(msg)
944 with patch("black.out", out), patch("black.err", err):
945 black.DebugVisitor.show(source)
946 actual = "\n".join(out_lines) + "\n"
948 if expected != actual:
949 log_name = black.dump_to_file(*out_lines)
953 f"AST print out is different. Actual version dumped to {log_name}",
956 def test_format_file_contents(self) -> None:
959 with self.assertRaises(black.NothingChanged):
960 black.format_file_contents(empty, mode=mode, fast=False)
962 with self.assertRaises(black.NothingChanged):
963 black.format_file_contents(just_nl, mode=mode, fast=False)
964 same = "j = [1, 2, 3]\n"
965 with self.assertRaises(black.NothingChanged):
966 black.format_file_contents(same, mode=mode, fast=False)
967 different = "j = [1,2,3]"
969 actual = black.format_file_contents(different, mode=mode, fast=False)
970 self.assertEqual(expected, actual)
971 invalid = "return if you can"
972 with self.assertRaises(black.InvalidInput) as e:
973 black.format_file_contents(invalid, mode=mode, fast=False)
974 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
976 def test_endmarker(self) -> None:
977 n = black.lib2to3_parse("\n")
978 self.assertEqual(n.type, black.syms.file_input)
979 self.assertEqual(len(n.children), 1)
980 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
982 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
983 def test_assertFormatEqual(self) -> None:
987 def out(msg: str, **kwargs: Any) -> None:
988 out_lines.append(msg)
990 def err(msg: str, **kwargs: Any) -> None:
991 err_lines.append(msg)
993 with patch("black.out", out), patch("black.err", err):
994 with self.assertRaises(AssertionError):
995 self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")
997 out_str = "".join(out_lines)
998 self.assertTrue("Expected tree:" in out_str)
999 self.assertTrue("Actual tree:" in out_str)
1000 self.assertEqual("".join(err_lines), "")
1002 def test_cache_broken_file(self) -> None:
1004 with cache_dir() as workspace:
1005 cache_file = black.get_cache_file(mode)
1006 with cache_file.open("w") as fobj:
1007 fobj.write("this is not a pickle")
1008 self.assertEqual(black.read_cache(mode), {})
1009 src = (workspace / "test.py").resolve()
1010 with src.open("w") as fobj:
1011 fobj.write("print('hello')")
1012 self.invokeBlack([str(src)])
1013 cache = black.read_cache(mode)
1014 self.assertIn(str(src), cache)
1016 def test_cache_single_file_already_cached(self) -> None:
1018 with cache_dir() as workspace:
1019 src = (workspace / "test.py").resolve()
1020 with src.open("w") as fobj:
1021 fobj.write("print('hello')")
1022 black.write_cache({}, [src], mode)
1023 self.invokeBlack([str(src)])
1024 with src.open("r") as fobj:
1025 self.assertEqual(fobj.read(), "print('hello')")
1028 def test_cache_multiple_files(self) -> None:
1030 with cache_dir() as workspace, patch(
1031 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1033 one = (workspace / "one.py").resolve()
1034 with one.open("w") as fobj:
1035 fobj.write("print('hello')")
1036 two = (workspace / "two.py").resolve()
1037 with two.open("w") as fobj:
1038 fobj.write("print('hello')")
1039 black.write_cache({}, [one], mode)
1040 self.invokeBlack([str(workspace)])
1041 with one.open("r") as fobj:
1042 self.assertEqual(fobj.read(), "print('hello')")
1043 with two.open("r") as fobj:
1044 self.assertEqual(fobj.read(), 'print("hello")\n')
1045 cache = black.read_cache(mode)
1046 self.assertIn(str(one), cache)
1047 self.assertIn(str(two), cache)
1049 def test_no_cache_when_writeback_diff(self) -> None:
1051 with cache_dir() as workspace:
1052 src = (workspace / "test.py").resolve()
1053 with src.open("w") as fobj:
1054 fobj.write("print('hello')")
1055 with patch("black.read_cache") as read_cache, patch(
1058 self.invokeBlack([str(src), "--diff"])
1059 cache_file = black.get_cache_file(mode)
1060 self.assertFalse(cache_file.exists())
1061 write_cache.assert_not_called()
1062 read_cache.assert_not_called()
1064 def test_no_cache_when_writeback_color_diff(self) -> None:
1066 with cache_dir() as workspace:
1067 src = (workspace / "test.py").resolve()
1068 with src.open("w") as fobj:
1069 fobj.write("print('hello')")
1070 with patch("black.read_cache") as read_cache, patch(
1073 self.invokeBlack([str(src), "--diff", "--color"])
1074 cache_file = black.get_cache_file(mode)
1075 self.assertFalse(cache_file.exists())
1076 write_cache.assert_not_called()
1077 read_cache.assert_not_called()
1080 def test_output_locking_when_writeback_diff(self) -> None:
1081 with cache_dir() as workspace:
1082 for tag in range(0, 4):
1083 src = (workspace / f"test{tag}.py").resolve()
1084 with src.open("w") as fobj:
1085 fobj.write("print('hello')")
1086 with patch("black.Manager", wraps=multiprocessing.Manager) as mgr:
1087 self.invokeBlack(["--diff", str(workspace)], exit_code=0)
1088 # this isn't quite doing what we want, but if it _isn't_
1089 # called then we cannot be using the lock it provides
1093 def test_output_locking_when_writeback_color_diff(self) -> None:
1094 with cache_dir() as workspace:
1095 for tag in range(0, 4):
1096 src = (workspace / f"test{tag}.py").resolve()
1097 with src.open("w") as fobj:
1098 fobj.write("print('hello')")
1099 with patch("black.Manager", wraps=multiprocessing.Manager) as mgr:
1100 self.invokeBlack(["--diff", "--color", str(workspace)], exit_code=0)
1101 # this isn't quite doing what we want, but if it _isn't_
1102 # called then we cannot be using the lock it provides
1105 def test_no_cache_when_stdin(self) -> None:
1108 result = CliRunner().invoke(
1109 black.main, ["-"], input=BytesIO(b"print('hello')")
1111 self.assertEqual(result.exit_code, 0)
1112 cache_file = black.get_cache_file(mode)
1113 self.assertFalse(cache_file.exists())
1115 def test_read_cache_no_cachefile(self) -> None:
1118 self.assertEqual(black.read_cache(mode), {})
1120 def test_write_cache_read_cache(self) -> None:
1122 with cache_dir() as workspace:
1123 src = (workspace / "test.py").resolve()
1125 black.write_cache({}, [src], mode)
1126 cache = black.read_cache(mode)
1127 self.assertIn(str(src), cache)
1128 self.assertEqual(cache[str(src)], black.get_cache_info(src))
1130 def test_filter_cached(self) -> None:
1131 with TemporaryDirectory() as workspace:
1132 path = Path(workspace)
1133 uncached = (path / "uncached").resolve()
1134 cached = (path / "cached").resolve()
1135 cached_but_changed = (path / "changed").resolve()
1138 cached_but_changed.touch()
1140 str(cached): black.get_cache_info(cached),
1141 str(cached_but_changed): (0.0, 0),
1143 todo, done = black.filter_cached(
1144 cache, {uncached, cached, cached_but_changed}
1146 self.assertEqual(todo, {uncached, cached_but_changed})
1147 self.assertEqual(done, {cached})
1149 def test_write_cache_creates_directory_if_needed(self) -> None:
1151 with cache_dir(exists=False) as workspace:
1152 self.assertFalse(workspace.exists())
1153 black.write_cache({}, [], mode)
1154 self.assertTrue(workspace.exists())
1157 def test_failed_formatting_does_not_get_cached(self) -> None:
1159 with cache_dir() as workspace, patch(
1160 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1162 failing = (workspace / "failing.py").resolve()
1163 with failing.open("w") as fobj:
1164 fobj.write("not actually python")
1165 clean = (workspace / "clean.py").resolve()
1166 with clean.open("w") as fobj:
1167 fobj.write('print("hello")\n')
1168 self.invokeBlack([str(workspace)], exit_code=123)
1169 cache = black.read_cache(mode)
1170 self.assertNotIn(str(failing), cache)
1171 self.assertIn(str(clean), cache)
1173 def test_write_cache_write_fail(self) -> None:
1175 with cache_dir(), patch.object(Path, "open") as mock:
1176 mock.side_effect = OSError
1177 black.write_cache({}, [], mode)
1180 @patch("black.ProcessPoolExecutor", MagicMock(side_effect=OSError))
1181 def test_works_in_mono_process_only_environment(self) -> None:
1182 with cache_dir() as workspace:
1184 (workspace / "one.py").resolve(),
1185 (workspace / "two.py").resolve(),
1187 f.write_text('print("hello")\n')
1188 self.invokeBlack([str(workspace)])
1191 def test_check_diff_use_together(self) -> None:
1193 # Files which will be reformatted.
1194 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1195 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1196 # Files which will not be reformatted.
1197 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1198 self.invokeBlack([str(src2), "--diff", "--check"])
1199 # Multi file command.
1200 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1202 def test_no_files(self) -> None:
1204 # Without an argument, black exits with error code 0.
1205 self.invokeBlack([])
1207 def test_broken_symlink(self) -> None:
1208 with cache_dir() as workspace:
1209 symlink = workspace / "broken_link.py"
1211 symlink.symlink_to("nonexistent.py")
1212 except OSError as e:
1213 self.skipTest(f"Can't create symlinks: {e}")
1214 self.invokeBlack([str(workspace.resolve())])
1216 def test_read_cache_line_lengths(self) -> None:
1218 short_mode = replace(DEFAULT_MODE, line_length=1)
1219 with cache_dir() as workspace:
1220 path = (workspace / "file.py").resolve()
1222 black.write_cache({}, [path], mode)
1223 one = black.read_cache(mode)
1224 self.assertIn(str(path), one)
1225 two = black.read_cache(short_mode)
1226 self.assertNotIn(str(path), two)
1228 def test_single_file_force_pyi(self) -> None:
1229 pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
1230 contents, expected = read_data("force_pyi")
1231 with cache_dir() as workspace:
1232 path = (workspace / "file.py").resolve()
1233 with open(path, "w") as fh:
1235 self.invokeBlack([str(path), "--pyi"])
1236 with open(path, "r") as fh:
1238 # verify cache with --pyi is separate
1239 pyi_cache = black.read_cache(pyi_mode)
1240 self.assertIn(str(path), pyi_cache)
1241 normal_cache = black.read_cache(DEFAULT_MODE)
1242 self.assertNotIn(str(path), normal_cache)
1243 self.assertFormatEqual(expected, actual)
1244 black.assert_equivalent(contents, actual)
1245 black.assert_stable(contents, actual, pyi_mode)
1248 def test_multi_file_force_pyi(self) -> None:
1249 reg_mode = DEFAULT_MODE
1250 pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
1251 contents, expected = read_data("force_pyi")
1252 with cache_dir() as workspace:
1254 (workspace / "file1.py").resolve(),
1255 (workspace / "file2.py").resolve(),
1258 with open(path, "w") as fh:
1260 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1262 with open(path, "r") as fh:
1264 self.assertEqual(actual, expected)
1265 # verify cache with --pyi is separate
1266 pyi_cache = black.read_cache(pyi_mode)
1267 normal_cache = black.read_cache(reg_mode)
1269 self.assertIn(str(path), pyi_cache)
1270 self.assertNotIn(str(path), normal_cache)
1272 def test_pipe_force_pyi(self) -> None:
1273 source, expected = read_data("force_pyi")
1274 result = CliRunner().invoke(
1275 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1277 self.assertEqual(result.exit_code, 0)
1278 actual = result.output
1279 self.assertFormatEqual(actual, expected)
1281 def test_single_file_force_py36(self) -> None:
1282 reg_mode = DEFAULT_MODE
1283 py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
1284 source, expected = read_data("force_py36")
1285 with cache_dir() as workspace:
1286 path = (workspace / "file.py").resolve()
1287 with open(path, "w") as fh:
1289 self.invokeBlack([str(path), *PY36_ARGS])
1290 with open(path, "r") as fh:
1292 # verify cache with --target-version is separate
1293 py36_cache = black.read_cache(py36_mode)
1294 self.assertIn(str(path), py36_cache)
1295 normal_cache = black.read_cache(reg_mode)
1296 self.assertNotIn(str(path), normal_cache)
1297 self.assertEqual(actual, expected)
1300 def test_multi_file_force_py36(self) -> None:
1301 reg_mode = DEFAULT_MODE
1302 py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
1303 source, expected = read_data("force_py36")
1304 with cache_dir() as workspace:
1306 (workspace / "file1.py").resolve(),
1307 (workspace / "file2.py").resolve(),
1310 with open(path, "w") as fh:
1312 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1314 with open(path, "r") as fh:
1316 self.assertEqual(actual, expected)
1317 # verify cache with --target-version is separate
1318 pyi_cache = black.read_cache(py36_mode)
1319 normal_cache = black.read_cache(reg_mode)
1321 self.assertIn(str(path), pyi_cache)
1322 self.assertNotIn(str(path), normal_cache)
1324 def test_pipe_force_py36(self) -> None:
1325 source, expected = read_data("force_py36")
1326 result = CliRunner().invoke(
1328 ["-", "-q", "--target-version=py36"],
1329 input=BytesIO(source.encode("utf8")),
1331 self.assertEqual(result.exit_code, 0)
1332 actual = result.output
1333 self.assertFormatEqual(actual, expected)
1335 def test_include_exclude(self) -> None:
1336 path = THIS_DIR / "data" / "include_exclude_tests"
1337 include = re.compile(r"\.pyi?$")
1338 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1339 report = black.Report()
1340 gitignore = PathSpec.from_lines("gitwildmatch", [])
1341 sources: List[Path] = []
1343 Path(path / "b/dont_exclude/a.py"),
1344 Path(path / "b/dont_exclude/a.pyi"),
1346 this_abs = THIS_DIR.resolve()
1348 black.gen_python_files(
1349 path.iterdir(), this_abs, include, exclude, None, report, gitignore
1352 self.assertEqual(sorted(expected), sorted(sources))
1354 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1355 def test_exclude_for_issue_1572(self) -> None:
1356 # Exclude shouldn't touch files that were explicitly given to Black through the
1357 # CLI. Exclude is supposed to only apply to the recursive discovery of files.
1358 # https://github.com/psf/black/issues/1572
1359 path = THIS_DIR / "data" / "include_exclude_tests"
1361 exclude = r"/exclude/|a\.py"
1362 src = str(path / "b/exclude/a.py")
1363 report = black.Report()
1364 expected = [Path(path / "b/exclude/a.py")]
1375 stdin_filename=None,
1378 self.assertEqual(sorted(expected), sorted(sources))
1380 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1381 def test_get_sources_with_stdin(self) -> None:
1383 exclude = r"/exclude/|a\.py"
1385 report = black.Report()
1386 expected = [Path("-")]
1397 stdin_filename=None,
1400 self.assertEqual(sorted(expected), sorted(sources))
1402 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1403 def test_get_sources_with_stdin_filename(self) -> None:
1405 exclude = r"/exclude/|a\.py"
1407 report = black.Report()
1408 stdin_filename = str(THIS_DIR / "data/collections.py")
1409 expected = [Path(f"__BLACK_STDIN_FILENAME__{stdin_filename}")]
1420 stdin_filename=stdin_filename,
1423 self.assertEqual(sorted(expected), sorted(sources))
1425 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1426 def test_get_sources_with_stdin_filename_and_exclude(self) -> None:
1427 # Exclude shouldn't exclude stdin_filename since it is mimicing the
1428 # file being passed directly. This is the same as
1429 # test_exclude_for_issue_1572
1430 path = THIS_DIR / "data" / "include_exclude_tests"
1432 exclude = r"/exclude/|a\.py"
1434 report = black.Report()
1435 stdin_filename = str(path / "b/exclude/a.py")
1436 expected = [Path(f"__BLACK_STDIN_FILENAME__{stdin_filename}")]
1447 stdin_filename=stdin_filename,
1450 self.assertEqual(sorted(expected), sorted(sources))
1452 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1453 def test_get_sources_with_stdin_filename_and_force_exclude(self) -> None:
1454 # Force exclude should exclude the file when passing it through
1456 path = THIS_DIR / "data" / "include_exclude_tests"
1458 force_exclude = r"/exclude/|a\.py"
1460 report = black.Report()
1461 stdin_filename = str(path / "b/exclude/a.py")
1470 force_exclude=force_exclude,
1472 stdin_filename=stdin_filename,
1475 self.assertEqual([], sorted(sources))
1477 def test_reformat_one_with_stdin(self) -> None:
1479 "black.format_stdin_to_stdout",
1480 return_value=lambda *args, **kwargs: black.Changed.YES,
1482 report = MagicMock()
1487 write_back=black.WriteBack.YES,
1491 fsts.assert_called_once()
1492 report.done.assert_called_with(path, black.Changed.YES)
1494 def test_reformat_one_with_stdin_filename(self) -> None:
1496 "black.format_stdin_to_stdout",
1497 return_value=lambda *args, **kwargs: black.Changed.YES,
1499 report = MagicMock()
1501 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1506 write_back=black.WriteBack.YES,
1510 fsts.assert_called_once()
1511 # __BLACK_STDIN_FILENAME__ should have been striped
1512 report.done.assert_called_with(expected, black.Changed.YES)
1514 def test_reformat_one_with_stdin_and_existing_path(self) -> None:
1516 "black.format_stdin_to_stdout",
1517 return_value=lambda *args, **kwargs: black.Changed.YES,
1519 report = MagicMock()
1520 # Even with an existing file, since we are forcing stdin, black
1521 # should output to stdout and not modify the file inplace
1522 p = Path(str(THIS_DIR / "data/collections.py"))
1523 # Make sure is_file actually returns True
1524 self.assertTrue(p.is_file())
1525 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1530 write_back=black.WriteBack.YES,
1534 fsts.assert_called_once()
1535 # __BLACK_STDIN_FILENAME__ should have been striped
1536 report.done.assert_called_with(expected, black.Changed.YES)
1538 def test_gitignore_exclude(self) -> None:
1539 path = THIS_DIR / "data" / "include_exclude_tests"
1540 include = re.compile(r"\.pyi?$")
1541 exclude = re.compile(r"")
1542 report = black.Report()
1543 gitignore = PathSpec.from_lines(
1544 "gitwildmatch", ["exclude/", ".definitely_exclude"]
1546 sources: List[Path] = []
1548 Path(path / "b/dont_exclude/a.py"),
1549 Path(path / "b/dont_exclude/a.pyi"),
1551 this_abs = THIS_DIR.resolve()
1553 black.gen_python_files(
1554 path.iterdir(), this_abs, include, exclude, None, report, gitignore
1557 self.assertEqual(sorted(expected), sorted(sources))
1559 def test_empty_include(self) -> None:
1560 path = THIS_DIR / "data" / "include_exclude_tests"
1561 report = black.Report()
1562 gitignore = PathSpec.from_lines("gitwildmatch", [])
1563 empty = re.compile(r"")
1564 sources: List[Path] = []
1566 Path(path / "b/exclude/a.pie"),
1567 Path(path / "b/exclude/a.py"),
1568 Path(path / "b/exclude/a.pyi"),
1569 Path(path / "b/dont_exclude/a.pie"),
1570 Path(path / "b/dont_exclude/a.py"),
1571 Path(path / "b/dont_exclude/a.pyi"),
1572 Path(path / "b/.definitely_exclude/a.pie"),
1573 Path(path / "b/.definitely_exclude/a.py"),
1574 Path(path / "b/.definitely_exclude/a.pyi"),
1576 this_abs = THIS_DIR.resolve()
1578 black.gen_python_files(
1582 re.compile(black.DEFAULT_EXCLUDES),
1588 self.assertEqual(sorted(expected), sorted(sources))
1590 def test_empty_exclude(self) -> None:
1591 path = THIS_DIR / "data" / "include_exclude_tests"
1592 report = black.Report()
1593 gitignore = PathSpec.from_lines("gitwildmatch", [])
1594 empty = re.compile(r"")
1595 sources: List[Path] = []
1597 Path(path / "b/dont_exclude/a.py"),
1598 Path(path / "b/dont_exclude/a.pyi"),
1599 Path(path / "b/exclude/a.py"),
1600 Path(path / "b/exclude/a.pyi"),
1601 Path(path / "b/.definitely_exclude/a.py"),
1602 Path(path / "b/.definitely_exclude/a.pyi"),
1604 this_abs = THIS_DIR.resolve()
1606 black.gen_python_files(
1609 re.compile(black.DEFAULT_INCLUDES),
1616 self.assertEqual(sorted(expected), sorted(sources))
1618 def test_invalid_include_exclude(self) -> None:
1619 for option in ["--include", "--exclude"]:
1620 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1622 def test_preserves_line_endings(self) -> None:
1623 with TemporaryDirectory() as workspace:
1624 test_file = Path(workspace) / "test.py"
1625 for nl in ["\n", "\r\n"]:
1626 contents = nl.join(["def f( ):", " pass"])
1627 test_file.write_bytes(contents.encode())
1628 ff(test_file, write_back=black.WriteBack.YES)
1629 updated_contents: bytes = test_file.read_bytes()
1630 self.assertIn(nl.encode(), updated_contents)
1632 self.assertNotIn(b"\r\n", updated_contents)
1634 def test_preserves_line_endings_via_stdin(self) -> None:
1635 for nl in ["\n", "\r\n"]:
1636 contents = nl.join(["def f( ):", " pass"])
1637 runner = BlackRunner()
1638 result = runner.invoke(
1639 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1641 self.assertEqual(result.exit_code, 0)
1642 output = runner.stdout_bytes
1643 self.assertIn(nl.encode("utf8"), output)
1645 self.assertNotIn(b"\r\n", output)
1647 def test_assert_equivalent_different_asts(self) -> None:
1648 with self.assertRaises(AssertionError):
1649 black.assert_equivalent("{}", "None")
1651 def test_symlink_out_of_root_directory(self) -> None:
1653 root = THIS_DIR.resolve()
1655 include = re.compile(black.DEFAULT_INCLUDES)
1656 exclude = re.compile(black.DEFAULT_EXCLUDES)
1657 report = black.Report()
1658 gitignore = PathSpec.from_lines("gitwildmatch", [])
1659 # `child` should behave like a symlink which resolved path is clearly
1660 # outside of the `root` directory.
1661 path.iterdir.return_value = [child]
1662 child.resolve.return_value = Path("/a/b/c")
1663 child.as_posix.return_value = "/a/b/c"
1664 child.is_symlink.return_value = True
1667 black.gen_python_files(
1668 path.iterdir(), root, include, exclude, None, report, gitignore
1671 except ValueError as ve:
1672 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1673 path.iterdir.assert_called_once()
1674 child.resolve.assert_called_once()
1675 child.is_symlink.assert_called_once()
1676 # `child` should behave like a strange file which resolved path is clearly
1677 # outside of the `root` directory.
1678 child.is_symlink.return_value = False
1679 with self.assertRaises(ValueError):
1681 black.gen_python_files(
1682 path.iterdir(), root, include, exclude, None, report, gitignore
1685 path.iterdir.assert_called()
1686 self.assertEqual(path.iterdir.call_count, 2)
1687 child.resolve.assert_called()
1688 self.assertEqual(child.resolve.call_count, 2)
1689 child.is_symlink.assert_called()
1690 self.assertEqual(child.is_symlink.call_count, 2)
1692 def test_shhh_click(self) -> None:
1694 from click import _unicodefun # type: ignore
1695 except ModuleNotFoundError:
1696 self.skipTest("Incompatible Click version")
1697 if not hasattr(_unicodefun, "_verify_python3_env"):
1698 self.skipTest("Incompatible Click version")
1699 # First, let's see if Click is crashing with a preferred ASCII charset.
1700 with patch("locale.getpreferredencoding") as gpe:
1701 gpe.return_value = "ASCII"
1702 with self.assertRaises(RuntimeError):
1703 _unicodefun._verify_python3_env()
1704 # Now, let's silence Click...
1706 # ...and confirm it's silent.
1707 with patch("locale.getpreferredencoding") as gpe:
1708 gpe.return_value = "ASCII"
1710 _unicodefun._verify_python3_env()
1711 except RuntimeError as re:
1712 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1714 def test_root_logger_not_used_directly(self) -> None:
1715 def fail(*args: Any, **kwargs: Any) -> None:
1716 self.fail("Record created with root logger")
1718 with patch.multiple(
1729 def test_invalid_config_return_code(self) -> None:
1730 tmp_file = Path(black.dump_to_file())
1732 tmp_config = Path(black.dump_to_file())
1734 args = ["--config", str(tmp_config), str(tmp_file)]
1735 self.invokeBlack(args, exit_code=2, ignore_config=False)
1739 def test_parse_pyproject_toml(self) -> None:
1740 test_toml_file = THIS_DIR / "test.toml"
1741 config = black.parse_pyproject_toml(str(test_toml_file))
1742 self.assertEqual(config["verbose"], 1)
1743 self.assertEqual(config["check"], "no")
1744 self.assertEqual(config["diff"], "y")
1745 self.assertEqual(config["color"], True)
1746 self.assertEqual(config["line_length"], 79)
1747 self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
1748 self.assertEqual(config["exclude"], r"\.pyi?$")
1749 self.assertEqual(config["include"], r"\.py?$")
1751 def test_read_pyproject_toml(self) -> None:
1752 test_toml_file = THIS_DIR / "test.toml"
1753 fake_ctx = FakeContext()
1754 black.read_pyproject_toml(fake_ctx, FakeParameter(), str(test_toml_file))
1755 config = fake_ctx.default_map
1756 self.assertEqual(config["verbose"], "1")
1757 self.assertEqual(config["check"], "no")
1758 self.assertEqual(config["diff"], "y")
1759 self.assertEqual(config["color"], "True")
1760 self.assertEqual(config["line_length"], "79")
1761 self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
1762 self.assertEqual(config["exclude"], r"\.pyi?$")
1763 self.assertEqual(config["include"], r"\.py?$")
1765 def test_find_project_root(self) -> None:
1766 with TemporaryDirectory() as workspace:
1767 root = Path(workspace)
1768 test_dir = root / "test"
1771 src_dir = root / "src"
1774 root_pyproject = root / "pyproject.toml"
1775 root_pyproject.touch()
1776 src_pyproject = src_dir / "pyproject.toml"
1777 src_pyproject.touch()
1778 src_python = src_dir / "foo.py"
1782 black.find_project_root((src_dir, test_dir)), root.resolve()
1784 self.assertEqual(black.find_project_root((src_dir,)), src_dir.resolve())
1785 self.assertEqual(black.find_project_root((src_python,)), src_dir.resolve())
1787 def test_bpo_33660_workaround(self) -> None:
1788 if system() == "Windows":
1791 # https://bugs.python.org/issue33660
1793 old_cwd = Path.cwd()
1797 path = Path("workspace") / "project"
1798 report = black.Report(verbose=True)
1799 normalized_path = black.normalize_path_maybe_ignore(path, root, report)
1800 self.assertEqual(normalized_path, "workspace/project")
1802 os.chdir(str(old_cwd))
1804 def test_newline_comment_interaction(self) -> None:
1805 source = "class A:\\\r\n# type: ignore\n pass\n"
1806 output = black.format_str(source, mode=DEFAULT_MODE)
1807 black.assert_stable(source, output, mode=DEFAULT_MODE)
1809 def test_bpo_2142_workaround(self) -> None:
1811 # https://bugs.python.org/issue2142
1813 source, _ = read_data("missing_final_newline.py")
1814 # read_data adds a trailing newline
1815 source = source.rstrip()
1816 expected, _ = read_data("missing_final_newline.diff")
1817 tmp_file = Path(black.dump_to_file(source, ensure_final_newline=False))
1818 diff_header = re.compile(
1819 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
1820 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
1823 result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
1824 self.assertEqual(result.exit_code, 0)
1827 actual = result.output
1828 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
1829 self.assertEqual(actual, expected)
1832 with open(black.__file__, "r", encoding="utf-8") as _bf:
1833 black_source_lines = _bf.readlines()
1836 def tracefunc(frame: types.FrameType, event: str, arg: Any) -> Callable:
1837 """Show function calls `from black/__init__.py` as they happen.
1839 Register this with `sys.settrace()` in a test you're debugging.
1844 stack = len(inspect.stack()) - 19
1846 filename = frame.f_code.co_filename
1847 lineno = frame.f_lineno
1848 func_sig_lineno = lineno - 1
1849 funcname = black_source_lines[func_sig_lineno].strip()
1850 while funcname.startswith("@"):
1851 func_sig_lineno += 1
1852 funcname = black_source_lines[func_sig_lineno].strip()
1853 if "black/__init__.py" in filename:
1854 print(f"{' ' * stack}{lineno}:{funcname}")
1858 if __name__ == "__main__":
1859 unittest.main(module="test_black")