All patches and comments are welcome. Please squash your changes to logical
commits before using git-format-patch and git-send-email to
patches@git.madduck.net.
If you'd read over the Git project's submission guidelines and adhered to them,
I'd be especially grateful.
5 from concurrent.futures import ThreadPoolExecutor
6 from contextlib import contextmanager
7 from dataclasses import replace
11 from pathlib import Path
12 from platform import system
15 from tempfile import TemporaryDirectory
27 from unittest.mock import patch, MagicMock
30 from click import unstyle
31 from click.testing import CliRunner
34 from black import Feature, TargetVersion
35 from black.cache import get_cache_file
36 from black.debug import DebugVisitor
37 from black.output import diff, color_diff
38 from black.report import Report
41 from pathspec import PathSpec
43 # Import other test classes
44 from tests.util import (
56 THIS_FILE = Path(__file__)
63 PY36_ARGS = [f"--target-version={version.name.lower()}" for version in PY36_VERSIONS]
67 # Match the time output in a diff, but nothing else
68 DIFF_TIME = re.compile(r"\t[\d-:+\. ]+")
72 def cache_dir(exists: bool = True) -> Iterator[Path]:
73 with TemporaryDirectory() as workspace:
74 cache_dir = Path(workspace)
76 cache_dir = cache_dir / "new"
77 with patch("black.cache.CACHE_DIR", cache_dir):
82 def event_loop() -> Iterator[None]:
83 policy = asyncio.get_event_loop_policy()
84 loop = policy.new_event_loop()
85 asyncio.set_event_loop(loop)
93 class FakeContext(click.Context):
94 """A fake click Context for when calling functions that need it."""
96 def __init__(self) -> None:
97 self.default_map: Dict[str, Any] = {}
100 class FakeParameter(click.Parameter):
101 """A fake click Parameter for when calling functions that need it."""
103 def __init__(self) -> None:
107 class BlackRunner(CliRunner):
108 """Make sure STDOUT and STDERR are kept separate when testing Black via its CLI."""
110 def __init__(self) -> None:
111 super().__init__(mix_stderr=False)
114 class BlackTestCase(BlackBaseTestCase):
116 self, args: List[str], exit_code: int = 0, ignore_config: bool = True
118 runner = BlackRunner()
120 args = ["--verbose", "--config", str(THIS_DIR / "empty.toml"), *args]
121 result = runner.invoke(black.main, args)
122 assert result.stdout_bytes is not None
123 assert result.stderr_bytes is not None
128 f"Failed with args: {args}\n"
129 f"stdout: {result.stdout_bytes.decode()!r}\n"
130 f"stderr: {result.stderr_bytes.decode()!r}\n"
131 f"exception: {result.exception}"
135 @patch("black.dump_to_file", dump_to_stderr)
136 def test_empty(self) -> None:
137 source = expected = ""
139 self.assertFormatEqual(expected, actual)
140 black.assert_equivalent(source, actual)
141 black.assert_stable(source, actual, DEFAULT_MODE)
143 def test_empty_ff(self) -> None:
145 tmp_file = Path(black.dump_to_file())
147 self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES))
148 with open(tmp_file, encoding="utf8") as f:
152 self.assertFormatEqual(expected, actual)
154 def test_piping(self) -> None:
155 source, expected = read_data("src/black/__init__", data=False)
156 result = BlackRunner().invoke(
158 ["-", "--fast", f"--line-length={black.DEFAULT_LINE_LENGTH}"],
159 input=BytesIO(source.encode("utf8")),
161 self.assertEqual(result.exit_code, 0)
162 self.assertFormatEqual(expected, result.output)
163 if source != result.output:
164 black.assert_equivalent(source, result.output)
165 black.assert_stable(source, result.output, DEFAULT_MODE)
167 def test_piping_diff(self) -> None:
168 diff_header = re.compile(
169 r"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d\d\d\d "
172 source, _ = read_data("expression.py")
173 expected, _ = read_data("expression.diff")
174 config = THIS_DIR / "data" / "empty_pyproject.toml"
178 f"--line-length={black.DEFAULT_LINE_LENGTH}",
180 f"--config={config}",
182 result = BlackRunner().invoke(
183 black.main, args, input=BytesIO(source.encode("utf8"))
185 self.assertEqual(result.exit_code, 0)
186 actual = diff_header.sub(DETERMINISTIC_HEADER, result.output)
187 actual = actual.rstrip() + "\n" # the diff output has a trailing space
188 self.assertEqual(expected, actual)
190 def test_piping_diff_with_color(self) -> None:
191 source, _ = read_data("expression.py")
192 config = THIS_DIR / "data" / "empty_pyproject.toml"
196 f"--line-length={black.DEFAULT_LINE_LENGTH}",
199 f"--config={config}",
201 result = BlackRunner().invoke(
202 black.main, args, input=BytesIO(source.encode("utf8"))
204 actual = result.output
205 # Again, the contents are checked in a different test, so only look for colors.
206 self.assertIn("\033[1;37m", actual)
207 self.assertIn("\033[36m", actual)
208 self.assertIn("\033[32m", actual)
209 self.assertIn("\033[31m", actual)
210 self.assertIn("\033[0m", actual)
212 @patch("black.dump_to_file", dump_to_stderr)
213 def _test_wip(self) -> None:
214 source, expected = read_data("wip")
215 sys.settrace(tracefunc)
218 experimental_string_processing=False,
219 target_versions={black.TargetVersion.PY38},
221 actual = fs(source, mode=mode)
223 self.assertFormatEqual(expected, actual)
224 black.assert_equivalent(source, actual)
225 black.assert_stable(source, actual, black.FileMode())
227 @unittest.expectedFailure
228 @patch("black.dump_to_file", dump_to_stderr)
229 def test_trailing_comma_optional_parens_stability1(self) -> None:
230 source, _expected = read_data("trailing_comma_optional_parens1")
232 black.assert_stable(source, actual, DEFAULT_MODE)
234 @unittest.expectedFailure
235 @patch("black.dump_to_file", dump_to_stderr)
236 def test_trailing_comma_optional_parens_stability2(self) -> None:
237 source, _expected = read_data("trailing_comma_optional_parens2")
239 black.assert_stable(source, actual, DEFAULT_MODE)
241 @unittest.expectedFailure
242 @patch("black.dump_to_file", dump_to_stderr)
243 def test_trailing_comma_optional_parens_stability3(self) -> None:
244 source, _expected = read_data("trailing_comma_optional_parens3")
246 black.assert_stable(source, actual, DEFAULT_MODE)
248 @patch("black.dump_to_file", dump_to_stderr)
249 def test_trailing_comma_optional_parens_stability1_pass2(self) -> None:
250 source, _expected = read_data("trailing_comma_optional_parens1")
251 actual = fs(fs(source)) # this is what `format_file_contents` does with --safe
252 black.assert_stable(source, actual, DEFAULT_MODE)
254 @patch("black.dump_to_file", dump_to_stderr)
255 def test_trailing_comma_optional_parens_stability2_pass2(self) -> None:
256 source, _expected = read_data("trailing_comma_optional_parens2")
257 actual = fs(fs(source)) # this is what `format_file_contents` does with --safe
258 black.assert_stable(source, actual, DEFAULT_MODE)
260 @patch("black.dump_to_file", dump_to_stderr)
261 def test_trailing_comma_optional_parens_stability3_pass2(self) -> None:
262 source, _expected = read_data("trailing_comma_optional_parens3")
263 actual = fs(fs(source)) # this is what `format_file_contents` does with --safe
264 black.assert_stable(source, actual, DEFAULT_MODE)
266 @patch("black.dump_to_file", dump_to_stderr)
267 def test_pep_572(self) -> None:
268 source, expected = read_data("pep_572")
270 self.assertFormatEqual(expected, actual)
271 black.assert_stable(source, actual, DEFAULT_MODE)
272 if sys.version_info >= (3, 8):
273 black.assert_equivalent(source, actual)
275 @patch("black.dump_to_file", dump_to_stderr)
276 def test_pep_572_remove_parens(self) -> None:
277 source, expected = read_data("pep_572_remove_parens")
279 self.assertFormatEqual(expected, actual)
280 black.assert_stable(source, actual, DEFAULT_MODE)
281 if sys.version_info >= (3, 8):
282 black.assert_equivalent(source, actual)
284 @patch("black.dump_to_file", dump_to_stderr)
285 def test_pep_572_do_not_remove_parens(self) -> None:
286 source, expected = read_data("pep_572_do_not_remove_parens")
287 # the AST safety checks will fail, but that's expected, just make sure no
288 # parentheses are touched
289 actual = black.format_str(source, mode=DEFAULT_MODE)
290 self.assertFormatEqual(expected, actual)
292 def test_pep_572_version_detection(self) -> None:
293 source, _ = read_data("pep_572")
294 root = black.lib2to3_parse(source)
295 features = black.get_features_used(root)
296 self.assertIn(black.Feature.ASSIGNMENT_EXPRESSIONS, features)
297 versions = black.detect_target_versions(root)
298 self.assertIn(black.TargetVersion.PY38, versions)
300 def test_expression_ff(self) -> None:
301 source, expected = read_data("expression")
302 tmp_file = Path(black.dump_to_file(source))
304 self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
305 with open(tmp_file, encoding="utf8") as f:
309 self.assertFormatEqual(expected, actual)
310 with patch("black.dump_to_file", dump_to_stderr):
311 black.assert_equivalent(source, actual)
312 black.assert_stable(source, actual, DEFAULT_MODE)
314 def test_expression_diff(self) -> None:
315 source, _ = read_data("expression.py")
316 config = THIS_DIR / "data" / "empty_pyproject.toml"
317 expected, _ = read_data("expression.diff")
318 tmp_file = Path(black.dump_to_file(source))
319 diff_header = re.compile(
320 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
321 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
324 result = BlackRunner().invoke(
325 black.main, ["--diff", str(tmp_file), f"--config={config}"]
327 self.assertEqual(result.exit_code, 0)
330 actual = result.output
331 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
332 if expected != actual:
333 dump = black.dump_to_file(actual)
335 "Expected diff isn't equal to the actual. If you made changes to"
336 " expression.py and this is an anticipated difference, overwrite"
337 f" tests/data/expression.diff with {dump}"
339 self.assertEqual(expected, actual, msg)
341 def test_expression_diff_with_color(self) -> None:
342 source, _ = read_data("expression.py")
343 config = THIS_DIR / "data" / "empty_pyproject.toml"
344 expected, _ = read_data("expression.diff")
345 tmp_file = Path(black.dump_to_file(source))
347 result = BlackRunner().invoke(
348 black.main, ["--diff", "--color", str(tmp_file), f"--config={config}"]
352 actual = result.output
353 # We check the contents of the diff in `test_expression_diff`. All
354 # we need to check here is that color codes exist in the result.
355 self.assertIn("\033[1;37m", actual)
356 self.assertIn("\033[36m", actual)
357 self.assertIn("\033[32m", actual)
358 self.assertIn("\033[31m", actual)
359 self.assertIn("\033[0m", actual)
361 @patch("black.dump_to_file", dump_to_stderr)
362 def test_pep_570(self) -> None:
363 source, expected = read_data("pep_570")
365 self.assertFormatEqual(expected, actual)
366 black.assert_stable(source, actual, DEFAULT_MODE)
367 if sys.version_info >= (3, 8):
368 black.assert_equivalent(source, actual)
370 def test_detect_pos_only_arguments(self) -> None:
371 source, _ = read_data("pep_570")
372 root = black.lib2to3_parse(source)
373 features = black.get_features_used(root)
374 self.assertIn(black.Feature.POS_ONLY_ARGUMENTS, features)
375 versions = black.detect_target_versions(root)
376 self.assertIn(black.TargetVersion.PY38, versions)
378 @patch("black.dump_to_file", dump_to_stderr)
379 def test_string_quotes(self) -> None:
380 source, expected = read_data("string_quotes")
381 mode = black.Mode(experimental_string_processing=True)
382 actual = fs(source, mode=mode)
383 self.assertFormatEqual(expected, actual)
384 black.assert_equivalent(source, actual)
385 black.assert_stable(source, actual, mode)
386 mode = replace(mode, string_normalization=False)
387 not_normalized = fs(source, mode=mode)
388 self.assertFormatEqual(source.replace("\\\n", ""), not_normalized)
389 black.assert_equivalent(source, not_normalized)
390 black.assert_stable(source, not_normalized, mode=mode)
392 @patch("black.dump_to_file", dump_to_stderr)
393 def test_docstring_no_string_normalization(self) -> None:
394 """Like test_docstring but with string normalization off."""
395 source, expected = read_data("docstring_no_string_normalization")
396 mode = replace(DEFAULT_MODE, string_normalization=False)
397 actual = fs(source, mode=mode)
398 self.assertFormatEqual(expected, actual)
399 black.assert_equivalent(source, actual)
400 black.assert_stable(source, actual, mode)
402 def test_long_strings_flag_disabled(self) -> None:
403 """Tests for turning off the string processing logic."""
404 source, expected = read_data("long_strings_flag_disabled")
405 mode = replace(DEFAULT_MODE, experimental_string_processing=False)
406 actual = fs(source, mode=mode)
407 self.assertFormatEqual(expected, actual)
408 black.assert_stable(expected, actual, mode)
410 @patch("black.dump_to_file", dump_to_stderr)
411 def test_numeric_literals(self) -> None:
412 source, expected = read_data("numeric_literals")
413 mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
414 actual = fs(source, mode=mode)
415 self.assertFormatEqual(expected, actual)
416 black.assert_equivalent(source, actual)
417 black.assert_stable(source, actual, mode)
419 @patch("black.dump_to_file", dump_to_stderr)
420 def test_numeric_literals_ignoring_underscores(self) -> None:
421 source, expected = read_data("numeric_literals_skip_underscores")
422 mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
423 actual = fs(source, mode=mode)
424 self.assertFormatEqual(expected, actual)
425 black.assert_equivalent(source, actual)
426 black.assert_stable(source, actual, mode)
428 def test_skip_magic_trailing_comma(self) -> None:
429 source, _ = read_data("expression.py")
430 expected, _ = read_data("expression_skip_magic_trailing_comma.diff")
431 tmp_file = Path(black.dump_to_file(source))
432 diff_header = re.compile(
433 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
434 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
437 result = BlackRunner().invoke(black.main, ["-C", "--diff", str(tmp_file)])
438 self.assertEqual(result.exit_code, 0)
441 actual = result.output
442 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
443 actual = actual.rstrip() + "\n" # the diff output has a trailing space
444 if expected != actual:
445 dump = black.dump_to_file(actual)
447 "Expected diff isn't equal to the actual. If you made changes to"
448 " expression.py and this is an anticipated difference, overwrite"
449 f" tests/data/expression_skip_magic_trailing_comma.diff with {dump}"
451 self.assertEqual(expected, actual, msg)
453 @pytest.mark.no_python2
454 def test_python2_should_fail_without_optional_install(self) -> None:
455 if sys.version_info < (3, 8):
457 "Python 3.6 and 3.7 will install typed-ast to work and as such will be"
458 " able to parse Python 2 syntax without explicitly specifying the"
463 tmp_file = Path(black.dump_to_file(source))
465 runner = BlackRunner()
466 result = runner.invoke(black.main, [str(tmp_file)])
467 self.assertEqual(result.exit_code, 123)
470 assert result.stderr_bytes is not None
472 result.stderr_bytes.decode()
479 "The requested source code has invalid Python 3 syntax."
480 "If you are trying to format Python 2 files please reinstall Black"
481 " with the 'python2' extra: `python3 -m pip install black[python2]`."
483 self.assertIn(msg, actual)
486 @patch("black.dump_to_file", dump_to_stderr)
487 def test_python2_print_function(self) -> None:
488 source, expected = read_data("python2_print_function")
489 mode = replace(DEFAULT_MODE, target_versions={TargetVersion.PY27})
490 actual = fs(source, mode=mode)
491 self.assertFormatEqual(expected, actual)
492 black.assert_equivalent(source, actual)
493 black.assert_stable(source, actual, mode)
495 @patch("black.dump_to_file", dump_to_stderr)
496 def test_stub(self) -> None:
497 mode = replace(DEFAULT_MODE, is_pyi=True)
498 source, expected = read_data("stub.pyi")
499 actual = fs(source, mode=mode)
500 self.assertFormatEqual(expected, actual)
501 black.assert_stable(source, actual, mode)
503 @patch("black.dump_to_file", dump_to_stderr)
504 def test_async_as_identifier(self) -> None:
505 source_path = (THIS_DIR / "data" / "async_as_identifier.py").resolve()
506 source, expected = read_data("async_as_identifier")
508 self.assertFormatEqual(expected, actual)
509 major, minor = sys.version_info[:2]
510 if major < 3 or (major <= 3 and minor < 7):
511 black.assert_equivalent(source, actual)
512 black.assert_stable(source, actual, DEFAULT_MODE)
513 # ensure black can parse this when the target is 3.6
514 self.invokeBlack([str(source_path), "--target-version", "py36"])
515 # but not on 3.7, because async/await is no longer an identifier
516 self.invokeBlack([str(source_path), "--target-version", "py37"], exit_code=123)
518 @patch("black.dump_to_file", dump_to_stderr)
519 def test_python37(self) -> None:
520 source_path = (THIS_DIR / "data" / "python37.py").resolve()
521 source, expected = read_data("python37")
523 self.assertFormatEqual(expected, actual)
524 major, minor = sys.version_info[:2]
525 if major > 3 or (major == 3 and minor >= 7):
526 black.assert_equivalent(source, actual)
527 black.assert_stable(source, actual, DEFAULT_MODE)
528 # ensure black can parse this when the target is 3.7
529 self.invokeBlack([str(source_path), "--target-version", "py37"])
530 # but not on 3.6, because we use async as a reserved keyword
531 self.invokeBlack([str(source_path), "--target-version", "py36"], exit_code=123)
533 @patch("black.dump_to_file", dump_to_stderr)
534 def test_python38(self) -> None:
535 source, expected = read_data("python38")
537 self.assertFormatEqual(expected, actual)
538 major, minor = sys.version_info[:2]
539 if major > 3 or (major == 3 and minor >= 8):
540 black.assert_equivalent(source, actual)
541 black.assert_stable(source, actual, DEFAULT_MODE)
543 @patch("black.dump_to_file", dump_to_stderr)
544 def test_python39(self) -> None:
545 source, expected = read_data("python39")
547 self.assertFormatEqual(expected, actual)
548 major, minor = sys.version_info[:2]
549 if major > 3 or (major == 3 and minor >= 9):
550 black.assert_equivalent(source, actual)
551 black.assert_stable(source, actual, DEFAULT_MODE)
553 def test_tab_comment_indentation(self) -> None:
554 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
555 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
556 self.assertFormatEqual(contents_spc, fs(contents_spc))
557 self.assertFormatEqual(contents_spc, fs(contents_tab))
559 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
560 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
561 self.assertFormatEqual(contents_spc, fs(contents_spc))
562 self.assertFormatEqual(contents_spc, fs(contents_tab))
564 # mixed tabs and spaces (valid Python 2 code)
565 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t# comment\n pass\n"
566 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
567 self.assertFormatEqual(contents_spc, fs(contents_spc))
568 self.assertFormatEqual(contents_spc, fs(contents_tab))
570 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t\t# comment\n pass\n"
571 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
572 self.assertFormatEqual(contents_spc, fs(contents_spc))
573 self.assertFormatEqual(contents_spc, fs(contents_tab))
575 def test_report_verbose(self) -> None:
576 report = Report(verbose=True)
580 def out(msg: str, **kwargs: Any) -> None:
581 out_lines.append(msg)
583 def err(msg: str, **kwargs: Any) -> None:
584 err_lines.append(msg)
586 with patch("black.output._out", out), patch("black.output._err", err):
587 report.done(Path("f1"), black.Changed.NO)
588 self.assertEqual(len(out_lines), 1)
589 self.assertEqual(len(err_lines), 0)
590 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
591 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
592 self.assertEqual(report.return_code, 0)
593 report.done(Path("f2"), black.Changed.YES)
594 self.assertEqual(len(out_lines), 2)
595 self.assertEqual(len(err_lines), 0)
596 self.assertEqual(out_lines[-1], "reformatted f2")
598 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
600 report.done(Path("f3"), black.Changed.CACHED)
601 self.assertEqual(len(out_lines), 3)
602 self.assertEqual(len(err_lines), 0)
604 out_lines[-1], "f3 wasn't modified on disk since last run."
607 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
609 self.assertEqual(report.return_code, 0)
611 self.assertEqual(report.return_code, 1)
613 report.failed(Path("e1"), "boom")
614 self.assertEqual(len(out_lines), 3)
615 self.assertEqual(len(err_lines), 1)
616 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
618 unstyle(str(report)),
619 "1 file reformatted, 2 files left unchanged, 1 file failed to"
622 self.assertEqual(report.return_code, 123)
623 report.done(Path("f3"), black.Changed.YES)
624 self.assertEqual(len(out_lines), 4)
625 self.assertEqual(len(err_lines), 1)
626 self.assertEqual(out_lines[-1], "reformatted f3")
628 unstyle(str(report)),
629 "2 files reformatted, 2 files left unchanged, 1 file failed to"
632 self.assertEqual(report.return_code, 123)
633 report.failed(Path("e2"), "boom")
634 self.assertEqual(len(out_lines), 4)
635 self.assertEqual(len(err_lines), 2)
636 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
638 unstyle(str(report)),
639 "2 files reformatted, 2 files left unchanged, 2 files failed to"
642 self.assertEqual(report.return_code, 123)
643 report.path_ignored(Path("wat"), "no match")
644 self.assertEqual(len(out_lines), 5)
645 self.assertEqual(len(err_lines), 2)
646 self.assertEqual(out_lines[-1], "wat ignored: no match")
648 unstyle(str(report)),
649 "2 files reformatted, 2 files left unchanged, 2 files failed to"
652 self.assertEqual(report.return_code, 123)
653 report.done(Path("f4"), black.Changed.NO)
654 self.assertEqual(len(out_lines), 6)
655 self.assertEqual(len(err_lines), 2)
656 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
658 unstyle(str(report)),
659 "2 files reformatted, 3 files left unchanged, 2 files failed to"
662 self.assertEqual(report.return_code, 123)
665 unstyle(str(report)),
666 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
667 " would fail to reformat.",
672 unstyle(str(report)),
673 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
674 " would fail to reformat.",
677 def test_report_quiet(self) -> None:
678 report = Report(quiet=True)
682 def out(msg: str, **kwargs: Any) -> None:
683 out_lines.append(msg)
685 def err(msg: str, **kwargs: Any) -> None:
686 err_lines.append(msg)
688 with patch("black.output._out", out), patch("black.output._err", err):
689 report.done(Path("f1"), black.Changed.NO)
690 self.assertEqual(len(out_lines), 0)
691 self.assertEqual(len(err_lines), 0)
692 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
693 self.assertEqual(report.return_code, 0)
694 report.done(Path("f2"), black.Changed.YES)
695 self.assertEqual(len(out_lines), 0)
696 self.assertEqual(len(err_lines), 0)
698 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
700 report.done(Path("f3"), black.Changed.CACHED)
701 self.assertEqual(len(out_lines), 0)
702 self.assertEqual(len(err_lines), 0)
704 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
706 self.assertEqual(report.return_code, 0)
708 self.assertEqual(report.return_code, 1)
710 report.failed(Path("e1"), "boom")
711 self.assertEqual(len(out_lines), 0)
712 self.assertEqual(len(err_lines), 1)
713 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
715 unstyle(str(report)),
716 "1 file reformatted, 2 files left unchanged, 1 file failed to"
719 self.assertEqual(report.return_code, 123)
720 report.done(Path("f3"), black.Changed.YES)
721 self.assertEqual(len(out_lines), 0)
722 self.assertEqual(len(err_lines), 1)
724 unstyle(str(report)),
725 "2 files reformatted, 2 files left unchanged, 1 file failed to"
728 self.assertEqual(report.return_code, 123)
729 report.failed(Path("e2"), "boom")
730 self.assertEqual(len(out_lines), 0)
731 self.assertEqual(len(err_lines), 2)
732 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
734 unstyle(str(report)),
735 "2 files reformatted, 2 files left unchanged, 2 files failed to"
738 self.assertEqual(report.return_code, 123)
739 report.path_ignored(Path("wat"), "no match")
740 self.assertEqual(len(out_lines), 0)
741 self.assertEqual(len(err_lines), 2)
743 unstyle(str(report)),
744 "2 files reformatted, 2 files left unchanged, 2 files failed to"
747 self.assertEqual(report.return_code, 123)
748 report.done(Path("f4"), black.Changed.NO)
749 self.assertEqual(len(out_lines), 0)
750 self.assertEqual(len(err_lines), 2)
752 unstyle(str(report)),
753 "2 files reformatted, 3 files left unchanged, 2 files failed to"
756 self.assertEqual(report.return_code, 123)
759 unstyle(str(report)),
760 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
761 " would fail to reformat.",
766 unstyle(str(report)),
767 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
768 " would fail to reformat.",
771 def test_report_normal(self) -> None:
772 report = black.Report()
776 def out(msg: str, **kwargs: Any) -> None:
777 out_lines.append(msg)
779 def err(msg: str, **kwargs: Any) -> None:
780 err_lines.append(msg)
782 with patch("black.output._out", out), patch("black.output._err", err):
783 report.done(Path("f1"), black.Changed.NO)
784 self.assertEqual(len(out_lines), 0)
785 self.assertEqual(len(err_lines), 0)
786 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
787 self.assertEqual(report.return_code, 0)
788 report.done(Path("f2"), black.Changed.YES)
789 self.assertEqual(len(out_lines), 1)
790 self.assertEqual(len(err_lines), 0)
791 self.assertEqual(out_lines[-1], "reformatted f2")
793 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
795 report.done(Path("f3"), black.Changed.CACHED)
796 self.assertEqual(len(out_lines), 1)
797 self.assertEqual(len(err_lines), 0)
798 self.assertEqual(out_lines[-1], "reformatted f2")
800 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
802 self.assertEqual(report.return_code, 0)
804 self.assertEqual(report.return_code, 1)
806 report.failed(Path("e1"), "boom")
807 self.assertEqual(len(out_lines), 1)
808 self.assertEqual(len(err_lines), 1)
809 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
811 unstyle(str(report)),
812 "1 file reformatted, 2 files left unchanged, 1 file failed to"
815 self.assertEqual(report.return_code, 123)
816 report.done(Path("f3"), black.Changed.YES)
817 self.assertEqual(len(out_lines), 2)
818 self.assertEqual(len(err_lines), 1)
819 self.assertEqual(out_lines[-1], "reformatted f3")
821 unstyle(str(report)),
822 "2 files reformatted, 2 files left unchanged, 1 file failed to"
825 self.assertEqual(report.return_code, 123)
826 report.failed(Path("e2"), "boom")
827 self.assertEqual(len(out_lines), 2)
828 self.assertEqual(len(err_lines), 2)
829 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
831 unstyle(str(report)),
832 "2 files reformatted, 2 files left unchanged, 2 files failed to"
835 self.assertEqual(report.return_code, 123)
836 report.path_ignored(Path("wat"), "no match")
837 self.assertEqual(len(out_lines), 2)
838 self.assertEqual(len(err_lines), 2)
840 unstyle(str(report)),
841 "2 files reformatted, 2 files left unchanged, 2 files failed to"
844 self.assertEqual(report.return_code, 123)
845 report.done(Path("f4"), black.Changed.NO)
846 self.assertEqual(len(out_lines), 2)
847 self.assertEqual(len(err_lines), 2)
849 unstyle(str(report)),
850 "2 files reformatted, 3 files left unchanged, 2 files failed to"
853 self.assertEqual(report.return_code, 123)
856 unstyle(str(report)),
857 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
858 " would fail to reformat.",
863 unstyle(str(report)),
864 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
865 " would fail to reformat.",
868 def test_lib2to3_parse(self) -> None:
869 with self.assertRaises(black.InvalidInput):
870 black.lib2to3_parse("invalid syntax")
873 black.lib2to3_parse(straddling)
874 black.lib2to3_parse(straddling, {TargetVersion.PY27})
875 black.lib2to3_parse(straddling, {TargetVersion.PY36})
876 black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
879 black.lib2to3_parse(py2_only)
880 black.lib2to3_parse(py2_only, {TargetVersion.PY27})
881 with self.assertRaises(black.InvalidInput):
882 black.lib2to3_parse(py2_only, {TargetVersion.PY36})
883 with self.assertRaises(black.InvalidInput):
884 black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
886 py3_only = "exec(x, end=y)"
887 black.lib2to3_parse(py3_only)
888 with self.assertRaises(black.InvalidInput):
889 black.lib2to3_parse(py3_only, {TargetVersion.PY27})
890 black.lib2to3_parse(py3_only, {TargetVersion.PY36})
891 black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
893 def test_get_features_used_decorator(self) -> None:
894 # Test the feature detection of new decorator syntax
895 # since this makes some test cases of test_get_features_used()
896 # fails if it fails, this is tested first so that a useful case
898 simples, relaxed = read_data("decorators")
899 # skip explanation comments at the top of the file
900 for simple_test in simples.split("##")[1:]:
901 node = black.lib2to3_parse(simple_test)
902 decorator = str(node.children[0].children[0]).strip()
904 Feature.RELAXED_DECORATORS,
905 black.get_features_used(node),
907 f"decorator '{decorator}' follows python<=3.8 syntax"
908 "but is detected as 3.9+"
909 # f"The full node is\n{node!r}"
912 # skip the '# output' comment at the top of the output part
913 for relaxed_test in relaxed.split("##")[1:]:
914 node = black.lib2to3_parse(relaxed_test)
915 decorator = str(node.children[0].children[0]).strip()
917 Feature.RELAXED_DECORATORS,
918 black.get_features_used(node),
920 f"decorator '{decorator}' uses python3.9+ syntax"
921 "but is detected as python<=3.8"
922 # f"The full node is\n{node!r}"
926 def test_get_features_used(self) -> None:
927 node = black.lib2to3_parse("def f(*, arg): ...\n")
928 self.assertEqual(black.get_features_used(node), set())
929 node = black.lib2to3_parse("def f(*, arg,): ...\n")
930 self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
931 node = black.lib2to3_parse("f(*arg,)\n")
933 black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
935 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
936 self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
937 node = black.lib2to3_parse("123_456\n")
938 self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
939 node = black.lib2to3_parse("123456\n")
940 self.assertEqual(black.get_features_used(node), set())
941 source, expected = read_data("function")
942 node = black.lib2to3_parse(source)
943 expected_features = {
944 Feature.TRAILING_COMMA_IN_CALL,
945 Feature.TRAILING_COMMA_IN_DEF,
948 self.assertEqual(black.get_features_used(node), expected_features)
949 node = black.lib2to3_parse(expected)
950 self.assertEqual(black.get_features_used(node), expected_features)
951 source, expected = read_data("expression")
952 node = black.lib2to3_parse(source)
953 self.assertEqual(black.get_features_used(node), set())
954 node = black.lib2to3_parse(expected)
955 self.assertEqual(black.get_features_used(node), set())
957 def test_get_future_imports(self) -> None:
958 node = black.lib2to3_parse("\n")
959 self.assertEqual(set(), black.get_future_imports(node))
960 node = black.lib2to3_parse("from __future__ import black\n")
961 self.assertEqual({"black"}, black.get_future_imports(node))
962 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
963 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
964 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
965 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
966 node = black.lib2to3_parse(
967 "from __future__ import multiple\nfrom __future__ import imports\n"
969 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
970 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
971 self.assertEqual({"black"}, black.get_future_imports(node))
972 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
973 self.assertEqual({"black"}, black.get_future_imports(node))
974 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
975 self.assertEqual(set(), black.get_future_imports(node))
976 node = black.lib2to3_parse("from some.module import black\n")
977 self.assertEqual(set(), black.get_future_imports(node))
978 node = black.lib2to3_parse(
979 "from __future__ import unicode_literals as _unicode_literals"
981 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
982 node = black.lib2to3_parse(
983 "from __future__ import unicode_literals as _lol, print"
985 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
987 def test_debug_visitor(self) -> None:
988 source, _ = read_data("debug_visitor.py")
989 expected, _ = read_data("debug_visitor.out")
993 def out(msg: str, **kwargs: Any) -> None:
994 out_lines.append(msg)
996 def err(msg: str, **kwargs: Any) -> None:
997 err_lines.append(msg)
999 with patch("black.debug.out", out):
1000 DebugVisitor.show(source)
1001 actual = "\n".join(out_lines) + "\n"
1003 if expected != actual:
1004 log_name = black.dump_to_file(*out_lines)
1008 f"AST print out is different. Actual version dumped to {log_name}",
1011 def test_format_file_contents(self) -> None:
1014 with self.assertRaises(black.NothingChanged):
1015 black.format_file_contents(empty, mode=mode, fast=False)
1017 with self.assertRaises(black.NothingChanged):
1018 black.format_file_contents(just_nl, mode=mode, fast=False)
1019 same = "j = [1, 2, 3]\n"
1020 with self.assertRaises(black.NothingChanged):
1021 black.format_file_contents(same, mode=mode, fast=False)
1022 different = "j = [1,2,3]"
1024 actual = black.format_file_contents(different, mode=mode, fast=False)
1025 self.assertEqual(expected, actual)
1026 invalid = "return if you can"
1027 with self.assertRaises(black.InvalidInput) as e:
1028 black.format_file_contents(invalid, mode=mode, fast=False)
1029 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
1031 def test_endmarker(self) -> None:
1032 n = black.lib2to3_parse("\n")
1033 self.assertEqual(n.type, black.syms.file_input)
1034 self.assertEqual(len(n.children), 1)
1035 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
1037 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
1038 def test_assertFormatEqual(self) -> None:
1042 def out(msg: str, **kwargs: Any) -> None:
1043 out_lines.append(msg)
1045 def err(msg: str, **kwargs: Any) -> None:
1046 err_lines.append(msg)
1048 with patch("black.output._out", out), patch("black.output._err", err):
1049 with self.assertRaises(AssertionError):
1050 self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")
1052 out_str = "".join(out_lines)
1053 self.assertTrue("Expected tree:" in out_str)
1054 self.assertTrue("Actual tree:" in out_str)
1055 self.assertEqual("".join(err_lines), "")
1057 def test_cache_broken_file(self) -> None:
1059 with cache_dir() as workspace:
1060 cache_file = get_cache_file(mode)
1061 with cache_file.open("w") as fobj:
1062 fobj.write("this is not a pickle")
1063 self.assertEqual(black.read_cache(mode), {})
1064 src = (workspace / "test.py").resolve()
1065 with src.open("w") as fobj:
1066 fobj.write("print('hello')")
1067 self.invokeBlack([str(src)])
1068 cache = black.read_cache(mode)
1069 self.assertIn(str(src), cache)
1071 def test_cache_single_file_already_cached(self) -> None:
1073 with cache_dir() as workspace:
1074 src = (workspace / "test.py").resolve()
1075 with src.open("w") as fobj:
1076 fobj.write("print('hello')")
1077 black.write_cache({}, [src], mode)
1078 self.invokeBlack([str(src)])
1079 with src.open("r") as fobj:
1080 self.assertEqual(fobj.read(), "print('hello')")
1083 def test_cache_multiple_files(self) -> None:
1085 with cache_dir() as workspace, patch(
1086 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1088 one = (workspace / "one.py").resolve()
1089 with one.open("w") as fobj:
1090 fobj.write("print('hello')")
1091 two = (workspace / "two.py").resolve()
1092 with two.open("w") as fobj:
1093 fobj.write("print('hello')")
1094 black.write_cache({}, [one], mode)
1095 self.invokeBlack([str(workspace)])
1096 with one.open("r") as fobj:
1097 self.assertEqual(fobj.read(), "print('hello')")
1098 with two.open("r") as fobj:
1099 self.assertEqual(fobj.read(), 'print("hello")\n')
1100 cache = black.read_cache(mode)
1101 self.assertIn(str(one), cache)
1102 self.assertIn(str(two), cache)
1104 def test_no_cache_when_writeback_diff(self) -> None:
1106 with cache_dir() as workspace:
1107 src = (workspace / "test.py").resolve()
1108 with src.open("w") as fobj:
1109 fobj.write("print('hello')")
1110 with patch("black.read_cache") as read_cache, patch(
1113 self.invokeBlack([str(src), "--diff"])
1114 cache_file = get_cache_file(mode)
1115 self.assertFalse(cache_file.exists())
1116 write_cache.assert_not_called()
1117 read_cache.assert_not_called()
1119 def test_no_cache_when_writeback_color_diff(self) -> None:
1121 with cache_dir() as workspace:
1122 src = (workspace / "test.py").resolve()
1123 with src.open("w") as fobj:
1124 fobj.write("print('hello')")
1125 with patch("black.read_cache") as read_cache, patch(
1128 self.invokeBlack([str(src), "--diff", "--color"])
1129 cache_file = get_cache_file(mode)
1130 self.assertFalse(cache_file.exists())
1131 write_cache.assert_not_called()
1132 read_cache.assert_not_called()
1135 def test_output_locking_when_writeback_diff(self) -> None:
1136 with cache_dir() as workspace:
1137 for tag in range(0, 4):
1138 src = (workspace / f"test{tag}.py").resolve()
1139 with src.open("w") as fobj:
1140 fobj.write("print('hello')")
1141 with patch("black.Manager", wraps=multiprocessing.Manager) as mgr:
1142 self.invokeBlack(["--diff", str(workspace)], exit_code=0)
1143 # this isn't quite doing what we want, but if it _isn't_
1144 # called then we cannot be using the lock it provides
1148 def test_output_locking_when_writeback_color_diff(self) -> None:
1149 with cache_dir() as workspace:
1150 for tag in range(0, 4):
1151 src = (workspace / f"test{tag}.py").resolve()
1152 with src.open("w") as fobj:
1153 fobj.write("print('hello')")
1154 with patch("black.Manager", wraps=multiprocessing.Manager) as mgr:
1155 self.invokeBlack(["--diff", "--color", str(workspace)], exit_code=0)
1156 # this isn't quite doing what we want, but if it _isn't_
1157 # called then we cannot be using the lock it provides
1160 def test_no_cache_when_stdin(self) -> None:
1163 result = CliRunner().invoke(
1164 black.main, ["-"], input=BytesIO(b"print('hello')")
1166 self.assertEqual(result.exit_code, 0)
1167 cache_file = get_cache_file(mode)
1168 self.assertFalse(cache_file.exists())
1170 def test_read_cache_no_cachefile(self) -> None:
1173 self.assertEqual(black.read_cache(mode), {})
1175 def test_write_cache_read_cache(self) -> None:
1177 with cache_dir() as workspace:
1178 src = (workspace / "test.py").resolve()
1180 black.write_cache({}, [src], mode)
1181 cache = black.read_cache(mode)
1182 self.assertIn(str(src), cache)
1183 self.assertEqual(cache[str(src)], black.get_cache_info(src))
1185 def test_filter_cached(self) -> None:
1186 with TemporaryDirectory() as workspace:
1187 path = Path(workspace)
1188 uncached = (path / "uncached").resolve()
1189 cached = (path / "cached").resolve()
1190 cached_but_changed = (path / "changed").resolve()
1193 cached_but_changed.touch()
1195 str(cached): black.get_cache_info(cached),
1196 str(cached_but_changed): (0.0, 0),
1198 todo, done = black.filter_cached(
1199 cache, {uncached, cached, cached_but_changed}
1201 self.assertEqual(todo, {uncached, cached_but_changed})
1202 self.assertEqual(done, {cached})
1204 def test_write_cache_creates_directory_if_needed(self) -> None:
1206 with cache_dir(exists=False) as workspace:
1207 self.assertFalse(workspace.exists())
1208 black.write_cache({}, [], mode)
1209 self.assertTrue(workspace.exists())
1212 def test_failed_formatting_does_not_get_cached(self) -> None:
1214 with cache_dir() as workspace, patch(
1215 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1217 failing = (workspace / "failing.py").resolve()
1218 with failing.open("w") as fobj:
1219 fobj.write("not actually python")
1220 clean = (workspace / "clean.py").resolve()
1221 with clean.open("w") as fobj:
1222 fobj.write('print("hello")\n')
1223 self.invokeBlack([str(workspace)], exit_code=123)
1224 cache = black.read_cache(mode)
1225 self.assertNotIn(str(failing), cache)
1226 self.assertIn(str(clean), cache)
1228 def test_write_cache_write_fail(self) -> None:
1230 with cache_dir(), patch.object(Path, "open") as mock:
1231 mock.side_effect = OSError
1232 black.write_cache({}, [], mode)
1235 @patch("black.ProcessPoolExecutor", MagicMock(side_effect=OSError))
1236 def test_works_in_mono_process_only_environment(self) -> None:
1237 with cache_dir() as workspace:
1239 (workspace / "one.py").resolve(),
1240 (workspace / "two.py").resolve(),
1242 f.write_text('print("hello")\n')
1243 self.invokeBlack([str(workspace)])
1246 def test_check_diff_use_together(self) -> None:
1248 # Files which will be reformatted.
1249 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1250 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1251 # Files which will not be reformatted.
1252 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1253 self.invokeBlack([str(src2), "--diff", "--check"])
1254 # Multi file command.
1255 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1257 def test_no_files(self) -> None:
1259 # Without an argument, black exits with error code 0.
1260 self.invokeBlack([])
1262 def test_broken_symlink(self) -> None:
1263 with cache_dir() as workspace:
1264 symlink = workspace / "broken_link.py"
1266 symlink.symlink_to("nonexistent.py")
1267 except OSError as e:
1268 self.skipTest(f"Can't create symlinks: {e}")
1269 self.invokeBlack([str(workspace.resolve())])
1271 def test_read_cache_line_lengths(self) -> None:
1273 short_mode = replace(DEFAULT_MODE, line_length=1)
1274 with cache_dir() as workspace:
1275 path = (workspace / "file.py").resolve()
1277 black.write_cache({}, [path], mode)
1278 one = black.read_cache(mode)
1279 self.assertIn(str(path), one)
1280 two = black.read_cache(short_mode)
1281 self.assertNotIn(str(path), two)
1283 def test_single_file_force_pyi(self) -> None:
1284 pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
1285 contents, expected = read_data("force_pyi")
1286 with cache_dir() as workspace:
1287 path = (workspace / "file.py").resolve()
1288 with open(path, "w") as fh:
1290 self.invokeBlack([str(path), "--pyi"])
1291 with open(path, "r") as fh:
1293 # verify cache with --pyi is separate
1294 pyi_cache = black.read_cache(pyi_mode)
1295 self.assertIn(str(path), pyi_cache)
1296 normal_cache = black.read_cache(DEFAULT_MODE)
1297 self.assertNotIn(str(path), normal_cache)
1298 self.assertFormatEqual(expected, actual)
1299 black.assert_equivalent(contents, actual)
1300 black.assert_stable(contents, actual, pyi_mode)
1303 def test_multi_file_force_pyi(self) -> None:
1304 reg_mode = DEFAULT_MODE
1305 pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
1306 contents, expected = read_data("force_pyi")
1307 with cache_dir() as workspace:
1309 (workspace / "file1.py").resolve(),
1310 (workspace / "file2.py").resolve(),
1313 with open(path, "w") as fh:
1315 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1317 with open(path, "r") as fh:
1319 self.assertEqual(actual, expected)
1320 # verify cache with --pyi is separate
1321 pyi_cache = black.read_cache(pyi_mode)
1322 normal_cache = black.read_cache(reg_mode)
1324 self.assertIn(str(path), pyi_cache)
1325 self.assertNotIn(str(path), normal_cache)
1327 def test_pipe_force_pyi(self) -> None:
1328 source, expected = read_data("force_pyi")
1329 result = CliRunner().invoke(
1330 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1332 self.assertEqual(result.exit_code, 0)
1333 actual = result.output
1334 self.assertFormatEqual(actual, expected)
1336 def test_single_file_force_py36(self) -> None:
1337 reg_mode = DEFAULT_MODE
1338 py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
1339 source, expected = read_data("force_py36")
1340 with cache_dir() as workspace:
1341 path = (workspace / "file.py").resolve()
1342 with open(path, "w") as fh:
1344 self.invokeBlack([str(path), *PY36_ARGS])
1345 with open(path, "r") as fh:
1347 # verify cache with --target-version is separate
1348 py36_cache = black.read_cache(py36_mode)
1349 self.assertIn(str(path), py36_cache)
1350 normal_cache = black.read_cache(reg_mode)
1351 self.assertNotIn(str(path), normal_cache)
1352 self.assertEqual(actual, expected)
1355 def test_multi_file_force_py36(self) -> None:
1356 reg_mode = DEFAULT_MODE
1357 py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
1358 source, expected = read_data("force_py36")
1359 with cache_dir() as workspace:
1361 (workspace / "file1.py").resolve(),
1362 (workspace / "file2.py").resolve(),
1365 with open(path, "w") as fh:
1367 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1369 with open(path, "r") as fh:
1371 self.assertEqual(actual, expected)
1372 # verify cache with --target-version is separate
1373 pyi_cache = black.read_cache(py36_mode)
1374 normal_cache = black.read_cache(reg_mode)
1376 self.assertIn(str(path), pyi_cache)
1377 self.assertNotIn(str(path), normal_cache)
1379 def test_pipe_force_py36(self) -> None:
1380 source, expected = read_data("force_py36")
1381 result = CliRunner().invoke(
1383 ["-", "-q", "--target-version=py36"],
1384 input=BytesIO(source.encode("utf8")),
1386 self.assertEqual(result.exit_code, 0)
1387 actual = result.output
1388 self.assertFormatEqual(actual, expected)
1390 def test_include_exclude(self) -> None:
1391 path = THIS_DIR / "data" / "include_exclude_tests"
1392 include = re.compile(r"\.pyi?$")
1393 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1394 report = black.Report()
1395 gitignore = PathSpec.from_lines("gitwildmatch", [])
1396 sources: List[Path] = []
1398 Path(path / "b/dont_exclude/a.py"),
1399 Path(path / "b/dont_exclude/a.pyi"),
1401 this_abs = THIS_DIR.resolve()
1403 black.gen_python_files(
1414 self.assertEqual(sorted(expected), sorted(sources))
1416 def test_gitignore_used_as_default(self) -> None:
1417 path = Path(THIS_DIR / "data" / "include_exclude_tests")
1418 include = re.compile(r"\.pyi?$")
1419 extend_exclude = re.compile(r"/exclude/")
1420 src = str(path / "b/")
1421 report = black.Report()
1422 expected: List[Path] = [
1423 path / "b/.definitely_exclude/a.py",
1424 path / "b/.definitely_exclude/a.pyi",
1434 extend_exclude=extend_exclude,
1437 stdin_filename=None,
1440 self.assertEqual(sorted(expected), sorted(sources))
1442 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1443 def test_exclude_for_issue_1572(self) -> None:
1444 # Exclude shouldn't touch files that were explicitly given to Black through the
1445 # CLI. Exclude is supposed to only apply to the recursive discovery of files.
1446 # https://github.com/psf/black/issues/1572
1447 path = THIS_DIR / "data" / "include_exclude_tests"
1449 exclude = r"/exclude/|a\.py"
1450 src = str(path / "b/exclude/a.py")
1451 report = black.Report()
1452 expected = [Path(path / "b/exclude/a.py")]
1459 include=re.compile(include),
1460 exclude=re.compile(exclude),
1461 extend_exclude=None,
1464 stdin_filename=None,
1467 self.assertEqual(sorted(expected), sorted(sources))
1469 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1470 def test_get_sources_with_stdin(self) -> None:
1472 exclude = r"/exclude/|a\.py"
1474 report = black.Report()
1475 expected = [Path("-")]
1482 include=re.compile(include),
1483 exclude=re.compile(exclude),
1484 extend_exclude=None,
1487 stdin_filename=None,
1490 self.assertEqual(sorted(expected), sorted(sources))
1492 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1493 def test_get_sources_with_stdin_filename(self) -> None:
1495 exclude = r"/exclude/|a\.py"
1497 report = black.Report()
1498 stdin_filename = str(THIS_DIR / "data/collections.py")
1499 expected = [Path(f"__BLACK_STDIN_FILENAME__{stdin_filename}")]
1506 include=re.compile(include),
1507 exclude=re.compile(exclude),
1508 extend_exclude=None,
1511 stdin_filename=stdin_filename,
1514 self.assertEqual(sorted(expected), sorted(sources))
1516 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1517 def test_get_sources_with_stdin_filename_and_exclude(self) -> None:
1518 # Exclude shouldn't exclude stdin_filename since it is mimicking the
1519 # file being passed directly. This is the same as
1520 # test_exclude_for_issue_1572
1521 path = THIS_DIR / "data" / "include_exclude_tests"
1523 exclude = r"/exclude/|a\.py"
1525 report = black.Report()
1526 stdin_filename = str(path / "b/exclude/a.py")
1527 expected = [Path(f"__BLACK_STDIN_FILENAME__{stdin_filename}")]
1534 include=re.compile(include),
1535 exclude=re.compile(exclude),
1536 extend_exclude=None,
1539 stdin_filename=stdin_filename,
1542 self.assertEqual(sorted(expected), sorted(sources))
1544 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1545 def test_get_sources_with_stdin_filename_and_extend_exclude(self) -> None:
1546 # Extend exclude shouldn't exclude stdin_filename since it is mimicking the
1547 # file being passed directly. This is the same as
1548 # test_exclude_for_issue_1572
1549 path = THIS_DIR / "data" / "include_exclude_tests"
1551 extend_exclude = r"/exclude/|a\.py"
1553 report = black.Report()
1554 stdin_filename = str(path / "b/exclude/a.py")
1555 expected = [Path(f"__BLACK_STDIN_FILENAME__{stdin_filename}")]
1562 include=re.compile(include),
1563 exclude=re.compile(""),
1564 extend_exclude=re.compile(extend_exclude),
1567 stdin_filename=stdin_filename,
1570 self.assertEqual(sorted(expected), sorted(sources))
1572 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1573 def test_get_sources_with_stdin_filename_and_force_exclude(self) -> None:
1574 # Force exclude should exclude the file when passing it through
1576 path = THIS_DIR / "data" / "include_exclude_tests"
1578 force_exclude = r"/exclude/|a\.py"
1580 report = black.Report()
1581 stdin_filename = str(path / "b/exclude/a.py")
1588 include=re.compile(include),
1589 exclude=re.compile(""),
1590 extend_exclude=None,
1591 force_exclude=re.compile(force_exclude),
1593 stdin_filename=stdin_filename,
1596 self.assertEqual([], sorted(sources))
1598 def test_reformat_one_with_stdin(self) -> None:
1600 "black.format_stdin_to_stdout",
1601 return_value=lambda *args, **kwargs: black.Changed.YES,
1603 report = MagicMock()
1608 write_back=black.WriteBack.YES,
1612 fsts.assert_called_once()
1613 report.done.assert_called_with(path, black.Changed.YES)
1615 def test_reformat_one_with_stdin_filename(self) -> None:
1617 "black.format_stdin_to_stdout",
1618 return_value=lambda *args, **kwargs: black.Changed.YES,
1620 report = MagicMock()
1622 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1627 write_back=black.WriteBack.YES,
1631 fsts.assert_called_once_with(
1632 fast=True, write_back=black.WriteBack.YES, mode=DEFAULT_MODE
1634 # __BLACK_STDIN_FILENAME__ should have been stripped
1635 report.done.assert_called_with(expected, black.Changed.YES)
1637 def test_reformat_one_with_stdin_filename_pyi(self) -> None:
1639 "black.format_stdin_to_stdout",
1640 return_value=lambda *args, **kwargs: black.Changed.YES,
1642 report = MagicMock()
1644 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1649 write_back=black.WriteBack.YES,
1653 fsts.assert_called_once_with(
1655 write_back=black.WriteBack.YES,
1656 mode=replace(DEFAULT_MODE, is_pyi=True),
1658 # __BLACK_STDIN_FILENAME__ should have been stripped
1659 report.done.assert_called_with(expected, black.Changed.YES)
1661 def test_reformat_one_with_stdin_and_existing_path(self) -> None:
1663 "black.format_stdin_to_stdout",
1664 return_value=lambda *args, **kwargs: black.Changed.YES,
1666 report = MagicMock()
1667 # Even with an existing file, since we are forcing stdin, black
1668 # should output to stdout and not modify the file inplace
1669 p = Path(str(THIS_DIR / "data/collections.py"))
1670 # Make sure is_file actually returns True
1671 self.assertTrue(p.is_file())
1672 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1677 write_back=black.WriteBack.YES,
1681 fsts.assert_called_once()
1682 # __BLACK_STDIN_FILENAME__ should have been stripped
1683 report.done.assert_called_with(expected, black.Changed.YES)
1685 def test_gitignore_exclude(self) -> None:
1686 path = THIS_DIR / "data" / "include_exclude_tests"
1687 include = re.compile(r"\.pyi?$")
1688 exclude = re.compile(r"")
1689 report = black.Report()
1690 gitignore = PathSpec.from_lines(
1691 "gitwildmatch", ["exclude/", ".definitely_exclude"]
1693 sources: List[Path] = []
1695 Path(path / "b/dont_exclude/a.py"),
1696 Path(path / "b/dont_exclude/a.pyi"),
1698 this_abs = THIS_DIR.resolve()
1700 black.gen_python_files(
1711 self.assertEqual(sorted(expected), sorted(sources))
1713 def test_nested_gitignore(self) -> None:
1714 path = Path(THIS_DIR / "data" / "nested_gitignore_tests")
1715 include = re.compile(r"\.pyi?$")
1716 exclude = re.compile(r"")
1717 root_gitignore = black.files.get_gitignore(path)
1718 report = black.Report()
1719 expected: List[Path] = [
1720 Path(path / "x.py"),
1721 Path(path / "root/b.py"),
1722 Path(path / "root/c.py"),
1723 Path(path / "root/child/c.py"),
1725 this_abs = THIS_DIR.resolve()
1727 black.gen_python_files(
1738 self.assertEqual(sorted(expected), sorted(sources))
1740 def test_empty_include(self) -> None:
1741 path = THIS_DIR / "data" / "include_exclude_tests"
1742 report = black.Report()
1743 gitignore = PathSpec.from_lines("gitwildmatch", [])
1744 empty = re.compile(r"")
1745 sources: List[Path] = []
1747 Path(path / "b/exclude/a.pie"),
1748 Path(path / "b/exclude/a.py"),
1749 Path(path / "b/exclude/a.pyi"),
1750 Path(path / "b/dont_exclude/a.pie"),
1751 Path(path / "b/dont_exclude/a.py"),
1752 Path(path / "b/dont_exclude/a.pyi"),
1753 Path(path / "b/.definitely_exclude/a.pie"),
1754 Path(path / "b/.definitely_exclude/a.py"),
1755 Path(path / "b/.definitely_exclude/a.pyi"),
1756 Path(path / ".gitignore"),
1757 Path(path / "pyproject.toml"),
1759 this_abs = THIS_DIR.resolve()
1761 black.gen_python_files(
1765 re.compile(black.DEFAULT_EXCLUDES),
1772 self.assertEqual(sorted(expected), sorted(sources))
1774 def test_extend_exclude(self) -> None:
1775 path = THIS_DIR / "data" / "include_exclude_tests"
1776 report = black.Report()
1777 gitignore = PathSpec.from_lines("gitwildmatch", [])
1778 sources: List[Path] = []
1780 Path(path / "b/exclude/a.py"),
1781 Path(path / "b/dont_exclude/a.py"),
1783 this_abs = THIS_DIR.resolve()
1785 black.gen_python_files(
1788 re.compile(black.DEFAULT_INCLUDES),
1789 re.compile(r"\.pyi$"),
1790 re.compile(r"\.definitely_exclude"),
1796 self.assertEqual(sorted(expected), sorted(sources))
1798 def test_invalid_cli_regex(self) -> None:
1799 for option in ["--include", "--exclude", "--extend-exclude", "--force-exclude"]:
1800 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1802 def test_required_version_matches_version(self) -> None:
1804 ["--required-version", black.__version__], exit_code=0, ignore_config=True
1807 def test_required_version_does_not_match_version(self) -> None:
1809 ["--required-version", "20.99b"], exit_code=1, ignore_config=True
1812 def test_preserves_line_endings(self) -> None:
1813 with TemporaryDirectory() as workspace:
1814 test_file = Path(workspace) / "test.py"
1815 for nl in ["\n", "\r\n"]:
1816 contents = nl.join(["def f( ):", " pass"])
1817 test_file.write_bytes(contents.encode())
1818 ff(test_file, write_back=black.WriteBack.YES)
1819 updated_contents: bytes = test_file.read_bytes()
1820 self.assertIn(nl.encode(), updated_contents)
1822 self.assertNotIn(b"\r\n", updated_contents)
1824 def test_preserves_line_endings_via_stdin(self) -> None:
1825 for nl in ["\n", "\r\n"]:
1826 contents = nl.join(["def f( ):", " pass"])
1827 runner = BlackRunner()
1828 result = runner.invoke(
1829 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1831 self.assertEqual(result.exit_code, 0)
1832 output = result.stdout_bytes
1833 self.assertIn(nl.encode("utf8"), output)
1835 self.assertNotIn(b"\r\n", output)
1837 def test_assert_equivalent_different_asts(self) -> None:
1838 with self.assertRaises(AssertionError):
1839 black.assert_equivalent("{}", "None")
1841 def test_symlink_out_of_root_directory(self) -> None:
1843 root = THIS_DIR.resolve()
1845 include = re.compile(black.DEFAULT_INCLUDES)
1846 exclude = re.compile(black.DEFAULT_EXCLUDES)
1847 report = black.Report()
1848 gitignore = PathSpec.from_lines("gitwildmatch", [])
1849 # `child` should behave like a symlink which resolved path is clearly
1850 # outside of the `root` directory.
1851 path.iterdir.return_value = [child]
1852 child.resolve.return_value = Path("/a/b/c")
1853 child.as_posix.return_value = "/a/b/c"
1854 child.is_symlink.return_value = True
1857 black.gen_python_files(
1868 except ValueError as ve:
1869 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1870 path.iterdir.assert_called_once()
1871 child.resolve.assert_called_once()
1872 child.is_symlink.assert_called_once()
1873 # `child` should behave like a strange file which resolved path is clearly
1874 # outside of the `root` directory.
1875 child.is_symlink.return_value = False
1876 with self.assertRaises(ValueError):
1878 black.gen_python_files(
1889 path.iterdir.assert_called()
1890 self.assertEqual(path.iterdir.call_count, 2)
1891 child.resolve.assert_called()
1892 self.assertEqual(child.resolve.call_count, 2)
1893 child.is_symlink.assert_called()
1894 self.assertEqual(child.is_symlink.call_count, 2)
1896 def test_shhh_click(self) -> None:
1898 from click import _unicodefun
1899 except ModuleNotFoundError:
1900 self.skipTest("Incompatible Click version")
1901 if not hasattr(_unicodefun, "_verify_python3_env"):
1902 self.skipTest("Incompatible Click version")
1903 # First, let's see if Click is crashing with a preferred ASCII charset.
1904 with patch("locale.getpreferredencoding") as gpe:
1905 gpe.return_value = "ASCII"
1906 with self.assertRaises(RuntimeError):
1907 _unicodefun._verify_python3_env() # type: ignore
1908 # Now, let's silence Click...
1910 # ...and confirm it's silent.
1911 with patch("locale.getpreferredencoding") as gpe:
1912 gpe.return_value = "ASCII"
1914 _unicodefun._verify_python3_env() # type: ignore
1915 except RuntimeError as re:
1916 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1918 def test_root_logger_not_used_directly(self) -> None:
1919 def fail(*args: Any, **kwargs: Any) -> None:
1920 self.fail("Record created with root logger")
1922 with patch.multiple(
1931 ff(THIS_DIR / "util.py")
1933 def test_invalid_config_return_code(self) -> None:
1934 tmp_file = Path(black.dump_to_file())
1936 tmp_config = Path(black.dump_to_file())
1938 args = ["--config", str(tmp_config), str(tmp_file)]
1939 self.invokeBlack(args, exit_code=2, ignore_config=False)
1943 def test_parse_pyproject_toml(self) -> None:
1944 test_toml_file = THIS_DIR / "test.toml"
1945 config = black.parse_pyproject_toml(str(test_toml_file))
1946 self.assertEqual(config["verbose"], 1)
1947 self.assertEqual(config["check"], "no")
1948 self.assertEqual(config["diff"], "y")
1949 self.assertEqual(config["color"], True)
1950 self.assertEqual(config["line_length"], 79)
1951 self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
1952 self.assertEqual(config["exclude"], r"\.pyi?$")
1953 self.assertEqual(config["include"], r"\.py?$")
1955 def test_read_pyproject_toml(self) -> None:
1956 test_toml_file = THIS_DIR / "test.toml"
1957 fake_ctx = FakeContext()
1958 black.read_pyproject_toml(fake_ctx, FakeParameter(), str(test_toml_file))
1959 config = fake_ctx.default_map
1960 self.assertEqual(config["verbose"], "1")
1961 self.assertEqual(config["check"], "no")
1962 self.assertEqual(config["diff"], "y")
1963 self.assertEqual(config["color"], "True")
1964 self.assertEqual(config["line_length"], "79")
1965 self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
1966 self.assertEqual(config["exclude"], r"\.pyi?$")
1967 self.assertEqual(config["include"], r"\.py?$")
1969 def test_find_project_root(self) -> None:
1970 with TemporaryDirectory() as workspace:
1971 root = Path(workspace)
1972 test_dir = root / "test"
1975 src_dir = root / "src"
1978 root_pyproject = root / "pyproject.toml"
1979 root_pyproject.touch()
1980 src_pyproject = src_dir / "pyproject.toml"
1981 src_pyproject.touch()
1982 src_python = src_dir / "foo.py"
1986 black.find_project_root((src_dir, test_dir)), root.resolve()
1988 self.assertEqual(black.find_project_root((src_dir,)), src_dir.resolve())
1989 self.assertEqual(black.find_project_root((src_python,)), src_dir.resolve())
1992 "black.files.find_user_pyproject_toml",
1993 black.files.find_user_pyproject_toml.__wrapped__,
1995 def test_find_user_pyproject_toml_linux(self) -> None:
1996 if system() == "Windows":
1999 # Test if XDG_CONFIG_HOME is checked
2000 with TemporaryDirectory() as workspace:
2001 tmp_user_config = Path(workspace) / "black"
2002 with patch.dict("os.environ", {"XDG_CONFIG_HOME": workspace}):
2004 black.files.find_user_pyproject_toml(), tmp_user_config.resolve()
2007 # Test fallback for XDG_CONFIG_HOME
2008 with patch.dict("os.environ"):
2009 os.environ.pop("XDG_CONFIG_HOME", None)
2010 fallback_user_config = Path("~/.config").expanduser() / "black"
2012 black.files.find_user_pyproject_toml(), fallback_user_config.resolve()
2015 def test_find_user_pyproject_toml_windows(self) -> None:
2016 if system() != "Windows":
2019 user_config_path = Path.home() / ".black"
2021 black.files.find_user_pyproject_toml(), user_config_path.resolve()
2024 def test_bpo_33660_workaround(self) -> None:
2025 if system() == "Windows":
2028 # https://bugs.python.org/issue33660
2030 old_cwd = Path.cwd()
2034 path = Path("workspace") / "project"
2035 report = black.Report(verbose=True)
2036 normalized_path = black.normalize_path_maybe_ignore(path, root, report)
2037 self.assertEqual(normalized_path, "workspace/project")
2039 os.chdir(str(old_cwd))
2041 def test_newline_comment_interaction(self) -> None:
2042 source = "class A:\\\r\n# type: ignore\n pass\n"
2043 output = black.format_str(source, mode=DEFAULT_MODE)
2044 black.assert_stable(source, output, mode=DEFAULT_MODE)
2046 def test_bpo_2142_workaround(self) -> None:
2048 # https://bugs.python.org/issue2142
2050 source, _ = read_data("missing_final_newline.py")
2051 # read_data adds a trailing newline
2052 source = source.rstrip()
2053 expected, _ = read_data("missing_final_newline.diff")
2054 tmp_file = Path(black.dump_to_file(source, ensure_final_newline=False))
2055 diff_header = re.compile(
2056 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
2057 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
2060 result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
2061 self.assertEqual(result.exit_code, 0)
2064 actual = result.output
2065 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
2066 self.assertEqual(actual, expected)
2068 @pytest.mark.python2
2069 def test_docstring_reformat_for_py27(self) -> None:
2071 Check that stripping trailing whitespace from Python 2 docstrings
2072 doesn't trigger a "not equivalent to source" error
2075 b'def foo():\r\n """Testing\r\n Testing """\r\n print "Foo"\r\n'
2077 expected = 'def foo():\n """Testing\n Testing"""\n print "Foo"\n'
2079 result = CliRunner().invoke(
2081 ["-", "-q", "--target-version=py27"],
2082 input=BytesIO(source),
2085 self.assertEqual(result.exit_code, 0)
2086 actual = result.output
2087 self.assertFormatEqual(actual, expected)
2090 def compare_results(
2091 result: click.testing.Result, expected_value: str, expected_exit_code: int
2093 """Helper method to test the value and exit code of a click Result."""
2095 result.output == expected_value
2096 ), "The output did not match the expected value."
2097 assert result.exit_code == expected_exit_code, "The exit code is incorrect."
2099 def test_code_option(self) -> None:
2100 """Test the code option with no changes."""
2101 code = 'print("Hello world")\n'
2102 args = ["--code", code]
2103 result = CliRunner().invoke(black.main, args)
2105 self.compare_results(result, code, 0)
2107 def test_code_option_changed(self) -> None:
2108 """Test the code option when changes are required."""
2109 code = "print('hello world')"
2110 formatted = black.format_str(code, mode=DEFAULT_MODE)
2112 args = ["--code", code]
2113 result = CliRunner().invoke(black.main, args)
2115 self.compare_results(result, formatted, 0)
2117 def test_code_option_check(self) -> None:
2118 """Test the code option when check is passed."""
2119 args = ["--check", "--code", 'print("Hello world")\n']
2120 result = CliRunner().invoke(black.main, args)
2121 self.compare_results(result, "", 0)
2123 def test_code_option_check_changed(self) -> None:
2124 """Test the code option when changes are required, and check is passed."""
2125 args = ["--check", "--code", "print('hello world')"]
2126 result = CliRunner().invoke(black.main, args)
2127 self.compare_results(result, "", 1)
2129 def test_code_option_diff(self) -> None:
2130 """Test the code option when diff is passed."""
2131 code = "print('hello world')"
2132 formatted = black.format_str(code, mode=DEFAULT_MODE)
2133 result_diff = diff(code, formatted, "STDIN", "STDOUT")
2135 args = ["--diff", "--code", code]
2136 result = CliRunner().invoke(black.main, args)
2138 # Remove time from diff
2139 output = DIFF_TIME.sub("", result.output)
2141 assert output == result_diff, "The output did not match the expected value."
2142 assert result.exit_code == 0, "The exit code is incorrect."
2144 def test_code_option_color_diff(self) -> None:
2145 """Test the code option when color and diff are passed."""
2146 code = "print('hello world')"
2147 formatted = black.format_str(code, mode=DEFAULT_MODE)
2149 result_diff = diff(code, formatted, "STDIN", "STDOUT")
2150 result_diff = color_diff(result_diff)
2152 args = ["--diff", "--color", "--code", code]
2153 result = CliRunner().invoke(black.main, args)
2155 # Remove time from diff
2156 output = DIFF_TIME.sub("", result.output)
2158 assert output == result_diff, "The output did not match the expected value."
2159 assert result.exit_code == 0, "The exit code is incorrect."
2161 def test_code_option_safe(self) -> None:
2162 """Test that the code option throws an error when the sanity checks fail."""
2163 # Patch black.assert_equivalent to ensure the sanity checks fail
2164 with patch.object(black, "assert_equivalent", side_effect=AssertionError):
2165 code = 'print("Hello world")'
2166 error_msg = f"{code}\nerror: cannot format <string>: \n"
2168 args = ["--safe", "--code", code]
2169 result = CliRunner().invoke(black.main, args)
2171 self.compare_results(result, error_msg, 123)
2173 def test_code_option_fast(self) -> None:
2174 """Test that the code option ignores errors when the sanity checks fail."""
2175 # Patch black.assert_equivalent to ensure the sanity checks fail
2176 with patch.object(black, "assert_equivalent", side_effect=AssertionError):
2177 code = 'print("Hello world")'
2178 formatted = black.format_str(code, mode=DEFAULT_MODE)
2180 args = ["--fast", "--code", code]
2181 result = CliRunner().invoke(black.main, args)
2183 self.compare_results(result, formatted, 0)
2185 def test_code_option_config(self) -> None:
2187 Test that the code option finds the pyproject.toml in the current directory.
2189 with patch.object(black, "parse_pyproject_toml", return_value={}) as parse:
2190 # Make sure we are in the project root with the pyproject file
2191 if not Path("tests").exists():
2194 args = ["--code", "print"]
2195 CliRunner().invoke(black.main, args)
2197 pyproject_path = Path(Path().cwd(), "pyproject.toml").resolve()
2199 len(parse.mock_calls) >= 1
2200 ), "Expected config parse to be called with the current directory."
2202 _, call_args, _ = parse.mock_calls[0]
2204 call_args[0].lower() == str(pyproject_path).lower()
2205 ), "Incorrect config loaded."
2207 def test_code_option_parent_config(self) -> None:
2209 Test that the code option finds the pyproject.toml in the parent directory.
2211 with patch.object(black, "parse_pyproject_toml", return_value={}) as parse:
2212 # Make sure we are in the tests directory
2213 if Path("tests").exists():
2216 args = ["--code", "print"]
2217 CliRunner().invoke(black.main, args)
2219 pyproject_path = Path(Path().cwd().parent, "pyproject.toml").resolve()
2221 len(parse.mock_calls) >= 1
2222 ), "Expected config parse to be called with the current directory."
2224 _, call_args, _ = parse.mock_calls[0]
2226 call_args[0].lower() == str(pyproject_path).lower()
2227 ), "Incorrect config loaded."
2230 with open(black.__file__, "r", encoding="utf-8") as _bf:
2231 black_source_lines = _bf.readlines()
2234 def tracefunc(frame: types.FrameType, event: str, arg: Any) -> Callable:
2235 """Show function calls `from black/__init__.py` as they happen.
2237 Register this with `sys.settrace()` in a test you're debugging.
2242 stack = len(inspect.stack()) - 19
2244 filename = frame.f_code.co_filename
2245 lineno = frame.f_lineno
2246 func_sig_lineno = lineno - 1
2247 funcname = black_source_lines[func_sig_lineno].strip()
2248 while funcname.startswith("@"):
2249 func_sig_lineno += 1
2250 funcname = black_source_lines[func_sig_lineno].strip()
2251 if "black/__init__.py" in filename:
2252 print(f"{' ' * stack}{lineno}:{funcname}")
2256 if __name__ == "__main__":
2257 unittest.main(module="test_black")