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_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] = {}
105 class FakeParameter(click.Parameter):
106 """A fake click Parameter for when calling functions that need it."""
108 def __init__(self) -> None:
112 class BlackRunner(CliRunner):
113 """Make sure STDOUT and STDERR are kept separate when testing Black via its CLI."""
115 def __init__(self) -> None:
116 super().__init__(mix_stderr=False)
120 args: List[str], exit_code: int = 0, ignore_config: bool = True
122 runner = BlackRunner()
124 args = ["--verbose", "--config", str(THIS_DIR / "empty.toml"), *args]
125 result = runner.invoke(black.main, args, catch_exceptions=False)
126 assert result.stdout_bytes is not None
127 assert result.stderr_bytes is not None
129 f"Failed with args: {args}\n"
130 f"stdout: {result.stdout_bytes.decode()!r}\n"
131 f"stderr: {result.stderr_bytes.decode()!r}\n"
132 f"exception: {result.exception}"
134 assert result.exit_code == exit_code, msg
137 class BlackTestCase(BlackBaseTestCase):
138 invokeBlack = staticmethod(invokeBlack)
140 def test_empty_ff(self) -> None:
142 tmp_file = Path(black.dump_to_file())
144 self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES))
145 with open(tmp_file, encoding="utf8") as f:
149 self.assertFormatEqual(expected, actual)
151 def test_piping(self) -> None:
152 source, expected = read_data("src/black/__init__", data=False)
153 result = BlackRunner().invoke(
155 ["-", "--fast", f"--line-length={black.DEFAULT_LINE_LENGTH}"],
156 input=BytesIO(source.encode("utf8")),
158 self.assertEqual(result.exit_code, 0)
159 self.assertFormatEqual(expected, result.output)
160 if source != result.output:
161 black.assert_equivalent(source, result.output)
162 black.assert_stable(source, result.output, DEFAULT_MODE)
164 def test_piping_diff(self) -> None:
165 diff_header = re.compile(
166 r"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d\d\d\d "
169 source, _ = read_data("expression.py")
170 expected, _ = read_data("expression.diff")
171 config = THIS_DIR / "data" / "empty_pyproject.toml"
175 f"--line-length={black.DEFAULT_LINE_LENGTH}",
177 f"--config={config}",
179 result = BlackRunner().invoke(
180 black.main, args, input=BytesIO(source.encode("utf8"))
182 self.assertEqual(result.exit_code, 0)
183 actual = diff_header.sub(DETERMINISTIC_HEADER, result.output)
184 actual = actual.rstrip() + "\n" # the diff output has a trailing space
185 self.assertEqual(expected, actual)
187 def test_piping_diff_with_color(self) -> None:
188 source, _ = read_data("expression.py")
189 config = THIS_DIR / "data" / "empty_pyproject.toml"
193 f"--line-length={black.DEFAULT_LINE_LENGTH}",
196 f"--config={config}",
198 result = BlackRunner().invoke(
199 black.main, args, input=BytesIO(source.encode("utf8"))
201 actual = result.output
202 # Again, the contents are checked in a different test, so only look for colors.
203 self.assertIn("\033[1m", actual)
204 self.assertIn("\033[36m", actual)
205 self.assertIn("\033[32m", actual)
206 self.assertIn("\033[31m", actual)
207 self.assertIn("\033[0m", actual)
209 @patch("black.dump_to_file", dump_to_stderr)
210 def _test_wip(self) -> None:
211 source, expected = read_data("wip")
212 sys.settrace(tracefunc)
215 experimental_string_processing=False,
216 target_versions={black.TargetVersion.PY38},
218 actual = fs(source, mode=mode)
220 self.assertFormatEqual(expected, actual)
221 black.assert_equivalent(source, actual)
222 black.assert_stable(source, actual, black.FileMode())
224 @unittest.expectedFailure
225 @patch("black.dump_to_file", dump_to_stderr)
226 def test_trailing_comma_optional_parens_stability1(self) -> None:
227 source, _expected = read_data("trailing_comma_optional_parens1")
229 black.assert_stable(source, actual, DEFAULT_MODE)
231 @unittest.expectedFailure
232 @patch("black.dump_to_file", dump_to_stderr)
233 def test_trailing_comma_optional_parens_stability2(self) -> None:
234 source, _expected = read_data("trailing_comma_optional_parens2")
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_stability3(self) -> None:
241 source, _expected = read_data("trailing_comma_optional_parens3")
243 black.assert_stable(source, actual, DEFAULT_MODE)
245 @patch("black.dump_to_file", dump_to_stderr)
246 def test_trailing_comma_optional_parens_stability1_pass2(self) -> None:
247 source, _expected = read_data("trailing_comma_optional_parens1")
248 actual = fs(fs(source)) # this is what `format_file_contents` does with --safe
249 black.assert_stable(source, actual, DEFAULT_MODE)
251 @patch("black.dump_to_file", dump_to_stderr)
252 def test_trailing_comma_optional_parens_stability2_pass2(self) -> None:
253 source, _expected = read_data("trailing_comma_optional_parens2")
254 actual = fs(fs(source)) # this is what `format_file_contents` does with --safe
255 black.assert_stable(source, actual, DEFAULT_MODE)
257 @patch("black.dump_to_file", dump_to_stderr)
258 def test_trailing_comma_optional_parens_stability3_pass2(self) -> None:
259 source, _expected = read_data("trailing_comma_optional_parens3")
260 actual = fs(fs(source)) # this is what `format_file_contents` does with --safe
261 black.assert_stable(source, actual, DEFAULT_MODE)
263 def test_pep_572_version_detection(self) -> None:
264 source, _ = read_data("pep_572")
265 root = black.lib2to3_parse(source)
266 features = black.get_features_used(root)
267 self.assertIn(black.Feature.ASSIGNMENT_EXPRESSIONS, features)
268 versions = black.detect_target_versions(root)
269 self.assertIn(black.TargetVersion.PY38, versions)
271 def test_expression_ff(self) -> None:
272 source, expected = read_data("expression")
273 tmp_file = Path(black.dump_to_file(source))
275 self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
276 with open(tmp_file, encoding="utf8") as f:
280 self.assertFormatEqual(expected, actual)
281 with patch("black.dump_to_file", dump_to_stderr):
282 black.assert_equivalent(source, actual)
283 black.assert_stable(source, actual, DEFAULT_MODE)
285 def test_expression_diff(self) -> None:
286 source, _ = read_data("expression.py")
287 config = THIS_DIR / "data" / "empty_pyproject.toml"
288 expected, _ = read_data("expression.diff")
289 tmp_file = Path(black.dump_to_file(source))
290 diff_header = re.compile(
291 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
292 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
295 result = BlackRunner().invoke(
296 black.main, ["--diff", str(tmp_file), f"--config={config}"]
298 self.assertEqual(result.exit_code, 0)
301 actual = result.output
302 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
303 if expected != actual:
304 dump = black.dump_to_file(actual)
306 "Expected diff isn't equal to the actual. If you made changes to"
307 " expression.py and this is an anticipated difference, overwrite"
308 f" tests/data/expression.diff with {dump}"
310 self.assertEqual(expected, actual, msg)
312 def test_expression_diff_with_color(self) -> None:
313 source, _ = read_data("expression.py")
314 config = THIS_DIR / "data" / "empty_pyproject.toml"
315 expected, _ = read_data("expression.diff")
316 tmp_file = Path(black.dump_to_file(source))
318 result = BlackRunner().invoke(
319 black.main, ["--diff", "--color", str(tmp_file), f"--config={config}"]
323 actual = result.output
324 # We check the contents of the diff in `test_expression_diff`. All
325 # we need to check here is that color codes exist in the result.
326 self.assertIn("\033[1m", actual)
327 self.assertIn("\033[36m", actual)
328 self.assertIn("\033[32m", actual)
329 self.assertIn("\033[31m", actual)
330 self.assertIn("\033[0m", actual)
332 def test_detect_pos_only_arguments(self) -> None:
333 source, _ = read_data("pep_570")
334 root = black.lib2to3_parse(source)
335 features = black.get_features_used(root)
336 self.assertIn(black.Feature.POS_ONLY_ARGUMENTS, features)
337 versions = black.detect_target_versions(root)
338 self.assertIn(black.TargetVersion.PY38, versions)
340 @patch("black.dump_to_file", dump_to_stderr)
341 def test_string_quotes(self) -> None:
342 source, expected = read_data("string_quotes")
343 mode = black.Mode(experimental_string_processing=True)
344 assert_format(source, expected, mode)
345 mode = replace(mode, string_normalization=False)
346 not_normalized = fs(source, mode=mode)
347 self.assertFormatEqual(source.replace("\\\n", ""), not_normalized)
348 black.assert_equivalent(source, not_normalized)
349 black.assert_stable(source, not_normalized, mode=mode)
351 def test_skip_magic_trailing_comma(self) -> None:
352 source, _ = read_data("expression.py")
353 expected, _ = read_data("expression_skip_magic_trailing_comma.diff")
354 tmp_file = Path(black.dump_to_file(source))
355 diff_header = re.compile(
356 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
357 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
360 result = BlackRunner().invoke(black.main, ["-C", "--diff", str(tmp_file)])
361 self.assertEqual(result.exit_code, 0)
364 actual = result.output
365 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
366 actual = actual.rstrip() + "\n" # the diff output has a trailing space
367 if expected != actual:
368 dump = black.dump_to_file(actual)
370 "Expected diff isn't equal to the actual. If you made changes to"
371 " expression.py and this is an anticipated difference, overwrite"
372 f" tests/data/expression_skip_magic_trailing_comma.diff with {dump}"
374 self.assertEqual(expected, actual, msg)
376 @patch("black.dump_to_file", dump_to_stderr)
377 def test_async_as_identifier(self) -> None:
378 source_path = (THIS_DIR / "data" / "async_as_identifier.py").resolve()
379 source, expected = read_data("async_as_identifier")
381 self.assertFormatEqual(expected, actual)
382 major, minor = sys.version_info[:2]
383 if major < 3 or (major <= 3 and minor < 7):
384 black.assert_equivalent(source, actual)
385 black.assert_stable(source, actual, DEFAULT_MODE)
386 # ensure black can parse this when the target is 3.6
387 self.invokeBlack([str(source_path), "--target-version", "py36"])
388 # but not on 3.7, because async/await is no longer an identifier
389 self.invokeBlack([str(source_path), "--target-version", "py37"], exit_code=123)
391 @patch("black.dump_to_file", dump_to_stderr)
392 def test_python37(self) -> None:
393 source_path = (THIS_DIR / "data" / "python37.py").resolve()
394 source, expected = read_data("python37")
396 self.assertFormatEqual(expected, actual)
397 major, minor = sys.version_info[:2]
398 if major > 3 or (major == 3 and minor >= 7):
399 black.assert_equivalent(source, actual)
400 black.assert_stable(source, actual, DEFAULT_MODE)
401 # ensure black can parse this when the target is 3.7
402 self.invokeBlack([str(source_path), "--target-version", "py37"])
403 # but not on 3.6, because we use async as a reserved keyword
404 self.invokeBlack([str(source_path), "--target-version", "py36"], exit_code=123)
406 def test_tab_comment_indentation(self) -> None:
407 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
408 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
409 self.assertFormatEqual(contents_spc, fs(contents_spc))
410 self.assertFormatEqual(contents_spc, fs(contents_tab))
412 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
413 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
414 self.assertFormatEqual(contents_spc, fs(contents_spc))
415 self.assertFormatEqual(contents_spc, fs(contents_tab))
417 # mixed tabs and spaces (valid Python 2 code)
418 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t# comment\n pass\n"
419 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
420 self.assertFormatEqual(contents_spc, fs(contents_spc))
421 self.assertFormatEqual(contents_spc, fs(contents_tab))
423 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t\t# comment\n pass\n"
424 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
425 self.assertFormatEqual(contents_spc, fs(contents_spc))
426 self.assertFormatEqual(contents_spc, fs(contents_tab))
428 def test_report_verbose(self) -> None:
429 report = Report(verbose=True)
433 def out(msg: str, **kwargs: Any) -> None:
434 out_lines.append(msg)
436 def err(msg: str, **kwargs: Any) -> None:
437 err_lines.append(msg)
439 with patch("black.output._out", out), patch("black.output._err", err):
440 report.done(Path("f1"), black.Changed.NO)
441 self.assertEqual(len(out_lines), 1)
442 self.assertEqual(len(err_lines), 0)
443 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
444 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
445 self.assertEqual(report.return_code, 0)
446 report.done(Path("f2"), black.Changed.YES)
447 self.assertEqual(len(out_lines), 2)
448 self.assertEqual(len(err_lines), 0)
449 self.assertEqual(out_lines[-1], "reformatted f2")
451 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
453 report.done(Path("f3"), black.Changed.CACHED)
454 self.assertEqual(len(out_lines), 3)
455 self.assertEqual(len(err_lines), 0)
457 out_lines[-1], "f3 wasn't modified on disk since last run."
460 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
462 self.assertEqual(report.return_code, 0)
464 self.assertEqual(report.return_code, 1)
466 report.failed(Path("e1"), "boom")
467 self.assertEqual(len(out_lines), 3)
468 self.assertEqual(len(err_lines), 1)
469 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
471 unstyle(str(report)),
472 "1 file reformatted, 2 files left unchanged, 1 file failed to"
475 self.assertEqual(report.return_code, 123)
476 report.done(Path("f3"), black.Changed.YES)
477 self.assertEqual(len(out_lines), 4)
478 self.assertEqual(len(err_lines), 1)
479 self.assertEqual(out_lines[-1], "reformatted f3")
481 unstyle(str(report)),
482 "2 files reformatted, 2 files left unchanged, 1 file failed to"
485 self.assertEqual(report.return_code, 123)
486 report.failed(Path("e2"), "boom")
487 self.assertEqual(len(out_lines), 4)
488 self.assertEqual(len(err_lines), 2)
489 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
491 unstyle(str(report)),
492 "2 files reformatted, 2 files left unchanged, 2 files failed to"
495 self.assertEqual(report.return_code, 123)
496 report.path_ignored(Path("wat"), "no match")
497 self.assertEqual(len(out_lines), 5)
498 self.assertEqual(len(err_lines), 2)
499 self.assertEqual(out_lines[-1], "wat ignored: no match")
501 unstyle(str(report)),
502 "2 files reformatted, 2 files left unchanged, 2 files failed to"
505 self.assertEqual(report.return_code, 123)
506 report.done(Path("f4"), black.Changed.NO)
507 self.assertEqual(len(out_lines), 6)
508 self.assertEqual(len(err_lines), 2)
509 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
511 unstyle(str(report)),
512 "2 files reformatted, 3 files left unchanged, 2 files failed to"
515 self.assertEqual(report.return_code, 123)
518 unstyle(str(report)),
519 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
520 " would fail to reformat.",
525 unstyle(str(report)),
526 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
527 " would fail to reformat.",
530 def test_report_quiet(self) -> None:
531 report = Report(quiet=True)
535 def out(msg: str, **kwargs: Any) -> None:
536 out_lines.append(msg)
538 def err(msg: str, **kwargs: Any) -> None:
539 err_lines.append(msg)
541 with patch("black.output._out", out), patch("black.output._err", err):
542 report.done(Path("f1"), black.Changed.NO)
543 self.assertEqual(len(out_lines), 0)
544 self.assertEqual(len(err_lines), 0)
545 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
546 self.assertEqual(report.return_code, 0)
547 report.done(Path("f2"), black.Changed.YES)
548 self.assertEqual(len(out_lines), 0)
549 self.assertEqual(len(err_lines), 0)
551 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
553 report.done(Path("f3"), black.Changed.CACHED)
554 self.assertEqual(len(out_lines), 0)
555 self.assertEqual(len(err_lines), 0)
557 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
559 self.assertEqual(report.return_code, 0)
561 self.assertEqual(report.return_code, 1)
563 report.failed(Path("e1"), "boom")
564 self.assertEqual(len(out_lines), 0)
565 self.assertEqual(len(err_lines), 1)
566 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
568 unstyle(str(report)),
569 "1 file reformatted, 2 files left unchanged, 1 file failed to"
572 self.assertEqual(report.return_code, 123)
573 report.done(Path("f3"), black.Changed.YES)
574 self.assertEqual(len(out_lines), 0)
575 self.assertEqual(len(err_lines), 1)
577 unstyle(str(report)),
578 "2 files reformatted, 2 files left unchanged, 1 file failed to"
581 self.assertEqual(report.return_code, 123)
582 report.failed(Path("e2"), "boom")
583 self.assertEqual(len(out_lines), 0)
584 self.assertEqual(len(err_lines), 2)
585 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
587 unstyle(str(report)),
588 "2 files reformatted, 2 files left unchanged, 2 files failed to"
591 self.assertEqual(report.return_code, 123)
592 report.path_ignored(Path("wat"), "no match")
593 self.assertEqual(len(out_lines), 0)
594 self.assertEqual(len(err_lines), 2)
596 unstyle(str(report)),
597 "2 files reformatted, 2 files left unchanged, 2 files failed to"
600 self.assertEqual(report.return_code, 123)
601 report.done(Path("f4"), black.Changed.NO)
602 self.assertEqual(len(out_lines), 0)
603 self.assertEqual(len(err_lines), 2)
605 unstyle(str(report)),
606 "2 files reformatted, 3 files left unchanged, 2 files failed to"
609 self.assertEqual(report.return_code, 123)
612 unstyle(str(report)),
613 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
614 " would fail to reformat.",
619 unstyle(str(report)),
620 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
621 " would fail to reformat.",
624 def test_report_normal(self) -> None:
625 report = black.Report()
629 def out(msg: str, **kwargs: Any) -> None:
630 out_lines.append(msg)
632 def err(msg: str, **kwargs: Any) -> None:
633 err_lines.append(msg)
635 with patch("black.output._out", out), patch("black.output._err", err):
636 report.done(Path("f1"), black.Changed.NO)
637 self.assertEqual(len(out_lines), 0)
638 self.assertEqual(len(err_lines), 0)
639 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
640 self.assertEqual(report.return_code, 0)
641 report.done(Path("f2"), black.Changed.YES)
642 self.assertEqual(len(out_lines), 1)
643 self.assertEqual(len(err_lines), 0)
644 self.assertEqual(out_lines[-1], "reformatted f2")
646 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
648 report.done(Path("f3"), black.Changed.CACHED)
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, 2 files left unchanged."
655 self.assertEqual(report.return_code, 0)
657 self.assertEqual(report.return_code, 1)
659 report.failed(Path("e1"), "boom")
660 self.assertEqual(len(out_lines), 1)
661 self.assertEqual(len(err_lines), 1)
662 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
664 unstyle(str(report)),
665 "1 file reformatted, 2 files left unchanged, 1 file failed to"
668 self.assertEqual(report.return_code, 123)
669 report.done(Path("f3"), black.Changed.YES)
670 self.assertEqual(len(out_lines), 2)
671 self.assertEqual(len(err_lines), 1)
672 self.assertEqual(out_lines[-1], "reformatted f3")
674 unstyle(str(report)),
675 "2 files reformatted, 2 files left unchanged, 1 file failed to"
678 self.assertEqual(report.return_code, 123)
679 report.failed(Path("e2"), "boom")
680 self.assertEqual(len(out_lines), 2)
681 self.assertEqual(len(err_lines), 2)
682 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
684 unstyle(str(report)),
685 "2 files reformatted, 2 files left unchanged, 2 files failed to"
688 self.assertEqual(report.return_code, 123)
689 report.path_ignored(Path("wat"), "no match")
690 self.assertEqual(len(out_lines), 2)
691 self.assertEqual(len(err_lines), 2)
693 unstyle(str(report)),
694 "2 files reformatted, 2 files left unchanged, 2 files failed to"
697 self.assertEqual(report.return_code, 123)
698 report.done(Path("f4"), black.Changed.NO)
699 self.assertEqual(len(out_lines), 2)
700 self.assertEqual(len(err_lines), 2)
702 unstyle(str(report)),
703 "2 files reformatted, 3 files left unchanged, 2 files failed to"
706 self.assertEqual(report.return_code, 123)
709 unstyle(str(report)),
710 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
711 " would fail to reformat.",
716 unstyle(str(report)),
717 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
718 " would fail to reformat.",
721 def test_lib2to3_parse(self) -> None:
722 with self.assertRaises(black.InvalidInput):
723 black.lib2to3_parse("invalid syntax")
726 black.lib2to3_parse(straddling)
727 black.lib2to3_parse(straddling, {TargetVersion.PY27})
728 black.lib2to3_parse(straddling, {TargetVersion.PY36})
729 black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
732 black.lib2to3_parse(py2_only)
733 black.lib2to3_parse(py2_only, {TargetVersion.PY27})
734 with self.assertRaises(black.InvalidInput):
735 black.lib2to3_parse(py2_only, {TargetVersion.PY36})
736 with self.assertRaises(black.InvalidInput):
737 black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
739 py3_only = "exec(x, end=y)"
740 black.lib2to3_parse(py3_only)
741 with self.assertRaises(black.InvalidInput):
742 black.lib2to3_parse(py3_only, {TargetVersion.PY27})
743 black.lib2to3_parse(py3_only, {TargetVersion.PY36})
744 black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
746 def test_get_features_used_decorator(self) -> None:
747 # Test the feature detection of new decorator syntax
748 # since this makes some test cases of test_get_features_used()
749 # fails if it fails, this is tested first so that a useful case
751 simples, relaxed = read_data("decorators")
752 # skip explanation comments at the top of the file
753 for simple_test in simples.split("##")[1:]:
754 node = black.lib2to3_parse(simple_test)
755 decorator = str(node.children[0].children[0]).strip()
757 Feature.RELAXED_DECORATORS,
758 black.get_features_used(node),
760 f"decorator '{decorator}' follows python<=3.8 syntax"
761 "but is detected as 3.9+"
762 # f"The full node is\n{node!r}"
765 # skip the '# output' comment at the top of the output part
766 for relaxed_test in relaxed.split("##")[1:]:
767 node = black.lib2to3_parse(relaxed_test)
768 decorator = str(node.children[0].children[0]).strip()
770 Feature.RELAXED_DECORATORS,
771 black.get_features_used(node),
773 f"decorator '{decorator}' uses python3.9+ syntax"
774 "but is detected as python<=3.8"
775 # f"The full node is\n{node!r}"
779 def test_get_features_used(self) -> None:
780 node = black.lib2to3_parse("def f(*, arg): ...\n")
781 self.assertEqual(black.get_features_used(node), set())
782 node = black.lib2to3_parse("def f(*, arg,): ...\n")
783 self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
784 node = black.lib2to3_parse("f(*arg,)\n")
786 black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
788 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
789 self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
790 node = black.lib2to3_parse("123_456\n")
791 self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
792 node = black.lib2to3_parse("123456\n")
793 self.assertEqual(black.get_features_used(node), set())
794 source, expected = read_data("function")
795 node = black.lib2to3_parse(source)
796 expected_features = {
797 Feature.TRAILING_COMMA_IN_CALL,
798 Feature.TRAILING_COMMA_IN_DEF,
801 self.assertEqual(black.get_features_used(node), expected_features)
802 node = black.lib2to3_parse(expected)
803 self.assertEqual(black.get_features_used(node), expected_features)
804 source, expected = read_data("expression")
805 node = black.lib2to3_parse(source)
806 self.assertEqual(black.get_features_used(node), set())
807 node = black.lib2to3_parse(expected)
808 self.assertEqual(black.get_features_used(node), set())
809 node = black.lib2to3_parse("lambda a, /, b: ...")
810 self.assertEqual(black.get_features_used(node), {Feature.POS_ONLY_ARGUMENTS})
811 node = black.lib2to3_parse("def fn(a, /, b): ...")
812 self.assertEqual(black.get_features_used(node), {Feature.POS_ONLY_ARGUMENTS})
813 node = black.lib2to3_parse("def fn(): yield a, b")
814 self.assertEqual(black.get_features_used(node), set())
815 node = black.lib2to3_parse("def fn(): return a, b")
816 self.assertEqual(black.get_features_used(node), set())
817 node = black.lib2to3_parse("def fn(): yield *b, c")
818 self.assertEqual(black.get_features_used(node), {Feature.UNPACKING_ON_FLOW})
819 node = black.lib2to3_parse("def fn(): return a, *b, c")
820 self.assertEqual(black.get_features_used(node), {Feature.UNPACKING_ON_FLOW})
821 node = black.lib2to3_parse("x = a, *b, c")
822 self.assertEqual(black.get_features_used(node), set())
823 node = black.lib2to3_parse("x: Any = regular")
824 self.assertEqual(black.get_features_used(node), set())
825 node = black.lib2to3_parse("x: Any = (regular, regular)")
826 self.assertEqual(black.get_features_used(node), set())
827 node = black.lib2to3_parse("x: Any = Complex(Type(1))[something]")
828 self.assertEqual(black.get_features_used(node), set())
829 node = black.lib2to3_parse("x: Tuple[int, ...] = a, b, c")
831 black.get_features_used(node), {Feature.ANN_ASSIGN_EXTENDED_RHS}
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("debug_visitor.py")
885 expected, _ = read_data("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.assertTrue("Expected tree:" in out_str)
951 self.assertTrue("Actual tree:" in out_str)
952 self.assertEqual("".join(err_lines), "")
955 @patch("black.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 = (THIS_DIR / "data" / "string_quotes.py").resolve()
970 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
971 # Files which will not be reformatted.
972 src2 = (THIS_DIR / "data" / "composition.py").resolve()
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_files(self) -> None:
979 # Without an argument, black exits with error code 0.
982 def test_broken_symlink(self) -> None:
983 with cache_dir() as workspace:
984 symlink = workspace / "broken_link.py"
986 symlink.symlink_to("nonexistent.py")
987 except (OSError, NotImplementedError) as e:
988 self.skipTest(f"Can't create symlinks: {e}")
989 self.invokeBlack([str(workspace.resolve())])
991 def test_single_file_force_pyi(self) -> None:
992 pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
993 contents, expected = read_data("force_pyi")
994 with cache_dir() as workspace:
995 path = (workspace / "file.py").resolve()
996 with open(path, "w") as fh:
998 self.invokeBlack([str(path), "--pyi"])
999 with open(path, "r") as fh:
1001 # verify cache with --pyi is separate
1002 pyi_cache = black.read_cache(pyi_mode)
1003 self.assertIn(str(path), pyi_cache)
1004 normal_cache = black.read_cache(DEFAULT_MODE)
1005 self.assertNotIn(str(path), normal_cache)
1006 self.assertFormatEqual(expected, actual)
1007 black.assert_equivalent(contents, actual)
1008 black.assert_stable(contents, actual, pyi_mode)
1011 def test_multi_file_force_pyi(self) -> None:
1012 reg_mode = DEFAULT_MODE
1013 pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
1014 contents, expected = read_data("force_pyi")
1015 with cache_dir() as workspace:
1017 (workspace / "file1.py").resolve(),
1018 (workspace / "file2.py").resolve(),
1021 with open(path, "w") as fh:
1023 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1025 with open(path, "r") as fh:
1027 self.assertEqual(actual, expected)
1028 # verify cache with --pyi is separate
1029 pyi_cache = black.read_cache(pyi_mode)
1030 normal_cache = black.read_cache(reg_mode)
1032 self.assertIn(str(path), pyi_cache)
1033 self.assertNotIn(str(path), normal_cache)
1035 def test_pipe_force_pyi(self) -> None:
1036 source, expected = read_data("force_pyi")
1037 result = CliRunner().invoke(
1038 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1040 self.assertEqual(result.exit_code, 0)
1041 actual = result.output
1042 self.assertFormatEqual(actual, expected)
1044 def test_single_file_force_py36(self) -> None:
1045 reg_mode = DEFAULT_MODE
1046 py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
1047 source, expected = read_data("force_py36")
1048 with cache_dir() as workspace:
1049 path = (workspace / "file.py").resolve()
1050 with open(path, "w") as fh:
1052 self.invokeBlack([str(path), *PY36_ARGS])
1053 with open(path, "r") as fh:
1055 # verify cache with --target-version is separate
1056 py36_cache = black.read_cache(py36_mode)
1057 self.assertIn(str(path), py36_cache)
1058 normal_cache = black.read_cache(reg_mode)
1059 self.assertNotIn(str(path), normal_cache)
1060 self.assertEqual(actual, expected)
1063 def test_multi_file_force_py36(self) -> None:
1064 reg_mode = DEFAULT_MODE
1065 py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
1066 source, expected = read_data("force_py36")
1067 with cache_dir() as workspace:
1069 (workspace / "file1.py").resolve(),
1070 (workspace / "file2.py").resolve(),
1073 with open(path, "w") as fh:
1075 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1077 with open(path, "r") as fh:
1079 self.assertEqual(actual, expected)
1080 # verify cache with --target-version is separate
1081 pyi_cache = black.read_cache(py36_mode)
1082 normal_cache = black.read_cache(reg_mode)
1084 self.assertIn(str(path), pyi_cache)
1085 self.assertNotIn(str(path), normal_cache)
1087 def test_pipe_force_py36(self) -> None:
1088 source, expected = read_data("force_py36")
1089 result = CliRunner().invoke(
1091 ["-", "-q", "--target-version=py36"],
1092 input=BytesIO(source.encode("utf8")),
1094 self.assertEqual(result.exit_code, 0)
1095 actual = result.output
1096 self.assertFormatEqual(actual, expected)
1098 @pytest.mark.incompatible_with_mypyc
1099 def test_reformat_one_with_stdin(self) -> None:
1101 "black.format_stdin_to_stdout",
1102 return_value=lambda *args, **kwargs: black.Changed.YES,
1104 report = MagicMock()
1109 write_back=black.WriteBack.YES,
1113 fsts.assert_called_once()
1114 report.done.assert_called_with(path, black.Changed.YES)
1116 @pytest.mark.incompatible_with_mypyc
1117 def test_reformat_one_with_stdin_filename(self) -> None:
1119 "black.format_stdin_to_stdout",
1120 return_value=lambda *args, **kwargs: black.Changed.YES,
1122 report = MagicMock()
1124 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1129 write_back=black.WriteBack.YES,
1133 fsts.assert_called_once_with(
1134 fast=True, write_back=black.WriteBack.YES, mode=DEFAULT_MODE
1136 # __BLACK_STDIN_FILENAME__ should have been stripped
1137 report.done.assert_called_with(expected, black.Changed.YES)
1139 @pytest.mark.incompatible_with_mypyc
1140 def test_reformat_one_with_stdin_filename_pyi(self) -> None:
1142 "black.format_stdin_to_stdout",
1143 return_value=lambda *args, **kwargs: black.Changed.YES,
1145 report = MagicMock()
1147 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1152 write_back=black.WriteBack.YES,
1156 fsts.assert_called_once_with(
1158 write_back=black.WriteBack.YES,
1159 mode=replace(DEFAULT_MODE, is_pyi=True),
1161 # __BLACK_STDIN_FILENAME__ should have been stripped
1162 report.done.assert_called_with(expected, black.Changed.YES)
1164 @pytest.mark.incompatible_with_mypyc
1165 def test_reformat_one_with_stdin_filename_ipynb(self) -> None:
1167 "black.format_stdin_to_stdout",
1168 return_value=lambda *args, **kwargs: black.Changed.YES,
1170 report = MagicMock()
1172 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1177 write_back=black.WriteBack.YES,
1181 fsts.assert_called_once_with(
1183 write_back=black.WriteBack.YES,
1184 mode=replace(DEFAULT_MODE, is_ipynb=True),
1186 # __BLACK_STDIN_FILENAME__ should have been stripped
1187 report.done.assert_called_with(expected, black.Changed.YES)
1189 @pytest.mark.incompatible_with_mypyc
1190 def test_reformat_one_with_stdin_and_existing_path(self) -> None:
1192 "black.format_stdin_to_stdout",
1193 return_value=lambda *args, **kwargs: black.Changed.YES,
1195 report = MagicMock()
1196 # Even with an existing file, since we are forcing stdin, black
1197 # should output to stdout and not modify the file inplace
1198 p = Path(str(THIS_DIR / "data/collections.py"))
1199 # Make sure is_file actually returns True
1200 self.assertTrue(p.is_file())
1201 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1206 write_back=black.WriteBack.YES,
1210 fsts.assert_called_once()
1211 # __BLACK_STDIN_FILENAME__ should have been stripped
1212 report.done.assert_called_with(expected, black.Changed.YES)
1214 def test_reformat_one_with_stdin_empty(self) -> None:
1215 output = io.StringIO()
1216 with patch("io.TextIOWrapper", lambda *args, **kwargs: output):
1218 black.format_stdin_to_stdout(
1221 write_back=black.WriteBack.YES,
1224 except io.UnsupportedOperation:
1225 pass # StringIO does not support detach
1226 assert output.getvalue() == ""
1228 def test_invalid_cli_regex(self) -> None:
1229 for option in ["--include", "--exclude", "--extend-exclude", "--force-exclude"]:
1230 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1232 def test_required_version_matches_version(self) -> None:
1234 ["--required-version", black.__version__], exit_code=0, ignore_config=True
1237 def test_required_version_does_not_match_version(self) -> None:
1239 ["--required-version", "20.99b"], exit_code=1, ignore_config=True
1242 def test_preserves_line_endings(self) -> None:
1243 with TemporaryDirectory() as workspace:
1244 test_file = Path(workspace) / "test.py"
1245 for nl in ["\n", "\r\n"]:
1246 contents = nl.join(["def f( ):", " pass"])
1247 test_file.write_bytes(contents.encode())
1248 ff(test_file, write_back=black.WriteBack.YES)
1249 updated_contents: bytes = test_file.read_bytes()
1250 self.assertIn(nl.encode(), updated_contents)
1252 self.assertNotIn(b"\r\n", updated_contents)
1254 def test_preserves_line_endings_via_stdin(self) -> None:
1255 for nl in ["\n", "\r\n"]:
1256 contents = nl.join(["def f( ):", " pass"])
1257 runner = BlackRunner()
1258 result = runner.invoke(
1259 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1261 self.assertEqual(result.exit_code, 0)
1262 output = result.stdout_bytes
1263 self.assertIn(nl.encode("utf8"), output)
1265 self.assertNotIn(b"\r\n", output)
1267 def test_assert_equivalent_different_asts(self) -> None:
1268 with self.assertRaises(AssertionError):
1269 black.assert_equivalent("{}", "None")
1271 def test_shhh_click(self) -> None:
1273 from click import _unicodefun
1274 except ModuleNotFoundError:
1275 self.skipTest("Incompatible Click version")
1276 if not hasattr(_unicodefun, "_verify_python3_env"):
1277 self.skipTest("Incompatible Click version")
1278 # First, let's see if Click is crashing with a preferred ASCII charset.
1279 with patch("locale.getpreferredencoding") as gpe:
1280 gpe.return_value = "ASCII"
1281 with self.assertRaises(RuntimeError):
1282 _unicodefun._verify_python3_env() # type: ignore
1283 # Now, let's silence Click...
1285 # ...and confirm it's silent.
1286 with patch("locale.getpreferredencoding") as gpe:
1287 gpe.return_value = "ASCII"
1289 _unicodefun._verify_python3_env() # type: ignore
1290 except RuntimeError as re:
1291 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1293 def test_root_logger_not_used_directly(self) -> None:
1294 def fail(*args: Any, **kwargs: Any) -> None:
1295 self.fail("Record created with root logger")
1297 with patch.multiple(
1306 ff(THIS_DIR / "util.py")
1308 def test_invalid_config_return_code(self) -> None:
1309 tmp_file = Path(black.dump_to_file())
1311 tmp_config = Path(black.dump_to_file())
1313 args = ["--config", str(tmp_config), str(tmp_file)]
1314 self.invokeBlack(args, exit_code=2, ignore_config=False)
1318 def test_parse_pyproject_toml(self) -> None:
1319 test_toml_file = THIS_DIR / "test.toml"
1320 config = black.parse_pyproject_toml(str(test_toml_file))
1321 self.assertEqual(config["verbose"], 1)
1322 self.assertEqual(config["check"], "no")
1323 self.assertEqual(config["diff"], "y")
1324 self.assertEqual(config["color"], True)
1325 self.assertEqual(config["line_length"], 79)
1326 self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
1327 self.assertEqual(config["exclude"], r"\.pyi?$")
1328 self.assertEqual(config["include"], r"\.py?$")
1330 def test_read_pyproject_toml(self) -> None:
1331 test_toml_file = THIS_DIR / "test.toml"
1332 fake_ctx = FakeContext()
1333 black.read_pyproject_toml(fake_ctx, FakeParameter(), str(test_toml_file))
1334 config = fake_ctx.default_map
1335 self.assertEqual(config["verbose"], "1")
1336 self.assertEqual(config["check"], "no")
1337 self.assertEqual(config["diff"], "y")
1338 self.assertEqual(config["color"], "True")
1339 self.assertEqual(config["line_length"], "79")
1340 self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
1341 self.assertEqual(config["exclude"], r"\.pyi?$")
1342 self.assertEqual(config["include"], r"\.py?$")
1344 @pytest.mark.incompatible_with_mypyc
1345 def test_find_project_root(self) -> None:
1346 with TemporaryDirectory() as workspace:
1347 root = Path(workspace)
1348 test_dir = root / "test"
1351 src_dir = root / "src"
1354 root_pyproject = root / "pyproject.toml"
1355 root_pyproject.touch()
1356 src_pyproject = src_dir / "pyproject.toml"
1357 src_pyproject.touch()
1358 src_python = src_dir / "foo.py"
1362 black.find_project_root((src_dir, test_dir)), root.resolve()
1364 self.assertEqual(black.find_project_root((src_dir,)), src_dir.resolve())
1365 self.assertEqual(black.find_project_root((src_python,)), src_dir.resolve())
1368 "black.files.find_user_pyproject_toml",
1369 black.files.find_user_pyproject_toml.__wrapped__,
1371 def test_find_user_pyproject_toml_linux(self) -> None:
1372 if system() == "Windows":
1375 # Test if XDG_CONFIG_HOME is checked
1376 with TemporaryDirectory() as workspace:
1377 tmp_user_config = Path(workspace) / "black"
1378 with patch.dict("os.environ", {"XDG_CONFIG_HOME": workspace}):
1380 black.files.find_user_pyproject_toml(), tmp_user_config.resolve()
1383 # Test fallback for XDG_CONFIG_HOME
1384 with patch.dict("os.environ"):
1385 os.environ.pop("XDG_CONFIG_HOME", None)
1386 fallback_user_config = Path("~/.config").expanduser() / "black"
1388 black.files.find_user_pyproject_toml(), fallback_user_config.resolve()
1391 def test_find_user_pyproject_toml_windows(self) -> None:
1392 if system() != "Windows":
1395 user_config_path = Path.home() / ".black"
1397 black.files.find_user_pyproject_toml(), user_config_path.resolve()
1400 def test_bpo_33660_workaround(self) -> None:
1401 if system() == "Windows":
1404 # https://bugs.python.org/issue33660
1406 with change_directory(root):
1407 path = Path("workspace") / "project"
1408 report = black.Report(verbose=True)
1409 normalized_path = black.normalize_path_maybe_ignore(path, root, report)
1410 self.assertEqual(normalized_path, "workspace/project")
1412 def test_newline_comment_interaction(self) -> None:
1413 source = "class A:\\\r\n# type: ignore\n pass\n"
1414 output = black.format_str(source, mode=DEFAULT_MODE)
1415 black.assert_stable(source, output, mode=DEFAULT_MODE)
1417 def test_bpo_2142_workaround(self) -> None:
1419 # https://bugs.python.org/issue2142
1421 source, _ = read_data("missing_final_newline.py")
1422 # read_data adds a trailing newline
1423 source = source.rstrip()
1424 expected, _ = read_data("missing_final_newline.diff")
1425 tmp_file = Path(black.dump_to_file(source, ensure_final_newline=False))
1426 diff_header = re.compile(
1427 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
1428 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
1431 result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
1432 self.assertEqual(result.exit_code, 0)
1435 actual = result.output
1436 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
1437 self.assertEqual(actual, expected)
1439 @pytest.mark.python2
1440 def test_docstring_reformat_for_py27(self) -> None:
1442 Check that stripping trailing whitespace from Python 2 docstrings
1443 doesn't trigger a "not equivalent to source" error
1446 b'def foo():\r\n """Testing\r\n Testing """\r\n print "Foo"\r\n'
1448 expected = 'def foo():\n """Testing\n Testing"""\n print "Foo"\n'
1450 result = BlackRunner().invoke(
1452 ["-", "-q", "--target-version=py27"],
1453 input=BytesIO(source),
1456 self.assertEqual(result.exit_code, 0)
1457 actual = result.stdout
1458 self.assertFormatEqual(actual, expected)
1461 def compare_results(
1462 result: click.testing.Result, expected_value: str, expected_exit_code: int
1464 """Helper method to test the value and exit code of a click Result."""
1466 result.output == expected_value
1467 ), "The output did not match the expected value."
1468 assert result.exit_code == expected_exit_code, "The exit code is incorrect."
1470 def test_code_option(self) -> None:
1471 """Test the code option with no changes."""
1472 code = 'print("Hello world")\n'
1473 args = ["--code", code]
1474 result = CliRunner().invoke(black.main, args)
1476 self.compare_results(result, code, 0)
1478 def test_code_option_changed(self) -> None:
1479 """Test the code option when changes are required."""
1480 code = "print('hello world')"
1481 formatted = black.format_str(code, mode=DEFAULT_MODE)
1483 args = ["--code", code]
1484 result = CliRunner().invoke(black.main, args)
1486 self.compare_results(result, formatted, 0)
1488 def test_code_option_check(self) -> None:
1489 """Test the code option when check is passed."""
1490 args = ["--check", "--code", 'print("Hello world")\n']
1491 result = CliRunner().invoke(black.main, args)
1492 self.compare_results(result, "", 0)
1494 def test_code_option_check_changed(self) -> None:
1495 """Test the code option when changes are required, and check is passed."""
1496 args = ["--check", "--code", "print('hello world')"]
1497 result = CliRunner().invoke(black.main, args)
1498 self.compare_results(result, "", 1)
1500 def test_code_option_diff(self) -> None:
1501 """Test the code option when diff is passed."""
1502 code = "print('hello world')"
1503 formatted = black.format_str(code, mode=DEFAULT_MODE)
1504 result_diff = diff(code, formatted, "STDIN", "STDOUT")
1506 args = ["--diff", "--code", code]
1507 result = CliRunner().invoke(black.main, args)
1509 # Remove time from diff
1510 output = DIFF_TIME.sub("", result.output)
1512 assert output == result_diff, "The output did not match the expected value."
1513 assert result.exit_code == 0, "The exit code is incorrect."
1515 def test_code_option_color_diff(self) -> None:
1516 """Test the code option when color and diff are passed."""
1517 code = "print('hello world')"
1518 formatted = black.format_str(code, mode=DEFAULT_MODE)
1520 result_diff = diff(code, formatted, "STDIN", "STDOUT")
1521 result_diff = color_diff(result_diff)
1523 args = ["--diff", "--color", "--code", code]
1524 result = CliRunner().invoke(black.main, args)
1526 # Remove time from diff
1527 output = DIFF_TIME.sub("", result.output)
1529 assert output == result_diff, "The output did not match the expected value."
1530 assert result.exit_code == 0, "The exit code is incorrect."
1532 @pytest.mark.incompatible_with_mypyc
1533 def test_code_option_safe(self) -> None:
1534 """Test that the code option throws an error when the sanity checks fail."""
1535 # Patch black.assert_equivalent to ensure the sanity checks fail
1536 with patch.object(black, "assert_equivalent", side_effect=AssertionError):
1537 code = 'print("Hello world")'
1538 error_msg = f"{code}\nerror: cannot format <string>: \n"
1540 args = ["--safe", "--code", code]
1541 result = CliRunner().invoke(black.main, args)
1543 self.compare_results(result, error_msg, 123)
1545 def test_code_option_fast(self) -> None:
1546 """Test that the code option ignores errors when the sanity checks fail."""
1547 # Patch black.assert_equivalent to ensure the sanity checks fail
1548 with patch.object(black, "assert_equivalent", side_effect=AssertionError):
1549 code = 'print("Hello world")'
1550 formatted = black.format_str(code, mode=DEFAULT_MODE)
1552 args = ["--fast", "--code", code]
1553 result = CliRunner().invoke(black.main, args)
1555 self.compare_results(result, formatted, 0)
1557 @pytest.mark.incompatible_with_mypyc
1558 def test_code_option_config(self) -> None:
1560 Test that the code option finds the pyproject.toml in the current directory.
1562 with patch.object(black, "parse_pyproject_toml", return_value={}) as parse:
1563 args = ["--code", "print"]
1564 # This is the only directory known to contain a pyproject.toml
1565 with change_directory(PROJECT_ROOT):
1566 CliRunner().invoke(black.main, args)
1567 pyproject_path = Path(Path.cwd(), "pyproject.toml").resolve()
1570 len(parse.mock_calls) >= 1
1571 ), "Expected config parse to be called with the current directory."
1573 _, call_args, _ = parse.mock_calls[0]
1575 call_args[0].lower() == str(pyproject_path).lower()
1576 ), "Incorrect config loaded."
1578 @pytest.mark.incompatible_with_mypyc
1579 def test_code_option_parent_config(self) -> None:
1581 Test that the code option finds the pyproject.toml in the parent directory.
1583 with patch.object(black, "parse_pyproject_toml", return_value={}) as parse:
1584 with change_directory(THIS_DIR):
1585 args = ["--code", "print"]
1586 CliRunner().invoke(black.main, args)
1588 pyproject_path = Path(Path().cwd().parent, "pyproject.toml").resolve()
1590 len(parse.mock_calls) >= 1
1591 ), "Expected config parse to be called with the current directory."
1593 _, call_args, _ = parse.mock_calls[0]
1595 call_args[0].lower() == str(pyproject_path).lower()
1596 ), "Incorrect config loaded."
1598 def test_for_handled_unexpected_eof_error(self) -> None:
1600 Test that an unexpected EOF SyntaxError is nicely presented.
1602 with pytest.raises(black.parsing.InvalidInput) as exc_info:
1603 black.lib2to3_parse("print(", {})
1605 exc_info.match("Cannot parse: 2:0: EOF in multi-line statement")
1607 def test_equivalency_ast_parse_failure_includes_error(self) -> None:
1608 with pytest.raises(AssertionError) as err:
1609 black.assert_equivalent("a«»a = 1", "a«»a = 1")
1612 # Unfortunately the SyntaxError message has changed in newer versions so we
1613 # can't match it directly.
1614 err.match("invalid character")
1615 err.match(r"\(<unknown>, line 1\)")
1619 def test_cache_broken_file(self) -> None:
1621 with cache_dir() as workspace:
1622 cache_file = get_cache_file(mode)
1623 cache_file.write_text("this is not a pickle")
1624 assert black.read_cache(mode) == {}
1625 src = (workspace / "test.py").resolve()
1626 src.write_text("print('hello')")
1627 invokeBlack([str(src)])
1628 cache = black.read_cache(mode)
1629 assert str(src) in cache
1631 def test_cache_single_file_already_cached(self) -> None:
1633 with cache_dir() as workspace:
1634 src = (workspace / "test.py").resolve()
1635 src.write_text("print('hello')")
1636 black.write_cache({}, [src], mode)
1637 invokeBlack([str(src)])
1638 assert src.read_text() == "print('hello')"
1641 def test_cache_multiple_files(self) -> None:
1643 with cache_dir() as workspace, patch(
1644 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1646 one = (workspace / "one.py").resolve()
1647 with one.open("w") as fobj:
1648 fobj.write("print('hello')")
1649 two = (workspace / "two.py").resolve()
1650 with two.open("w") as fobj:
1651 fobj.write("print('hello')")
1652 black.write_cache({}, [one], mode)
1653 invokeBlack([str(workspace)])
1654 with one.open("r") as fobj:
1655 assert fobj.read() == "print('hello')"
1656 with two.open("r") as fobj:
1657 assert fobj.read() == 'print("hello")\n'
1658 cache = black.read_cache(mode)
1659 assert str(one) in cache
1660 assert str(two) in cache
1662 @pytest.mark.parametrize("color", [False, True], ids=["no-color", "with-color"])
1663 def test_no_cache_when_writeback_diff(self, color: bool) -> None:
1665 with cache_dir() as workspace:
1666 src = (workspace / "test.py").resolve()
1667 with src.open("w") as fobj:
1668 fobj.write("print('hello')")
1669 with patch("black.read_cache") as read_cache, patch(
1672 cmd = [str(src), "--diff"]
1674 cmd.append("--color")
1676 cache_file = get_cache_file(mode)
1677 assert cache_file.exists() is False
1678 write_cache.assert_not_called()
1679 read_cache.assert_not_called()
1681 @pytest.mark.parametrize("color", [False, True], ids=["no-color", "with-color"])
1683 def test_output_locking_when_writeback_diff(self, color: bool) -> None:
1684 with cache_dir() as workspace:
1685 for tag in range(0, 4):
1686 src = (workspace / f"test{tag}.py").resolve()
1687 with src.open("w") as fobj:
1688 fobj.write("print('hello')")
1689 with patch("black.Manager", wraps=multiprocessing.Manager) as mgr:
1690 cmd = ["--diff", str(workspace)]
1692 cmd.append("--color")
1693 invokeBlack(cmd, exit_code=0)
1694 # this isn't quite doing what we want, but if it _isn't_
1695 # called then we cannot be using the lock it provides
1698 def test_no_cache_when_stdin(self) -> None:
1701 result = CliRunner().invoke(
1702 black.main, ["-"], input=BytesIO(b"print('hello')")
1704 assert not result.exit_code
1705 cache_file = get_cache_file(mode)
1706 assert not cache_file.exists()
1708 def test_read_cache_no_cachefile(self) -> None:
1711 assert black.read_cache(mode) == {}
1713 def test_write_cache_read_cache(self) -> None:
1715 with cache_dir() as workspace:
1716 src = (workspace / "test.py").resolve()
1718 black.write_cache({}, [src], mode)
1719 cache = black.read_cache(mode)
1720 assert str(src) in cache
1721 assert cache[str(src)] == black.get_cache_info(src)
1723 def test_filter_cached(self) -> None:
1724 with TemporaryDirectory() as workspace:
1725 path = Path(workspace)
1726 uncached = (path / "uncached").resolve()
1727 cached = (path / "cached").resolve()
1728 cached_but_changed = (path / "changed").resolve()
1731 cached_but_changed.touch()
1733 str(cached): black.get_cache_info(cached),
1734 str(cached_but_changed): (0.0, 0),
1736 todo, done = black.filter_cached(
1737 cache, {uncached, cached, cached_but_changed}
1739 assert todo == {uncached, cached_but_changed}
1740 assert done == {cached}
1742 def test_write_cache_creates_directory_if_needed(self) -> None:
1744 with cache_dir(exists=False) as workspace:
1745 assert not workspace.exists()
1746 black.write_cache({}, [], mode)
1747 assert workspace.exists()
1750 def test_failed_formatting_does_not_get_cached(self) -> None:
1752 with cache_dir() as workspace, patch(
1753 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1755 failing = (workspace / "failing.py").resolve()
1756 with failing.open("w") as fobj:
1757 fobj.write("not actually python")
1758 clean = (workspace / "clean.py").resolve()
1759 with clean.open("w") as fobj:
1760 fobj.write('print("hello")\n')
1761 invokeBlack([str(workspace)], exit_code=123)
1762 cache = black.read_cache(mode)
1763 assert str(failing) not in cache
1764 assert str(clean) in cache
1766 def test_write_cache_write_fail(self) -> None:
1768 with cache_dir(), patch.object(Path, "open") as mock:
1769 mock.side_effect = OSError
1770 black.write_cache({}, [], mode)
1772 def test_read_cache_line_lengths(self) -> None:
1774 short_mode = replace(DEFAULT_MODE, line_length=1)
1775 with cache_dir() as workspace:
1776 path = (workspace / "file.py").resolve()
1778 black.write_cache({}, [path], mode)
1779 one = black.read_cache(mode)
1780 assert str(path) in one
1781 two = black.read_cache(short_mode)
1782 assert str(path) not in two
1785 def assert_collected_sources(
1786 src: Sequence[Union[str, Path]],
1787 expected: Sequence[Union[str, Path]],
1789 exclude: Optional[str] = None,
1790 include: Optional[str] = None,
1791 extend_exclude: Optional[str] = None,
1792 force_exclude: Optional[str] = None,
1793 stdin_filename: Optional[str] = None,
1795 gs_src = tuple(str(Path(s)) for s in src)
1796 gs_expected = [Path(s) for s in expected]
1797 gs_exclude = None if exclude is None else compile_pattern(exclude)
1798 gs_include = DEFAULT_INCLUDE if include is None else compile_pattern(include)
1799 gs_extend_exclude = (
1800 None if extend_exclude is None else compile_pattern(extend_exclude)
1802 gs_force_exclude = None if force_exclude is None else compile_pattern(force_exclude)
1803 collected = black.get_sources(
1810 extend_exclude=gs_extend_exclude,
1811 force_exclude=gs_force_exclude,
1812 report=black.Report(),
1813 stdin_filename=stdin_filename,
1815 assert sorted(collected) == sorted(gs_expected)
1818 class TestFileCollection:
1819 def test_include_exclude(self) -> None:
1820 path = THIS_DIR / "data" / "include_exclude_tests"
1823 Path(path / "b/dont_exclude/a.py"),
1824 Path(path / "b/dont_exclude/a.pyi"),
1826 assert_collected_sources(
1830 exclude=r"/exclude/|/\.definitely_exclude/",
1833 def test_gitignore_used_as_default(self) -> None:
1834 base = Path(DATA_DIR / "include_exclude_tests")
1836 base / "b/.definitely_exclude/a.py",
1837 base / "b/.definitely_exclude/a.pyi",
1840 assert_collected_sources(src, expected, extend_exclude=r"/exclude/")
1842 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1843 def test_exclude_for_issue_1572(self) -> None:
1844 # Exclude shouldn't touch files that were explicitly given to Black through the
1845 # CLI. Exclude is supposed to only apply to the recursive discovery of files.
1846 # https://github.com/psf/black/issues/1572
1847 path = DATA_DIR / "include_exclude_tests"
1848 src = [path / "b/exclude/a.py"]
1849 expected = [path / "b/exclude/a.py"]
1850 assert_collected_sources(src, expected, include="", exclude=r"/exclude/|a\.py")
1852 def test_gitignore_exclude(self) -> None:
1853 path = THIS_DIR / "data" / "include_exclude_tests"
1854 include = re.compile(r"\.pyi?$")
1855 exclude = re.compile(r"")
1856 report = black.Report()
1857 gitignore = PathSpec.from_lines(
1858 "gitwildmatch", ["exclude/", ".definitely_exclude"]
1860 sources: List[Path] = []
1862 Path(path / "b/dont_exclude/a.py"),
1863 Path(path / "b/dont_exclude/a.pyi"),
1865 this_abs = THIS_DIR.resolve()
1867 black.gen_python_files(
1880 assert sorted(expected) == sorted(sources)
1882 def test_nested_gitignore(self) -> None:
1883 path = Path(THIS_DIR / "data" / "nested_gitignore_tests")
1884 include = re.compile(r"\.pyi?$")
1885 exclude = re.compile(r"")
1886 root_gitignore = black.files.get_gitignore(path)
1887 report = black.Report()
1888 expected: List[Path] = [
1889 Path(path / "x.py"),
1890 Path(path / "root/b.py"),
1891 Path(path / "root/c.py"),
1892 Path(path / "root/child/c.py"),
1894 this_abs = THIS_DIR.resolve()
1896 black.gen_python_files(
1909 assert sorted(expected) == sorted(sources)
1911 def test_invalid_gitignore(self) -> None:
1912 path = THIS_DIR / "data" / "invalid_gitignore_tests"
1913 empty_config = path / "pyproject.toml"
1914 result = BlackRunner().invoke(
1915 black.main, ["--verbose", "--config", str(empty_config), str(path)]
1917 assert result.exit_code == 1
1918 assert result.stderr_bytes is not None
1920 gitignore = path / ".gitignore"
1921 assert f"Could not parse {gitignore}" in result.stderr_bytes.decode()
1923 def test_invalid_nested_gitignore(self) -> None:
1924 path = THIS_DIR / "data" / "invalid_nested_gitignore_tests"
1925 empty_config = path / "pyproject.toml"
1926 result = BlackRunner().invoke(
1927 black.main, ["--verbose", "--config", str(empty_config), str(path)]
1929 assert result.exit_code == 1
1930 assert result.stderr_bytes is not None
1932 gitignore = path / "a" / ".gitignore"
1933 assert f"Could not parse {gitignore}" in result.stderr_bytes.decode()
1935 def test_empty_include(self) -> None:
1936 path = DATA_DIR / "include_exclude_tests"
1939 Path(path / "b/exclude/a.pie"),
1940 Path(path / "b/exclude/a.py"),
1941 Path(path / "b/exclude/a.pyi"),
1942 Path(path / "b/dont_exclude/a.pie"),
1943 Path(path / "b/dont_exclude/a.py"),
1944 Path(path / "b/dont_exclude/a.pyi"),
1945 Path(path / "b/.definitely_exclude/a.pie"),
1946 Path(path / "b/.definitely_exclude/a.py"),
1947 Path(path / "b/.definitely_exclude/a.pyi"),
1948 Path(path / ".gitignore"),
1949 Path(path / "pyproject.toml"),
1951 # Setting exclude explicitly to an empty string to block .gitignore usage.
1952 assert_collected_sources(src, expected, include="", exclude="")
1954 def test_extend_exclude(self) -> None:
1955 path = DATA_DIR / "include_exclude_tests"
1958 Path(path / "b/exclude/a.py"),
1959 Path(path / "b/dont_exclude/a.py"),
1961 assert_collected_sources(
1962 src, expected, exclude=r"\.pyi$", extend_exclude=r"\.definitely_exclude"
1965 @pytest.mark.incompatible_with_mypyc
1966 def test_symlink_out_of_root_directory(self) -> None:
1968 root = THIS_DIR.resolve()
1970 include = re.compile(black.DEFAULT_INCLUDES)
1971 exclude = re.compile(black.DEFAULT_EXCLUDES)
1972 report = black.Report()
1973 gitignore = PathSpec.from_lines("gitwildmatch", [])
1974 # `child` should behave like a symlink which resolved path is clearly
1975 # outside of the `root` directory.
1976 path.iterdir.return_value = [child]
1977 child.resolve.return_value = Path("/a/b/c")
1978 child.as_posix.return_value = "/a/b/c"
1979 child.is_symlink.return_value = True
1982 black.gen_python_files(
1995 except ValueError as ve:
1996 pytest.fail(f"`get_python_files_in_dir()` failed: {ve}")
1997 path.iterdir.assert_called_once()
1998 child.resolve.assert_called_once()
1999 child.is_symlink.assert_called_once()
2000 # `child` should behave like a strange file which resolved path is clearly
2001 # outside of the `root` directory.
2002 child.is_symlink.return_value = False
2003 with pytest.raises(ValueError):
2005 black.gen_python_files(
2018 path.iterdir.assert_called()
2019 assert path.iterdir.call_count == 2
2020 child.resolve.assert_called()
2021 assert child.resolve.call_count == 2
2022 child.is_symlink.assert_called()
2023 assert child.is_symlink.call_count == 2
2025 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
2026 def test_get_sources_with_stdin(self) -> None:
2029 assert_collected_sources(src, expected, include="", exclude=r"/exclude/|a\.py")
2031 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
2032 def test_get_sources_with_stdin_filename(self) -> None:
2034 stdin_filename = str(THIS_DIR / "data/collections.py")
2035 expected = [f"__BLACK_STDIN_FILENAME__{stdin_filename}"]
2036 assert_collected_sources(
2039 exclude=r"/exclude/a\.py",
2040 stdin_filename=stdin_filename,
2043 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
2044 def test_get_sources_with_stdin_filename_and_exclude(self) -> None:
2045 # Exclude shouldn't exclude stdin_filename since it is mimicking the
2046 # file being passed directly. This is the same as
2047 # test_exclude_for_issue_1572
2048 path = DATA_DIR / "include_exclude_tests"
2050 stdin_filename = str(path / "b/exclude/a.py")
2051 expected = [f"__BLACK_STDIN_FILENAME__{stdin_filename}"]
2052 assert_collected_sources(
2055 exclude=r"/exclude/|a\.py",
2056 stdin_filename=stdin_filename,
2059 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
2060 def test_get_sources_with_stdin_filename_and_extend_exclude(self) -> None:
2061 # Extend exclude shouldn't exclude stdin_filename since it is mimicking the
2062 # file being passed directly. This is the same as
2063 # test_exclude_for_issue_1572
2065 path = THIS_DIR / "data" / "include_exclude_tests"
2066 stdin_filename = str(path / "b/exclude/a.py")
2067 expected = [f"__BLACK_STDIN_FILENAME__{stdin_filename}"]
2068 assert_collected_sources(
2071 extend_exclude=r"/exclude/|a\.py",
2072 stdin_filename=stdin_filename,
2075 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
2076 def test_get_sources_with_stdin_filename_and_force_exclude(self) -> None:
2077 # Force exclude should exclude the file when passing it through
2079 path = THIS_DIR / "data" / "include_exclude_tests"
2080 stdin_filename = str(path / "b/exclude/a.py")
2081 assert_collected_sources(
2084 force_exclude=r"/exclude/|a\.py",
2085 stdin_filename=stdin_filename,
2089 @pytest.mark.python2
2090 @pytest.mark.parametrize("explicit", [True, False], ids=["explicit", "autodetection"])
2091 def test_python_2_deprecation_with_target_version(explicit: bool) -> None:
2094 str(THIS_DIR / "empty.toml"),
2095 str(DATA_DIR / "python2.py"),
2099 args.append("--target-version=py27")
2101 result = BlackRunner().invoke(black.main, args)
2102 assert "DEPRECATION: Python 2 support will be removed" in result.stderr
2105 @pytest.mark.python2
2106 def test_python_2_deprecation_autodetection_extended() -> None:
2107 # this test has a similar construction to test_get_features_used_decorator
2108 python2, non_python2 = read_data("python2_detection")
2109 for python2_case in python2.split("###"):
2110 node = black.lib2to3_parse(python2_case)
2111 assert black.detect_target_versions(node) == {TargetVersion.PY27}, python2_case
2112 for non_python2_case in non_python2.split("###"):
2113 node = black.lib2to3_parse(non_python2_case)
2114 assert black.detect_target_versions(node) != {
2120 with open(black.__file__, "r", encoding="utf-8") as _bf:
2121 black_source_lines = _bf.readlines()
2122 except UnicodeDecodeError:
2123 if not black.COMPILED:
2128 frame: types.FrameType, event: str, arg: Any
2129 ) -> Callable[[types.FrameType, str, Any], Any]:
2130 """Show function calls `from black/__init__.py` as they happen.
2132 Register this with `sys.settrace()` in a test you're debugging.
2137 stack = len(inspect.stack()) - 19
2139 filename = frame.f_code.co_filename
2140 lineno = frame.f_lineno
2141 func_sig_lineno = lineno - 1
2142 funcname = black_source_lines[func_sig_lineno].strip()
2143 while funcname.startswith("@"):
2144 func_sig_lineno += 1
2145 funcname = black_source_lines[func_sig_lineno].strip()
2146 if "black/__init__.py" in filename:
2147 print(f"{' ' * stack}{lineno}:{funcname}")