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.
13 from concurrent.futures import ThreadPoolExecutor
14 from contextlib import contextmanager, redirect_stderr
15 from dataclasses import replace
16 from io import BytesIO
17 from pathlib import Path
18 from platform import system
19 from tempfile import TemporaryDirectory
31 from unittest.mock import MagicMock, patch
35 from click import unstyle
36 from click.testing import CliRunner
37 from pathspec import PathSpec
41 from black import Feature, TargetVersion
42 from black import re_compile_maybe_verbose as compile_pattern
43 from black.cache import get_cache_dir, get_cache_file
44 from black.debug import DebugVisitor
45 from black.output import color_diff, diff
46 from black.report import Report
48 # Import other test classes
49 from tests.util import (
67 THIS_FILE = Path(__file__)
68 EMPTY_CONFIG = THIS_DIR / "data" / "empty_pyproject.toml"
69 PY36_ARGS = [f"--target-version={version.name.lower()}" for version in PY36_VERSIONS]
70 DEFAULT_EXCLUDE = black.re_compile_maybe_verbose(black.const.DEFAULT_EXCLUDES)
71 DEFAULT_INCLUDE = black.re_compile_maybe_verbose(black.const.DEFAULT_INCLUDES)
75 # Match the time output in a diff, but nothing else
76 DIFF_TIME = re.compile(r"\t[\d\-:+\. ]+")
80 def cache_dir(exists: bool = True) -> Iterator[Path]:
81 with TemporaryDirectory() as workspace:
82 cache_dir = Path(workspace)
84 cache_dir = cache_dir / "new"
85 with patch("black.cache.CACHE_DIR", cache_dir):
90 def event_loop() -> Iterator[None]:
91 policy = asyncio.get_event_loop_policy()
92 loop = policy.new_event_loop()
93 asyncio.set_event_loop(loop)
101 class FakeContext(click.Context):
102 """A fake click Context for when calling functions that need it."""
104 def __init__(self) -> None:
105 self.default_map: Dict[str, Any] = {}
106 # Dummy root, since most of the tests don't care about it
107 self.obj: Dict[str, Any] = {"root": PROJECT_ROOT}
110 class FakeParameter(click.Parameter):
111 """A fake click Parameter for when calling functions that need it."""
113 def __init__(self) -> None:
117 class BlackRunner(CliRunner):
118 """Make sure STDOUT and STDERR are kept separate when testing Black via its CLI."""
120 def __init__(self) -> None:
121 super().__init__(mix_stderr=False)
125 args: List[str], exit_code: int = 0, ignore_config: bool = True
127 runner = BlackRunner()
129 args = ["--verbose", "--config", str(THIS_DIR / "empty.toml"), *args]
130 result = runner.invoke(black.main, args, catch_exceptions=False)
131 assert result.stdout_bytes is not None
132 assert result.stderr_bytes is not None
134 f"Failed with args: {args}\n"
135 f"stdout: {result.stdout_bytes.decode()!r}\n"
136 f"stderr: {result.stderr_bytes.decode()!r}\n"
137 f"exception: {result.exception}"
139 assert result.exit_code == exit_code, msg
142 class BlackTestCase(BlackBaseTestCase):
143 invokeBlack = staticmethod(invokeBlack)
145 def test_empty_ff(self) -> None:
147 tmp_file = Path(black.dump_to_file())
149 self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES))
150 with open(tmp_file, encoding="utf8") as f:
154 self.assertFormatEqual(expected, actual)
156 def test_experimental_string_processing_warns(self) -> None:
158 black.mode.Deprecated, black.Mode, experimental_string_processing=True
161 def test_piping(self) -> None:
162 source, expected = read_data_from_file(PROJECT_ROOT / "src/black/__init__.py")
163 result = BlackRunner().invoke(
168 f"--line-length={black.DEFAULT_LINE_LENGTH}",
169 f"--config={EMPTY_CONFIG}",
171 input=BytesIO(source.encode("utf8")),
173 self.assertEqual(result.exit_code, 0)
174 self.assertFormatEqual(expected, result.output)
175 if source != result.output:
176 black.assert_equivalent(source, result.output)
177 black.assert_stable(source, result.output, DEFAULT_MODE)
179 def test_piping_diff(self) -> None:
180 diff_header = re.compile(
181 r"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d\d\d\d "
184 source, _ = read_data("simple_cases", "expression.py")
185 expected, _ = read_data("simple_cases", "expression.diff")
189 f"--line-length={black.DEFAULT_LINE_LENGTH}",
191 f"--config={EMPTY_CONFIG}",
193 result = BlackRunner().invoke(
194 black.main, args, input=BytesIO(source.encode("utf8"))
196 self.assertEqual(result.exit_code, 0)
197 actual = diff_header.sub(DETERMINISTIC_HEADER, result.output)
198 actual = actual.rstrip() + "\n" # the diff output has a trailing space
199 self.assertEqual(expected, actual)
201 def test_piping_diff_with_color(self) -> None:
202 source, _ = read_data("simple_cases", "expression.py")
206 f"--line-length={black.DEFAULT_LINE_LENGTH}",
209 f"--config={EMPTY_CONFIG}",
211 result = BlackRunner().invoke(
212 black.main, args, input=BytesIO(source.encode("utf8"))
214 actual = result.output
215 # Again, the contents are checked in a different test, so only look for colors.
216 self.assertIn("\033[1m", actual)
217 self.assertIn("\033[36m", actual)
218 self.assertIn("\033[32m", actual)
219 self.assertIn("\033[31m", actual)
220 self.assertIn("\033[0m", actual)
222 @patch("black.dump_to_file", dump_to_stderr)
223 def _test_wip(self) -> None:
224 source, expected = read_data("miscellaneous", "wip")
225 sys.settrace(tracefunc)
228 experimental_string_processing=False,
229 target_versions={black.TargetVersion.PY38},
231 actual = fs(source, mode=mode)
233 self.assertFormatEqual(expected, actual)
234 black.assert_equivalent(source, actual)
235 black.assert_stable(source, actual, black.FileMode())
237 def test_pep_572_version_detection(self) -> None:
238 source, _ = read_data("py_38", "pep_572")
239 root = black.lib2to3_parse(source)
240 features = black.get_features_used(root)
241 self.assertIn(black.Feature.ASSIGNMENT_EXPRESSIONS, features)
242 versions = black.detect_target_versions(root)
243 self.assertIn(black.TargetVersion.PY38, versions)
245 def test_expression_ff(self) -> None:
246 source, expected = read_data("simple_cases", "expression.py")
247 tmp_file = Path(black.dump_to_file(source))
249 self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
250 with open(tmp_file, encoding="utf8") as f:
254 self.assertFormatEqual(expected, actual)
255 with patch("black.dump_to_file", dump_to_stderr):
256 black.assert_equivalent(source, actual)
257 black.assert_stable(source, actual, DEFAULT_MODE)
259 def test_expression_diff(self) -> None:
260 source, _ = read_data("simple_cases", "expression.py")
261 expected, _ = read_data("simple_cases", "expression.diff")
262 tmp_file = Path(black.dump_to_file(source))
263 diff_header = re.compile(
264 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
265 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
268 result = BlackRunner().invoke(
269 black.main, ["--diff", str(tmp_file), f"--config={EMPTY_CONFIG}"]
271 self.assertEqual(result.exit_code, 0)
274 actual = result.output
275 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
276 if expected != actual:
277 dump = black.dump_to_file(actual)
279 "Expected diff isn't equal to the actual. If you made changes to"
280 " expression.py and this is an anticipated difference, overwrite"
281 f" tests/data/expression.diff with {dump}"
283 self.assertEqual(expected, actual, msg)
285 def test_expression_diff_with_color(self) -> None:
286 source, _ = read_data("simple_cases", "expression.py")
287 expected, _ = read_data("simple_cases", "expression.diff")
288 tmp_file = Path(black.dump_to_file(source))
290 result = BlackRunner().invoke(
292 ["--diff", "--color", str(tmp_file), f"--config={EMPTY_CONFIG}"],
296 actual = result.output
297 # We check the contents of the diff in `test_expression_diff`. All
298 # we need to check here is that color codes exist in the result.
299 self.assertIn("\033[1m", actual)
300 self.assertIn("\033[36m", actual)
301 self.assertIn("\033[32m", actual)
302 self.assertIn("\033[31m", actual)
303 self.assertIn("\033[0m", actual)
305 def test_detect_pos_only_arguments(self) -> None:
306 source, _ = read_data("py_38", "pep_570")
307 root = black.lib2to3_parse(source)
308 features = black.get_features_used(root)
309 self.assertIn(black.Feature.POS_ONLY_ARGUMENTS, features)
310 versions = black.detect_target_versions(root)
311 self.assertIn(black.TargetVersion.PY38, versions)
313 def test_detect_debug_f_strings(self) -> None:
314 root = black.lib2to3_parse("""f"{x=}" """)
315 features = black.get_features_used(root)
316 self.assertIn(black.Feature.DEBUG_F_STRINGS, features)
317 versions = black.detect_target_versions(root)
318 self.assertIn(black.TargetVersion.PY38, versions)
320 root = black.lib2to3_parse(
321 """f"{x}"\nf'{"="}'\nf'{(x:=5)}'\nf'{f(a="3=")}'\nf'{x:=10}'\n"""
323 features = black.get_features_used(root)
324 self.assertNotIn(black.Feature.DEBUG_F_STRINGS, features)
326 # We don't yet support feature version detection in nested f-strings
327 root = black.lib2to3_parse(
328 """f"heard a rumour that { f'{1+1=}' } ... seems like it could be true" """
330 features = black.get_features_used(root)
331 self.assertNotIn(black.Feature.DEBUG_F_STRINGS, features)
333 @patch("black.dump_to_file", dump_to_stderr)
334 def test_string_quotes(self) -> None:
335 source, expected = read_data("miscellaneous", "string_quotes")
336 mode = black.Mode(preview=True)
337 assert_format(source, expected, mode)
338 mode = replace(mode, string_normalization=False)
339 not_normalized = fs(source, mode=mode)
340 self.assertFormatEqual(source.replace("\\\n", ""), not_normalized)
341 black.assert_equivalent(source, not_normalized)
342 black.assert_stable(source, not_normalized, mode=mode)
344 def test_skip_magic_trailing_comma(self) -> None:
345 source, _ = read_data("simple_cases", "expression")
346 expected, _ = read_data(
347 "miscellaneous", "expression_skip_magic_trailing_comma.diff"
349 tmp_file = Path(black.dump_to_file(source))
350 diff_header = re.compile(
351 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
352 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
355 result = BlackRunner().invoke(
356 black.main, ["-C", "--diff", str(tmp_file), f"--config={EMPTY_CONFIG}"]
358 self.assertEqual(result.exit_code, 0)
361 actual = result.output
362 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
363 actual = actual.rstrip() + "\n" # the diff output has a trailing space
364 if expected != actual:
365 dump = black.dump_to_file(actual)
367 "Expected diff isn't equal to the actual. If you made changes to"
368 " expression.py and this is an anticipated difference, overwrite"
369 f" tests/data/expression_skip_magic_trailing_comma.diff with {dump}"
371 self.assertEqual(expected, actual, msg)
373 @patch("black.dump_to_file", dump_to_stderr)
374 def test_async_as_identifier(self) -> None:
375 source_path = get_case_path("miscellaneous", "async_as_identifier")
376 source, expected = read_data_from_file(source_path)
378 self.assertFormatEqual(expected, actual)
379 major, minor = sys.version_info[:2]
380 if major < 3 or (major <= 3 and minor < 7):
381 black.assert_equivalent(source, actual)
382 black.assert_stable(source, actual, DEFAULT_MODE)
383 # ensure black can parse this when the target is 3.6
384 self.invokeBlack([str(source_path), "--target-version", "py36"])
385 # but not on 3.7, because async/await is no longer an identifier
386 self.invokeBlack([str(source_path), "--target-version", "py37"], exit_code=123)
388 @patch("black.dump_to_file", dump_to_stderr)
389 def test_python37(self) -> None:
390 source_path = get_case_path("py_37", "python37")
391 source, expected = read_data_from_file(source_path)
393 self.assertFormatEqual(expected, actual)
394 major, minor = sys.version_info[:2]
395 if major > 3 or (major == 3 and minor >= 7):
396 black.assert_equivalent(source, actual)
397 black.assert_stable(source, actual, DEFAULT_MODE)
398 # ensure black can parse this when the target is 3.7
399 self.invokeBlack([str(source_path), "--target-version", "py37"])
400 # but not on 3.6, because we use async as a reserved keyword
401 self.invokeBlack([str(source_path), "--target-version", "py36"], exit_code=123)
403 def test_tab_comment_indentation(self) -> None:
404 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
405 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
406 self.assertFormatEqual(contents_spc, fs(contents_spc))
407 self.assertFormatEqual(contents_spc, fs(contents_tab))
409 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
410 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
411 self.assertFormatEqual(contents_spc, fs(contents_spc))
412 self.assertFormatEqual(contents_spc, fs(contents_tab))
414 # mixed tabs and spaces (valid Python 2 code)
415 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t# comment\n pass\n"
416 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
417 self.assertFormatEqual(contents_spc, fs(contents_spc))
418 self.assertFormatEqual(contents_spc, fs(contents_tab))
420 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t\t# comment\n pass\n"
421 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
422 self.assertFormatEqual(contents_spc, fs(contents_spc))
423 self.assertFormatEqual(contents_spc, fs(contents_tab))
425 def test_report_verbose(self) -> None:
426 report = Report(verbose=True)
430 def out(msg: str, **kwargs: Any) -> None:
431 out_lines.append(msg)
433 def err(msg: str, **kwargs: Any) -> None:
434 err_lines.append(msg)
436 with patch("black.output._out", out), patch("black.output._err", err):
437 report.done(Path("f1"), black.Changed.NO)
438 self.assertEqual(len(out_lines), 1)
439 self.assertEqual(len(err_lines), 0)
440 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
441 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
442 self.assertEqual(report.return_code, 0)
443 report.done(Path("f2"), black.Changed.YES)
444 self.assertEqual(len(out_lines), 2)
445 self.assertEqual(len(err_lines), 0)
446 self.assertEqual(out_lines[-1], "reformatted f2")
448 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
450 report.done(Path("f3"), black.Changed.CACHED)
451 self.assertEqual(len(out_lines), 3)
452 self.assertEqual(len(err_lines), 0)
454 out_lines[-1], "f3 wasn't modified on disk since last run."
457 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
459 self.assertEqual(report.return_code, 0)
461 self.assertEqual(report.return_code, 1)
463 report.failed(Path("e1"), "boom")
464 self.assertEqual(len(out_lines), 3)
465 self.assertEqual(len(err_lines), 1)
466 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
468 unstyle(str(report)),
469 "1 file reformatted, 2 files left unchanged, 1 file failed to"
472 self.assertEqual(report.return_code, 123)
473 report.done(Path("f3"), black.Changed.YES)
474 self.assertEqual(len(out_lines), 4)
475 self.assertEqual(len(err_lines), 1)
476 self.assertEqual(out_lines[-1], "reformatted f3")
478 unstyle(str(report)),
479 "2 files reformatted, 2 files left unchanged, 1 file failed to"
482 self.assertEqual(report.return_code, 123)
483 report.failed(Path("e2"), "boom")
484 self.assertEqual(len(out_lines), 4)
485 self.assertEqual(len(err_lines), 2)
486 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
488 unstyle(str(report)),
489 "2 files reformatted, 2 files left unchanged, 2 files failed to"
492 self.assertEqual(report.return_code, 123)
493 report.path_ignored(Path("wat"), "no match")
494 self.assertEqual(len(out_lines), 5)
495 self.assertEqual(len(err_lines), 2)
496 self.assertEqual(out_lines[-1], "wat ignored: no match")
498 unstyle(str(report)),
499 "2 files reformatted, 2 files left unchanged, 2 files failed to"
502 self.assertEqual(report.return_code, 123)
503 report.done(Path("f4"), black.Changed.NO)
504 self.assertEqual(len(out_lines), 6)
505 self.assertEqual(len(err_lines), 2)
506 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
508 unstyle(str(report)),
509 "2 files reformatted, 3 files left unchanged, 2 files failed to"
512 self.assertEqual(report.return_code, 123)
515 unstyle(str(report)),
516 "2 files would be reformatted, 3 files would be left unchanged, 2"
517 " files would fail to reformat.",
522 unstyle(str(report)),
523 "2 files would be reformatted, 3 files would be left unchanged, 2"
524 " files would fail to reformat.",
527 def test_report_quiet(self) -> None:
528 report = Report(quiet=True)
532 def out(msg: str, **kwargs: Any) -> None:
533 out_lines.append(msg)
535 def err(msg: str, **kwargs: Any) -> None:
536 err_lines.append(msg)
538 with patch("black.output._out", out), patch("black.output._err", err):
539 report.done(Path("f1"), black.Changed.NO)
540 self.assertEqual(len(out_lines), 0)
541 self.assertEqual(len(err_lines), 0)
542 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
543 self.assertEqual(report.return_code, 0)
544 report.done(Path("f2"), black.Changed.YES)
545 self.assertEqual(len(out_lines), 0)
546 self.assertEqual(len(err_lines), 0)
548 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
550 report.done(Path("f3"), black.Changed.CACHED)
551 self.assertEqual(len(out_lines), 0)
552 self.assertEqual(len(err_lines), 0)
554 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
556 self.assertEqual(report.return_code, 0)
558 self.assertEqual(report.return_code, 1)
560 report.failed(Path("e1"), "boom")
561 self.assertEqual(len(out_lines), 0)
562 self.assertEqual(len(err_lines), 1)
563 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
565 unstyle(str(report)),
566 "1 file reformatted, 2 files left unchanged, 1 file failed to"
569 self.assertEqual(report.return_code, 123)
570 report.done(Path("f3"), black.Changed.YES)
571 self.assertEqual(len(out_lines), 0)
572 self.assertEqual(len(err_lines), 1)
574 unstyle(str(report)),
575 "2 files reformatted, 2 files left unchanged, 1 file failed to"
578 self.assertEqual(report.return_code, 123)
579 report.failed(Path("e2"), "boom")
580 self.assertEqual(len(out_lines), 0)
581 self.assertEqual(len(err_lines), 2)
582 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
584 unstyle(str(report)),
585 "2 files reformatted, 2 files left unchanged, 2 files failed to"
588 self.assertEqual(report.return_code, 123)
589 report.path_ignored(Path("wat"), "no match")
590 self.assertEqual(len(out_lines), 0)
591 self.assertEqual(len(err_lines), 2)
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), 0)
600 self.assertEqual(len(err_lines), 2)
602 unstyle(str(report)),
603 "2 files reformatted, 3 files left unchanged, 2 files failed to"
606 self.assertEqual(report.return_code, 123)
609 unstyle(str(report)),
610 "2 files would be reformatted, 3 files would be left unchanged, 2"
611 " files would fail to reformat.",
616 unstyle(str(report)),
617 "2 files would be reformatted, 3 files would be left unchanged, 2"
618 " files would fail to reformat.",
621 def test_report_normal(self) -> None:
622 report = black.Report()
626 def out(msg: str, **kwargs: Any) -> None:
627 out_lines.append(msg)
629 def err(msg: str, **kwargs: Any) -> None:
630 err_lines.append(msg)
632 with patch("black.output._out", out), patch("black.output._err", err):
633 report.done(Path("f1"), black.Changed.NO)
634 self.assertEqual(len(out_lines), 0)
635 self.assertEqual(len(err_lines), 0)
636 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
637 self.assertEqual(report.return_code, 0)
638 report.done(Path("f2"), black.Changed.YES)
639 self.assertEqual(len(out_lines), 1)
640 self.assertEqual(len(err_lines), 0)
641 self.assertEqual(out_lines[-1], "reformatted f2")
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), 1)
647 self.assertEqual(len(err_lines), 0)
648 self.assertEqual(out_lines[-1], "reformatted f2")
650 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
652 self.assertEqual(report.return_code, 0)
654 self.assertEqual(report.return_code, 1)
656 report.failed(Path("e1"), "boom")
657 self.assertEqual(len(out_lines), 1)
658 self.assertEqual(len(err_lines), 1)
659 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
661 unstyle(str(report)),
662 "1 file reformatted, 2 files left unchanged, 1 file failed to"
665 self.assertEqual(report.return_code, 123)
666 report.done(Path("f3"), black.Changed.YES)
667 self.assertEqual(len(out_lines), 2)
668 self.assertEqual(len(err_lines), 1)
669 self.assertEqual(out_lines[-1], "reformatted f3")
671 unstyle(str(report)),
672 "2 files reformatted, 2 files left unchanged, 1 file failed to"
675 self.assertEqual(report.return_code, 123)
676 report.failed(Path("e2"), "boom")
677 self.assertEqual(len(out_lines), 2)
678 self.assertEqual(len(err_lines), 2)
679 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
681 unstyle(str(report)),
682 "2 files reformatted, 2 files left unchanged, 2 files failed to"
685 self.assertEqual(report.return_code, 123)
686 report.path_ignored(Path("wat"), "no match")
687 self.assertEqual(len(out_lines), 2)
688 self.assertEqual(len(err_lines), 2)
690 unstyle(str(report)),
691 "2 files reformatted, 2 files left unchanged, 2 files failed to"
694 self.assertEqual(report.return_code, 123)
695 report.done(Path("f4"), black.Changed.NO)
696 self.assertEqual(len(out_lines), 2)
697 self.assertEqual(len(err_lines), 2)
699 unstyle(str(report)),
700 "2 files reformatted, 3 files left unchanged, 2 files failed to"
703 self.assertEqual(report.return_code, 123)
706 unstyle(str(report)),
707 "2 files would be reformatted, 3 files would be left unchanged, 2"
708 " files would fail to reformat.",
713 unstyle(str(report)),
714 "2 files would be reformatted, 3 files would be left unchanged, 2"
715 " files would fail to reformat.",
718 def test_lib2to3_parse(self) -> None:
719 with self.assertRaises(black.InvalidInput):
720 black.lib2to3_parse("invalid syntax")
723 black.lib2to3_parse(straddling)
724 black.lib2to3_parse(straddling, {TargetVersion.PY36})
727 with self.assertRaises(black.InvalidInput):
728 black.lib2to3_parse(py2_only, {TargetVersion.PY36})
730 py3_only = "exec(x, end=y)"
731 black.lib2to3_parse(py3_only)
732 black.lib2to3_parse(py3_only, {TargetVersion.PY36})
734 def test_get_features_used_decorator(self) -> None:
735 # Test the feature detection of new decorator syntax
736 # since this makes some test cases of test_get_features_used()
737 # fails if it fails, this is tested first so that a useful case
739 simples, relaxed = read_data("miscellaneous", "decorators")
740 # skip explanation comments at the top of the file
741 for simple_test in simples.split("##")[1:]:
742 node = black.lib2to3_parse(simple_test)
743 decorator = str(node.children[0].children[0]).strip()
745 Feature.RELAXED_DECORATORS,
746 black.get_features_used(node),
748 f"decorator '{decorator}' follows python<=3.8 syntax"
749 "but is detected as 3.9+"
750 # f"The full node is\n{node!r}"
753 # skip the '# output' comment at the top of the output part
754 for relaxed_test in relaxed.split("##")[1:]:
755 node = black.lib2to3_parse(relaxed_test)
756 decorator = str(node.children[0].children[0]).strip()
758 Feature.RELAXED_DECORATORS,
759 black.get_features_used(node),
761 f"decorator '{decorator}' uses python3.9+ syntax"
762 "but is detected as python<=3.8"
763 # f"The full node is\n{node!r}"
767 def test_get_features_used(self) -> None:
768 node = black.lib2to3_parse("def f(*, arg): ...\n")
769 self.assertEqual(black.get_features_used(node), set())
770 node = black.lib2to3_parse("def f(*, arg,): ...\n")
771 self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
772 node = black.lib2to3_parse("f(*arg,)\n")
774 black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
776 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
777 self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
778 node = black.lib2to3_parse("123_456\n")
779 self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
780 node = black.lib2to3_parse("123456\n")
781 self.assertEqual(black.get_features_used(node), set())
782 source, expected = read_data("simple_cases", "function")
783 node = black.lib2to3_parse(source)
784 expected_features = {
785 Feature.TRAILING_COMMA_IN_CALL,
786 Feature.TRAILING_COMMA_IN_DEF,
789 self.assertEqual(black.get_features_used(node), expected_features)
790 node = black.lib2to3_parse(expected)
791 self.assertEqual(black.get_features_used(node), expected_features)
792 source, expected = read_data("simple_cases", "expression")
793 node = black.lib2to3_parse(source)
794 self.assertEqual(black.get_features_used(node), set())
795 node = black.lib2to3_parse(expected)
796 self.assertEqual(black.get_features_used(node), set())
797 node = black.lib2to3_parse("lambda a, /, b: ...")
798 self.assertEqual(black.get_features_used(node), {Feature.POS_ONLY_ARGUMENTS})
799 node = black.lib2to3_parse("def fn(a, /, b): ...")
800 self.assertEqual(black.get_features_used(node), {Feature.POS_ONLY_ARGUMENTS})
801 node = black.lib2to3_parse("def fn(): yield a, b")
802 self.assertEqual(black.get_features_used(node), set())
803 node = black.lib2to3_parse("def fn(): return a, b")
804 self.assertEqual(black.get_features_used(node), set())
805 node = black.lib2to3_parse("def fn(): yield *b, c")
806 self.assertEqual(black.get_features_used(node), {Feature.UNPACKING_ON_FLOW})
807 node = black.lib2to3_parse("def fn(): return a, *b, c")
808 self.assertEqual(black.get_features_used(node), {Feature.UNPACKING_ON_FLOW})
809 node = black.lib2to3_parse("x = a, *b, c")
810 self.assertEqual(black.get_features_used(node), set())
811 node = black.lib2to3_parse("x: Any = regular")
812 self.assertEqual(black.get_features_used(node), set())
813 node = black.lib2to3_parse("x: Any = (regular, regular)")
814 self.assertEqual(black.get_features_used(node), set())
815 node = black.lib2to3_parse("x: Any = Complex(Type(1))[something]")
816 self.assertEqual(black.get_features_used(node), set())
817 node = black.lib2to3_parse("x: Tuple[int, ...] = a, b, c")
819 black.get_features_used(node), {Feature.ANN_ASSIGN_EXTENDED_RHS}
821 node = black.lib2to3_parse("try: pass\nexcept Something: pass")
822 self.assertEqual(black.get_features_used(node), set())
823 node = black.lib2to3_parse("try: pass\nexcept (*Something,): pass")
824 self.assertEqual(black.get_features_used(node), set())
825 node = black.lib2to3_parse("try: pass\nexcept *Group: pass")
826 self.assertEqual(black.get_features_used(node), {Feature.EXCEPT_STAR})
827 node = black.lib2to3_parse("a[*b]")
828 self.assertEqual(black.get_features_used(node), {Feature.VARIADIC_GENERICS})
829 node = black.lib2to3_parse("a[x, *y(), z] = t")
830 self.assertEqual(black.get_features_used(node), {Feature.VARIADIC_GENERICS})
831 node = black.lib2to3_parse("def fn(*args: *T): pass")
832 self.assertEqual(black.get_features_used(node), {Feature.VARIADIC_GENERICS})
834 def test_get_features_used_for_future_flags(self) -> None:
835 for src, features in [
836 ("from __future__ import annotations", {Feature.FUTURE_ANNOTATIONS}),
838 "from __future__ import (other, annotations)",
839 {Feature.FUTURE_ANNOTATIONS},
841 ("a = 1 + 2\nfrom something import annotations", set()),
842 ("from __future__ import x, y", set()),
844 with self.subTest(src=src, features=features):
845 node = black.lib2to3_parse(src)
846 future_imports = black.get_future_imports(node)
848 black.get_features_used(node, future_imports=future_imports),
852 def test_get_future_imports(self) -> None:
853 node = black.lib2to3_parse("\n")
854 self.assertEqual(set(), black.get_future_imports(node))
855 node = black.lib2to3_parse("from __future__ import black\n")
856 self.assertEqual({"black"}, black.get_future_imports(node))
857 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
858 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
859 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
860 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
861 node = black.lib2to3_parse(
862 "from __future__ import multiple\nfrom __future__ import imports\n"
864 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
865 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
866 self.assertEqual({"black"}, black.get_future_imports(node))
867 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
868 self.assertEqual({"black"}, black.get_future_imports(node))
869 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
870 self.assertEqual(set(), black.get_future_imports(node))
871 node = black.lib2to3_parse("from some.module import black\n")
872 self.assertEqual(set(), black.get_future_imports(node))
873 node = black.lib2to3_parse(
874 "from __future__ import unicode_literals as _unicode_literals"
876 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
877 node = black.lib2to3_parse(
878 "from __future__ import unicode_literals as _lol, print"
880 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
882 @pytest.mark.incompatible_with_mypyc
883 def test_debug_visitor(self) -> None:
884 source, _ = read_data("miscellaneous", "debug_visitor")
885 expected, _ = read_data("miscellaneous", "debug_visitor.out")
889 def out(msg: str, **kwargs: Any) -> None:
890 out_lines.append(msg)
892 def err(msg: str, **kwargs: Any) -> None:
893 err_lines.append(msg)
895 with patch("black.debug.out", out):
896 DebugVisitor.show(source)
897 actual = "\n".join(out_lines) + "\n"
899 if expected != actual:
900 log_name = black.dump_to_file(*out_lines)
904 f"AST print out is different. Actual version dumped to {log_name}",
907 def test_format_file_contents(self) -> None:
910 with self.assertRaises(black.NothingChanged):
911 black.format_file_contents(empty, mode=mode, fast=False)
913 with self.assertRaises(black.NothingChanged):
914 black.format_file_contents(just_nl, mode=mode, fast=False)
915 same = "j = [1, 2, 3]\n"
916 with self.assertRaises(black.NothingChanged):
917 black.format_file_contents(same, mode=mode, fast=False)
918 different = "j = [1,2,3]"
920 actual = black.format_file_contents(different, mode=mode, fast=False)
921 self.assertEqual(expected, actual)
922 invalid = "return if you can"
923 with self.assertRaises(black.InvalidInput) as e:
924 black.format_file_contents(invalid, mode=mode, fast=False)
925 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
927 def test_endmarker(self) -> None:
928 n = black.lib2to3_parse("\n")
929 self.assertEqual(n.type, black.syms.file_input)
930 self.assertEqual(len(n.children), 1)
931 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
933 @pytest.mark.incompatible_with_mypyc
934 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
935 def test_assertFormatEqual(self) -> None:
939 def out(msg: str, **kwargs: Any) -> None:
940 out_lines.append(msg)
942 def err(msg: str, **kwargs: Any) -> None:
943 err_lines.append(msg)
945 with patch("black.output._out", out), patch("black.output._err", err):
946 with self.assertRaises(AssertionError):
947 self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")
949 out_str = "".join(out_lines)
950 self.assertIn("Expected tree:", out_str)
951 self.assertIn("Actual tree:", out_str)
952 self.assertEqual("".join(err_lines), "")
955 @patch("concurrent.futures.ProcessPoolExecutor", MagicMock(side_effect=OSError))
956 def test_works_in_mono_process_only_environment(self) -> None:
957 with cache_dir() as workspace:
959 (workspace / "one.py").resolve(),
960 (workspace / "two.py").resolve(),
962 f.write_text('print("hello")\n')
963 self.invokeBlack([str(workspace)])
966 def test_check_diff_use_together(self) -> None:
968 # Files which will be reformatted.
969 src1 = get_case_path("miscellaneous", "string_quotes")
970 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
971 # Files which will not be reformatted.
972 src2 = get_case_path("simple_cases", "composition")
973 self.invokeBlack([str(src2), "--diff", "--check"])
974 # Multi file command.
975 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
977 def test_no_src_fails(self) -> None:
979 self.invokeBlack([], exit_code=1)
981 def test_src_and_code_fails(self) -> None:
983 self.invokeBlack([".", "-c", "0"], exit_code=1)
985 def test_broken_symlink(self) -> None:
986 with cache_dir() as workspace:
987 symlink = workspace / "broken_link.py"
989 symlink.symlink_to("nonexistent.py")
990 except (OSError, NotImplementedError) as e:
991 self.skipTest(f"Can't create symlinks: {e}")
992 self.invokeBlack([str(workspace.resolve())])
994 def test_single_file_force_pyi(self) -> None:
995 pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
996 contents, expected = read_data("miscellaneous", "force_pyi")
997 with cache_dir() as workspace:
998 path = (workspace / "file.py").resolve()
999 with open(path, "w") as fh:
1001 self.invokeBlack([str(path), "--pyi"])
1002 with open(path, "r") as fh:
1004 # verify cache with --pyi is separate
1005 pyi_cache = black.read_cache(pyi_mode)
1006 self.assertIn(str(path), pyi_cache)
1007 normal_cache = black.read_cache(DEFAULT_MODE)
1008 self.assertNotIn(str(path), normal_cache)
1009 self.assertFormatEqual(expected, actual)
1010 black.assert_equivalent(contents, actual)
1011 black.assert_stable(contents, actual, pyi_mode)
1014 def test_multi_file_force_pyi(self) -> None:
1015 reg_mode = DEFAULT_MODE
1016 pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
1017 contents, expected = read_data("miscellaneous", "force_pyi")
1018 with cache_dir() as workspace:
1020 (workspace / "file1.py").resolve(),
1021 (workspace / "file2.py").resolve(),
1024 with open(path, "w") as fh:
1026 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1028 with open(path, "r") as fh:
1030 self.assertEqual(actual, expected)
1031 # verify cache with --pyi is separate
1032 pyi_cache = black.read_cache(pyi_mode)
1033 normal_cache = black.read_cache(reg_mode)
1035 self.assertIn(str(path), pyi_cache)
1036 self.assertNotIn(str(path), normal_cache)
1038 def test_pipe_force_pyi(self) -> None:
1039 source, expected = read_data("miscellaneous", "force_pyi")
1040 result = CliRunner().invoke(
1041 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1043 self.assertEqual(result.exit_code, 0)
1044 actual = result.output
1045 self.assertFormatEqual(actual, expected)
1047 def test_single_file_force_py36(self) -> None:
1048 reg_mode = DEFAULT_MODE
1049 py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
1050 source, expected = read_data("miscellaneous", "force_py36")
1051 with cache_dir() as workspace:
1052 path = (workspace / "file.py").resolve()
1053 with open(path, "w") as fh:
1055 self.invokeBlack([str(path), *PY36_ARGS])
1056 with open(path, "r") as fh:
1058 # verify cache with --target-version is separate
1059 py36_cache = black.read_cache(py36_mode)
1060 self.assertIn(str(path), py36_cache)
1061 normal_cache = black.read_cache(reg_mode)
1062 self.assertNotIn(str(path), normal_cache)
1063 self.assertEqual(actual, expected)
1066 def test_multi_file_force_py36(self) -> None:
1067 reg_mode = DEFAULT_MODE
1068 py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
1069 source, expected = read_data("miscellaneous", "force_py36")
1070 with cache_dir() as workspace:
1072 (workspace / "file1.py").resolve(),
1073 (workspace / "file2.py").resolve(),
1076 with open(path, "w") as fh:
1078 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1080 with open(path, "r") as fh:
1082 self.assertEqual(actual, expected)
1083 # verify cache with --target-version is separate
1084 pyi_cache = black.read_cache(py36_mode)
1085 normal_cache = black.read_cache(reg_mode)
1087 self.assertIn(str(path), pyi_cache)
1088 self.assertNotIn(str(path), normal_cache)
1090 def test_pipe_force_py36(self) -> None:
1091 source, expected = read_data("miscellaneous", "force_py36")
1092 result = CliRunner().invoke(
1094 ["-", "-q", "--target-version=py36"],
1095 input=BytesIO(source.encode("utf8")),
1097 self.assertEqual(result.exit_code, 0)
1098 actual = result.output
1099 self.assertFormatEqual(actual, expected)
1101 @pytest.mark.incompatible_with_mypyc
1102 def test_reformat_one_with_stdin(self) -> None:
1104 "black.format_stdin_to_stdout",
1105 return_value=lambda *args, **kwargs: black.Changed.YES,
1107 report = MagicMock()
1112 write_back=black.WriteBack.YES,
1116 fsts.assert_called_once()
1117 report.done.assert_called_with(path, black.Changed.YES)
1119 @pytest.mark.incompatible_with_mypyc
1120 def test_reformat_one_with_stdin_filename(self) -> None:
1122 "black.format_stdin_to_stdout",
1123 return_value=lambda *args, **kwargs: black.Changed.YES,
1125 report = MagicMock()
1127 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1132 write_back=black.WriteBack.YES,
1136 fsts.assert_called_once_with(
1137 fast=True, write_back=black.WriteBack.YES, mode=DEFAULT_MODE
1139 # __BLACK_STDIN_FILENAME__ should have been stripped
1140 report.done.assert_called_with(expected, black.Changed.YES)
1142 @pytest.mark.incompatible_with_mypyc
1143 def test_reformat_one_with_stdin_filename_pyi(self) -> None:
1145 "black.format_stdin_to_stdout",
1146 return_value=lambda *args, **kwargs: black.Changed.YES,
1148 report = MagicMock()
1150 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1155 write_back=black.WriteBack.YES,
1159 fsts.assert_called_once_with(
1161 write_back=black.WriteBack.YES,
1162 mode=replace(DEFAULT_MODE, is_pyi=True),
1164 # __BLACK_STDIN_FILENAME__ should have been stripped
1165 report.done.assert_called_with(expected, black.Changed.YES)
1167 @pytest.mark.incompatible_with_mypyc
1168 def test_reformat_one_with_stdin_filename_ipynb(self) -> None:
1170 "black.format_stdin_to_stdout",
1171 return_value=lambda *args, **kwargs: black.Changed.YES,
1173 report = MagicMock()
1175 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1180 write_back=black.WriteBack.YES,
1184 fsts.assert_called_once_with(
1186 write_back=black.WriteBack.YES,
1187 mode=replace(DEFAULT_MODE, is_ipynb=True),
1189 # __BLACK_STDIN_FILENAME__ should have been stripped
1190 report.done.assert_called_with(expected, black.Changed.YES)
1192 @pytest.mark.incompatible_with_mypyc
1193 def test_reformat_one_with_stdin_and_existing_path(self) -> None:
1195 "black.format_stdin_to_stdout",
1196 return_value=lambda *args, **kwargs: black.Changed.YES,
1198 report = MagicMock()
1199 # Even with an existing file, since we are forcing stdin, black
1200 # should output to stdout and not modify the file inplace
1201 p = THIS_DIR / "data" / "simple_cases" / "collections.py"
1202 # Make sure is_file actually returns True
1203 self.assertTrue(p.is_file())
1204 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1209 write_back=black.WriteBack.YES,
1213 fsts.assert_called_once()
1214 # __BLACK_STDIN_FILENAME__ should have been stripped
1215 report.done.assert_called_with(expected, black.Changed.YES)
1217 def test_reformat_one_with_stdin_empty(self) -> None:
1218 output = io.StringIO()
1219 with patch("io.TextIOWrapper", lambda *args, **kwargs: output):
1221 black.format_stdin_to_stdout(
1224 write_back=black.WriteBack.YES,
1227 except io.UnsupportedOperation:
1228 pass # StringIO does not support detach
1229 assert output.getvalue() == ""
1231 def test_invalid_cli_regex(self) -> None:
1232 for option in ["--include", "--exclude", "--extend-exclude", "--force-exclude"]:
1233 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1235 def test_required_version_matches_version(self) -> None:
1237 ["--required-version", black.__version__, "-c", "0"],
1242 def test_required_version_matches_partial_version(self) -> None:
1244 ["--required-version", black.__version__.split(".")[0], "-c", "0"],
1249 def test_required_version_does_not_match_on_minor_version(self) -> None:
1251 ["--required-version", black.__version__.split(".")[0] + ".999", "-c", "0"],
1256 def test_required_version_does_not_match_version(self) -> None:
1257 result = BlackRunner().invoke(
1259 ["--required-version", "20.99b", "-c", "0"],
1261 self.assertEqual(result.exit_code, 1)
1262 self.assertIn("required version", result.stderr)
1264 def test_preserves_line_endings(self) -> None:
1265 with TemporaryDirectory() as workspace:
1266 test_file = Path(workspace) / "test.py"
1267 for nl in ["\n", "\r\n"]:
1268 contents = nl.join(["def f( ):", " pass"])
1269 test_file.write_bytes(contents.encode())
1270 ff(test_file, write_back=black.WriteBack.YES)
1271 updated_contents: bytes = test_file.read_bytes()
1272 self.assertIn(nl.encode(), updated_contents)
1274 self.assertNotIn(b"\r\n", updated_contents)
1276 def test_preserves_line_endings_via_stdin(self) -> None:
1277 for nl in ["\n", "\r\n"]:
1278 contents = nl.join(["def f( ):", " pass"])
1279 runner = BlackRunner()
1280 result = runner.invoke(
1281 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1283 self.assertEqual(result.exit_code, 0)
1284 output = result.stdout_bytes
1285 self.assertIn(nl.encode("utf8"), output)
1287 self.assertNotIn(b"\r\n", output)
1289 def test_assert_equivalent_different_asts(self) -> None:
1290 with self.assertRaises(AssertionError):
1291 black.assert_equivalent("{}", "None")
1293 def test_shhh_click(self) -> None:
1295 from click import _unicodefun # type: ignore
1297 self.skipTest("Incompatible Click version")
1299 if not hasattr(_unicodefun, "_verify_python_env"):
1300 self.skipTest("Incompatible Click version")
1302 # First, let's see if Click is crashing with a preferred ASCII charset.
1303 with patch("locale.getpreferredencoding") as gpe:
1304 gpe.return_value = "ASCII"
1305 with self.assertRaises(RuntimeError):
1306 _unicodefun._verify_python_env()
1307 # Now, let's silence Click...
1309 # ...and confirm it's silent.
1310 with patch("locale.getpreferredencoding") as gpe:
1311 gpe.return_value = "ASCII"
1313 _unicodefun._verify_python_env()
1314 except RuntimeError as re:
1315 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1317 def test_root_logger_not_used_directly(self) -> None:
1318 def fail(*args: Any, **kwargs: Any) -> None:
1319 self.fail("Record created with root logger")
1321 with patch.multiple(
1330 ff(THIS_DIR / "util.py")
1332 def test_invalid_config_return_code(self) -> None:
1333 tmp_file = Path(black.dump_to_file())
1335 tmp_config = Path(black.dump_to_file())
1337 args = ["--config", str(tmp_config), str(tmp_file)]
1338 self.invokeBlack(args, exit_code=2, ignore_config=False)
1342 def test_parse_pyproject_toml(self) -> None:
1343 test_toml_file = THIS_DIR / "test.toml"
1344 config = black.parse_pyproject_toml(str(test_toml_file))
1345 self.assertEqual(config["verbose"], 1)
1346 self.assertEqual(config["check"], "no")
1347 self.assertEqual(config["diff"], "y")
1348 self.assertEqual(config["color"], True)
1349 self.assertEqual(config["line_length"], 79)
1350 self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
1351 self.assertEqual(config["python_cell_magics"], ["custom1", "custom2"])
1352 self.assertEqual(config["exclude"], r"\.pyi?$")
1353 self.assertEqual(config["include"], r"\.py?$")
1355 def test_read_pyproject_toml(self) -> None:
1356 test_toml_file = THIS_DIR / "test.toml"
1357 fake_ctx = FakeContext()
1358 black.read_pyproject_toml(fake_ctx, FakeParameter(), str(test_toml_file))
1359 config = fake_ctx.default_map
1360 self.assertEqual(config["verbose"], "1")
1361 self.assertEqual(config["check"], "no")
1362 self.assertEqual(config["diff"], "y")
1363 self.assertEqual(config["color"], "True")
1364 self.assertEqual(config["line_length"], "79")
1365 self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
1366 self.assertEqual(config["exclude"], r"\.pyi?$")
1367 self.assertEqual(config["include"], r"\.py?$")
1369 @pytest.mark.incompatible_with_mypyc
1370 def test_find_project_root(self) -> None:
1371 with TemporaryDirectory() as workspace:
1372 root = Path(workspace)
1373 test_dir = root / "test"
1376 src_dir = root / "src"
1379 root_pyproject = root / "pyproject.toml"
1380 root_pyproject.touch()
1381 src_pyproject = src_dir / "pyproject.toml"
1382 src_pyproject.touch()
1383 src_python = src_dir / "foo.py"
1387 black.find_project_root((src_dir, test_dir)),
1388 (root.resolve(), "pyproject.toml"),
1391 black.find_project_root((src_dir,)),
1392 (src_dir.resolve(), "pyproject.toml"),
1395 black.find_project_root((src_python,)),
1396 (src_dir.resolve(), "pyproject.toml"),
1399 with change_directory(test_dir):
1401 black.find_project_root(("-",), stdin_filename="../src/a.py"),
1402 (src_dir.resolve(), "pyproject.toml"),
1406 "black.files.find_user_pyproject_toml",
1408 def test_find_pyproject_toml(self, find_user_pyproject_toml: MagicMock) -> None:
1409 find_user_pyproject_toml.side_effect = RuntimeError()
1411 with redirect_stderr(io.StringIO()) as stderr:
1412 result = black.files.find_pyproject_toml(
1413 path_search_start=(str(Path.cwd().root),)
1416 assert result is None
1417 err = stderr.getvalue()
1418 assert "Ignoring user configuration" in err
1421 "black.files.find_user_pyproject_toml",
1422 black.files.find_user_pyproject_toml.__wrapped__,
1424 def test_find_user_pyproject_toml_linux(self) -> None:
1425 if system() == "Windows":
1428 # Test if XDG_CONFIG_HOME is checked
1429 with TemporaryDirectory() as workspace:
1430 tmp_user_config = Path(workspace) / "black"
1431 with patch.dict("os.environ", {"XDG_CONFIG_HOME": workspace}):
1433 black.files.find_user_pyproject_toml(), tmp_user_config.resolve()
1436 # Test fallback for XDG_CONFIG_HOME
1437 with patch.dict("os.environ"):
1438 os.environ.pop("XDG_CONFIG_HOME", None)
1439 fallback_user_config = Path("~/.config").expanduser() / "black"
1441 black.files.find_user_pyproject_toml(), fallback_user_config.resolve()
1444 def test_find_user_pyproject_toml_windows(self) -> None:
1445 if system() != "Windows":
1448 user_config_path = Path.home() / ".black"
1450 black.files.find_user_pyproject_toml(), user_config_path.resolve()
1453 def test_bpo_33660_workaround(self) -> None:
1454 if system() == "Windows":
1457 # https://bugs.python.org/issue33660
1459 with change_directory(root):
1460 path = Path("workspace") / "project"
1461 report = black.Report(verbose=True)
1462 normalized_path = black.normalize_path_maybe_ignore(path, root, report)
1463 self.assertEqual(normalized_path, "workspace/project")
1465 def test_normalize_path_ignore_windows_junctions_outside_of_root(self) -> None:
1466 if system() != "Windows":
1469 with TemporaryDirectory() as workspace:
1470 root = Path(workspace)
1471 junction_dir = root / "junction"
1472 junction_target_outside_of_root = root / ".."
1473 os.system(f"mklink /J {junction_dir} {junction_target_outside_of_root}")
1475 report = black.Report(verbose=True)
1476 normalized_path = black.normalize_path_maybe_ignore(
1477 junction_dir, root, report
1479 # Manually delete for Python < 3.8
1480 os.system(f"rmdir {junction_dir}")
1482 self.assertEqual(normalized_path, None)
1484 def test_newline_comment_interaction(self) -> None:
1485 source = "class A:\\\r\n# type: ignore\n pass\n"
1486 output = black.format_str(source, mode=DEFAULT_MODE)
1487 black.assert_stable(source, output, mode=DEFAULT_MODE)
1489 def test_bpo_2142_workaround(self) -> None:
1490 # https://bugs.python.org/issue2142
1492 source, _ = read_data("miscellaneous", "missing_final_newline")
1493 # read_data adds a trailing newline
1494 source = source.rstrip()
1495 expected, _ = read_data("miscellaneous", "missing_final_newline.diff")
1496 tmp_file = Path(black.dump_to_file(source, ensure_final_newline=False))
1497 diff_header = re.compile(
1498 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
1499 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
1502 result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
1503 self.assertEqual(result.exit_code, 0)
1506 actual = result.output
1507 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
1508 self.assertEqual(actual, expected)
1511 def compare_results(
1512 result: click.testing.Result, expected_value: str, expected_exit_code: int
1514 """Helper method to test the value and exit code of a click Result."""
1516 result.output == expected_value
1517 ), "The output did not match the expected value."
1518 assert result.exit_code == expected_exit_code, "The exit code is incorrect."
1520 def test_code_option(self) -> None:
1521 """Test the code option with no changes."""
1522 code = 'print("Hello world")\n'
1523 args = ["--code", code]
1524 result = CliRunner().invoke(black.main, args)
1526 self.compare_results(result, code, 0)
1528 def test_code_option_changed(self) -> None:
1529 """Test the code option when changes are required."""
1530 code = "print('hello world')"
1531 formatted = black.format_str(code, mode=DEFAULT_MODE)
1533 args = ["--code", code]
1534 result = CliRunner().invoke(black.main, args)
1536 self.compare_results(result, formatted, 0)
1538 def test_code_option_check(self) -> None:
1539 """Test the code option when check is passed."""
1540 args = ["--check", "--code", 'print("Hello world")\n']
1541 result = CliRunner().invoke(black.main, args)
1542 self.compare_results(result, "", 0)
1544 def test_code_option_check_changed(self) -> None:
1545 """Test the code option when changes are required, and check is passed."""
1546 args = ["--check", "--code", "print('hello world')"]
1547 result = CliRunner().invoke(black.main, args)
1548 self.compare_results(result, "", 1)
1550 def test_code_option_diff(self) -> None:
1551 """Test the code option when diff is passed."""
1552 code = "print('hello world')"
1553 formatted = black.format_str(code, mode=DEFAULT_MODE)
1554 result_diff = diff(code, formatted, "STDIN", "STDOUT")
1556 args = ["--diff", "--code", code]
1557 result = CliRunner().invoke(black.main, args)
1559 # Remove time from diff
1560 output = DIFF_TIME.sub("", result.output)
1562 assert output == result_diff, "The output did not match the expected value."
1563 assert result.exit_code == 0, "The exit code is incorrect."
1565 def test_code_option_color_diff(self) -> None:
1566 """Test the code option when color and diff are passed."""
1567 code = "print('hello world')"
1568 formatted = black.format_str(code, mode=DEFAULT_MODE)
1570 result_diff = diff(code, formatted, "STDIN", "STDOUT")
1571 result_diff = color_diff(result_diff)
1573 args = ["--diff", "--color", "--code", code]
1574 result = CliRunner().invoke(black.main, args)
1576 # Remove time from diff
1577 output = DIFF_TIME.sub("", result.output)
1579 assert output == result_diff, "The output did not match the expected value."
1580 assert result.exit_code == 0, "The exit code is incorrect."
1582 @pytest.mark.incompatible_with_mypyc
1583 def test_code_option_safe(self) -> None:
1584 """Test that the code option throws an error when the sanity checks fail."""
1585 # Patch black.assert_equivalent to ensure the sanity checks fail
1586 with patch.object(black, "assert_equivalent", side_effect=AssertionError):
1587 code = 'print("Hello world")'
1588 error_msg = f"{code}\nerror: cannot format <string>: \n"
1590 args = ["--safe", "--code", code]
1591 result = CliRunner().invoke(black.main, args)
1593 self.compare_results(result, error_msg, 123)
1595 def test_code_option_fast(self) -> None:
1596 """Test that the code option ignores errors when the sanity checks fail."""
1597 # Patch black.assert_equivalent to ensure the sanity checks fail
1598 with patch.object(black, "assert_equivalent", side_effect=AssertionError):
1599 code = 'print("Hello world")'
1600 formatted = black.format_str(code, mode=DEFAULT_MODE)
1602 args = ["--fast", "--code", code]
1603 result = CliRunner().invoke(black.main, args)
1605 self.compare_results(result, formatted, 0)
1607 @pytest.mark.incompatible_with_mypyc
1608 def test_code_option_config(self) -> None:
1610 Test that the code option finds the pyproject.toml in the current directory.
1612 with patch.object(black, "parse_pyproject_toml", return_value={}) as parse:
1613 args = ["--code", "print"]
1614 # This is the only directory known to contain a pyproject.toml
1615 with change_directory(PROJECT_ROOT):
1616 CliRunner().invoke(black.main, args)
1617 pyproject_path = Path(Path.cwd(), "pyproject.toml").resolve()
1620 len(parse.mock_calls) >= 1
1621 ), "Expected config parse to be called with the current directory."
1623 _, call_args, _ = parse.mock_calls[0]
1625 call_args[0].lower() == str(pyproject_path).lower()
1626 ), "Incorrect config loaded."
1628 @pytest.mark.incompatible_with_mypyc
1629 def test_code_option_parent_config(self) -> None:
1631 Test that the code option finds the pyproject.toml in the parent directory.
1633 with patch.object(black, "parse_pyproject_toml", return_value={}) as parse:
1634 with change_directory(THIS_DIR):
1635 args = ["--code", "print"]
1636 CliRunner().invoke(black.main, args)
1638 pyproject_path = Path(Path().cwd().parent, "pyproject.toml").resolve()
1640 len(parse.mock_calls) >= 1
1641 ), "Expected config parse to be called with the current directory."
1643 _, call_args, _ = parse.mock_calls[0]
1645 call_args[0].lower() == str(pyproject_path).lower()
1646 ), "Incorrect config loaded."
1648 def test_for_handled_unexpected_eof_error(self) -> None:
1650 Test that an unexpected EOF SyntaxError is nicely presented.
1652 with pytest.raises(black.parsing.InvalidInput) as exc_info:
1653 black.lib2to3_parse("print(", {})
1655 exc_info.match("Cannot parse: 2:0: EOF in multi-line statement")
1657 def test_equivalency_ast_parse_failure_includes_error(self) -> None:
1658 with pytest.raises(AssertionError) as err:
1659 black.assert_equivalent("a«»a = 1", "a«»a = 1")
1662 # Unfortunately the SyntaxError message has changed in newer versions so we
1663 # can't match it directly.
1664 err.match("invalid character")
1665 err.match(r"\(<unknown>, line 1\)")
1669 def test_get_cache_dir(
1672 monkeypatch: pytest.MonkeyPatch,
1674 # Create multiple cache directories
1675 workspace1 = tmp_path / "ws1"
1677 workspace2 = tmp_path / "ws2"
1680 # Force user_cache_dir to use the temporary directory for easier assertions
1681 patch_user_cache_dir = patch(
1682 target="black.cache.user_cache_dir",
1684 return_value=str(workspace1),
1687 # If BLACK_CACHE_DIR is not set, use user_cache_dir
1688 monkeypatch.delenv("BLACK_CACHE_DIR", raising=False)
1689 with patch_user_cache_dir:
1690 assert get_cache_dir() == workspace1
1692 # If it is set, use the path provided in the env var.
1693 monkeypatch.setenv("BLACK_CACHE_DIR", str(workspace2))
1694 assert get_cache_dir() == workspace2
1696 def test_cache_broken_file(self) -> None:
1698 with cache_dir() as workspace:
1699 cache_file = get_cache_file(mode)
1700 cache_file.write_text("this is not a pickle")
1701 assert black.read_cache(mode) == {}
1702 src = (workspace / "test.py").resolve()
1703 src.write_text("print('hello')")
1704 invokeBlack([str(src)])
1705 cache = black.read_cache(mode)
1706 assert str(src) in cache
1708 def test_cache_single_file_already_cached(self) -> None:
1710 with cache_dir() as workspace:
1711 src = (workspace / "test.py").resolve()
1712 src.write_text("print('hello')")
1713 black.write_cache({}, [src], mode)
1714 invokeBlack([str(src)])
1715 assert src.read_text() == "print('hello')"
1718 def test_cache_multiple_files(self) -> None:
1720 with cache_dir() as workspace, patch(
1721 "concurrent.futures.ProcessPoolExecutor", new=ThreadPoolExecutor
1723 one = (workspace / "one.py").resolve()
1724 with one.open("w") as fobj:
1725 fobj.write("print('hello')")
1726 two = (workspace / "two.py").resolve()
1727 with two.open("w") as fobj:
1728 fobj.write("print('hello')")
1729 black.write_cache({}, [one], mode)
1730 invokeBlack([str(workspace)])
1731 with one.open("r") as fobj:
1732 assert fobj.read() == "print('hello')"
1733 with two.open("r") as fobj:
1734 assert fobj.read() == 'print("hello")\n'
1735 cache = black.read_cache(mode)
1736 assert str(one) in cache
1737 assert str(two) in cache
1739 @pytest.mark.parametrize("color", [False, True], ids=["no-color", "with-color"])
1740 def test_no_cache_when_writeback_diff(self, color: bool) -> None:
1742 with cache_dir() as workspace:
1743 src = (workspace / "test.py").resolve()
1744 with src.open("w") as fobj:
1745 fobj.write("print('hello')")
1746 with patch("black.read_cache") as read_cache, patch(
1749 cmd = [str(src), "--diff"]
1751 cmd.append("--color")
1753 cache_file = get_cache_file(mode)
1754 assert cache_file.exists() is False
1755 write_cache.assert_not_called()
1756 read_cache.assert_not_called()
1758 @pytest.mark.parametrize("color", [False, True], ids=["no-color", "with-color"])
1760 def test_output_locking_when_writeback_diff(self, color: bool) -> None:
1761 with cache_dir() as workspace:
1762 for tag in range(0, 4):
1763 src = (workspace / f"test{tag}.py").resolve()
1764 with src.open("w") as fobj:
1765 fobj.write("print('hello')")
1767 "black.concurrency.Manager", wraps=multiprocessing.Manager
1769 cmd = ["--diff", str(workspace)]
1771 cmd.append("--color")
1772 invokeBlack(cmd, exit_code=0)
1773 # this isn't quite doing what we want, but if it _isn't_
1774 # called then we cannot be using the lock it provides
1777 def test_no_cache_when_stdin(self) -> None:
1780 result = CliRunner().invoke(
1781 black.main, ["-"], input=BytesIO(b"print('hello')")
1783 assert not result.exit_code
1784 cache_file = get_cache_file(mode)
1785 assert not cache_file.exists()
1787 def test_read_cache_no_cachefile(self) -> None:
1790 assert black.read_cache(mode) == {}
1792 def test_write_cache_read_cache(self) -> None:
1794 with cache_dir() as workspace:
1795 src = (workspace / "test.py").resolve()
1797 black.write_cache({}, [src], mode)
1798 cache = black.read_cache(mode)
1799 assert str(src) in cache
1800 assert cache[str(src)] == black.get_cache_info(src)
1802 def test_filter_cached(self) -> None:
1803 with TemporaryDirectory() as workspace:
1804 path = Path(workspace)
1805 uncached = (path / "uncached").resolve()
1806 cached = (path / "cached").resolve()
1807 cached_but_changed = (path / "changed").resolve()
1810 cached_but_changed.touch()
1812 str(cached): black.get_cache_info(cached),
1813 str(cached_but_changed): (0.0, 0),
1815 todo, done = black.cache.filter_cached(
1816 cache, {uncached, cached, cached_but_changed}
1818 assert todo == {uncached, cached_but_changed}
1819 assert done == {cached}
1821 def test_write_cache_creates_directory_if_needed(self) -> None:
1823 with cache_dir(exists=False) as workspace:
1824 assert not workspace.exists()
1825 black.write_cache({}, [], mode)
1826 assert workspace.exists()
1829 def test_failed_formatting_does_not_get_cached(self) -> None:
1831 with cache_dir() as workspace, patch(
1832 "concurrent.futures.ProcessPoolExecutor", new=ThreadPoolExecutor
1834 failing = (workspace / "failing.py").resolve()
1835 with failing.open("w") as fobj:
1836 fobj.write("not actually python")
1837 clean = (workspace / "clean.py").resolve()
1838 with clean.open("w") as fobj:
1839 fobj.write('print("hello")\n')
1840 invokeBlack([str(workspace)], exit_code=123)
1841 cache = black.read_cache(mode)
1842 assert str(failing) not in cache
1843 assert str(clean) in cache
1845 def test_write_cache_write_fail(self) -> None:
1847 with cache_dir(), patch.object(Path, "open") as mock:
1848 mock.side_effect = OSError
1849 black.write_cache({}, [], mode)
1851 def test_read_cache_line_lengths(self) -> None:
1853 short_mode = replace(DEFAULT_MODE, line_length=1)
1854 with cache_dir() as workspace:
1855 path = (workspace / "file.py").resolve()
1857 black.write_cache({}, [path], mode)
1858 one = black.read_cache(mode)
1859 assert str(path) in one
1860 two = black.read_cache(short_mode)
1861 assert str(path) not in two
1864 def assert_collected_sources(
1865 src: Sequence[Union[str, Path]],
1866 expected: Sequence[Union[str, Path]],
1868 ctx: Optional[FakeContext] = None,
1869 exclude: Optional[str] = None,
1870 include: Optional[str] = None,
1871 extend_exclude: Optional[str] = None,
1872 force_exclude: Optional[str] = None,
1873 stdin_filename: Optional[str] = None,
1875 gs_src = tuple(str(Path(s)) for s in src)
1876 gs_expected = [Path(s) for s in expected]
1877 gs_exclude = None if exclude is None else compile_pattern(exclude)
1878 gs_include = DEFAULT_INCLUDE if include is None else compile_pattern(include)
1879 gs_extend_exclude = (
1880 None if extend_exclude is None else compile_pattern(extend_exclude)
1882 gs_force_exclude = None if force_exclude is None else compile_pattern(force_exclude)
1883 collected = black.get_sources(
1884 ctx=ctx or FakeContext(),
1890 extend_exclude=gs_extend_exclude,
1891 force_exclude=gs_force_exclude,
1892 report=black.Report(),
1893 stdin_filename=stdin_filename,
1895 assert sorted(collected) == sorted(gs_expected)
1898 class TestFileCollection:
1899 def test_include_exclude(self) -> None:
1900 path = THIS_DIR / "data" / "include_exclude_tests"
1903 Path(path / "b/dont_exclude/a.py"),
1904 Path(path / "b/dont_exclude/a.pyi"),
1906 assert_collected_sources(
1910 exclude=r"/exclude/|/\.definitely_exclude/",
1913 def test_gitignore_used_as_default(self) -> None:
1914 base = Path(DATA_DIR / "include_exclude_tests")
1916 base / "b/.definitely_exclude/a.py",
1917 base / "b/.definitely_exclude/a.pyi",
1921 ctx.obj["root"] = base
1922 assert_collected_sources(src, expected, ctx=ctx, extend_exclude=r"/exclude/")
1924 @patch("black.find_project_root", lambda *args: (THIS_DIR.resolve(), None))
1925 def test_exclude_for_issue_1572(self) -> None:
1926 # Exclude shouldn't touch files that were explicitly given to Black through the
1927 # CLI. Exclude is supposed to only apply to the recursive discovery of files.
1928 # https://github.com/psf/black/issues/1572
1929 path = DATA_DIR / "include_exclude_tests"
1930 src = [path / "b/exclude/a.py"]
1931 expected = [path / "b/exclude/a.py"]
1932 assert_collected_sources(src, expected, include="", exclude=r"/exclude/|a\.py")
1934 def test_gitignore_exclude(self) -> None:
1935 path = THIS_DIR / "data" / "include_exclude_tests"
1936 include = re.compile(r"\.pyi?$")
1937 exclude = re.compile(r"")
1938 report = black.Report()
1939 gitignore = PathSpec.from_lines(
1940 "gitwildmatch", ["exclude/", ".definitely_exclude"]
1942 sources: List[Path] = []
1944 Path(path / "b/dont_exclude/a.py"),
1945 Path(path / "b/dont_exclude/a.pyi"),
1947 this_abs = THIS_DIR.resolve()
1949 black.gen_python_files(
1962 assert sorted(expected) == sorted(sources)
1964 def test_nested_gitignore(self) -> None:
1965 path = Path(THIS_DIR / "data" / "nested_gitignore_tests")
1966 include = re.compile(r"\.pyi?$")
1967 exclude = re.compile(r"")
1968 root_gitignore = black.files.get_gitignore(path)
1969 report = black.Report()
1970 expected: List[Path] = [
1971 Path(path / "x.py"),
1972 Path(path / "root/b.py"),
1973 Path(path / "root/c.py"),
1974 Path(path / "root/child/c.py"),
1976 this_abs = THIS_DIR.resolve()
1978 black.gen_python_files(
1991 assert sorted(expected) == sorted(sources)
1993 def test_nested_gitignore_directly_in_source_directory(self) -> None:
1994 # https://github.com/psf/black/issues/2598
1995 path = Path(DATA_DIR / "nested_gitignore_tests")
1996 src = Path(path / "root" / "child")
1997 expected = [src / "a.py", src / "c.py"]
1998 assert_collected_sources([src], expected)
2000 def test_invalid_gitignore(self) -> None:
2001 path = THIS_DIR / "data" / "invalid_gitignore_tests"
2002 empty_config = path / "pyproject.toml"
2003 result = BlackRunner().invoke(
2004 black.main, ["--verbose", "--config", str(empty_config), str(path)]
2006 assert result.exit_code == 1
2007 assert result.stderr_bytes is not None
2009 gitignore = path / ".gitignore"
2010 assert f"Could not parse {gitignore}" in result.stderr_bytes.decode()
2012 def test_invalid_nested_gitignore(self) -> None:
2013 path = THIS_DIR / "data" / "invalid_nested_gitignore_tests"
2014 empty_config = path / "pyproject.toml"
2015 result = BlackRunner().invoke(
2016 black.main, ["--verbose", "--config", str(empty_config), str(path)]
2018 assert result.exit_code == 1
2019 assert result.stderr_bytes is not None
2021 gitignore = path / "a" / ".gitignore"
2022 assert f"Could not parse {gitignore}" in result.stderr_bytes.decode()
2024 def test_empty_include(self) -> None:
2025 path = DATA_DIR / "include_exclude_tests"
2028 Path(path / "b/exclude/a.pie"),
2029 Path(path / "b/exclude/a.py"),
2030 Path(path / "b/exclude/a.pyi"),
2031 Path(path / "b/dont_exclude/a.pie"),
2032 Path(path / "b/dont_exclude/a.py"),
2033 Path(path / "b/dont_exclude/a.pyi"),
2034 Path(path / "b/.definitely_exclude/a.pie"),
2035 Path(path / "b/.definitely_exclude/a.py"),
2036 Path(path / "b/.definitely_exclude/a.pyi"),
2037 Path(path / ".gitignore"),
2038 Path(path / "pyproject.toml"),
2040 # Setting exclude explicitly to an empty string to block .gitignore usage.
2041 assert_collected_sources(src, expected, include="", exclude="")
2043 def test_extend_exclude(self) -> None:
2044 path = DATA_DIR / "include_exclude_tests"
2047 Path(path / "b/exclude/a.py"),
2048 Path(path / "b/dont_exclude/a.py"),
2050 assert_collected_sources(
2051 src, expected, exclude=r"\.pyi$", extend_exclude=r"\.definitely_exclude"
2054 @pytest.mark.incompatible_with_mypyc
2055 def test_symlink_out_of_root_directory(self) -> None:
2057 root = THIS_DIR.resolve()
2059 include = re.compile(black.DEFAULT_INCLUDES)
2060 exclude = re.compile(black.DEFAULT_EXCLUDES)
2061 report = black.Report()
2062 gitignore = PathSpec.from_lines("gitwildmatch", [])
2063 # `child` should behave like a symlink which resolved path is clearly
2064 # outside of the `root` directory.
2065 path.iterdir.return_value = [child]
2066 child.resolve.return_value = Path("/a/b/c")
2067 child.as_posix.return_value = "/a/b/c"
2070 black.gen_python_files(
2083 except ValueError as ve:
2084 pytest.fail(f"`get_python_files_in_dir()` failed: {ve}")
2085 path.iterdir.assert_called_once()
2086 child.resolve.assert_called_once()
2088 @patch("black.find_project_root", lambda *args: (THIS_DIR.resolve(), None))
2089 def test_get_sources_with_stdin(self) -> None:
2092 assert_collected_sources(src, expected, include="", exclude=r"/exclude/|a\.py")
2094 @patch("black.find_project_root", lambda *args: (THIS_DIR.resolve(), None))
2095 def test_get_sources_with_stdin_filename(self) -> None:
2097 stdin_filename = str(THIS_DIR / "data/collections.py")
2098 expected = [f"__BLACK_STDIN_FILENAME__{stdin_filename}"]
2099 assert_collected_sources(
2102 exclude=r"/exclude/a\.py",
2103 stdin_filename=stdin_filename,
2106 @patch("black.find_project_root", lambda *args: (THIS_DIR.resolve(), None))
2107 def test_get_sources_with_stdin_filename_and_exclude(self) -> None:
2108 # Exclude shouldn't exclude stdin_filename since it is mimicking the
2109 # file being passed directly. This is the same as
2110 # test_exclude_for_issue_1572
2111 path = DATA_DIR / "include_exclude_tests"
2113 stdin_filename = str(path / "b/exclude/a.py")
2114 expected = [f"__BLACK_STDIN_FILENAME__{stdin_filename}"]
2115 assert_collected_sources(
2118 exclude=r"/exclude/|a\.py",
2119 stdin_filename=stdin_filename,
2122 @patch("black.find_project_root", lambda *args: (THIS_DIR.resolve(), None))
2123 def test_get_sources_with_stdin_filename_and_extend_exclude(self) -> None:
2124 # Extend exclude shouldn't exclude stdin_filename since it is mimicking the
2125 # file being passed directly. This is the same as
2126 # test_exclude_for_issue_1572
2128 path = THIS_DIR / "data" / "include_exclude_tests"
2129 stdin_filename = str(path / "b/exclude/a.py")
2130 expected = [f"__BLACK_STDIN_FILENAME__{stdin_filename}"]
2131 assert_collected_sources(
2134 extend_exclude=r"/exclude/|a\.py",
2135 stdin_filename=stdin_filename,
2138 @patch("black.find_project_root", lambda *args: (THIS_DIR.resolve(), None))
2139 def test_get_sources_with_stdin_filename_and_force_exclude(self) -> None:
2140 # Force exclude should exclude the file when passing it through
2142 path = THIS_DIR / "data" / "include_exclude_tests"
2143 stdin_filename = str(path / "b/exclude/a.py")
2144 assert_collected_sources(
2147 force_exclude=r"/exclude/|a\.py",
2148 stdin_filename=stdin_filename,
2153 with open(black.__file__, "r", encoding="utf-8") as _bf:
2154 black_source_lines = _bf.readlines()
2155 except UnicodeDecodeError:
2156 if not black.COMPILED:
2161 frame: types.FrameType, event: str, arg: Any
2162 ) -> Callable[[types.FrameType, str, Any], Any]:
2163 """Show function calls `from black/__init__.py` as they happen.
2165 Register this with `sys.settrace()` in a test you're debugging.
2170 stack = len(inspect.stack()) - 19
2172 filename = frame.f_code.co_filename
2173 lineno = frame.f_lineno
2174 func_sig_lineno = lineno - 1
2175 funcname = black_source_lines[func_sig_lineno].strip()
2176 while funcname.startswith("@"):
2177 func_sig_lineno += 1
2178 funcname = black_source_lines[func_sig_lineno].strip()
2179 if "black/__init__.py" in filename:
2180 print(f"{' ' * stack}{lineno}:{funcname}")