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.
12 from concurrent.futures import ThreadPoolExecutor
13 from contextlib import contextmanager
14 from dataclasses import replace
15 from io import BytesIO
16 from pathlib import Path
17 from platform import system
18 from tempfile import TemporaryDirectory
30 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 (
65 THIS_FILE = Path(__file__)
66 PY36_ARGS = [f"--target-version={version.name.lower()}" for version in PY36_VERSIONS]
67 DEFAULT_EXCLUDE = black.re_compile_maybe_verbose(black.const.DEFAULT_EXCLUDES)
68 DEFAULT_INCLUDE = black.re_compile_maybe_verbose(black.const.DEFAULT_INCLUDES)
72 # Match the time output in a diff, but nothing else
73 DIFF_TIME = re.compile(r"\t[\d\-:+\. ]+")
77 def cache_dir(exists: bool = True) -> Iterator[Path]:
78 with TemporaryDirectory() as workspace:
79 cache_dir = Path(workspace)
81 cache_dir = cache_dir / "new"
82 with patch("black.cache.CACHE_DIR", cache_dir):
87 def event_loop() -> Iterator[None]:
88 policy = asyncio.get_event_loop_policy()
89 loop = policy.new_event_loop()
90 asyncio.set_event_loop(loop)
98 class FakeContext(click.Context):
99 """A fake click Context for when calling functions that need it."""
101 def __init__(self) -> None:
102 self.default_map: Dict[str, Any] = {}
103 # Dummy root, since most of the tests don't care about it
104 self.obj: Dict[str, Any] = {"root": PROJECT_ROOT}
107 class FakeParameter(click.Parameter):
108 """A fake click Parameter for when calling functions that need it."""
110 def __init__(self) -> None:
114 class BlackRunner(CliRunner):
115 """Make sure STDOUT and STDERR are kept separate when testing Black via its CLI."""
117 def __init__(self) -> None:
118 super().__init__(mix_stderr=False)
122 args: List[str], exit_code: int = 0, ignore_config: bool = True
124 runner = BlackRunner()
126 args = ["--verbose", "--config", str(THIS_DIR / "empty.toml"), *args]
127 result = runner.invoke(black.main, args, catch_exceptions=False)
128 assert result.stdout_bytes is not None
129 assert result.stderr_bytes is not None
131 f"Failed with args: {args}\n"
132 f"stdout: {result.stdout_bytes.decode()!r}\n"
133 f"stderr: {result.stderr_bytes.decode()!r}\n"
134 f"exception: {result.exception}"
136 assert result.exit_code == exit_code, msg
139 class BlackTestCase(BlackBaseTestCase):
140 invokeBlack = staticmethod(invokeBlack)
142 def test_empty_ff(self) -> None:
144 tmp_file = Path(black.dump_to_file())
146 self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES))
147 with open(tmp_file, encoding="utf8") as f:
151 self.assertFormatEqual(expected, actual)
153 def test_experimental_string_processing_warns(self) -> None:
155 black.mode.Deprecated, black.Mode, experimental_string_processing=True
158 def test_piping(self) -> None:
159 source, expected = read_data("src/black/__init__", data=False)
160 result = BlackRunner().invoke(
162 ["-", "--fast", f"--line-length={black.DEFAULT_LINE_LENGTH}"],
163 input=BytesIO(source.encode("utf8")),
165 self.assertEqual(result.exit_code, 0)
166 self.assertFormatEqual(expected, result.output)
167 if source != result.output:
168 black.assert_equivalent(source, result.output)
169 black.assert_stable(source, result.output, DEFAULT_MODE)
171 def test_piping_diff(self) -> None:
172 diff_header = re.compile(
173 r"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d\d\d\d "
176 source, _ = read_data("expression.py")
177 expected, _ = read_data("expression.diff")
178 config = THIS_DIR / "data" / "empty_pyproject.toml"
182 f"--line-length={black.DEFAULT_LINE_LENGTH}",
184 f"--config={config}",
186 result = BlackRunner().invoke(
187 black.main, args, input=BytesIO(source.encode("utf8"))
189 self.assertEqual(result.exit_code, 0)
190 actual = diff_header.sub(DETERMINISTIC_HEADER, result.output)
191 actual = actual.rstrip() + "\n" # the diff output has a trailing space
192 self.assertEqual(expected, actual)
194 def test_piping_diff_with_color(self) -> None:
195 source, _ = read_data("expression.py")
196 config = THIS_DIR / "data" / "empty_pyproject.toml"
200 f"--line-length={black.DEFAULT_LINE_LENGTH}",
203 f"--config={config}",
205 result = BlackRunner().invoke(
206 black.main, args, input=BytesIO(source.encode("utf8"))
208 actual = result.output
209 # Again, the contents are checked in a different test, so only look for colors.
210 self.assertIn("\033[1m", actual)
211 self.assertIn("\033[36m", actual)
212 self.assertIn("\033[32m", actual)
213 self.assertIn("\033[31m", actual)
214 self.assertIn("\033[0m", actual)
216 @patch("black.dump_to_file", dump_to_stderr)
217 def _test_wip(self) -> None:
218 source, expected = read_data("wip")
219 sys.settrace(tracefunc)
222 experimental_string_processing=False,
223 target_versions={black.TargetVersion.PY38},
225 actual = fs(source, mode=mode)
227 self.assertFormatEqual(expected, actual)
228 black.assert_equivalent(source, actual)
229 black.assert_stable(source, actual, black.FileMode())
231 @unittest.expectedFailure
232 @patch("black.dump_to_file", dump_to_stderr)
233 def test_trailing_comma_optional_parens_stability1(self) -> None:
234 source, _expected = read_data("trailing_comma_optional_parens1")
236 black.assert_stable(source, actual, DEFAULT_MODE)
238 @unittest.expectedFailure
239 @patch("black.dump_to_file", dump_to_stderr)
240 def test_trailing_comma_optional_parens_stability2(self) -> None:
241 source, _expected = read_data("trailing_comma_optional_parens2")
243 black.assert_stable(source, actual, DEFAULT_MODE)
245 @unittest.expectedFailure
246 @patch("black.dump_to_file", dump_to_stderr)
247 def test_trailing_comma_optional_parens_stability3(self) -> None:
248 source, _expected = read_data("trailing_comma_optional_parens3")
250 black.assert_stable(source, actual, DEFAULT_MODE)
252 @patch("black.dump_to_file", dump_to_stderr)
253 def test_trailing_comma_optional_parens_stability1_pass2(self) -> None:
254 source, _expected = read_data("trailing_comma_optional_parens1")
255 actual = fs(fs(source)) # this is what `format_file_contents` does with --safe
256 black.assert_stable(source, actual, DEFAULT_MODE)
258 @patch("black.dump_to_file", dump_to_stderr)
259 def test_trailing_comma_optional_parens_stability2_pass2(self) -> None:
260 source, _expected = read_data("trailing_comma_optional_parens2")
261 actual = fs(fs(source)) # this is what `format_file_contents` does with --safe
262 black.assert_stable(source, actual, DEFAULT_MODE)
264 @patch("black.dump_to_file", dump_to_stderr)
265 def test_trailing_comma_optional_parens_stability3_pass2(self) -> None:
266 source, _expected = read_data("trailing_comma_optional_parens3")
267 actual = fs(fs(source)) # this is what `format_file_contents` does with --safe
268 black.assert_stable(source, actual, DEFAULT_MODE)
270 def test_pep_572_version_detection(self) -> None:
271 source, _ = read_data("pep_572")
272 root = black.lib2to3_parse(source)
273 features = black.get_features_used(root)
274 self.assertIn(black.Feature.ASSIGNMENT_EXPRESSIONS, features)
275 versions = black.detect_target_versions(root)
276 self.assertIn(black.TargetVersion.PY38, versions)
278 def test_expression_ff(self) -> None:
279 source, expected = read_data("expression")
280 tmp_file = Path(black.dump_to_file(source))
282 self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
283 with open(tmp_file, encoding="utf8") as f:
287 self.assertFormatEqual(expected, actual)
288 with patch("black.dump_to_file", dump_to_stderr):
289 black.assert_equivalent(source, actual)
290 black.assert_stable(source, actual, DEFAULT_MODE)
292 def test_expression_diff(self) -> None:
293 source, _ = read_data("expression.py")
294 config = THIS_DIR / "data" / "empty_pyproject.toml"
295 expected, _ = read_data("expression.diff")
296 tmp_file = Path(black.dump_to_file(source))
297 diff_header = re.compile(
298 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
299 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
302 result = BlackRunner().invoke(
303 black.main, ["--diff", str(tmp_file), f"--config={config}"]
305 self.assertEqual(result.exit_code, 0)
308 actual = result.output
309 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
310 if expected != actual:
311 dump = black.dump_to_file(actual)
313 "Expected diff isn't equal to the actual. If you made changes to"
314 " expression.py and this is an anticipated difference, overwrite"
315 f" tests/data/expression.diff with {dump}"
317 self.assertEqual(expected, actual, msg)
319 def test_expression_diff_with_color(self) -> None:
320 source, _ = read_data("expression.py")
321 config = THIS_DIR / "data" / "empty_pyproject.toml"
322 expected, _ = read_data("expression.diff")
323 tmp_file = Path(black.dump_to_file(source))
325 result = BlackRunner().invoke(
326 black.main, ["--diff", "--color", str(tmp_file), f"--config={config}"]
330 actual = result.output
331 # We check the contents of the diff in `test_expression_diff`. All
332 # we need to check here is that color codes exist in the result.
333 self.assertIn("\033[1m", actual)
334 self.assertIn("\033[36m", actual)
335 self.assertIn("\033[32m", actual)
336 self.assertIn("\033[31m", actual)
337 self.assertIn("\033[0m", actual)
339 def test_detect_pos_only_arguments(self) -> None:
340 source, _ = read_data("pep_570")
341 root = black.lib2to3_parse(source)
342 features = black.get_features_used(root)
343 self.assertIn(black.Feature.POS_ONLY_ARGUMENTS, features)
344 versions = black.detect_target_versions(root)
345 self.assertIn(black.TargetVersion.PY38, versions)
347 @patch("black.dump_to_file", dump_to_stderr)
348 def test_string_quotes(self) -> None:
349 source, expected = read_data("string_quotes")
350 mode = black.Mode(preview=True)
351 assert_format(source, expected, mode)
352 mode = replace(mode, string_normalization=False)
353 not_normalized = fs(source, mode=mode)
354 self.assertFormatEqual(source.replace("\\\n", ""), not_normalized)
355 black.assert_equivalent(source, not_normalized)
356 black.assert_stable(source, not_normalized, mode=mode)
358 def test_skip_magic_trailing_comma(self) -> None:
359 source, _ = read_data("expression.py")
360 expected, _ = read_data("expression_skip_magic_trailing_comma.diff")
361 tmp_file = Path(black.dump_to_file(source))
362 diff_header = re.compile(
363 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
364 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
367 result = BlackRunner().invoke(black.main, ["-C", "--diff", str(tmp_file)])
368 self.assertEqual(result.exit_code, 0)
371 actual = result.output
372 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
373 actual = actual.rstrip() + "\n" # the diff output has a trailing space
374 if expected != actual:
375 dump = black.dump_to_file(actual)
377 "Expected diff isn't equal to the actual. If you made changes to"
378 " expression.py and this is an anticipated difference, overwrite"
379 f" tests/data/expression_skip_magic_trailing_comma.diff with {dump}"
381 self.assertEqual(expected, actual, msg)
383 @patch("black.dump_to_file", dump_to_stderr)
384 def test_async_as_identifier(self) -> None:
385 source_path = (THIS_DIR / "data" / "async_as_identifier.py").resolve()
386 source, expected = read_data("async_as_identifier")
388 self.assertFormatEqual(expected, actual)
389 major, minor = sys.version_info[:2]
390 if major < 3 or (major <= 3 and minor < 7):
391 black.assert_equivalent(source, actual)
392 black.assert_stable(source, actual, DEFAULT_MODE)
393 # ensure black can parse this when the target is 3.6
394 self.invokeBlack([str(source_path), "--target-version", "py36"])
395 # but not on 3.7, because async/await is no longer an identifier
396 self.invokeBlack([str(source_path), "--target-version", "py37"], exit_code=123)
398 @patch("black.dump_to_file", dump_to_stderr)
399 def test_python37(self) -> None:
400 source_path = (THIS_DIR / "data" / "python37.py").resolve()
401 source, expected = read_data("python37")
403 self.assertFormatEqual(expected, actual)
404 major, minor = sys.version_info[:2]
405 if major > 3 or (major == 3 and minor >= 7):
406 black.assert_equivalent(source, actual)
407 black.assert_stable(source, actual, DEFAULT_MODE)
408 # ensure black can parse this when the target is 3.7
409 self.invokeBlack([str(source_path), "--target-version", "py37"])
410 # but not on 3.6, because we use async as a reserved keyword
411 self.invokeBlack([str(source_path), "--target-version", "py36"], exit_code=123)
413 def test_tab_comment_indentation(self) -> None:
414 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
415 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
416 self.assertFormatEqual(contents_spc, fs(contents_spc))
417 self.assertFormatEqual(contents_spc, fs(contents_tab))
419 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
420 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
421 self.assertFormatEqual(contents_spc, fs(contents_spc))
422 self.assertFormatEqual(contents_spc, fs(contents_tab))
424 # mixed tabs and spaces (valid Python 2 code)
425 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t# comment\n pass\n"
426 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
427 self.assertFormatEqual(contents_spc, fs(contents_spc))
428 self.assertFormatEqual(contents_spc, fs(contents_tab))
430 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t\t# comment\n pass\n"
431 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
432 self.assertFormatEqual(contents_spc, fs(contents_spc))
433 self.assertFormatEqual(contents_spc, fs(contents_tab))
435 def test_report_verbose(self) -> None:
436 report = Report(verbose=True)
440 def out(msg: str, **kwargs: Any) -> None:
441 out_lines.append(msg)
443 def err(msg: str, **kwargs: Any) -> None:
444 err_lines.append(msg)
446 with patch("black.output._out", out), patch("black.output._err", err):
447 report.done(Path("f1"), black.Changed.NO)
448 self.assertEqual(len(out_lines), 1)
449 self.assertEqual(len(err_lines), 0)
450 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
451 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
452 self.assertEqual(report.return_code, 0)
453 report.done(Path("f2"), black.Changed.YES)
454 self.assertEqual(len(out_lines), 2)
455 self.assertEqual(len(err_lines), 0)
456 self.assertEqual(out_lines[-1], "reformatted f2")
458 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
460 report.done(Path("f3"), black.Changed.CACHED)
461 self.assertEqual(len(out_lines), 3)
462 self.assertEqual(len(err_lines), 0)
464 out_lines[-1], "f3 wasn't modified on disk since last run."
467 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
469 self.assertEqual(report.return_code, 0)
471 self.assertEqual(report.return_code, 1)
473 report.failed(Path("e1"), "boom")
474 self.assertEqual(len(out_lines), 3)
475 self.assertEqual(len(err_lines), 1)
476 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
478 unstyle(str(report)),
479 "1 file reformatted, 2 files left unchanged, 1 file failed to"
482 self.assertEqual(report.return_code, 123)
483 report.done(Path("f3"), black.Changed.YES)
484 self.assertEqual(len(out_lines), 4)
485 self.assertEqual(len(err_lines), 1)
486 self.assertEqual(out_lines[-1], "reformatted f3")
488 unstyle(str(report)),
489 "2 files reformatted, 2 files left unchanged, 1 file failed to"
492 self.assertEqual(report.return_code, 123)
493 report.failed(Path("e2"), "boom")
494 self.assertEqual(len(out_lines), 4)
495 self.assertEqual(len(err_lines), 2)
496 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
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.path_ignored(Path("wat"), "no match")
504 self.assertEqual(len(out_lines), 5)
505 self.assertEqual(len(err_lines), 2)
506 self.assertEqual(out_lines[-1], "wat ignored: no match")
508 unstyle(str(report)),
509 "2 files reformatted, 2 files left unchanged, 2 files failed to"
512 self.assertEqual(report.return_code, 123)
513 report.done(Path("f4"), black.Changed.NO)
514 self.assertEqual(len(out_lines), 6)
515 self.assertEqual(len(err_lines), 2)
516 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
518 unstyle(str(report)),
519 "2 files reformatted, 3 files left unchanged, 2 files failed to"
522 self.assertEqual(report.return_code, 123)
525 unstyle(str(report)),
526 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
527 " would fail to reformat.",
532 unstyle(str(report)),
533 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
534 " would fail to reformat.",
537 def test_report_quiet(self) -> None:
538 report = Report(quiet=True)
542 def out(msg: str, **kwargs: Any) -> None:
543 out_lines.append(msg)
545 def err(msg: str, **kwargs: Any) -> None:
546 err_lines.append(msg)
548 with patch("black.output._out", out), patch("black.output._err", err):
549 report.done(Path("f1"), black.Changed.NO)
550 self.assertEqual(len(out_lines), 0)
551 self.assertEqual(len(err_lines), 0)
552 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
553 self.assertEqual(report.return_code, 0)
554 report.done(Path("f2"), black.Changed.YES)
555 self.assertEqual(len(out_lines), 0)
556 self.assertEqual(len(err_lines), 0)
558 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
560 report.done(Path("f3"), black.Changed.CACHED)
561 self.assertEqual(len(out_lines), 0)
562 self.assertEqual(len(err_lines), 0)
564 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
566 self.assertEqual(report.return_code, 0)
568 self.assertEqual(report.return_code, 1)
570 report.failed(Path("e1"), "boom")
571 self.assertEqual(len(out_lines), 0)
572 self.assertEqual(len(err_lines), 1)
573 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
575 unstyle(str(report)),
576 "1 file reformatted, 2 files left unchanged, 1 file failed to"
579 self.assertEqual(report.return_code, 123)
580 report.done(Path("f3"), black.Changed.YES)
581 self.assertEqual(len(out_lines), 0)
582 self.assertEqual(len(err_lines), 1)
584 unstyle(str(report)),
585 "2 files reformatted, 2 files left unchanged, 1 file failed to"
588 self.assertEqual(report.return_code, 123)
589 report.failed(Path("e2"), "boom")
590 self.assertEqual(len(out_lines), 0)
591 self.assertEqual(len(err_lines), 2)
592 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
594 unstyle(str(report)),
595 "2 files reformatted, 2 files left unchanged, 2 files failed to"
598 self.assertEqual(report.return_code, 123)
599 report.path_ignored(Path("wat"), "no match")
600 self.assertEqual(len(out_lines), 0)
601 self.assertEqual(len(err_lines), 2)
603 unstyle(str(report)),
604 "2 files reformatted, 2 files left unchanged, 2 files failed to"
607 self.assertEqual(report.return_code, 123)
608 report.done(Path("f4"), black.Changed.NO)
609 self.assertEqual(len(out_lines), 0)
610 self.assertEqual(len(err_lines), 2)
612 unstyle(str(report)),
613 "2 files reformatted, 3 files left unchanged, 2 files failed to"
616 self.assertEqual(report.return_code, 123)
619 unstyle(str(report)),
620 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
621 " would fail to reformat.",
626 unstyle(str(report)),
627 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
628 " would fail to reformat.",
631 def test_report_normal(self) -> None:
632 report = black.Report()
636 def out(msg: str, **kwargs: Any) -> None:
637 out_lines.append(msg)
639 def err(msg: str, **kwargs: Any) -> None:
640 err_lines.append(msg)
642 with patch("black.output._out", out), patch("black.output._err", err):
643 report.done(Path("f1"), black.Changed.NO)
644 self.assertEqual(len(out_lines), 0)
645 self.assertEqual(len(err_lines), 0)
646 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
647 self.assertEqual(report.return_code, 0)
648 report.done(Path("f2"), black.Changed.YES)
649 self.assertEqual(len(out_lines), 1)
650 self.assertEqual(len(err_lines), 0)
651 self.assertEqual(out_lines[-1], "reformatted f2")
653 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
655 report.done(Path("f3"), black.Changed.CACHED)
656 self.assertEqual(len(out_lines), 1)
657 self.assertEqual(len(err_lines), 0)
658 self.assertEqual(out_lines[-1], "reformatted f2")
660 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
662 self.assertEqual(report.return_code, 0)
664 self.assertEqual(report.return_code, 1)
666 report.failed(Path("e1"), "boom")
667 self.assertEqual(len(out_lines), 1)
668 self.assertEqual(len(err_lines), 1)
669 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
671 unstyle(str(report)),
672 "1 file reformatted, 2 files left unchanged, 1 file failed to"
675 self.assertEqual(report.return_code, 123)
676 report.done(Path("f3"), black.Changed.YES)
677 self.assertEqual(len(out_lines), 2)
678 self.assertEqual(len(err_lines), 1)
679 self.assertEqual(out_lines[-1], "reformatted f3")
681 unstyle(str(report)),
682 "2 files reformatted, 2 files left unchanged, 1 file failed to"
685 self.assertEqual(report.return_code, 123)
686 report.failed(Path("e2"), "boom")
687 self.assertEqual(len(out_lines), 2)
688 self.assertEqual(len(err_lines), 2)
689 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
691 unstyle(str(report)),
692 "2 files reformatted, 2 files left unchanged, 2 files failed to"
695 self.assertEqual(report.return_code, 123)
696 report.path_ignored(Path("wat"), "no match")
697 self.assertEqual(len(out_lines), 2)
698 self.assertEqual(len(err_lines), 2)
700 unstyle(str(report)),
701 "2 files reformatted, 2 files left unchanged, 2 files failed to"
704 self.assertEqual(report.return_code, 123)
705 report.done(Path("f4"), black.Changed.NO)
706 self.assertEqual(len(out_lines), 2)
707 self.assertEqual(len(err_lines), 2)
709 unstyle(str(report)),
710 "2 files reformatted, 3 files left unchanged, 2 files failed to"
713 self.assertEqual(report.return_code, 123)
716 unstyle(str(report)),
717 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
718 " would fail to reformat.",
723 unstyle(str(report)),
724 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
725 " would fail to reformat.",
728 def test_lib2to3_parse(self) -> None:
729 with self.assertRaises(black.InvalidInput):
730 black.lib2to3_parse("invalid syntax")
733 black.lib2to3_parse(straddling)
734 black.lib2to3_parse(straddling, {TargetVersion.PY36})
737 with self.assertRaises(black.InvalidInput):
738 black.lib2to3_parse(py2_only, {TargetVersion.PY36})
740 py3_only = "exec(x, end=y)"
741 black.lib2to3_parse(py3_only)
742 black.lib2to3_parse(py3_only, {TargetVersion.PY36})
744 def test_get_features_used_decorator(self) -> None:
745 # Test the feature detection of new decorator syntax
746 # since this makes some test cases of test_get_features_used()
747 # fails if it fails, this is tested first so that a useful case
749 simples, relaxed = read_data("decorators")
750 # skip explanation comments at the top of the file
751 for simple_test in simples.split("##")[1:]:
752 node = black.lib2to3_parse(simple_test)
753 decorator = str(node.children[0].children[0]).strip()
755 Feature.RELAXED_DECORATORS,
756 black.get_features_used(node),
758 f"decorator '{decorator}' follows python<=3.8 syntax"
759 "but is detected as 3.9+"
760 # f"The full node is\n{node!r}"
763 # skip the '# output' comment at the top of the output part
764 for relaxed_test in relaxed.split("##")[1:]:
765 node = black.lib2to3_parse(relaxed_test)
766 decorator = str(node.children[0].children[0]).strip()
768 Feature.RELAXED_DECORATORS,
769 black.get_features_used(node),
771 f"decorator '{decorator}' uses python3.9+ syntax"
772 "but is detected as python<=3.8"
773 # f"The full node is\n{node!r}"
777 def test_get_features_used(self) -> None:
778 node = black.lib2to3_parse("def f(*, arg): ...\n")
779 self.assertEqual(black.get_features_used(node), set())
780 node = black.lib2to3_parse("def f(*, arg,): ...\n")
781 self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
782 node = black.lib2to3_parse("f(*arg,)\n")
784 black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
786 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
787 self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
788 node = black.lib2to3_parse("123_456\n")
789 self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
790 node = black.lib2to3_parse("123456\n")
791 self.assertEqual(black.get_features_used(node), set())
792 source, expected = read_data("function")
793 node = black.lib2to3_parse(source)
794 expected_features = {
795 Feature.TRAILING_COMMA_IN_CALL,
796 Feature.TRAILING_COMMA_IN_DEF,
799 self.assertEqual(black.get_features_used(node), expected_features)
800 node = black.lib2to3_parse(expected)
801 self.assertEqual(black.get_features_used(node), expected_features)
802 source, expected = read_data("expression")
803 node = black.lib2to3_parse(source)
804 self.assertEqual(black.get_features_used(node), set())
805 node = black.lib2to3_parse(expected)
806 self.assertEqual(black.get_features_used(node), set())
807 node = black.lib2to3_parse("lambda a, /, b: ...")
808 self.assertEqual(black.get_features_used(node), {Feature.POS_ONLY_ARGUMENTS})
809 node = black.lib2to3_parse("def fn(a, /, b): ...")
810 self.assertEqual(black.get_features_used(node), {Feature.POS_ONLY_ARGUMENTS})
811 node = black.lib2to3_parse("def fn(): yield a, b")
812 self.assertEqual(black.get_features_used(node), set())
813 node = black.lib2to3_parse("def fn(): return a, b")
814 self.assertEqual(black.get_features_used(node), set())
815 node = black.lib2to3_parse("def fn(): yield *b, c")
816 self.assertEqual(black.get_features_used(node), {Feature.UNPACKING_ON_FLOW})
817 node = black.lib2to3_parse("def fn(): return a, *b, c")
818 self.assertEqual(black.get_features_used(node), {Feature.UNPACKING_ON_FLOW})
819 node = black.lib2to3_parse("x = a, *b, c")
820 self.assertEqual(black.get_features_used(node), set())
821 node = black.lib2to3_parse("x: Any = regular")
822 self.assertEqual(black.get_features_used(node), set())
823 node = black.lib2to3_parse("x: Any = (regular, regular)")
824 self.assertEqual(black.get_features_used(node), set())
825 node = black.lib2to3_parse("x: Any = Complex(Type(1))[something]")
826 self.assertEqual(black.get_features_used(node), set())
827 node = black.lib2to3_parse("x: Tuple[int, ...] = a, b, c")
829 black.get_features_used(node), {Feature.ANN_ASSIGN_EXTENDED_RHS}
832 def test_get_features_used_for_future_flags(self) -> None:
833 for src, features in [
834 ("from __future__ import annotations", {Feature.FUTURE_ANNOTATIONS}),
836 "from __future__ import (other, annotations)",
837 {Feature.FUTURE_ANNOTATIONS},
839 ("a = 1 + 2\nfrom something import annotations", set()),
840 ("from __future__ import x, y", set()),
842 with self.subTest(src=src, features=features):
843 node = black.lib2to3_parse(src)
844 future_imports = black.get_future_imports(node)
846 black.get_features_used(node, future_imports=future_imports),
850 def test_get_future_imports(self) -> None:
851 node = black.lib2to3_parse("\n")
852 self.assertEqual(set(), black.get_future_imports(node))
853 node = black.lib2to3_parse("from __future__ import black\n")
854 self.assertEqual({"black"}, black.get_future_imports(node))
855 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
856 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
857 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
858 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
859 node = black.lib2to3_parse(
860 "from __future__ import multiple\nfrom __future__ import imports\n"
862 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
863 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
864 self.assertEqual({"black"}, black.get_future_imports(node))
865 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
866 self.assertEqual({"black"}, black.get_future_imports(node))
867 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
868 self.assertEqual(set(), black.get_future_imports(node))
869 node = black.lib2to3_parse("from some.module import black\n")
870 self.assertEqual(set(), black.get_future_imports(node))
871 node = black.lib2to3_parse(
872 "from __future__ import unicode_literals as _unicode_literals"
874 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
875 node = black.lib2to3_parse(
876 "from __future__ import unicode_literals as _lol, print"
878 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
880 @pytest.mark.incompatible_with_mypyc
881 def test_debug_visitor(self) -> None:
882 source, _ = read_data("debug_visitor.py")
883 expected, _ = read_data("debug_visitor.out")
887 def out(msg: str, **kwargs: Any) -> None:
888 out_lines.append(msg)
890 def err(msg: str, **kwargs: Any) -> None:
891 err_lines.append(msg)
893 with patch("black.debug.out", out):
894 DebugVisitor.show(source)
895 actual = "\n".join(out_lines) + "\n"
897 if expected != actual:
898 log_name = black.dump_to_file(*out_lines)
902 f"AST print out is different. Actual version dumped to {log_name}",
905 def test_format_file_contents(self) -> None:
908 with self.assertRaises(black.NothingChanged):
909 black.format_file_contents(empty, mode=mode, fast=False)
911 with self.assertRaises(black.NothingChanged):
912 black.format_file_contents(just_nl, mode=mode, fast=False)
913 same = "j = [1, 2, 3]\n"
914 with self.assertRaises(black.NothingChanged):
915 black.format_file_contents(same, mode=mode, fast=False)
916 different = "j = [1,2,3]"
918 actual = black.format_file_contents(different, mode=mode, fast=False)
919 self.assertEqual(expected, actual)
920 invalid = "return if you can"
921 with self.assertRaises(black.InvalidInput) as e:
922 black.format_file_contents(invalid, mode=mode, fast=False)
923 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
925 def test_endmarker(self) -> None:
926 n = black.lib2to3_parse("\n")
927 self.assertEqual(n.type, black.syms.file_input)
928 self.assertEqual(len(n.children), 1)
929 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
931 @pytest.mark.incompatible_with_mypyc
932 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
933 def test_assertFormatEqual(self) -> None:
937 def out(msg: str, **kwargs: Any) -> None:
938 out_lines.append(msg)
940 def err(msg: str, **kwargs: Any) -> None:
941 err_lines.append(msg)
943 with patch("black.output._out", out), patch("black.output._err", err):
944 with self.assertRaises(AssertionError):
945 self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")
947 out_str = "".join(out_lines)
948 self.assertIn("Expected tree:", out_str)
949 self.assertIn("Actual tree:", out_str)
950 self.assertEqual("".join(err_lines), "")
953 @patch("black.ProcessPoolExecutor", MagicMock(side_effect=OSError))
954 def test_works_in_mono_process_only_environment(self) -> None:
955 with cache_dir() as workspace:
957 (workspace / "one.py").resolve(),
958 (workspace / "two.py").resolve(),
960 f.write_text('print("hello")\n')
961 self.invokeBlack([str(workspace)])
964 def test_check_diff_use_together(self) -> None:
966 # Files which will be reformatted.
967 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
968 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
969 # Files which will not be reformatted.
970 src2 = (THIS_DIR / "data" / "composition.py").resolve()
971 self.invokeBlack([str(src2), "--diff", "--check"])
972 # Multi file command.
973 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
975 def test_no_files(self) -> None:
977 # Without an argument, black exits with error code 0.
980 def test_broken_symlink(self) -> None:
981 with cache_dir() as workspace:
982 symlink = workspace / "broken_link.py"
984 symlink.symlink_to("nonexistent.py")
985 except (OSError, NotImplementedError) as e:
986 self.skipTest(f"Can't create symlinks: {e}")
987 self.invokeBlack([str(workspace.resolve())])
989 def test_single_file_force_pyi(self) -> None:
990 pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
991 contents, expected = read_data("force_pyi")
992 with cache_dir() as workspace:
993 path = (workspace / "file.py").resolve()
994 with open(path, "w") as fh:
996 self.invokeBlack([str(path), "--pyi"])
997 with open(path, "r") as fh:
999 # verify cache with --pyi is separate
1000 pyi_cache = black.read_cache(pyi_mode)
1001 self.assertIn(str(path), pyi_cache)
1002 normal_cache = black.read_cache(DEFAULT_MODE)
1003 self.assertNotIn(str(path), normal_cache)
1004 self.assertFormatEqual(expected, actual)
1005 black.assert_equivalent(contents, actual)
1006 black.assert_stable(contents, actual, pyi_mode)
1009 def test_multi_file_force_pyi(self) -> None:
1010 reg_mode = DEFAULT_MODE
1011 pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
1012 contents, expected = read_data("force_pyi")
1013 with cache_dir() as workspace:
1015 (workspace / "file1.py").resolve(),
1016 (workspace / "file2.py").resolve(),
1019 with open(path, "w") as fh:
1021 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1023 with open(path, "r") as fh:
1025 self.assertEqual(actual, expected)
1026 # verify cache with --pyi is separate
1027 pyi_cache = black.read_cache(pyi_mode)
1028 normal_cache = black.read_cache(reg_mode)
1030 self.assertIn(str(path), pyi_cache)
1031 self.assertNotIn(str(path), normal_cache)
1033 def test_pipe_force_pyi(self) -> None:
1034 source, expected = read_data("force_pyi")
1035 result = CliRunner().invoke(
1036 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1038 self.assertEqual(result.exit_code, 0)
1039 actual = result.output
1040 self.assertFormatEqual(actual, expected)
1042 def test_single_file_force_py36(self) -> None:
1043 reg_mode = DEFAULT_MODE
1044 py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
1045 source, expected = read_data("force_py36")
1046 with cache_dir() as workspace:
1047 path = (workspace / "file.py").resolve()
1048 with open(path, "w") as fh:
1050 self.invokeBlack([str(path), *PY36_ARGS])
1051 with open(path, "r") as fh:
1053 # verify cache with --target-version is separate
1054 py36_cache = black.read_cache(py36_mode)
1055 self.assertIn(str(path), py36_cache)
1056 normal_cache = black.read_cache(reg_mode)
1057 self.assertNotIn(str(path), normal_cache)
1058 self.assertEqual(actual, expected)
1061 def test_multi_file_force_py36(self) -> None:
1062 reg_mode = DEFAULT_MODE
1063 py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
1064 source, expected = read_data("force_py36")
1065 with cache_dir() as workspace:
1067 (workspace / "file1.py").resolve(),
1068 (workspace / "file2.py").resolve(),
1071 with open(path, "w") as fh:
1073 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1075 with open(path, "r") as fh:
1077 self.assertEqual(actual, expected)
1078 # verify cache with --target-version is separate
1079 pyi_cache = black.read_cache(py36_mode)
1080 normal_cache = black.read_cache(reg_mode)
1082 self.assertIn(str(path), pyi_cache)
1083 self.assertNotIn(str(path), normal_cache)
1085 def test_pipe_force_py36(self) -> None:
1086 source, expected = read_data("force_py36")
1087 result = CliRunner().invoke(
1089 ["-", "-q", "--target-version=py36"],
1090 input=BytesIO(source.encode("utf8")),
1092 self.assertEqual(result.exit_code, 0)
1093 actual = result.output
1094 self.assertFormatEqual(actual, expected)
1096 @pytest.mark.incompatible_with_mypyc
1097 def test_reformat_one_with_stdin(self) -> None:
1099 "black.format_stdin_to_stdout",
1100 return_value=lambda *args, **kwargs: black.Changed.YES,
1102 report = MagicMock()
1107 write_back=black.WriteBack.YES,
1111 fsts.assert_called_once()
1112 report.done.assert_called_with(path, black.Changed.YES)
1114 @pytest.mark.incompatible_with_mypyc
1115 def test_reformat_one_with_stdin_filename(self) -> None:
1117 "black.format_stdin_to_stdout",
1118 return_value=lambda *args, **kwargs: black.Changed.YES,
1120 report = MagicMock()
1122 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1127 write_back=black.WriteBack.YES,
1131 fsts.assert_called_once_with(
1132 fast=True, write_back=black.WriteBack.YES, mode=DEFAULT_MODE
1134 # __BLACK_STDIN_FILENAME__ should have been stripped
1135 report.done.assert_called_with(expected, black.Changed.YES)
1137 @pytest.mark.incompatible_with_mypyc
1138 def test_reformat_one_with_stdin_filename_pyi(self) -> None:
1140 "black.format_stdin_to_stdout",
1141 return_value=lambda *args, **kwargs: black.Changed.YES,
1143 report = MagicMock()
1145 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1150 write_back=black.WriteBack.YES,
1154 fsts.assert_called_once_with(
1156 write_back=black.WriteBack.YES,
1157 mode=replace(DEFAULT_MODE, is_pyi=True),
1159 # __BLACK_STDIN_FILENAME__ should have been stripped
1160 report.done.assert_called_with(expected, black.Changed.YES)
1162 @pytest.mark.incompatible_with_mypyc
1163 def test_reformat_one_with_stdin_filename_ipynb(self) -> None:
1165 "black.format_stdin_to_stdout",
1166 return_value=lambda *args, **kwargs: black.Changed.YES,
1168 report = MagicMock()
1170 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1175 write_back=black.WriteBack.YES,
1179 fsts.assert_called_once_with(
1181 write_back=black.WriteBack.YES,
1182 mode=replace(DEFAULT_MODE, is_ipynb=True),
1184 # __BLACK_STDIN_FILENAME__ should have been stripped
1185 report.done.assert_called_with(expected, black.Changed.YES)
1187 @pytest.mark.incompatible_with_mypyc
1188 def test_reformat_one_with_stdin_and_existing_path(self) -> None:
1190 "black.format_stdin_to_stdout",
1191 return_value=lambda *args, **kwargs: black.Changed.YES,
1193 report = MagicMock()
1194 # Even with an existing file, since we are forcing stdin, black
1195 # should output to stdout and not modify the file inplace
1196 p = Path(str(THIS_DIR / "data/collections.py"))
1197 # Make sure is_file actually returns True
1198 self.assertTrue(p.is_file())
1199 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1204 write_back=black.WriteBack.YES,
1208 fsts.assert_called_once()
1209 # __BLACK_STDIN_FILENAME__ should have been stripped
1210 report.done.assert_called_with(expected, black.Changed.YES)
1212 def test_reformat_one_with_stdin_empty(self) -> None:
1213 output = io.StringIO()
1214 with patch("io.TextIOWrapper", lambda *args, **kwargs: output):
1216 black.format_stdin_to_stdout(
1219 write_back=black.WriteBack.YES,
1222 except io.UnsupportedOperation:
1223 pass # StringIO does not support detach
1224 assert output.getvalue() == ""
1226 def test_invalid_cli_regex(self) -> None:
1227 for option in ["--include", "--exclude", "--extend-exclude", "--force-exclude"]:
1228 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1230 def test_required_version_matches_version(self) -> None:
1232 ["--required-version", black.__version__], exit_code=0, ignore_config=True
1235 def test_required_version_does_not_match_version(self) -> None:
1237 ["--required-version", "20.99b"], exit_code=1, ignore_config=True
1240 def test_preserves_line_endings(self) -> None:
1241 with TemporaryDirectory() as workspace:
1242 test_file = Path(workspace) / "test.py"
1243 for nl in ["\n", "\r\n"]:
1244 contents = nl.join(["def f( ):", " pass"])
1245 test_file.write_bytes(contents.encode())
1246 ff(test_file, write_back=black.WriteBack.YES)
1247 updated_contents: bytes = test_file.read_bytes()
1248 self.assertIn(nl.encode(), updated_contents)
1250 self.assertNotIn(b"\r\n", updated_contents)
1252 def test_preserves_line_endings_via_stdin(self) -> None:
1253 for nl in ["\n", "\r\n"]:
1254 contents = nl.join(["def f( ):", " pass"])
1255 runner = BlackRunner()
1256 result = runner.invoke(
1257 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1259 self.assertEqual(result.exit_code, 0)
1260 output = result.stdout_bytes
1261 self.assertIn(nl.encode("utf8"), output)
1263 self.assertNotIn(b"\r\n", output)
1265 def test_assert_equivalent_different_asts(self) -> None:
1266 with self.assertRaises(AssertionError):
1267 black.assert_equivalent("{}", "None")
1269 def test_shhh_click(self) -> None:
1271 from click import _unicodefun
1272 except ModuleNotFoundError:
1273 self.skipTest("Incompatible Click version")
1274 if not hasattr(_unicodefun, "_verify_python3_env"):
1275 self.skipTest("Incompatible Click version")
1276 # First, let's see if Click is crashing with a preferred ASCII charset.
1277 with patch("locale.getpreferredencoding") as gpe:
1278 gpe.return_value = "ASCII"
1279 with self.assertRaises(RuntimeError):
1280 _unicodefun._verify_python3_env() # type: ignore
1281 # Now, let's silence Click...
1283 # ...and confirm it's silent.
1284 with patch("locale.getpreferredencoding") as gpe:
1285 gpe.return_value = "ASCII"
1287 _unicodefun._verify_python3_env() # type: ignore
1288 except RuntimeError as re:
1289 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1291 def test_root_logger_not_used_directly(self) -> None:
1292 def fail(*args: Any, **kwargs: Any) -> None:
1293 self.fail("Record created with root logger")
1295 with patch.multiple(
1304 ff(THIS_DIR / "util.py")
1306 def test_invalid_config_return_code(self) -> None:
1307 tmp_file = Path(black.dump_to_file())
1309 tmp_config = Path(black.dump_to_file())
1311 args = ["--config", str(tmp_config), str(tmp_file)]
1312 self.invokeBlack(args, exit_code=2, ignore_config=False)
1316 def test_parse_pyproject_toml(self) -> None:
1317 test_toml_file = THIS_DIR / "test.toml"
1318 config = black.parse_pyproject_toml(str(test_toml_file))
1319 self.assertEqual(config["verbose"], 1)
1320 self.assertEqual(config["check"], "no")
1321 self.assertEqual(config["diff"], "y")
1322 self.assertEqual(config["color"], True)
1323 self.assertEqual(config["line_length"], 79)
1324 self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
1325 self.assertEqual(config["python_cell_magics"], ["custom1", "custom2"])
1326 self.assertEqual(config["exclude"], r"\.pyi?$")
1327 self.assertEqual(config["include"], r"\.py?$")
1329 def test_read_pyproject_toml(self) -> None:
1330 test_toml_file = THIS_DIR / "test.toml"
1331 fake_ctx = FakeContext()
1332 black.read_pyproject_toml(fake_ctx, FakeParameter(), str(test_toml_file))
1333 config = fake_ctx.default_map
1334 self.assertEqual(config["verbose"], "1")
1335 self.assertEqual(config["check"], "no")
1336 self.assertEqual(config["diff"], "y")
1337 self.assertEqual(config["color"], "True")
1338 self.assertEqual(config["line_length"], "79")
1339 self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
1340 self.assertEqual(config["exclude"], r"\.pyi?$")
1341 self.assertEqual(config["include"], r"\.py?$")
1343 @pytest.mark.incompatible_with_mypyc
1344 def test_find_project_root(self) -> None:
1345 with TemporaryDirectory() as workspace:
1346 root = Path(workspace)
1347 test_dir = root / "test"
1350 src_dir = root / "src"
1353 root_pyproject = root / "pyproject.toml"
1354 root_pyproject.touch()
1355 src_pyproject = src_dir / "pyproject.toml"
1356 src_pyproject.touch()
1357 src_python = src_dir / "foo.py"
1361 black.find_project_root((src_dir, test_dir)),
1362 (root.resolve(), "pyproject.toml"),
1365 black.find_project_root((src_dir,)),
1366 (src_dir.resolve(), "pyproject.toml"),
1369 black.find_project_root((src_python,)),
1370 (src_dir.resolve(), "pyproject.toml"),
1374 "black.files.find_user_pyproject_toml",
1375 black.files.find_user_pyproject_toml.__wrapped__,
1377 def test_find_user_pyproject_toml_linux(self) -> None:
1378 if system() == "Windows":
1381 # Test if XDG_CONFIG_HOME is checked
1382 with TemporaryDirectory() as workspace:
1383 tmp_user_config = Path(workspace) / "black"
1384 with patch.dict("os.environ", {"XDG_CONFIG_HOME": workspace}):
1386 black.files.find_user_pyproject_toml(), tmp_user_config.resolve()
1389 # Test fallback for XDG_CONFIG_HOME
1390 with patch.dict("os.environ"):
1391 os.environ.pop("XDG_CONFIG_HOME", None)
1392 fallback_user_config = Path("~/.config").expanduser() / "black"
1394 black.files.find_user_pyproject_toml(), fallback_user_config.resolve()
1397 def test_find_user_pyproject_toml_windows(self) -> None:
1398 if system() != "Windows":
1401 user_config_path = Path.home() / ".black"
1403 black.files.find_user_pyproject_toml(), user_config_path.resolve()
1406 def test_bpo_33660_workaround(self) -> None:
1407 if system() == "Windows":
1410 # https://bugs.python.org/issue33660
1412 with change_directory(root):
1413 path = Path("workspace") / "project"
1414 report = black.Report(verbose=True)
1415 normalized_path = black.normalize_path_maybe_ignore(path, root, report)
1416 self.assertEqual(normalized_path, "workspace/project")
1418 def test_newline_comment_interaction(self) -> None:
1419 source = "class A:\\\r\n# type: ignore\n pass\n"
1420 output = black.format_str(source, mode=DEFAULT_MODE)
1421 black.assert_stable(source, output, mode=DEFAULT_MODE)
1423 def test_bpo_2142_workaround(self) -> None:
1425 # https://bugs.python.org/issue2142
1427 source, _ = read_data("missing_final_newline.py")
1428 # read_data adds a trailing newline
1429 source = source.rstrip()
1430 expected, _ = read_data("missing_final_newline.diff")
1431 tmp_file = Path(black.dump_to_file(source, ensure_final_newline=False))
1432 diff_header = re.compile(
1433 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
1434 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
1437 result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
1438 self.assertEqual(result.exit_code, 0)
1441 actual = result.output
1442 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
1443 self.assertEqual(actual, expected)
1446 def compare_results(
1447 result: click.testing.Result, expected_value: str, expected_exit_code: int
1449 """Helper method to test the value and exit code of a click Result."""
1451 result.output == expected_value
1452 ), "The output did not match the expected value."
1453 assert result.exit_code == expected_exit_code, "The exit code is incorrect."
1455 def test_code_option(self) -> None:
1456 """Test the code option with no changes."""
1457 code = 'print("Hello world")\n'
1458 args = ["--code", code]
1459 result = CliRunner().invoke(black.main, args)
1461 self.compare_results(result, code, 0)
1463 def test_code_option_changed(self) -> None:
1464 """Test the code option when changes are required."""
1465 code = "print('hello world')"
1466 formatted = black.format_str(code, mode=DEFAULT_MODE)
1468 args = ["--code", code]
1469 result = CliRunner().invoke(black.main, args)
1471 self.compare_results(result, formatted, 0)
1473 def test_code_option_check(self) -> None:
1474 """Test the code option when check is passed."""
1475 args = ["--check", "--code", 'print("Hello world")\n']
1476 result = CliRunner().invoke(black.main, args)
1477 self.compare_results(result, "", 0)
1479 def test_code_option_check_changed(self) -> None:
1480 """Test the code option when changes are required, and check is passed."""
1481 args = ["--check", "--code", "print('hello world')"]
1482 result = CliRunner().invoke(black.main, args)
1483 self.compare_results(result, "", 1)
1485 def test_code_option_diff(self) -> None:
1486 """Test the code option when diff is passed."""
1487 code = "print('hello world')"
1488 formatted = black.format_str(code, mode=DEFAULT_MODE)
1489 result_diff = diff(code, formatted, "STDIN", "STDOUT")
1491 args = ["--diff", "--code", code]
1492 result = CliRunner().invoke(black.main, args)
1494 # Remove time from diff
1495 output = DIFF_TIME.sub("", result.output)
1497 assert output == result_diff, "The output did not match the expected value."
1498 assert result.exit_code == 0, "The exit code is incorrect."
1500 def test_code_option_color_diff(self) -> None:
1501 """Test the code option when color and diff are passed."""
1502 code = "print('hello world')"
1503 formatted = black.format_str(code, mode=DEFAULT_MODE)
1505 result_diff = diff(code, formatted, "STDIN", "STDOUT")
1506 result_diff = color_diff(result_diff)
1508 args = ["--diff", "--color", "--code", code]
1509 result = CliRunner().invoke(black.main, args)
1511 # Remove time from diff
1512 output = DIFF_TIME.sub("", result.output)
1514 assert output == result_diff, "The output did not match the expected value."
1515 assert result.exit_code == 0, "The exit code is incorrect."
1517 @pytest.mark.incompatible_with_mypyc
1518 def test_code_option_safe(self) -> None:
1519 """Test that the code option throws an error when the sanity checks fail."""
1520 # Patch black.assert_equivalent to ensure the sanity checks fail
1521 with patch.object(black, "assert_equivalent", side_effect=AssertionError):
1522 code = 'print("Hello world")'
1523 error_msg = f"{code}\nerror: cannot format <string>: \n"
1525 args = ["--safe", "--code", code]
1526 result = CliRunner().invoke(black.main, args)
1528 self.compare_results(result, error_msg, 123)
1530 def test_code_option_fast(self) -> None:
1531 """Test that the code option ignores errors when the sanity checks fail."""
1532 # Patch black.assert_equivalent to ensure the sanity checks fail
1533 with patch.object(black, "assert_equivalent", side_effect=AssertionError):
1534 code = 'print("Hello world")'
1535 formatted = black.format_str(code, mode=DEFAULT_MODE)
1537 args = ["--fast", "--code", code]
1538 result = CliRunner().invoke(black.main, args)
1540 self.compare_results(result, formatted, 0)
1542 @pytest.mark.incompatible_with_mypyc
1543 def test_code_option_config(self) -> None:
1545 Test that the code option finds the pyproject.toml in the current directory.
1547 with patch.object(black, "parse_pyproject_toml", return_value={}) as parse:
1548 args = ["--code", "print"]
1549 # This is the only directory known to contain a pyproject.toml
1550 with change_directory(PROJECT_ROOT):
1551 CliRunner().invoke(black.main, args)
1552 pyproject_path = Path(Path.cwd(), "pyproject.toml").resolve()
1555 len(parse.mock_calls) >= 1
1556 ), "Expected config parse to be called with the current directory."
1558 _, call_args, _ = parse.mock_calls[0]
1560 call_args[0].lower() == str(pyproject_path).lower()
1561 ), "Incorrect config loaded."
1563 @pytest.mark.incompatible_with_mypyc
1564 def test_code_option_parent_config(self) -> None:
1566 Test that the code option finds the pyproject.toml in the parent directory.
1568 with patch.object(black, "parse_pyproject_toml", return_value={}) as parse:
1569 with change_directory(THIS_DIR):
1570 args = ["--code", "print"]
1571 CliRunner().invoke(black.main, args)
1573 pyproject_path = Path(Path().cwd().parent, "pyproject.toml").resolve()
1575 len(parse.mock_calls) >= 1
1576 ), "Expected config parse to be called with the current directory."
1578 _, call_args, _ = parse.mock_calls[0]
1580 call_args[0].lower() == str(pyproject_path).lower()
1581 ), "Incorrect config loaded."
1583 def test_for_handled_unexpected_eof_error(self) -> None:
1585 Test that an unexpected EOF SyntaxError is nicely presented.
1587 with pytest.raises(black.parsing.InvalidInput) as exc_info:
1588 black.lib2to3_parse("print(", {})
1590 exc_info.match("Cannot parse: 2:0: EOF in multi-line statement")
1592 def test_equivalency_ast_parse_failure_includes_error(self) -> None:
1593 with pytest.raises(AssertionError) as err:
1594 black.assert_equivalent("a«»a = 1", "a«»a = 1")
1597 # Unfortunately the SyntaxError message has changed in newer versions so we
1598 # can't match it directly.
1599 err.match("invalid character")
1600 err.match(r"\(<unknown>, line 1\)")
1604 def test_get_cache_dir(
1607 monkeypatch: pytest.MonkeyPatch,
1609 # Create multiple cache directories
1610 workspace1 = tmp_path / "ws1"
1612 workspace2 = tmp_path / "ws2"
1615 # Force user_cache_dir to use the temporary directory for easier assertions
1616 patch_user_cache_dir = patch(
1617 target="black.cache.user_cache_dir",
1619 return_value=str(workspace1),
1622 # If BLACK_CACHE_DIR is not set, use user_cache_dir
1623 monkeypatch.delenv("BLACK_CACHE_DIR", raising=False)
1624 with patch_user_cache_dir:
1625 assert get_cache_dir() == workspace1
1627 # If it is set, use the path provided in the env var.
1628 monkeypatch.setenv("BLACK_CACHE_DIR", str(workspace2))
1629 assert get_cache_dir() == workspace2
1631 def test_cache_broken_file(self) -> None:
1633 with cache_dir() as workspace:
1634 cache_file = get_cache_file(mode)
1635 cache_file.write_text("this is not a pickle")
1636 assert black.read_cache(mode) == {}
1637 src = (workspace / "test.py").resolve()
1638 src.write_text("print('hello')")
1639 invokeBlack([str(src)])
1640 cache = black.read_cache(mode)
1641 assert str(src) in cache
1643 def test_cache_single_file_already_cached(self) -> None:
1645 with cache_dir() as workspace:
1646 src = (workspace / "test.py").resolve()
1647 src.write_text("print('hello')")
1648 black.write_cache({}, [src], mode)
1649 invokeBlack([str(src)])
1650 assert src.read_text() == "print('hello')"
1653 def test_cache_multiple_files(self) -> None:
1655 with cache_dir() as workspace, patch(
1656 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1658 one = (workspace / "one.py").resolve()
1659 with one.open("w") as fobj:
1660 fobj.write("print('hello')")
1661 two = (workspace / "two.py").resolve()
1662 with two.open("w") as fobj:
1663 fobj.write("print('hello')")
1664 black.write_cache({}, [one], mode)
1665 invokeBlack([str(workspace)])
1666 with one.open("r") as fobj:
1667 assert fobj.read() == "print('hello')"
1668 with two.open("r") as fobj:
1669 assert fobj.read() == 'print("hello")\n'
1670 cache = black.read_cache(mode)
1671 assert str(one) in cache
1672 assert str(two) in cache
1674 @pytest.mark.parametrize("color", [False, True], ids=["no-color", "with-color"])
1675 def test_no_cache_when_writeback_diff(self, color: bool) -> None:
1677 with cache_dir() as workspace:
1678 src = (workspace / "test.py").resolve()
1679 with src.open("w") as fobj:
1680 fobj.write("print('hello')")
1681 with patch("black.read_cache") as read_cache, patch(
1684 cmd = [str(src), "--diff"]
1686 cmd.append("--color")
1688 cache_file = get_cache_file(mode)
1689 assert cache_file.exists() is False
1690 write_cache.assert_not_called()
1691 read_cache.assert_not_called()
1693 @pytest.mark.parametrize("color", [False, True], ids=["no-color", "with-color"])
1695 def test_output_locking_when_writeback_diff(self, color: bool) -> None:
1696 with cache_dir() as workspace:
1697 for tag in range(0, 4):
1698 src = (workspace / f"test{tag}.py").resolve()
1699 with src.open("w") as fobj:
1700 fobj.write("print('hello')")
1701 with patch("black.Manager", wraps=multiprocessing.Manager) as mgr:
1702 cmd = ["--diff", str(workspace)]
1704 cmd.append("--color")
1705 invokeBlack(cmd, exit_code=0)
1706 # this isn't quite doing what we want, but if it _isn't_
1707 # called then we cannot be using the lock it provides
1710 def test_no_cache_when_stdin(self) -> None:
1713 result = CliRunner().invoke(
1714 black.main, ["-"], input=BytesIO(b"print('hello')")
1716 assert not result.exit_code
1717 cache_file = get_cache_file(mode)
1718 assert not cache_file.exists()
1720 def test_read_cache_no_cachefile(self) -> None:
1723 assert black.read_cache(mode) == {}
1725 def test_write_cache_read_cache(self) -> None:
1727 with cache_dir() as workspace:
1728 src = (workspace / "test.py").resolve()
1730 black.write_cache({}, [src], mode)
1731 cache = black.read_cache(mode)
1732 assert str(src) in cache
1733 assert cache[str(src)] == black.get_cache_info(src)
1735 def test_filter_cached(self) -> None:
1736 with TemporaryDirectory() as workspace:
1737 path = Path(workspace)
1738 uncached = (path / "uncached").resolve()
1739 cached = (path / "cached").resolve()
1740 cached_but_changed = (path / "changed").resolve()
1743 cached_but_changed.touch()
1745 str(cached): black.get_cache_info(cached),
1746 str(cached_but_changed): (0.0, 0),
1748 todo, done = black.filter_cached(
1749 cache, {uncached, cached, cached_but_changed}
1751 assert todo == {uncached, cached_but_changed}
1752 assert done == {cached}
1754 def test_write_cache_creates_directory_if_needed(self) -> None:
1756 with cache_dir(exists=False) as workspace:
1757 assert not workspace.exists()
1758 black.write_cache({}, [], mode)
1759 assert workspace.exists()
1762 def test_failed_formatting_does_not_get_cached(self) -> None:
1764 with cache_dir() as workspace, patch(
1765 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1767 failing = (workspace / "failing.py").resolve()
1768 with failing.open("w") as fobj:
1769 fobj.write("not actually python")
1770 clean = (workspace / "clean.py").resolve()
1771 with clean.open("w") as fobj:
1772 fobj.write('print("hello")\n')
1773 invokeBlack([str(workspace)], exit_code=123)
1774 cache = black.read_cache(mode)
1775 assert str(failing) not in cache
1776 assert str(clean) in cache
1778 def test_write_cache_write_fail(self) -> None:
1780 with cache_dir(), patch.object(Path, "open") as mock:
1781 mock.side_effect = OSError
1782 black.write_cache({}, [], mode)
1784 def test_read_cache_line_lengths(self) -> None:
1786 short_mode = replace(DEFAULT_MODE, line_length=1)
1787 with cache_dir() as workspace:
1788 path = (workspace / "file.py").resolve()
1790 black.write_cache({}, [path], mode)
1791 one = black.read_cache(mode)
1792 assert str(path) in one
1793 two = black.read_cache(short_mode)
1794 assert str(path) not in two
1797 def assert_collected_sources(
1798 src: Sequence[Union[str, Path]],
1799 expected: Sequence[Union[str, Path]],
1801 ctx: Optional[FakeContext] = None,
1802 exclude: Optional[str] = None,
1803 include: Optional[str] = None,
1804 extend_exclude: Optional[str] = None,
1805 force_exclude: Optional[str] = None,
1806 stdin_filename: Optional[str] = None,
1808 gs_src = tuple(str(Path(s)) for s in src)
1809 gs_expected = [Path(s) for s in expected]
1810 gs_exclude = None if exclude is None else compile_pattern(exclude)
1811 gs_include = DEFAULT_INCLUDE if include is None else compile_pattern(include)
1812 gs_extend_exclude = (
1813 None if extend_exclude is None else compile_pattern(extend_exclude)
1815 gs_force_exclude = None if force_exclude is None else compile_pattern(force_exclude)
1816 collected = black.get_sources(
1817 ctx=ctx or FakeContext(),
1823 extend_exclude=gs_extend_exclude,
1824 force_exclude=gs_force_exclude,
1825 report=black.Report(),
1826 stdin_filename=stdin_filename,
1828 assert sorted(collected) == sorted(gs_expected)
1831 class TestFileCollection:
1832 def test_include_exclude(self) -> None:
1833 path = THIS_DIR / "data" / "include_exclude_tests"
1836 Path(path / "b/dont_exclude/a.py"),
1837 Path(path / "b/dont_exclude/a.pyi"),
1839 assert_collected_sources(
1843 exclude=r"/exclude/|/\.definitely_exclude/",
1846 def test_gitignore_used_as_default(self) -> None:
1847 base = Path(DATA_DIR / "include_exclude_tests")
1849 base / "b/.definitely_exclude/a.py",
1850 base / "b/.definitely_exclude/a.pyi",
1854 ctx.obj["root"] = base
1855 assert_collected_sources(src, expected, ctx=ctx, extend_exclude=r"/exclude/")
1857 @patch("black.find_project_root", lambda *args: (THIS_DIR.resolve(), None))
1858 def test_exclude_for_issue_1572(self) -> None:
1859 # Exclude shouldn't touch files that were explicitly given to Black through the
1860 # CLI. Exclude is supposed to only apply to the recursive discovery of files.
1861 # https://github.com/psf/black/issues/1572
1862 path = DATA_DIR / "include_exclude_tests"
1863 src = [path / "b/exclude/a.py"]
1864 expected = [path / "b/exclude/a.py"]
1865 assert_collected_sources(src, expected, include="", exclude=r"/exclude/|a\.py")
1867 def test_gitignore_exclude(self) -> None:
1868 path = THIS_DIR / "data" / "include_exclude_tests"
1869 include = re.compile(r"\.pyi?$")
1870 exclude = re.compile(r"")
1871 report = black.Report()
1872 gitignore = PathSpec.from_lines(
1873 "gitwildmatch", ["exclude/", ".definitely_exclude"]
1875 sources: List[Path] = []
1877 Path(path / "b/dont_exclude/a.py"),
1878 Path(path / "b/dont_exclude/a.pyi"),
1880 this_abs = THIS_DIR.resolve()
1882 black.gen_python_files(
1895 assert sorted(expected) == sorted(sources)
1897 def test_nested_gitignore(self) -> None:
1898 path = Path(THIS_DIR / "data" / "nested_gitignore_tests")
1899 include = re.compile(r"\.pyi?$")
1900 exclude = re.compile(r"")
1901 root_gitignore = black.files.get_gitignore(path)
1902 report = black.Report()
1903 expected: List[Path] = [
1904 Path(path / "x.py"),
1905 Path(path / "root/b.py"),
1906 Path(path / "root/c.py"),
1907 Path(path / "root/child/c.py"),
1909 this_abs = THIS_DIR.resolve()
1911 black.gen_python_files(
1924 assert sorted(expected) == sorted(sources)
1926 def test_invalid_gitignore(self) -> None:
1927 path = THIS_DIR / "data" / "invalid_gitignore_tests"
1928 empty_config = path / "pyproject.toml"
1929 result = BlackRunner().invoke(
1930 black.main, ["--verbose", "--config", str(empty_config), str(path)]
1932 assert result.exit_code == 1
1933 assert result.stderr_bytes is not None
1935 gitignore = path / ".gitignore"
1936 assert f"Could not parse {gitignore}" in result.stderr_bytes.decode()
1938 def test_invalid_nested_gitignore(self) -> None:
1939 path = THIS_DIR / "data" / "invalid_nested_gitignore_tests"
1940 empty_config = path / "pyproject.toml"
1941 result = BlackRunner().invoke(
1942 black.main, ["--verbose", "--config", str(empty_config), str(path)]
1944 assert result.exit_code == 1
1945 assert result.stderr_bytes is not None
1947 gitignore = path / "a" / ".gitignore"
1948 assert f"Could not parse {gitignore}" in result.stderr_bytes.decode()
1950 def test_empty_include(self) -> None:
1951 path = DATA_DIR / "include_exclude_tests"
1954 Path(path / "b/exclude/a.pie"),
1955 Path(path / "b/exclude/a.py"),
1956 Path(path / "b/exclude/a.pyi"),
1957 Path(path / "b/dont_exclude/a.pie"),
1958 Path(path / "b/dont_exclude/a.py"),
1959 Path(path / "b/dont_exclude/a.pyi"),
1960 Path(path / "b/.definitely_exclude/a.pie"),
1961 Path(path / "b/.definitely_exclude/a.py"),
1962 Path(path / "b/.definitely_exclude/a.pyi"),
1963 Path(path / ".gitignore"),
1964 Path(path / "pyproject.toml"),
1966 # Setting exclude explicitly to an empty string to block .gitignore usage.
1967 assert_collected_sources(src, expected, include="", exclude="")
1969 def test_extend_exclude(self) -> None:
1970 path = DATA_DIR / "include_exclude_tests"
1973 Path(path / "b/exclude/a.py"),
1974 Path(path / "b/dont_exclude/a.py"),
1976 assert_collected_sources(
1977 src, expected, exclude=r"\.pyi$", extend_exclude=r"\.definitely_exclude"
1980 @pytest.mark.incompatible_with_mypyc
1981 def test_symlink_out_of_root_directory(self) -> None:
1983 root = THIS_DIR.resolve()
1985 include = re.compile(black.DEFAULT_INCLUDES)
1986 exclude = re.compile(black.DEFAULT_EXCLUDES)
1987 report = black.Report()
1988 gitignore = PathSpec.from_lines("gitwildmatch", [])
1989 # `child` should behave like a symlink which resolved path is clearly
1990 # outside of the `root` directory.
1991 path.iterdir.return_value = [child]
1992 child.resolve.return_value = Path("/a/b/c")
1993 child.as_posix.return_value = "/a/b/c"
1994 child.is_symlink.return_value = True
1997 black.gen_python_files(
2010 except ValueError as ve:
2011 pytest.fail(f"`get_python_files_in_dir()` failed: {ve}")
2012 path.iterdir.assert_called_once()
2013 child.resolve.assert_called_once()
2014 child.is_symlink.assert_called_once()
2015 # `child` should behave like a strange file which resolved path is clearly
2016 # outside of the `root` directory.
2017 child.is_symlink.return_value = False
2018 with pytest.raises(ValueError):
2020 black.gen_python_files(
2033 path.iterdir.assert_called()
2034 assert path.iterdir.call_count == 2
2035 child.resolve.assert_called()
2036 assert child.resolve.call_count == 2
2037 child.is_symlink.assert_called()
2038 assert child.is_symlink.call_count == 2
2040 @patch("black.find_project_root", lambda *args: (THIS_DIR.resolve(), None))
2041 def test_get_sources_with_stdin(self) -> None:
2044 assert_collected_sources(src, expected, include="", exclude=r"/exclude/|a\.py")
2046 @patch("black.find_project_root", lambda *args: (THIS_DIR.resolve(), None))
2047 def test_get_sources_with_stdin_filename(self) -> None:
2049 stdin_filename = str(THIS_DIR / "data/collections.py")
2050 expected = [f"__BLACK_STDIN_FILENAME__{stdin_filename}"]
2051 assert_collected_sources(
2054 exclude=r"/exclude/a\.py",
2055 stdin_filename=stdin_filename,
2058 @patch("black.find_project_root", lambda *args: (THIS_DIR.resolve(), None))
2059 def test_get_sources_with_stdin_filename_and_exclude(self) -> None:
2060 # Exclude shouldn't exclude stdin_filename since it is mimicking the
2061 # file being passed directly. This is the same as
2062 # test_exclude_for_issue_1572
2063 path = DATA_DIR / "include_exclude_tests"
2065 stdin_filename = str(path / "b/exclude/a.py")
2066 expected = [f"__BLACK_STDIN_FILENAME__{stdin_filename}"]
2067 assert_collected_sources(
2070 exclude=r"/exclude/|a\.py",
2071 stdin_filename=stdin_filename,
2074 @patch("black.find_project_root", lambda *args: (THIS_DIR.resolve(), None))
2075 def test_get_sources_with_stdin_filename_and_extend_exclude(self) -> None:
2076 # Extend exclude shouldn't exclude stdin_filename since it is mimicking the
2077 # file being passed directly. This is the same as
2078 # test_exclude_for_issue_1572
2080 path = THIS_DIR / "data" / "include_exclude_tests"
2081 stdin_filename = str(path / "b/exclude/a.py")
2082 expected = [f"__BLACK_STDIN_FILENAME__{stdin_filename}"]
2083 assert_collected_sources(
2086 extend_exclude=r"/exclude/|a\.py",
2087 stdin_filename=stdin_filename,
2090 @patch("black.find_project_root", lambda *args: (THIS_DIR.resolve(), None))
2091 def test_get_sources_with_stdin_filename_and_force_exclude(self) -> None:
2092 # Force exclude should exclude the file when passing it through
2094 path = THIS_DIR / "data" / "include_exclude_tests"
2095 stdin_filename = str(path / "b/exclude/a.py")
2096 assert_collected_sources(
2099 force_exclude=r"/exclude/|a\.py",
2100 stdin_filename=stdin_filename,
2105 with open(black.__file__, "r", encoding="utf-8") as _bf:
2106 black_source_lines = _bf.readlines()
2107 except UnicodeDecodeError:
2108 if not black.COMPILED:
2113 frame: types.FrameType, event: str, arg: Any
2114 ) -> Callable[[types.FrameType, str, Any], Any]:
2115 """Show function calls `from black/__init__.py` as they happen.
2117 Register this with `sys.settrace()` in a test you're debugging.
2122 stack = len(inspect.stack()) - 19
2124 filename = frame.f_code.co_filename
2125 lineno = frame.f_lineno
2126 func_sig_lineno = lineno - 1
2127 funcname = black_source_lines[func_sig_lineno].strip()
2128 while funcname.startswith("@"):
2129 func_sig_lineno += 1
2130 funcname = black_source_lines[func_sig_lineno].strip()
2131 if "black/__init__.py" in filename:
2132 print(f"{' ' * stack}{lineno}:{funcname}")