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 files"
517 " would fail to reformat.",
522 unstyle(str(report)),
523 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
524 " 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 files"
611 " would fail to reformat.",
616 unstyle(str(report)),
617 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
618 " 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 files"
708 " would fail to reformat.",
713 unstyle(str(report)),
714 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
715 " 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"),
1400 "black.files.find_user_pyproject_toml",
1402 def test_find_pyproject_toml(self, find_user_pyproject_toml: MagicMock) -> None:
1403 find_user_pyproject_toml.side_effect = RuntimeError()
1405 with redirect_stderr(io.StringIO()) as stderr:
1406 result = black.files.find_pyproject_toml(
1407 path_search_start=(str(Path.cwd().root),)
1410 assert result is None
1411 err = stderr.getvalue()
1412 assert "Ignoring user configuration" in err
1415 "black.files.find_user_pyproject_toml",
1416 black.files.find_user_pyproject_toml.__wrapped__,
1418 def test_find_user_pyproject_toml_linux(self) -> None:
1419 if system() == "Windows":
1422 # Test if XDG_CONFIG_HOME is checked
1423 with TemporaryDirectory() as workspace:
1424 tmp_user_config = Path(workspace) / "black"
1425 with patch.dict("os.environ", {"XDG_CONFIG_HOME": workspace}):
1427 black.files.find_user_pyproject_toml(), tmp_user_config.resolve()
1430 # Test fallback for XDG_CONFIG_HOME
1431 with patch.dict("os.environ"):
1432 os.environ.pop("XDG_CONFIG_HOME", None)
1433 fallback_user_config = Path("~/.config").expanduser() / "black"
1435 black.files.find_user_pyproject_toml(), fallback_user_config.resolve()
1438 def test_find_user_pyproject_toml_windows(self) -> None:
1439 if system() != "Windows":
1442 user_config_path = Path.home() / ".black"
1444 black.files.find_user_pyproject_toml(), user_config_path.resolve()
1447 def test_bpo_33660_workaround(self) -> None:
1448 if system() == "Windows":
1451 # https://bugs.python.org/issue33660
1453 with change_directory(root):
1454 path = Path("workspace") / "project"
1455 report = black.Report(verbose=True)
1456 normalized_path = black.normalize_path_maybe_ignore(path, root, report)
1457 self.assertEqual(normalized_path, "workspace/project")
1459 def test_normalize_path_ignore_windows_junctions_outside_of_root(self) -> None:
1460 if system() != "Windows":
1463 with TemporaryDirectory() as workspace:
1464 root = Path(workspace)
1465 junction_dir = root / "junction"
1466 junction_target_outside_of_root = root / ".."
1467 os.system(f"mklink /J {junction_dir} {junction_target_outside_of_root}")
1469 report = black.Report(verbose=True)
1470 normalized_path = black.normalize_path_maybe_ignore(
1471 junction_dir, root, report
1473 # Manually delete for Python < 3.8
1474 os.system(f"rmdir {junction_dir}")
1476 self.assertEqual(normalized_path, None)
1478 def test_newline_comment_interaction(self) -> None:
1479 source = "class A:\\\r\n# type: ignore\n pass\n"
1480 output = black.format_str(source, mode=DEFAULT_MODE)
1481 black.assert_stable(source, output, mode=DEFAULT_MODE)
1483 def test_bpo_2142_workaround(self) -> None:
1484 # https://bugs.python.org/issue2142
1486 source, _ = read_data("miscellaneous", "missing_final_newline")
1487 # read_data adds a trailing newline
1488 source = source.rstrip()
1489 expected, _ = read_data("miscellaneous", "missing_final_newline.diff")
1490 tmp_file = Path(black.dump_to_file(source, ensure_final_newline=False))
1491 diff_header = re.compile(
1492 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
1493 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
1496 result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
1497 self.assertEqual(result.exit_code, 0)
1500 actual = result.output
1501 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
1502 self.assertEqual(actual, expected)
1505 def compare_results(
1506 result: click.testing.Result, expected_value: str, expected_exit_code: int
1508 """Helper method to test the value and exit code of a click Result."""
1510 result.output == expected_value
1511 ), "The output did not match the expected value."
1512 assert result.exit_code == expected_exit_code, "The exit code is incorrect."
1514 def test_code_option(self) -> None:
1515 """Test the code option with no changes."""
1516 code = 'print("Hello world")\n'
1517 args = ["--code", code]
1518 result = CliRunner().invoke(black.main, args)
1520 self.compare_results(result, code, 0)
1522 def test_code_option_changed(self) -> None:
1523 """Test the code option when changes are required."""
1524 code = "print('hello world')"
1525 formatted = black.format_str(code, mode=DEFAULT_MODE)
1527 args = ["--code", code]
1528 result = CliRunner().invoke(black.main, args)
1530 self.compare_results(result, formatted, 0)
1532 def test_code_option_check(self) -> None:
1533 """Test the code option when check is passed."""
1534 args = ["--check", "--code", 'print("Hello world")\n']
1535 result = CliRunner().invoke(black.main, args)
1536 self.compare_results(result, "", 0)
1538 def test_code_option_check_changed(self) -> None:
1539 """Test the code option when changes are required, and check is passed."""
1540 args = ["--check", "--code", "print('hello world')"]
1541 result = CliRunner().invoke(black.main, args)
1542 self.compare_results(result, "", 1)
1544 def test_code_option_diff(self) -> None:
1545 """Test the code option when diff is passed."""
1546 code = "print('hello world')"
1547 formatted = black.format_str(code, mode=DEFAULT_MODE)
1548 result_diff = diff(code, formatted, "STDIN", "STDOUT")
1550 args = ["--diff", "--code", code]
1551 result = CliRunner().invoke(black.main, args)
1553 # Remove time from diff
1554 output = DIFF_TIME.sub("", result.output)
1556 assert output == result_diff, "The output did not match the expected value."
1557 assert result.exit_code == 0, "The exit code is incorrect."
1559 def test_code_option_color_diff(self) -> None:
1560 """Test the code option when color and diff are passed."""
1561 code = "print('hello world')"
1562 formatted = black.format_str(code, mode=DEFAULT_MODE)
1564 result_diff = diff(code, formatted, "STDIN", "STDOUT")
1565 result_diff = color_diff(result_diff)
1567 args = ["--diff", "--color", "--code", code]
1568 result = CliRunner().invoke(black.main, args)
1570 # Remove time from diff
1571 output = DIFF_TIME.sub("", result.output)
1573 assert output == result_diff, "The output did not match the expected value."
1574 assert result.exit_code == 0, "The exit code is incorrect."
1576 @pytest.mark.incompatible_with_mypyc
1577 def test_code_option_safe(self) -> None:
1578 """Test that the code option throws an error when the sanity checks fail."""
1579 # Patch black.assert_equivalent to ensure the sanity checks fail
1580 with patch.object(black, "assert_equivalent", side_effect=AssertionError):
1581 code = 'print("Hello world")'
1582 error_msg = f"{code}\nerror: cannot format <string>: \n"
1584 args = ["--safe", "--code", code]
1585 result = CliRunner().invoke(black.main, args)
1587 self.compare_results(result, error_msg, 123)
1589 def test_code_option_fast(self) -> None:
1590 """Test that the code option ignores errors when the sanity checks fail."""
1591 # Patch black.assert_equivalent to ensure the sanity checks fail
1592 with patch.object(black, "assert_equivalent", side_effect=AssertionError):
1593 code = 'print("Hello world")'
1594 formatted = black.format_str(code, mode=DEFAULT_MODE)
1596 args = ["--fast", "--code", code]
1597 result = CliRunner().invoke(black.main, args)
1599 self.compare_results(result, formatted, 0)
1601 @pytest.mark.incompatible_with_mypyc
1602 def test_code_option_config(self) -> None:
1604 Test that the code option finds the pyproject.toml in the current directory.
1606 with patch.object(black, "parse_pyproject_toml", return_value={}) as parse:
1607 args = ["--code", "print"]
1608 # This is the only directory known to contain a pyproject.toml
1609 with change_directory(PROJECT_ROOT):
1610 CliRunner().invoke(black.main, args)
1611 pyproject_path = Path(Path.cwd(), "pyproject.toml").resolve()
1614 len(parse.mock_calls) >= 1
1615 ), "Expected config parse to be called with the current directory."
1617 _, call_args, _ = parse.mock_calls[0]
1619 call_args[0].lower() == str(pyproject_path).lower()
1620 ), "Incorrect config loaded."
1622 @pytest.mark.incompatible_with_mypyc
1623 def test_code_option_parent_config(self) -> None:
1625 Test that the code option finds the pyproject.toml in the parent directory.
1627 with patch.object(black, "parse_pyproject_toml", return_value={}) as parse:
1628 with change_directory(THIS_DIR):
1629 args = ["--code", "print"]
1630 CliRunner().invoke(black.main, args)
1632 pyproject_path = Path(Path().cwd().parent, "pyproject.toml").resolve()
1634 len(parse.mock_calls) >= 1
1635 ), "Expected config parse to be called with the current directory."
1637 _, call_args, _ = parse.mock_calls[0]
1639 call_args[0].lower() == str(pyproject_path).lower()
1640 ), "Incorrect config loaded."
1642 def test_for_handled_unexpected_eof_error(self) -> None:
1644 Test that an unexpected EOF SyntaxError is nicely presented.
1646 with pytest.raises(black.parsing.InvalidInput) as exc_info:
1647 black.lib2to3_parse("print(", {})
1649 exc_info.match("Cannot parse: 2:0: EOF in multi-line statement")
1651 def test_equivalency_ast_parse_failure_includes_error(self) -> None:
1652 with pytest.raises(AssertionError) as err:
1653 black.assert_equivalent("a«»a = 1", "a«»a = 1")
1656 # Unfortunately the SyntaxError message has changed in newer versions so we
1657 # can't match it directly.
1658 err.match("invalid character")
1659 err.match(r"\(<unknown>, line 1\)")
1663 def test_get_cache_dir(
1666 monkeypatch: pytest.MonkeyPatch,
1668 # Create multiple cache directories
1669 workspace1 = tmp_path / "ws1"
1671 workspace2 = tmp_path / "ws2"
1674 # Force user_cache_dir to use the temporary directory for easier assertions
1675 patch_user_cache_dir = patch(
1676 target="black.cache.user_cache_dir",
1678 return_value=str(workspace1),
1681 # If BLACK_CACHE_DIR is not set, use user_cache_dir
1682 monkeypatch.delenv("BLACK_CACHE_DIR", raising=False)
1683 with patch_user_cache_dir:
1684 assert get_cache_dir() == workspace1
1686 # If it is set, use the path provided in the env var.
1687 monkeypatch.setenv("BLACK_CACHE_DIR", str(workspace2))
1688 assert get_cache_dir() == workspace2
1690 def test_cache_broken_file(self) -> None:
1692 with cache_dir() as workspace:
1693 cache_file = get_cache_file(mode)
1694 cache_file.write_text("this is not a pickle")
1695 assert black.read_cache(mode) == {}
1696 src = (workspace / "test.py").resolve()
1697 src.write_text("print('hello')")
1698 invokeBlack([str(src)])
1699 cache = black.read_cache(mode)
1700 assert str(src) in cache
1702 def test_cache_single_file_already_cached(self) -> None:
1704 with cache_dir() as workspace:
1705 src = (workspace / "test.py").resolve()
1706 src.write_text("print('hello')")
1707 black.write_cache({}, [src], mode)
1708 invokeBlack([str(src)])
1709 assert src.read_text() == "print('hello')"
1712 def test_cache_multiple_files(self) -> None:
1714 with cache_dir() as workspace, patch(
1715 "concurrent.futures.ProcessPoolExecutor", new=ThreadPoolExecutor
1717 one = (workspace / "one.py").resolve()
1718 with one.open("w") as fobj:
1719 fobj.write("print('hello')")
1720 two = (workspace / "two.py").resolve()
1721 with two.open("w") as fobj:
1722 fobj.write("print('hello')")
1723 black.write_cache({}, [one], mode)
1724 invokeBlack([str(workspace)])
1725 with one.open("r") as fobj:
1726 assert fobj.read() == "print('hello')"
1727 with two.open("r") as fobj:
1728 assert fobj.read() == 'print("hello")\n'
1729 cache = black.read_cache(mode)
1730 assert str(one) in cache
1731 assert str(two) in cache
1733 @pytest.mark.parametrize("color", [False, True], ids=["no-color", "with-color"])
1734 def test_no_cache_when_writeback_diff(self, color: bool) -> None:
1736 with cache_dir() as workspace:
1737 src = (workspace / "test.py").resolve()
1738 with src.open("w") as fobj:
1739 fobj.write("print('hello')")
1740 with patch("black.read_cache") as read_cache, patch(
1743 cmd = [str(src), "--diff"]
1745 cmd.append("--color")
1747 cache_file = get_cache_file(mode)
1748 assert cache_file.exists() is False
1749 write_cache.assert_not_called()
1750 read_cache.assert_not_called()
1752 @pytest.mark.parametrize("color", [False, True], ids=["no-color", "with-color"])
1754 def test_output_locking_when_writeback_diff(self, color: bool) -> None:
1755 with cache_dir() as workspace:
1756 for tag in range(0, 4):
1757 src = (workspace / f"test{tag}.py").resolve()
1758 with src.open("w") as fobj:
1759 fobj.write("print('hello')")
1760 with patch("black.Manager", wraps=multiprocessing.Manager) as mgr:
1761 cmd = ["--diff", str(workspace)]
1763 cmd.append("--color")
1764 invokeBlack(cmd, exit_code=0)
1765 # this isn't quite doing what we want, but if it _isn't_
1766 # called then we cannot be using the lock it provides
1769 def test_no_cache_when_stdin(self) -> None:
1772 result = CliRunner().invoke(
1773 black.main, ["-"], input=BytesIO(b"print('hello')")
1775 assert not result.exit_code
1776 cache_file = get_cache_file(mode)
1777 assert not cache_file.exists()
1779 def test_read_cache_no_cachefile(self) -> None:
1782 assert black.read_cache(mode) == {}
1784 def test_write_cache_read_cache(self) -> None:
1786 with cache_dir() as workspace:
1787 src = (workspace / "test.py").resolve()
1789 black.write_cache({}, [src], mode)
1790 cache = black.read_cache(mode)
1791 assert str(src) in cache
1792 assert cache[str(src)] == black.get_cache_info(src)
1794 def test_filter_cached(self) -> None:
1795 with TemporaryDirectory() as workspace:
1796 path = Path(workspace)
1797 uncached = (path / "uncached").resolve()
1798 cached = (path / "cached").resolve()
1799 cached_but_changed = (path / "changed").resolve()
1802 cached_but_changed.touch()
1804 str(cached): black.get_cache_info(cached),
1805 str(cached_but_changed): (0.0, 0),
1807 todo, done = black.filter_cached(
1808 cache, {uncached, cached, cached_but_changed}
1810 assert todo == {uncached, cached_but_changed}
1811 assert done == {cached}
1813 def test_write_cache_creates_directory_if_needed(self) -> None:
1815 with cache_dir(exists=False) as workspace:
1816 assert not workspace.exists()
1817 black.write_cache({}, [], mode)
1818 assert workspace.exists()
1821 def test_failed_formatting_does_not_get_cached(self) -> None:
1823 with cache_dir() as workspace, patch(
1824 "concurrent.futures.ProcessPoolExecutor", new=ThreadPoolExecutor
1826 failing = (workspace / "failing.py").resolve()
1827 with failing.open("w") as fobj:
1828 fobj.write("not actually python")
1829 clean = (workspace / "clean.py").resolve()
1830 with clean.open("w") as fobj:
1831 fobj.write('print("hello")\n')
1832 invokeBlack([str(workspace)], exit_code=123)
1833 cache = black.read_cache(mode)
1834 assert str(failing) not in cache
1835 assert str(clean) in cache
1837 def test_write_cache_write_fail(self) -> None:
1839 with cache_dir(), patch.object(Path, "open") as mock:
1840 mock.side_effect = OSError
1841 black.write_cache({}, [], mode)
1843 def test_read_cache_line_lengths(self) -> None:
1845 short_mode = replace(DEFAULT_MODE, line_length=1)
1846 with cache_dir() as workspace:
1847 path = (workspace / "file.py").resolve()
1849 black.write_cache({}, [path], mode)
1850 one = black.read_cache(mode)
1851 assert str(path) in one
1852 two = black.read_cache(short_mode)
1853 assert str(path) not in two
1856 def assert_collected_sources(
1857 src: Sequence[Union[str, Path]],
1858 expected: Sequence[Union[str, Path]],
1860 ctx: Optional[FakeContext] = None,
1861 exclude: Optional[str] = None,
1862 include: Optional[str] = None,
1863 extend_exclude: Optional[str] = None,
1864 force_exclude: Optional[str] = None,
1865 stdin_filename: Optional[str] = None,
1867 gs_src = tuple(str(Path(s)) for s in src)
1868 gs_expected = [Path(s) for s in expected]
1869 gs_exclude = None if exclude is None else compile_pattern(exclude)
1870 gs_include = DEFAULT_INCLUDE if include is None else compile_pattern(include)
1871 gs_extend_exclude = (
1872 None if extend_exclude is None else compile_pattern(extend_exclude)
1874 gs_force_exclude = None if force_exclude is None else compile_pattern(force_exclude)
1875 collected = black.get_sources(
1876 ctx=ctx or FakeContext(),
1882 extend_exclude=gs_extend_exclude,
1883 force_exclude=gs_force_exclude,
1884 report=black.Report(),
1885 stdin_filename=stdin_filename,
1887 assert sorted(collected) == sorted(gs_expected)
1890 class TestFileCollection:
1891 def test_include_exclude(self) -> None:
1892 path = THIS_DIR / "data" / "include_exclude_tests"
1895 Path(path / "b/dont_exclude/a.py"),
1896 Path(path / "b/dont_exclude/a.pyi"),
1898 assert_collected_sources(
1902 exclude=r"/exclude/|/\.definitely_exclude/",
1905 def test_gitignore_used_as_default(self) -> None:
1906 base = Path(DATA_DIR / "include_exclude_tests")
1908 base / "b/.definitely_exclude/a.py",
1909 base / "b/.definitely_exclude/a.pyi",
1913 ctx.obj["root"] = base
1914 assert_collected_sources(src, expected, ctx=ctx, extend_exclude=r"/exclude/")
1916 @patch("black.find_project_root", lambda *args: (THIS_DIR.resolve(), None))
1917 def test_exclude_for_issue_1572(self) -> None:
1918 # Exclude shouldn't touch files that were explicitly given to Black through the
1919 # CLI. Exclude is supposed to only apply to the recursive discovery of files.
1920 # https://github.com/psf/black/issues/1572
1921 path = DATA_DIR / "include_exclude_tests"
1922 src = [path / "b/exclude/a.py"]
1923 expected = [path / "b/exclude/a.py"]
1924 assert_collected_sources(src, expected, include="", exclude=r"/exclude/|a\.py")
1926 def test_gitignore_exclude(self) -> None:
1927 path = THIS_DIR / "data" / "include_exclude_tests"
1928 include = re.compile(r"\.pyi?$")
1929 exclude = re.compile(r"")
1930 report = black.Report()
1931 gitignore = PathSpec.from_lines(
1932 "gitwildmatch", ["exclude/", ".definitely_exclude"]
1934 sources: List[Path] = []
1936 Path(path / "b/dont_exclude/a.py"),
1937 Path(path / "b/dont_exclude/a.pyi"),
1939 this_abs = THIS_DIR.resolve()
1941 black.gen_python_files(
1954 assert sorted(expected) == sorted(sources)
1956 def test_nested_gitignore(self) -> None:
1957 path = Path(THIS_DIR / "data" / "nested_gitignore_tests")
1958 include = re.compile(r"\.pyi?$")
1959 exclude = re.compile(r"")
1960 root_gitignore = black.files.get_gitignore(path)
1961 report = black.Report()
1962 expected: List[Path] = [
1963 Path(path / "x.py"),
1964 Path(path / "root/b.py"),
1965 Path(path / "root/c.py"),
1966 Path(path / "root/child/c.py"),
1968 this_abs = THIS_DIR.resolve()
1970 black.gen_python_files(
1983 assert sorted(expected) == sorted(sources)
1985 def test_invalid_gitignore(self) -> None:
1986 path = THIS_DIR / "data" / "invalid_gitignore_tests"
1987 empty_config = path / "pyproject.toml"
1988 result = BlackRunner().invoke(
1989 black.main, ["--verbose", "--config", str(empty_config), str(path)]
1991 assert result.exit_code == 1
1992 assert result.stderr_bytes is not None
1994 gitignore = path / ".gitignore"
1995 assert f"Could not parse {gitignore}" in result.stderr_bytes.decode()
1997 def test_invalid_nested_gitignore(self) -> None:
1998 path = THIS_DIR / "data" / "invalid_nested_gitignore_tests"
1999 empty_config = path / "pyproject.toml"
2000 result = BlackRunner().invoke(
2001 black.main, ["--verbose", "--config", str(empty_config), str(path)]
2003 assert result.exit_code == 1
2004 assert result.stderr_bytes is not None
2006 gitignore = path / "a" / ".gitignore"
2007 assert f"Could not parse {gitignore}" in result.stderr_bytes.decode()
2009 def test_empty_include(self) -> None:
2010 path = DATA_DIR / "include_exclude_tests"
2013 Path(path / "b/exclude/a.pie"),
2014 Path(path / "b/exclude/a.py"),
2015 Path(path / "b/exclude/a.pyi"),
2016 Path(path / "b/dont_exclude/a.pie"),
2017 Path(path / "b/dont_exclude/a.py"),
2018 Path(path / "b/dont_exclude/a.pyi"),
2019 Path(path / "b/.definitely_exclude/a.pie"),
2020 Path(path / "b/.definitely_exclude/a.py"),
2021 Path(path / "b/.definitely_exclude/a.pyi"),
2022 Path(path / ".gitignore"),
2023 Path(path / "pyproject.toml"),
2025 # Setting exclude explicitly to an empty string to block .gitignore usage.
2026 assert_collected_sources(src, expected, include="", exclude="")
2028 def test_extend_exclude(self) -> None:
2029 path = DATA_DIR / "include_exclude_tests"
2032 Path(path / "b/exclude/a.py"),
2033 Path(path / "b/dont_exclude/a.py"),
2035 assert_collected_sources(
2036 src, expected, exclude=r"\.pyi$", extend_exclude=r"\.definitely_exclude"
2039 @pytest.mark.incompatible_with_mypyc
2040 def test_symlink_out_of_root_directory(self) -> None:
2042 root = THIS_DIR.resolve()
2044 include = re.compile(black.DEFAULT_INCLUDES)
2045 exclude = re.compile(black.DEFAULT_EXCLUDES)
2046 report = black.Report()
2047 gitignore = PathSpec.from_lines("gitwildmatch", [])
2048 # `child` should behave like a symlink which resolved path is clearly
2049 # outside of the `root` directory.
2050 path.iterdir.return_value = [child]
2051 child.resolve.return_value = Path("/a/b/c")
2052 child.as_posix.return_value = "/a/b/c"
2055 black.gen_python_files(
2068 except ValueError as ve:
2069 pytest.fail(f"`get_python_files_in_dir()` failed: {ve}")
2070 path.iterdir.assert_called_once()
2071 child.resolve.assert_called_once()
2073 @patch("black.find_project_root", lambda *args: (THIS_DIR.resolve(), None))
2074 def test_get_sources_with_stdin(self) -> None:
2077 assert_collected_sources(src, expected, include="", exclude=r"/exclude/|a\.py")
2079 @patch("black.find_project_root", lambda *args: (THIS_DIR.resolve(), None))
2080 def test_get_sources_with_stdin_filename(self) -> None:
2082 stdin_filename = str(THIS_DIR / "data/collections.py")
2083 expected = [f"__BLACK_STDIN_FILENAME__{stdin_filename}"]
2084 assert_collected_sources(
2087 exclude=r"/exclude/a\.py",
2088 stdin_filename=stdin_filename,
2091 @patch("black.find_project_root", lambda *args: (THIS_DIR.resolve(), None))
2092 def test_get_sources_with_stdin_filename_and_exclude(self) -> None:
2093 # Exclude shouldn't exclude stdin_filename since it is mimicking the
2094 # file being passed directly. This is the same as
2095 # test_exclude_for_issue_1572
2096 path = DATA_DIR / "include_exclude_tests"
2098 stdin_filename = str(path / "b/exclude/a.py")
2099 expected = [f"__BLACK_STDIN_FILENAME__{stdin_filename}"]
2100 assert_collected_sources(
2103 exclude=r"/exclude/|a\.py",
2104 stdin_filename=stdin_filename,
2107 @patch("black.find_project_root", lambda *args: (THIS_DIR.resolve(), None))
2108 def test_get_sources_with_stdin_filename_and_extend_exclude(self) -> None:
2109 # Extend exclude shouldn't exclude stdin_filename since it is mimicking the
2110 # file being passed directly. This is the same as
2111 # test_exclude_for_issue_1572
2113 path = THIS_DIR / "data" / "include_exclude_tests"
2114 stdin_filename = str(path / "b/exclude/a.py")
2115 expected = [f"__BLACK_STDIN_FILENAME__{stdin_filename}"]
2116 assert_collected_sources(
2119 extend_exclude=r"/exclude/|a\.py",
2120 stdin_filename=stdin_filename,
2123 @patch("black.find_project_root", lambda *args: (THIS_DIR.resolve(), None))
2124 def test_get_sources_with_stdin_filename_and_force_exclude(self) -> None:
2125 # Force exclude should exclude the file when passing it through
2127 path = THIS_DIR / "data" / "include_exclude_tests"
2128 stdin_filename = str(path / "b/exclude/a.py")
2129 assert_collected_sources(
2132 force_exclude=r"/exclude/|a\.py",
2133 stdin_filename=stdin_filename,
2138 with open(black.__file__, "r", encoding="utf-8") as _bf:
2139 black_source_lines = _bf.readlines()
2140 except UnicodeDecodeError:
2141 if not black.COMPILED:
2146 frame: types.FrameType, event: str, arg: Any
2147 ) -> Callable[[types.FrameType, str, Any], Any]:
2148 """Show function calls `from black/__init__.py` as they happen.
2150 Register this with `sys.settrace()` in a test you're debugging.
2155 stack = len(inspect.stack()) - 19
2157 filename = frame.f_code.co_filename
2158 lineno = frame.f_lineno
2159 func_sig_lineno = lineno - 1
2160 funcname = black_source_lines[func_sig_lineno].strip()
2161 while funcname.startswith("@"):
2162 func_sig_lineno += 1
2163 funcname = black_source_lines[func_sig_lineno].strip()
2164 if "black/__init__.py" in filename:
2165 print(f"{' ' * stack}{lineno}:{funcname}")