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
10 from io import BytesIO
12 from pathlib import Path
13 from platform import system
16 from tempfile import TemporaryDirectory
28 from unittest.mock import patch, MagicMock
31 from click import unstyle
32 from click.testing import CliRunner
35 from black import Feature, TargetVersion
36 from black.cache import get_cache_file
37 from black.debug import DebugVisitor
38 from black.output import diff, color_diff
39 from black.report import Report
42 from pathspec import PathSpec
44 # Import other test classes
45 from tests.util import (
57 THIS_FILE = Path(__file__)
64 PY36_ARGS = [f"--target-version={version.name.lower()}" for version in PY36_VERSIONS]
68 # Match the time output in a diff, but nothing else
69 DIFF_TIME = re.compile(r"\t[\d-:+\. ]+")
73 def cache_dir(exists: bool = True) -> Iterator[Path]:
74 with TemporaryDirectory() as workspace:
75 cache_dir = Path(workspace)
77 cache_dir = cache_dir / "new"
78 with patch("black.cache.CACHE_DIR", cache_dir):
83 def event_loop() -> Iterator[None]:
84 policy = asyncio.get_event_loop_policy()
85 loop = policy.new_event_loop()
86 asyncio.set_event_loop(loop)
94 class FakeContext(click.Context):
95 """A fake click Context for when calling functions that need it."""
97 def __init__(self) -> None:
98 self.default_map: Dict[str, Any] = {}
101 class FakeParameter(click.Parameter):
102 """A fake click Parameter for when calling functions that need it."""
104 def __init__(self) -> None:
108 class BlackRunner(CliRunner):
109 """Make sure STDOUT and STDERR are kept separate when testing Black via its CLI."""
111 def __init__(self) -> None:
112 super().__init__(mix_stderr=False)
115 class BlackTestCase(BlackBaseTestCase):
117 self, args: List[str], exit_code: int = 0, ignore_config: bool = True
119 runner = BlackRunner()
121 args = ["--verbose", "--config", str(THIS_DIR / "empty.toml"), *args]
122 result = runner.invoke(black.main, args)
123 assert result.stdout_bytes is not None
124 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}"
136 @patch("black.dump_to_file", dump_to_stderr)
137 def test_empty(self) -> None:
138 source = expected = ""
140 self.assertFormatEqual(expected, actual)
141 black.assert_equivalent(source, actual)
142 black.assert_stable(source, actual, DEFAULT_MODE)
144 def test_empty_ff(self) -> None:
146 tmp_file = Path(black.dump_to_file())
148 self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES))
149 with open(tmp_file, encoding="utf8") as f:
153 self.assertFormatEqual(expected, actual)
155 def test_piping(self) -> None:
156 source, expected = read_data("src/black/__init__", data=False)
157 result = BlackRunner().invoke(
159 ["-", "--fast", f"--line-length={black.DEFAULT_LINE_LENGTH}"],
160 input=BytesIO(source.encode("utf8")),
162 self.assertEqual(result.exit_code, 0)
163 self.assertFormatEqual(expected, result.output)
164 if source != result.output:
165 black.assert_equivalent(source, result.output)
166 black.assert_stable(source, result.output, DEFAULT_MODE)
168 def test_piping_diff(self) -> None:
169 diff_header = re.compile(
170 r"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d\d\d\d "
173 source, _ = read_data("expression.py")
174 expected, _ = read_data("expression.diff")
175 config = THIS_DIR / "data" / "empty_pyproject.toml"
179 f"--line-length={black.DEFAULT_LINE_LENGTH}",
181 f"--config={config}",
183 result = BlackRunner().invoke(
184 black.main, args, input=BytesIO(source.encode("utf8"))
186 self.assertEqual(result.exit_code, 0)
187 actual = diff_header.sub(DETERMINISTIC_HEADER, result.output)
188 actual = actual.rstrip() + "\n" # the diff output has a trailing space
189 self.assertEqual(expected, actual)
191 def test_piping_diff_with_color(self) -> None:
192 source, _ = read_data("expression.py")
193 config = THIS_DIR / "data" / "empty_pyproject.toml"
197 f"--line-length={black.DEFAULT_LINE_LENGTH}",
200 f"--config={config}",
202 result = BlackRunner().invoke(
203 black.main, args, input=BytesIO(source.encode("utf8"))
205 actual = result.output
206 # Again, the contents are checked in a different test, so only look for colors.
207 self.assertIn("\033[1;37m", actual)
208 self.assertIn("\033[36m", actual)
209 self.assertIn("\033[32m", actual)
210 self.assertIn("\033[31m", actual)
211 self.assertIn("\033[0m", actual)
213 @patch("black.dump_to_file", dump_to_stderr)
214 def _test_wip(self) -> None:
215 source, expected = read_data("wip")
216 sys.settrace(tracefunc)
219 experimental_string_processing=False,
220 target_versions={black.TargetVersion.PY38},
222 actual = fs(source, mode=mode)
224 self.assertFormatEqual(expected, actual)
225 black.assert_equivalent(source, actual)
226 black.assert_stable(source, actual, black.FileMode())
228 @unittest.expectedFailure
229 @patch("black.dump_to_file", dump_to_stderr)
230 def test_trailing_comma_optional_parens_stability1(self) -> None:
231 source, _expected = read_data("trailing_comma_optional_parens1")
233 black.assert_stable(source, actual, DEFAULT_MODE)
235 @unittest.expectedFailure
236 @patch("black.dump_to_file", dump_to_stderr)
237 def test_trailing_comma_optional_parens_stability2(self) -> None:
238 source, _expected = read_data("trailing_comma_optional_parens2")
240 black.assert_stable(source, actual, DEFAULT_MODE)
242 @unittest.expectedFailure
243 @patch("black.dump_to_file", dump_to_stderr)
244 def test_trailing_comma_optional_parens_stability3(self) -> None:
245 source, _expected = read_data("trailing_comma_optional_parens3")
247 black.assert_stable(source, actual, DEFAULT_MODE)
249 @patch("black.dump_to_file", dump_to_stderr)
250 def test_trailing_comma_optional_parens_stability1_pass2(self) -> None:
251 source, _expected = read_data("trailing_comma_optional_parens1")
252 actual = fs(fs(source)) # this is what `format_file_contents` does with --safe
253 black.assert_stable(source, actual, DEFAULT_MODE)
255 @patch("black.dump_to_file", dump_to_stderr)
256 def test_trailing_comma_optional_parens_stability2_pass2(self) -> None:
257 source, _expected = read_data("trailing_comma_optional_parens2")
258 actual = fs(fs(source)) # this is what `format_file_contents` does with --safe
259 black.assert_stable(source, actual, DEFAULT_MODE)
261 @patch("black.dump_to_file", dump_to_stderr)
262 def test_trailing_comma_optional_parens_stability3_pass2(self) -> None:
263 source, _expected = read_data("trailing_comma_optional_parens3")
264 actual = fs(fs(source)) # this is what `format_file_contents` does with --safe
265 black.assert_stable(source, actual, DEFAULT_MODE)
267 @patch("black.dump_to_file", dump_to_stderr)
268 def test_pep_572(self) -> None:
269 source, expected = read_data("pep_572")
271 self.assertFormatEqual(expected, actual)
272 black.assert_stable(source, actual, DEFAULT_MODE)
273 if sys.version_info >= (3, 8):
274 black.assert_equivalent(source, actual)
276 @patch("black.dump_to_file", dump_to_stderr)
277 def test_pep_572_remove_parens(self) -> None:
278 source, expected = read_data("pep_572_remove_parens")
280 self.assertFormatEqual(expected, actual)
281 black.assert_stable(source, actual, DEFAULT_MODE)
282 if sys.version_info >= (3, 8):
283 black.assert_equivalent(source, actual)
285 @patch("black.dump_to_file", dump_to_stderr)
286 def test_pep_572_do_not_remove_parens(self) -> None:
287 source, expected = read_data("pep_572_do_not_remove_parens")
288 # the AST safety checks will fail, but that's expected, just make sure no
289 # parentheses are touched
290 actual = black.format_str(source, mode=DEFAULT_MODE)
291 self.assertFormatEqual(expected, actual)
293 def test_pep_572_version_detection(self) -> None:
294 source, _ = read_data("pep_572")
295 root = black.lib2to3_parse(source)
296 features = black.get_features_used(root)
297 self.assertIn(black.Feature.ASSIGNMENT_EXPRESSIONS, features)
298 versions = black.detect_target_versions(root)
299 self.assertIn(black.TargetVersion.PY38, versions)
301 def test_expression_ff(self) -> None:
302 source, expected = read_data("expression")
303 tmp_file = Path(black.dump_to_file(source))
305 self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
306 with open(tmp_file, encoding="utf8") as f:
310 self.assertFormatEqual(expected, actual)
311 with patch("black.dump_to_file", dump_to_stderr):
312 black.assert_equivalent(source, actual)
313 black.assert_stable(source, actual, DEFAULT_MODE)
315 def test_expression_diff(self) -> None:
316 source, _ = read_data("expression.py")
317 config = THIS_DIR / "data" / "empty_pyproject.toml"
318 expected, _ = read_data("expression.diff")
319 tmp_file = Path(black.dump_to_file(source))
320 diff_header = re.compile(
321 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
322 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
325 result = BlackRunner().invoke(
326 black.main, ["--diff", str(tmp_file), f"--config={config}"]
328 self.assertEqual(result.exit_code, 0)
331 actual = result.output
332 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
333 if expected != actual:
334 dump = black.dump_to_file(actual)
336 "Expected diff isn't equal to the actual. If you made changes to"
337 " expression.py and this is an anticipated difference, overwrite"
338 f" tests/data/expression.diff with {dump}"
340 self.assertEqual(expected, actual, msg)
342 def test_expression_diff_with_color(self) -> None:
343 source, _ = read_data("expression.py")
344 config = THIS_DIR / "data" / "empty_pyproject.toml"
345 expected, _ = read_data("expression.diff")
346 tmp_file = Path(black.dump_to_file(source))
348 result = BlackRunner().invoke(
349 black.main, ["--diff", "--color", str(tmp_file), f"--config={config}"]
353 actual = result.output
354 # We check the contents of the diff in `test_expression_diff`. All
355 # we need to check here is that color codes exist in the result.
356 self.assertIn("\033[1;37m", actual)
357 self.assertIn("\033[36m", actual)
358 self.assertIn("\033[32m", actual)
359 self.assertIn("\033[31m", actual)
360 self.assertIn("\033[0m", actual)
362 @patch("black.dump_to_file", dump_to_stderr)
363 def test_pep_570(self) -> None:
364 source, expected = read_data("pep_570")
366 self.assertFormatEqual(expected, actual)
367 black.assert_stable(source, actual, DEFAULT_MODE)
368 if sys.version_info >= (3, 8):
369 black.assert_equivalent(source, actual)
371 def test_detect_pos_only_arguments(self) -> None:
372 source, _ = read_data("pep_570")
373 root = black.lib2to3_parse(source)
374 features = black.get_features_used(root)
375 self.assertIn(black.Feature.POS_ONLY_ARGUMENTS, features)
376 versions = black.detect_target_versions(root)
377 self.assertIn(black.TargetVersion.PY38, versions)
379 @patch("black.dump_to_file", dump_to_stderr)
380 def test_string_quotes(self) -> None:
381 source, expected = read_data("string_quotes")
382 mode = black.Mode(experimental_string_processing=True)
383 actual = fs(source, mode=mode)
384 self.assertFormatEqual(expected, actual)
385 black.assert_equivalent(source, actual)
386 black.assert_stable(source, actual, mode)
387 mode = replace(mode, string_normalization=False)
388 not_normalized = fs(source, mode=mode)
389 self.assertFormatEqual(source.replace("\\\n", ""), not_normalized)
390 black.assert_equivalent(source, not_normalized)
391 black.assert_stable(source, not_normalized, mode=mode)
393 @patch("black.dump_to_file", dump_to_stderr)
394 def test_docstring_no_string_normalization(self) -> None:
395 """Like test_docstring but with string normalization off."""
396 source, expected = read_data("docstring_no_string_normalization")
397 mode = replace(DEFAULT_MODE, string_normalization=False)
398 actual = fs(source, mode=mode)
399 self.assertFormatEqual(expected, actual)
400 black.assert_equivalent(source, actual)
401 black.assert_stable(source, actual, mode)
403 def test_long_strings_flag_disabled(self) -> None:
404 """Tests for turning off the string processing logic."""
405 source, expected = read_data("long_strings_flag_disabled")
406 mode = replace(DEFAULT_MODE, experimental_string_processing=False)
407 actual = fs(source, mode=mode)
408 self.assertFormatEqual(expected, actual)
409 black.assert_stable(expected, actual, mode)
411 @patch("black.dump_to_file", dump_to_stderr)
412 def test_numeric_literals(self) -> None:
413 source, expected = read_data("numeric_literals")
414 mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
415 actual = fs(source, mode=mode)
416 self.assertFormatEqual(expected, actual)
417 black.assert_equivalent(source, actual)
418 black.assert_stable(source, actual, mode)
420 @patch("black.dump_to_file", dump_to_stderr)
421 def test_numeric_literals_ignoring_underscores(self) -> None:
422 source, expected = read_data("numeric_literals_skip_underscores")
423 mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
424 actual = fs(source, mode=mode)
425 self.assertFormatEqual(expected, actual)
426 black.assert_equivalent(source, actual)
427 black.assert_stable(source, actual, mode)
429 def test_skip_magic_trailing_comma(self) -> None:
430 source, _ = read_data("expression.py")
431 expected, _ = read_data("expression_skip_magic_trailing_comma.diff")
432 tmp_file = Path(black.dump_to_file(source))
433 diff_header = re.compile(
434 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
435 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
438 result = BlackRunner().invoke(black.main, ["-C", "--diff", str(tmp_file)])
439 self.assertEqual(result.exit_code, 0)
442 actual = result.output
443 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
444 actual = actual.rstrip() + "\n" # the diff output has a trailing space
445 if expected != actual:
446 dump = black.dump_to_file(actual)
448 "Expected diff isn't equal to the actual. If you made changes to"
449 " expression.py and this is an anticipated difference, overwrite"
450 f" tests/data/expression_skip_magic_trailing_comma.diff with {dump}"
452 self.assertEqual(expected, actual, msg)
454 @pytest.mark.no_python2
455 def test_python2_should_fail_without_optional_install(self) -> None:
456 if sys.version_info < (3, 8):
458 "Python 3.6 and 3.7 will install typed-ast to work and as such will be"
459 " able to parse Python 2 syntax without explicitly specifying the"
464 tmp_file = Path(black.dump_to_file(source))
466 runner = BlackRunner()
467 result = runner.invoke(black.main, [str(tmp_file)])
468 self.assertEqual(result.exit_code, 123)
471 assert result.stderr_bytes is not None
473 result.stderr_bytes.decode()
480 "The requested source code has invalid Python 3 syntax."
481 "If you are trying to format Python 2 files please reinstall Black"
482 " with the 'python2' extra: `python3 -m pip install black[python2]`."
484 self.assertIn(msg, actual)
487 @patch("black.dump_to_file", dump_to_stderr)
488 def test_python2_print_function(self) -> None:
489 source, expected = read_data("python2_print_function")
490 mode = replace(DEFAULT_MODE, target_versions={TargetVersion.PY27})
491 actual = fs(source, mode=mode)
492 self.assertFormatEqual(expected, actual)
493 black.assert_equivalent(source, actual)
494 black.assert_stable(source, actual, mode)
496 @patch("black.dump_to_file", dump_to_stderr)
497 def test_stub(self) -> None:
498 mode = replace(DEFAULT_MODE, is_pyi=True)
499 source, expected = read_data("stub.pyi")
500 actual = fs(source, mode=mode)
501 self.assertFormatEqual(expected, actual)
502 black.assert_stable(source, actual, mode)
504 @patch("black.dump_to_file", dump_to_stderr)
505 def test_async_as_identifier(self) -> None:
506 source_path = (THIS_DIR / "data" / "async_as_identifier.py").resolve()
507 source, expected = read_data("async_as_identifier")
509 self.assertFormatEqual(expected, actual)
510 major, minor = sys.version_info[:2]
511 if major < 3 or (major <= 3 and minor < 7):
512 black.assert_equivalent(source, actual)
513 black.assert_stable(source, actual, DEFAULT_MODE)
514 # ensure black can parse this when the target is 3.6
515 self.invokeBlack([str(source_path), "--target-version", "py36"])
516 # but not on 3.7, because async/await is no longer an identifier
517 self.invokeBlack([str(source_path), "--target-version", "py37"], exit_code=123)
519 @patch("black.dump_to_file", dump_to_stderr)
520 def test_python37(self) -> None:
521 source_path = (THIS_DIR / "data" / "python37.py").resolve()
522 source, expected = read_data("python37")
524 self.assertFormatEqual(expected, actual)
525 major, minor = sys.version_info[:2]
526 if major > 3 or (major == 3 and minor >= 7):
527 black.assert_equivalent(source, actual)
528 black.assert_stable(source, actual, DEFAULT_MODE)
529 # ensure black can parse this when the target is 3.7
530 self.invokeBlack([str(source_path), "--target-version", "py37"])
531 # but not on 3.6, because we use async as a reserved keyword
532 self.invokeBlack([str(source_path), "--target-version", "py36"], exit_code=123)
534 @patch("black.dump_to_file", dump_to_stderr)
535 def test_python38(self) -> None:
536 source, expected = read_data("python38")
538 self.assertFormatEqual(expected, actual)
539 major, minor = sys.version_info[:2]
540 if major > 3 or (major == 3 and minor >= 8):
541 black.assert_equivalent(source, actual)
542 black.assert_stable(source, actual, DEFAULT_MODE)
544 @patch("black.dump_to_file", dump_to_stderr)
545 def test_python39(self) -> None:
546 source, expected = read_data("python39")
548 self.assertFormatEqual(expected, actual)
549 major, minor = sys.version_info[:2]
550 if major > 3 or (major == 3 and minor >= 9):
551 black.assert_equivalent(source, actual)
552 black.assert_stable(source, actual, DEFAULT_MODE)
554 def test_tab_comment_indentation(self) -> None:
555 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
556 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
557 self.assertFormatEqual(contents_spc, fs(contents_spc))
558 self.assertFormatEqual(contents_spc, fs(contents_tab))
560 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
561 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
562 self.assertFormatEqual(contents_spc, fs(contents_spc))
563 self.assertFormatEqual(contents_spc, fs(contents_tab))
565 # mixed tabs and spaces (valid Python 2 code)
566 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t# comment\n pass\n"
567 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
568 self.assertFormatEqual(contents_spc, fs(contents_spc))
569 self.assertFormatEqual(contents_spc, fs(contents_tab))
571 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t\t# comment\n pass\n"
572 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
573 self.assertFormatEqual(contents_spc, fs(contents_spc))
574 self.assertFormatEqual(contents_spc, fs(contents_tab))
576 def test_report_verbose(self) -> None:
577 report = Report(verbose=True)
581 def out(msg: str, **kwargs: Any) -> None:
582 out_lines.append(msg)
584 def err(msg: str, **kwargs: Any) -> None:
585 err_lines.append(msg)
587 with patch("black.output._out", out), patch("black.output._err", err):
588 report.done(Path("f1"), black.Changed.NO)
589 self.assertEqual(len(out_lines), 1)
590 self.assertEqual(len(err_lines), 0)
591 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
592 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
593 self.assertEqual(report.return_code, 0)
594 report.done(Path("f2"), black.Changed.YES)
595 self.assertEqual(len(out_lines), 2)
596 self.assertEqual(len(err_lines), 0)
597 self.assertEqual(out_lines[-1], "reformatted f2")
599 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
601 report.done(Path("f3"), black.Changed.CACHED)
602 self.assertEqual(len(out_lines), 3)
603 self.assertEqual(len(err_lines), 0)
605 out_lines[-1], "f3 wasn't modified on disk since last run."
608 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
610 self.assertEqual(report.return_code, 0)
612 self.assertEqual(report.return_code, 1)
614 report.failed(Path("e1"), "boom")
615 self.assertEqual(len(out_lines), 3)
616 self.assertEqual(len(err_lines), 1)
617 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
619 unstyle(str(report)),
620 "1 file reformatted, 2 files left unchanged, 1 file failed to"
623 self.assertEqual(report.return_code, 123)
624 report.done(Path("f3"), black.Changed.YES)
625 self.assertEqual(len(out_lines), 4)
626 self.assertEqual(len(err_lines), 1)
627 self.assertEqual(out_lines[-1], "reformatted f3")
629 unstyle(str(report)),
630 "2 files reformatted, 2 files left unchanged, 1 file failed to"
633 self.assertEqual(report.return_code, 123)
634 report.failed(Path("e2"), "boom")
635 self.assertEqual(len(out_lines), 4)
636 self.assertEqual(len(err_lines), 2)
637 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
639 unstyle(str(report)),
640 "2 files reformatted, 2 files left unchanged, 2 files failed to"
643 self.assertEqual(report.return_code, 123)
644 report.path_ignored(Path("wat"), "no match")
645 self.assertEqual(len(out_lines), 5)
646 self.assertEqual(len(err_lines), 2)
647 self.assertEqual(out_lines[-1], "wat ignored: no match")
649 unstyle(str(report)),
650 "2 files reformatted, 2 files left unchanged, 2 files failed to"
653 self.assertEqual(report.return_code, 123)
654 report.done(Path("f4"), black.Changed.NO)
655 self.assertEqual(len(out_lines), 6)
656 self.assertEqual(len(err_lines), 2)
657 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
659 unstyle(str(report)),
660 "2 files reformatted, 3 files left unchanged, 2 files failed to"
663 self.assertEqual(report.return_code, 123)
666 unstyle(str(report)),
667 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
668 " would fail to reformat.",
673 unstyle(str(report)),
674 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
675 " would fail to reformat.",
678 def test_report_quiet(self) -> None:
679 report = Report(quiet=True)
683 def out(msg: str, **kwargs: Any) -> None:
684 out_lines.append(msg)
686 def err(msg: str, **kwargs: Any) -> None:
687 err_lines.append(msg)
689 with patch("black.output._out", out), patch("black.output._err", err):
690 report.done(Path("f1"), black.Changed.NO)
691 self.assertEqual(len(out_lines), 0)
692 self.assertEqual(len(err_lines), 0)
693 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
694 self.assertEqual(report.return_code, 0)
695 report.done(Path("f2"), black.Changed.YES)
696 self.assertEqual(len(out_lines), 0)
697 self.assertEqual(len(err_lines), 0)
699 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
701 report.done(Path("f3"), black.Changed.CACHED)
702 self.assertEqual(len(out_lines), 0)
703 self.assertEqual(len(err_lines), 0)
705 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
707 self.assertEqual(report.return_code, 0)
709 self.assertEqual(report.return_code, 1)
711 report.failed(Path("e1"), "boom")
712 self.assertEqual(len(out_lines), 0)
713 self.assertEqual(len(err_lines), 1)
714 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
716 unstyle(str(report)),
717 "1 file reformatted, 2 files left unchanged, 1 file failed to"
720 self.assertEqual(report.return_code, 123)
721 report.done(Path("f3"), black.Changed.YES)
722 self.assertEqual(len(out_lines), 0)
723 self.assertEqual(len(err_lines), 1)
725 unstyle(str(report)),
726 "2 files reformatted, 2 files left unchanged, 1 file failed to"
729 self.assertEqual(report.return_code, 123)
730 report.failed(Path("e2"), "boom")
731 self.assertEqual(len(out_lines), 0)
732 self.assertEqual(len(err_lines), 2)
733 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
735 unstyle(str(report)),
736 "2 files reformatted, 2 files left unchanged, 2 files failed to"
739 self.assertEqual(report.return_code, 123)
740 report.path_ignored(Path("wat"), "no match")
741 self.assertEqual(len(out_lines), 0)
742 self.assertEqual(len(err_lines), 2)
744 unstyle(str(report)),
745 "2 files reformatted, 2 files left unchanged, 2 files failed to"
748 self.assertEqual(report.return_code, 123)
749 report.done(Path("f4"), black.Changed.NO)
750 self.assertEqual(len(out_lines), 0)
751 self.assertEqual(len(err_lines), 2)
753 unstyle(str(report)),
754 "2 files reformatted, 3 files left unchanged, 2 files failed to"
757 self.assertEqual(report.return_code, 123)
760 unstyle(str(report)),
761 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
762 " would fail to reformat.",
767 unstyle(str(report)),
768 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
769 " would fail to reformat.",
772 def test_report_normal(self) -> None:
773 report = black.Report()
777 def out(msg: str, **kwargs: Any) -> None:
778 out_lines.append(msg)
780 def err(msg: str, **kwargs: Any) -> None:
781 err_lines.append(msg)
783 with patch("black.output._out", out), patch("black.output._err", err):
784 report.done(Path("f1"), black.Changed.NO)
785 self.assertEqual(len(out_lines), 0)
786 self.assertEqual(len(err_lines), 0)
787 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
788 self.assertEqual(report.return_code, 0)
789 report.done(Path("f2"), black.Changed.YES)
790 self.assertEqual(len(out_lines), 1)
791 self.assertEqual(len(err_lines), 0)
792 self.assertEqual(out_lines[-1], "reformatted f2")
794 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
796 report.done(Path("f3"), black.Changed.CACHED)
797 self.assertEqual(len(out_lines), 1)
798 self.assertEqual(len(err_lines), 0)
799 self.assertEqual(out_lines[-1], "reformatted f2")
801 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
803 self.assertEqual(report.return_code, 0)
805 self.assertEqual(report.return_code, 1)
807 report.failed(Path("e1"), "boom")
808 self.assertEqual(len(out_lines), 1)
809 self.assertEqual(len(err_lines), 1)
810 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
812 unstyle(str(report)),
813 "1 file reformatted, 2 files left unchanged, 1 file failed to"
816 self.assertEqual(report.return_code, 123)
817 report.done(Path("f3"), black.Changed.YES)
818 self.assertEqual(len(out_lines), 2)
819 self.assertEqual(len(err_lines), 1)
820 self.assertEqual(out_lines[-1], "reformatted f3")
822 unstyle(str(report)),
823 "2 files reformatted, 2 files left unchanged, 1 file failed to"
826 self.assertEqual(report.return_code, 123)
827 report.failed(Path("e2"), "boom")
828 self.assertEqual(len(out_lines), 2)
829 self.assertEqual(len(err_lines), 2)
830 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
832 unstyle(str(report)),
833 "2 files reformatted, 2 files left unchanged, 2 files failed to"
836 self.assertEqual(report.return_code, 123)
837 report.path_ignored(Path("wat"), "no match")
838 self.assertEqual(len(out_lines), 2)
839 self.assertEqual(len(err_lines), 2)
841 unstyle(str(report)),
842 "2 files reformatted, 2 files left unchanged, 2 files failed to"
845 self.assertEqual(report.return_code, 123)
846 report.done(Path("f4"), black.Changed.NO)
847 self.assertEqual(len(out_lines), 2)
848 self.assertEqual(len(err_lines), 2)
850 unstyle(str(report)),
851 "2 files reformatted, 3 files left unchanged, 2 files failed to"
854 self.assertEqual(report.return_code, 123)
857 unstyle(str(report)),
858 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
859 " would fail to reformat.",
864 unstyle(str(report)),
865 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
866 " would fail to reformat.",
869 def test_lib2to3_parse(self) -> None:
870 with self.assertRaises(black.InvalidInput):
871 black.lib2to3_parse("invalid syntax")
874 black.lib2to3_parse(straddling)
875 black.lib2to3_parse(straddling, {TargetVersion.PY27})
876 black.lib2to3_parse(straddling, {TargetVersion.PY36})
877 black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
880 black.lib2to3_parse(py2_only)
881 black.lib2to3_parse(py2_only, {TargetVersion.PY27})
882 with self.assertRaises(black.InvalidInput):
883 black.lib2to3_parse(py2_only, {TargetVersion.PY36})
884 with self.assertRaises(black.InvalidInput):
885 black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
887 py3_only = "exec(x, end=y)"
888 black.lib2to3_parse(py3_only)
889 with self.assertRaises(black.InvalidInput):
890 black.lib2to3_parse(py3_only, {TargetVersion.PY27})
891 black.lib2to3_parse(py3_only, {TargetVersion.PY36})
892 black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
894 def test_get_features_used_decorator(self) -> None:
895 # Test the feature detection of new decorator syntax
896 # since this makes some test cases of test_get_features_used()
897 # fails if it fails, this is tested first so that a useful case
899 simples, relaxed = read_data("decorators")
900 # skip explanation comments at the top of the file
901 for simple_test in simples.split("##")[1:]:
902 node = black.lib2to3_parse(simple_test)
903 decorator = str(node.children[0].children[0]).strip()
905 Feature.RELAXED_DECORATORS,
906 black.get_features_used(node),
908 f"decorator '{decorator}' follows python<=3.8 syntax"
909 "but is detected as 3.9+"
910 # f"The full node is\n{node!r}"
913 # skip the '# output' comment at the top of the output part
914 for relaxed_test in relaxed.split("##")[1:]:
915 node = black.lib2to3_parse(relaxed_test)
916 decorator = str(node.children[0].children[0]).strip()
918 Feature.RELAXED_DECORATORS,
919 black.get_features_used(node),
921 f"decorator '{decorator}' uses python3.9+ syntax"
922 "but is detected as python<=3.8"
923 # f"The full node is\n{node!r}"
927 def test_get_features_used(self) -> None:
928 node = black.lib2to3_parse("def f(*, arg): ...\n")
929 self.assertEqual(black.get_features_used(node), set())
930 node = black.lib2to3_parse("def f(*, arg,): ...\n")
931 self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
932 node = black.lib2to3_parse("f(*arg,)\n")
934 black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
936 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
937 self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
938 node = black.lib2to3_parse("123_456\n")
939 self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
940 node = black.lib2to3_parse("123456\n")
941 self.assertEqual(black.get_features_used(node), set())
942 source, expected = read_data("function")
943 node = black.lib2to3_parse(source)
944 expected_features = {
945 Feature.TRAILING_COMMA_IN_CALL,
946 Feature.TRAILING_COMMA_IN_DEF,
949 self.assertEqual(black.get_features_used(node), expected_features)
950 node = black.lib2to3_parse(expected)
951 self.assertEqual(black.get_features_used(node), expected_features)
952 source, expected = read_data("expression")
953 node = black.lib2to3_parse(source)
954 self.assertEqual(black.get_features_used(node), set())
955 node = black.lib2to3_parse(expected)
956 self.assertEqual(black.get_features_used(node), set())
958 def test_get_future_imports(self) -> None:
959 node = black.lib2to3_parse("\n")
960 self.assertEqual(set(), black.get_future_imports(node))
961 node = black.lib2to3_parse("from __future__ import black\n")
962 self.assertEqual({"black"}, black.get_future_imports(node))
963 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
964 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
965 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
966 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
967 node = black.lib2to3_parse(
968 "from __future__ import multiple\nfrom __future__ import imports\n"
970 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
971 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
972 self.assertEqual({"black"}, black.get_future_imports(node))
973 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
974 self.assertEqual({"black"}, black.get_future_imports(node))
975 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
976 self.assertEqual(set(), black.get_future_imports(node))
977 node = black.lib2to3_parse("from some.module import black\n")
978 self.assertEqual(set(), black.get_future_imports(node))
979 node = black.lib2to3_parse(
980 "from __future__ import unicode_literals as _unicode_literals"
982 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
983 node = black.lib2to3_parse(
984 "from __future__ import unicode_literals as _lol, print"
986 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
988 def test_debug_visitor(self) -> None:
989 source, _ = read_data("debug_visitor.py")
990 expected, _ = read_data("debug_visitor.out")
994 def out(msg: str, **kwargs: Any) -> None:
995 out_lines.append(msg)
997 def err(msg: str, **kwargs: Any) -> None:
998 err_lines.append(msg)
1000 with patch("black.debug.out", out):
1001 DebugVisitor.show(source)
1002 actual = "\n".join(out_lines) + "\n"
1004 if expected != actual:
1005 log_name = black.dump_to_file(*out_lines)
1009 f"AST print out is different. Actual version dumped to {log_name}",
1012 def test_format_file_contents(self) -> None:
1015 with self.assertRaises(black.NothingChanged):
1016 black.format_file_contents(empty, mode=mode, fast=False)
1018 with self.assertRaises(black.NothingChanged):
1019 black.format_file_contents(just_nl, mode=mode, fast=False)
1020 same = "j = [1, 2, 3]\n"
1021 with self.assertRaises(black.NothingChanged):
1022 black.format_file_contents(same, mode=mode, fast=False)
1023 different = "j = [1,2,3]"
1025 actual = black.format_file_contents(different, mode=mode, fast=False)
1026 self.assertEqual(expected, actual)
1027 invalid = "return if you can"
1028 with self.assertRaises(black.InvalidInput) as e:
1029 black.format_file_contents(invalid, mode=mode, fast=False)
1030 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
1032 def test_endmarker(self) -> None:
1033 n = black.lib2to3_parse("\n")
1034 self.assertEqual(n.type, black.syms.file_input)
1035 self.assertEqual(len(n.children), 1)
1036 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
1038 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
1039 def test_assertFormatEqual(self) -> None:
1043 def out(msg: str, **kwargs: Any) -> None:
1044 out_lines.append(msg)
1046 def err(msg: str, **kwargs: Any) -> None:
1047 err_lines.append(msg)
1049 with patch("black.output._out", out), patch("black.output._err", err):
1050 with self.assertRaises(AssertionError):
1051 self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")
1053 out_str = "".join(out_lines)
1054 self.assertTrue("Expected tree:" in out_str)
1055 self.assertTrue("Actual tree:" in out_str)
1056 self.assertEqual("".join(err_lines), "")
1058 def test_cache_broken_file(self) -> None:
1060 with cache_dir() as workspace:
1061 cache_file = get_cache_file(mode)
1062 with cache_file.open("w") as fobj:
1063 fobj.write("this is not a pickle")
1064 self.assertEqual(black.read_cache(mode), {})
1065 src = (workspace / "test.py").resolve()
1066 with src.open("w") as fobj:
1067 fobj.write("print('hello')")
1068 self.invokeBlack([str(src)])
1069 cache = black.read_cache(mode)
1070 self.assertIn(str(src), cache)
1072 def test_cache_single_file_already_cached(self) -> None:
1074 with cache_dir() as workspace:
1075 src = (workspace / "test.py").resolve()
1076 with src.open("w") as fobj:
1077 fobj.write("print('hello')")
1078 black.write_cache({}, [src], mode)
1079 self.invokeBlack([str(src)])
1080 with src.open("r") as fobj:
1081 self.assertEqual(fobj.read(), "print('hello')")
1084 def test_cache_multiple_files(self) -> None:
1086 with cache_dir() as workspace, patch(
1087 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1089 one = (workspace / "one.py").resolve()
1090 with one.open("w") as fobj:
1091 fobj.write("print('hello')")
1092 two = (workspace / "two.py").resolve()
1093 with two.open("w") as fobj:
1094 fobj.write("print('hello')")
1095 black.write_cache({}, [one], mode)
1096 self.invokeBlack([str(workspace)])
1097 with one.open("r") as fobj:
1098 self.assertEqual(fobj.read(), "print('hello')")
1099 with two.open("r") as fobj:
1100 self.assertEqual(fobj.read(), 'print("hello")\n')
1101 cache = black.read_cache(mode)
1102 self.assertIn(str(one), cache)
1103 self.assertIn(str(two), cache)
1105 def test_no_cache_when_writeback_diff(self) -> None:
1107 with cache_dir() as workspace:
1108 src = (workspace / "test.py").resolve()
1109 with src.open("w") as fobj:
1110 fobj.write("print('hello')")
1111 with patch("black.read_cache") as read_cache, patch(
1114 self.invokeBlack([str(src), "--diff"])
1115 cache_file = get_cache_file(mode)
1116 self.assertFalse(cache_file.exists())
1117 write_cache.assert_not_called()
1118 read_cache.assert_not_called()
1120 def test_no_cache_when_writeback_color_diff(self) -> None:
1122 with cache_dir() as workspace:
1123 src = (workspace / "test.py").resolve()
1124 with src.open("w") as fobj:
1125 fobj.write("print('hello')")
1126 with patch("black.read_cache") as read_cache, patch(
1129 self.invokeBlack([str(src), "--diff", "--color"])
1130 cache_file = get_cache_file(mode)
1131 self.assertFalse(cache_file.exists())
1132 write_cache.assert_not_called()
1133 read_cache.assert_not_called()
1136 def test_output_locking_when_writeback_diff(self) -> None:
1137 with cache_dir() as workspace:
1138 for tag in range(0, 4):
1139 src = (workspace / f"test{tag}.py").resolve()
1140 with src.open("w") as fobj:
1141 fobj.write("print('hello')")
1142 with patch("black.Manager", wraps=multiprocessing.Manager) as mgr:
1143 self.invokeBlack(["--diff", str(workspace)], exit_code=0)
1144 # this isn't quite doing what we want, but if it _isn't_
1145 # called then we cannot be using the lock it provides
1149 def test_output_locking_when_writeback_color_diff(self) -> None:
1150 with cache_dir() as workspace:
1151 for tag in range(0, 4):
1152 src = (workspace / f"test{tag}.py").resolve()
1153 with src.open("w") as fobj:
1154 fobj.write("print('hello')")
1155 with patch("black.Manager", wraps=multiprocessing.Manager) as mgr:
1156 self.invokeBlack(["--diff", "--color", str(workspace)], exit_code=0)
1157 # this isn't quite doing what we want, but if it _isn't_
1158 # called then we cannot be using the lock it provides
1161 def test_no_cache_when_stdin(self) -> None:
1164 result = CliRunner().invoke(
1165 black.main, ["-"], input=BytesIO(b"print('hello')")
1167 self.assertEqual(result.exit_code, 0)
1168 cache_file = get_cache_file(mode)
1169 self.assertFalse(cache_file.exists())
1171 def test_read_cache_no_cachefile(self) -> None:
1174 self.assertEqual(black.read_cache(mode), {})
1176 def test_write_cache_read_cache(self) -> None:
1178 with cache_dir() as workspace:
1179 src = (workspace / "test.py").resolve()
1181 black.write_cache({}, [src], mode)
1182 cache = black.read_cache(mode)
1183 self.assertIn(str(src), cache)
1184 self.assertEqual(cache[str(src)], black.get_cache_info(src))
1186 def test_filter_cached(self) -> None:
1187 with TemporaryDirectory() as workspace:
1188 path = Path(workspace)
1189 uncached = (path / "uncached").resolve()
1190 cached = (path / "cached").resolve()
1191 cached_but_changed = (path / "changed").resolve()
1194 cached_but_changed.touch()
1196 str(cached): black.get_cache_info(cached),
1197 str(cached_but_changed): (0.0, 0),
1199 todo, done = black.filter_cached(
1200 cache, {uncached, cached, cached_but_changed}
1202 self.assertEqual(todo, {uncached, cached_but_changed})
1203 self.assertEqual(done, {cached})
1205 def test_write_cache_creates_directory_if_needed(self) -> None:
1207 with cache_dir(exists=False) as workspace:
1208 self.assertFalse(workspace.exists())
1209 black.write_cache({}, [], mode)
1210 self.assertTrue(workspace.exists())
1213 def test_failed_formatting_does_not_get_cached(self) -> None:
1215 with cache_dir() as workspace, patch(
1216 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1218 failing = (workspace / "failing.py").resolve()
1219 with failing.open("w") as fobj:
1220 fobj.write("not actually python")
1221 clean = (workspace / "clean.py").resolve()
1222 with clean.open("w") as fobj:
1223 fobj.write('print("hello")\n')
1224 self.invokeBlack([str(workspace)], exit_code=123)
1225 cache = black.read_cache(mode)
1226 self.assertNotIn(str(failing), cache)
1227 self.assertIn(str(clean), cache)
1229 def test_write_cache_write_fail(self) -> None:
1231 with cache_dir(), patch.object(Path, "open") as mock:
1232 mock.side_effect = OSError
1233 black.write_cache({}, [], mode)
1236 @patch("black.ProcessPoolExecutor", MagicMock(side_effect=OSError))
1237 def test_works_in_mono_process_only_environment(self) -> None:
1238 with cache_dir() as workspace:
1240 (workspace / "one.py").resolve(),
1241 (workspace / "two.py").resolve(),
1243 f.write_text('print("hello")\n')
1244 self.invokeBlack([str(workspace)])
1247 def test_check_diff_use_together(self) -> None:
1249 # Files which will be reformatted.
1250 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1251 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1252 # Files which will not be reformatted.
1253 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1254 self.invokeBlack([str(src2), "--diff", "--check"])
1255 # Multi file command.
1256 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1258 def test_no_files(self) -> None:
1260 # Without an argument, black exits with error code 0.
1261 self.invokeBlack([])
1263 def test_broken_symlink(self) -> None:
1264 with cache_dir() as workspace:
1265 symlink = workspace / "broken_link.py"
1267 symlink.symlink_to("nonexistent.py")
1268 except OSError as e:
1269 self.skipTest(f"Can't create symlinks: {e}")
1270 self.invokeBlack([str(workspace.resolve())])
1272 def test_read_cache_line_lengths(self) -> None:
1274 short_mode = replace(DEFAULT_MODE, line_length=1)
1275 with cache_dir() as workspace:
1276 path = (workspace / "file.py").resolve()
1278 black.write_cache({}, [path], mode)
1279 one = black.read_cache(mode)
1280 self.assertIn(str(path), one)
1281 two = black.read_cache(short_mode)
1282 self.assertNotIn(str(path), two)
1284 def test_single_file_force_pyi(self) -> None:
1285 pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
1286 contents, expected = read_data("force_pyi")
1287 with cache_dir() as workspace:
1288 path = (workspace / "file.py").resolve()
1289 with open(path, "w") as fh:
1291 self.invokeBlack([str(path), "--pyi"])
1292 with open(path, "r") as fh:
1294 # verify cache with --pyi is separate
1295 pyi_cache = black.read_cache(pyi_mode)
1296 self.assertIn(str(path), pyi_cache)
1297 normal_cache = black.read_cache(DEFAULT_MODE)
1298 self.assertNotIn(str(path), normal_cache)
1299 self.assertFormatEqual(expected, actual)
1300 black.assert_equivalent(contents, actual)
1301 black.assert_stable(contents, actual, pyi_mode)
1304 def test_multi_file_force_pyi(self) -> None:
1305 reg_mode = DEFAULT_MODE
1306 pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
1307 contents, expected = read_data("force_pyi")
1308 with cache_dir() as workspace:
1310 (workspace / "file1.py").resolve(),
1311 (workspace / "file2.py").resolve(),
1314 with open(path, "w") as fh:
1316 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1318 with open(path, "r") as fh:
1320 self.assertEqual(actual, expected)
1321 # verify cache with --pyi is separate
1322 pyi_cache = black.read_cache(pyi_mode)
1323 normal_cache = black.read_cache(reg_mode)
1325 self.assertIn(str(path), pyi_cache)
1326 self.assertNotIn(str(path), normal_cache)
1328 def test_pipe_force_pyi(self) -> None:
1329 source, expected = read_data("force_pyi")
1330 result = CliRunner().invoke(
1331 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1333 self.assertEqual(result.exit_code, 0)
1334 actual = result.output
1335 self.assertFormatEqual(actual, expected)
1337 def test_single_file_force_py36(self) -> None:
1338 reg_mode = DEFAULT_MODE
1339 py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
1340 source, expected = read_data("force_py36")
1341 with cache_dir() as workspace:
1342 path = (workspace / "file.py").resolve()
1343 with open(path, "w") as fh:
1345 self.invokeBlack([str(path), *PY36_ARGS])
1346 with open(path, "r") as fh:
1348 # verify cache with --target-version is separate
1349 py36_cache = black.read_cache(py36_mode)
1350 self.assertIn(str(path), py36_cache)
1351 normal_cache = black.read_cache(reg_mode)
1352 self.assertNotIn(str(path), normal_cache)
1353 self.assertEqual(actual, expected)
1356 def test_multi_file_force_py36(self) -> None:
1357 reg_mode = DEFAULT_MODE
1358 py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
1359 source, expected = read_data("force_py36")
1360 with cache_dir() as workspace:
1362 (workspace / "file1.py").resolve(),
1363 (workspace / "file2.py").resolve(),
1366 with open(path, "w") as fh:
1368 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1370 with open(path, "r") as fh:
1372 self.assertEqual(actual, expected)
1373 # verify cache with --target-version is separate
1374 pyi_cache = black.read_cache(py36_mode)
1375 normal_cache = black.read_cache(reg_mode)
1377 self.assertIn(str(path), pyi_cache)
1378 self.assertNotIn(str(path), normal_cache)
1380 def test_pipe_force_py36(self) -> None:
1381 source, expected = read_data("force_py36")
1382 result = CliRunner().invoke(
1384 ["-", "-q", "--target-version=py36"],
1385 input=BytesIO(source.encode("utf8")),
1387 self.assertEqual(result.exit_code, 0)
1388 actual = result.output
1389 self.assertFormatEqual(actual, expected)
1391 def test_include_exclude(self) -> None:
1392 path = THIS_DIR / "data" / "include_exclude_tests"
1393 include = re.compile(r"\.pyi?$")
1394 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1395 report = black.Report()
1396 gitignore = PathSpec.from_lines("gitwildmatch", [])
1397 sources: List[Path] = []
1399 Path(path / "b/dont_exclude/a.py"),
1400 Path(path / "b/dont_exclude/a.pyi"),
1402 this_abs = THIS_DIR.resolve()
1404 black.gen_python_files(
1415 self.assertEqual(sorted(expected), sorted(sources))
1417 def test_gitignore_used_as_default(self) -> None:
1418 path = Path(THIS_DIR / "data" / "include_exclude_tests")
1419 include = re.compile(r"\.pyi?$")
1420 extend_exclude = re.compile(r"/exclude/")
1421 src = str(path / "b/")
1422 report = black.Report()
1423 expected: List[Path] = [
1424 path / "b/.definitely_exclude/a.py",
1425 path / "b/.definitely_exclude/a.pyi",
1435 extend_exclude=extend_exclude,
1438 stdin_filename=None,
1441 self.assertEqual(sorted(expected), sorted(sources))
1443 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1444 def test_exclude_for_issue_1572(self) -> None:
1445 # Exclude shouldn't touch files that were explicitly given to Black through the
1446 # CLI. Exclude is supposed to only apply to the recursive discovery of files.
1447 # https://github.com/psf/black/issues/1572
1448 path = THIS_DIR / "data" / "include_exclude_tests"
1450 exclude = r"/exclude/|a\.py"
1451 src = str(path / "b/exclude/a.py")
1452 report = black.Report()
1453 expected = [Path(path / "b/exclude/a.py")]
1460 include=re.compile(include),
1461 exclude=re.compile(exclude),
1462 extend_exclude=None,
1465 stdin_filename=None,
1468 self.assertEqual(sorted(expected), sorted(sources))
1470 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1471 def test_get_sources_with_stdin(self) -> None:
1473 exclude = r"/exclude/|a\.py"
1475 report = black.Report()
1476 expected = [Path("-")]
1483 include=re.compile(include),
1484 exclude=re.compile(exclude),
1485 extend_exclude=None,
1488 stdin_filename=None,
1491 self.assertEqual(sorted(expected), sorted(sources))
1493 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1494 def test_get_sources_with_stdin_filename(self) -> None:
1496 exclude = r"/exclude/|a\.py"
1498 report = black.Report()
1499 stdin_filename = str(THIS_DIR / "data/collections.py")
1500 expected = [Path(f"__BLACK_STDIN_FILENAME__{stdin_filename}")]
1507 include=re.compile(include),
1508 exclude=re.compile(exclude),
1509 extend_exclude=None,
1512 stdin_filename=stdin_filename,
1515 self.assertEqual(sorted(expected), sorted(sources))
1517 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1518 def test_get_sources_with_stdin_filename_and_exclude(self) -> None:
1519 # Exclude shouldn't exclude stdin_filename since it is mimicking the
1520 # file being passed directly. This is the same as
1521 # test_exclude_for_issue_1572
1522 path = THIS_DIR / "data" / "include_exclude_tests"
1524 exclude = r"/exclude/|a\.py"
1526 report = black.Report()
1527 stdin_filename = str(path / "b/exclude/a.py")
1528 expected = [Path(f"__BLACK_STDIN_FILENAME__{stdin_filename}")]
1535 include=re.compile(include),
1536 exclude=re.compile(exclude),
1537 extend_exclude=None,
1540 stdin_filename=stdin_filename,
1543 self.assertEqual(sorted(expected), sorted(sources))
1545 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1546 def test_get_sources_with_stdin_filename_and_extend_exclude(self) -> None:
1547 # Extend exclude shouldn't exclude stdin_filename since it is mimicking the
1548 # file being passed directly. This is the same as
1549 # test_exclude_for_issue_1572
1550 path = THIS_DIR / "data" / "include_exclude_tests"
1552 extend_exclude = r"/exclude/|a\.py"
1554 report = black.Report()
1555 stdin_filename = str(path / "b/exclude/a.py")
1556 expected = [Path(f"__BLACK_STDIN_FILENAME__{stdin_filename}")]
1563 include=re.compile(include),
1564 exclude=re.compile(""),
1565 extend_exclude=re.compile(extend_exclude),
1568 stdin_filename=stdin_filename,
1571 self.assertEqual(sorted(expected), sorted(sources))
1573 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1574 def test_get_sources_with_stdin_filename_and_force_exclude(self) -> None:
1575 # Force exclude should exclude the file when passing it through
1577 path = THIS_DIR / "data" / "include_exclude_tests"
1579 force_exclude = r"/exclude/|a\.py"
1581 report = black.Report()
1582 stdin_filename = str(path / "b/exclude/a.py")
1589 include=re.compile(include),
1590 exclude=re.compile(""),
1591 extend_exclude=None,
1592 force_exclude=re.compile(force_exclude),
1594 stdin_filename=stdin_filename,
1597 self.assertEqual([], sorted(sources))
1599 def test_reformat_one_with_stdin(self) -> None:
1601 "black.format_stdin_to_stdout",
1602 return_value=lambda *args, **kwargs: black.Changed.YES,
1604 report = MagicMock()
1609 write_back=black.WriteBack.YES,
1613 fsts.assert_called_once()
1614 report.done.assert_called_with(path, black.Changed.YES)
1616 def test_reformat_one_with_stdin_filename(self) -> None:
1618 "black.format_stdin_to_stdout",
1619 return_value=lambda *args, **kwargs: black.Changed.YES,
1621 report = MagicMock()
1623 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1628 write_back=black.WriteBack.YES,
1632 fsts.assert_called_once_with(
1633 fast=True, write_back=black.WriteBack.YES, mode=DEFAULT_MODE
1635 # __BLACK_STDIN_FILENAME__ should have been stripped
1636 report.done.assert_called_with(expected, black.Changed.YES)
1638 def test_reformat_one_with_stdin_filename_pyi(self) -> None:
1640 "black.format_stdin_to_stdout",
1641 return_value=lambda *args, **kwargs: black.Changed.YES,
1643 report = MagicMock()
1645 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1650 write_back=black.WriteBack.YES,
1654 fsts.assert_called_once_with(
1656 write_back=black.WriteBack.YES,
1657 mode=replace(DEFAULT_MODE, is_pyi=True),
1659 # __BLACK_STDIN_FILENAME__ should have been stripped
1660 report.done.assert_called_with(expected, black.Changed.YES)
1662 def test_reformat_one_with_stdin_and_existing_path(self) -> None:
1664 "black.format_stdin_to_stdout",
1665 return_value=lambda *args, **kwargs: black.Changed.YES,
1667 report = MagicMock()
1668 # Even with an existing file, since we are forcing stdin, black
1669 # should output to stdout and not modify the file inplace
1670 p = Path(str(THIS_DIR / "data/collections.py"))
1671 # Make sure is_file actually returns True
1672 self.assertTrue(p.is_file())
1673 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1678 write_back=black.WriteBack.YES,
1682 fsts.assert_called_once()
1683 # __BLACK_STDIN_FILENAME__ should have been stripped
1684 report.done.assert_called_with(expected, black.Changed.YES)
1686 def test_reformat_one_with_stdin_empty(self) -> None:
1687 output = io.StringIO()
1688 with patch("io.TextIOWrapper", lambda *args, **kwargs: output):
1690 black.format_stdin_to_stdout(
1693 write_back=black.WriteBack.YES,
1696 except io.UnsupportedOperation:
1697 pass # StringIO does not support detach
1698 assert output.getvalue() == ""
1700 def test_gitignore_exclude(self) -> None:
1701 path = THIS_DIR / "data" / "include_exclude_tests"
1702 include = re.compile(r"\.pyi?$")
1703 exclude = re.compile(r"")
1704 report = black.Report()
1705 gitignore = PathSpec.from_lines(
1706 "gitwildmatch", ["exclude/", ".definitely_exclude"]
1708 sources: List[Path] = []
1710 Path(path / "b/dont_exclude/a.py"),
1711 Path(path / "b/dont_exclude/a.pyi"),
1713 this_abs = THIS_DIR.resolve()
1715 black.gen_python_files(
1726 self.assertEqual(sorted(expected), sorted(sources))
1728 def test_nested_gitignore(self) -> None:
1729 path = Path(THIS_DIR / "data" / "nested_gitignore_tests")
1730 include = re.compile(r"\.pyi?$")
1731 exclude = re.compile(r"")
1732 root_gitignore = black.files.get_gitignore(path)
1733 report = black.Report()
1734 expected: List[Path] = [
1735 Path(path / "x.py"),
1736 Path(path / "root/b.py"),
1737 Path(path / "root/c.py"),
1738 Path(path / "root/child/c.py"),
1740 this_abs = THIS_DIR.resolve()
1742 black.gen_python_files(
1753 self.assertEqual(sorted(expected), sorted(sources))
1755 def test_empty_include(self) -> None:
1756 path = THIS_DIR / "data" / "include_exclude_tests"
1757 report = black.Report()
1758 gitignore = PathSpec.from_lines("gitwildmatch", [])
1759 empty = re.compile(r"")
1760 sources: List[Path] = []
1762 Path(path / "b/exclude/a.pie"),
1763 Path(path / "b/exclude/a.py"),
1764 Path(path / "b/exclude/a.pyi"),
1765 Path(path / "b/dont_exclude/a.pie"),
1766 Path(path / "b/dont_exclude/a.py"),
1767 Path(path / "b/dont_exclude/a.pyi"),
1768 Path(path / "b/.definitely_exclude/a.pie"),
1769 Path(path / "b/.definitely_exclude/a.py"),
1770 Path(path / "b/.definitely_exclude/a.pyi"),
1771 Path(path / ".gitignore"),
1772 Path(path / "pyproject.toml"),
1774 this_abs = THIS_DIR.resolve()
1776 black.gen_python_files(
1780 re.compile(black.DEFAULT_EXCLUDES),
1787 self.assertEqual(sorted(expected), sorted(sources))
1789 def test_extend_exclude(self) -> None:
1790 path = THIS_DIR / "data" / "include_exclude_tests"
1791 report = black.Report()
1792 gitignore = PathSpec.from_lines("gitwildmatch", [])
1793 sources: List[Path] = []
1795 Path(path / "b/exclude/a.py"),
1796 Path(path / "b/dont_exclude/a.py"),
1798 this_abs = THIS_DIR.resolve()
1800 black.gen_python_files(
1803 re.compile(black.DEFAULT_INCLUDES),
1804 re.compile(r"\.pyi$"),
1805 re.compile(r"\.definitely_exclude"),
1811 self.assertEqual(sorted(expected), sorted(sources))
1813 def test_invalid_cli_regex(self) -> None:
1814 for option in ["--include", "--exclude", "--extend-exclude", "--force-exclude"]:
1815 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1817 def test_required_version_matches_version(self) -> None:
1819 ["--required-version", black.__version__], exit_code=0, ignore_config=True
1822 def test_required_version_does_not_match_version(self) -> None:
1824 ["--required-version", "20.99b"], exit_code=1, ignore_config=True
1827 def test_preserves_line_endings(self) -> None:
1828 with TemporaryDirectory() as workspace:
1829 test_file = Path(workspace) / "test.py"
1830 for nl in ["\n", "\r\n"]:
1831 contents = nl.join(["def f( ):", " pass"])
1832 test_file.write_bytes(contents.encode())
1833 ff(test_file, write_back=black.WriteBack.YES)
1834 updated_contents: bytes = test_file.read_bytes()
1835 self.assertIn(nl.encode(), updated_contents)
1837 self.assertNotIn(b"\r\n", updated_contents)
1839 def test_preserves_line_endings_via_stdin(self) -> None:
1840 for nl in ["\n", "\r\n"]:
1841 contents = nl.join(["def f( ):", " pass"])
1842 runner = BlackRunner()
1843 result = runner.invoke(
1844 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1846 self.assertEqual(result.exit_code, 0)
1847 output = result.stdout_bytes
1848 self.assertIn(nl.encode("utf8"), output)
1850 self.assertNotIn(b"\r\n", output)
1852 def test_assert_equivalent_different_asts(self) -> None:
1853 with self.assertRaises(AssertionError):
1854 black.assert_equivalent("{}", "None")
1856 def test_symlink_out_of_root_directory(self) -> None:
1858 root = THIS_DIR.resolve()
1860 include = re.compile(black.DEFAULT_INCLUDES)
1861 exclude = re.compile(black.DEFAULT_EXCLUDES)
1862 report = black.Report()
1863 gitignore = PathSpec.from_lines("gitwildmatch", [])
1864 # `child` should behave like a symlink which resolved path is clearly
1865 # outside of the `root` directory.
1866 path.iterdir.return_value = [child]
1867 child.resolve.return_value = Path("/a/b/c")
1868 child.as_posix.return_value = "/a/b/c"
1869 child.is_symlink.return_value = True
1872 black.gen_python_files(
1883 except ValueError as ve:
1884 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1885 path.iterdir.assert_called_once()
1886 child.resolve.assert_called_once()
1887 child.is_symlink.assert_called_once()
1888 # `child` should behave like a strange file which resolved path is clearly
1889 # outside of the `root` directory.
1890 child.is_symlink.return_value = False
1891 with self.assertRaises(ValueError):
1893 black.gen_python_files(
1904 path.iterdir.assert_called()
1905 self.assertEqual(path.iterdir.call_count, 2)
1906 child.resolve.assert_called()
1907 self.assertEqual(child.resolve.call_count, 2)
1908 child.is_symlink.assert_called()
1909 self.assertEqual(child.is_symlink.call_count, 2)
1911 def test_shhh_click(self) -> None:
1913 from click import _unicodefun
1914 except ModuleNotFoundError:
1915 self.skipTest("Incompatible Click version")
1916 if not hasattr(_unicodefun, "_verify_python3_env"):
1917 self.skipTest("Incompatible Click version")
1918 # First, let's see if Click is crashing with a preferred ASCII charset.
1919 with patch("locale.getpreferredencoding") as gpe:
1920 gpe.return_value = "ASCII"
1921 with self.assertRaises(RuntimeError):
1922 _unicodefun._verify_python3_env() # type: ignore
1923 # Now, let's silence Click...
1925 # ...and confirm it's silent.
1926 with patch("locale.getpreferredencoding") as gpe:
1927 gpe.return_value = "ASCII"
1929 _unicodefun._verify_python3_env() # type: ignore
1930 except RuntimeError as re:
1931 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1933 def test_root_logger_not_used_directly(self) -> None:
1934 def fail(*args: Any, **kwargs: Any) -> None:
1935 self.fail("Record created with root logger")
1937 with patch.multiple(
1946 ff(THIS_DIR / "util.py")
1948 def test_invalid_config_return_code(self) -> None:
1949 tmp_file = Path(black.dump_to_file())
1951 tmp_config = Path(black.dump_to_file())
1953 args = ["--config", str(tmp_config), str(tmp_file)]
1954 self.invokeBlack(args, exit_code=2, ignore_config=False)
1958 def test_parse_pyproject_toml(self) -> None:
1959 test_toml_file = THIS_DIR / "test.toml"
1960 config = black.parse_pyproject_toml(str(test_toml_file))
1961 self.assertEqual(config["verbose"], 1)
1962 self.assertEqual(config["check"], "no")
1963 self.assertEqual(config["diff"], "y")
1964 self.assertEqual(config["color"], True)
1965 self.assertEqual(config["line_length"], 79)
1966 self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
1967 self.assertEqual(config["exclude"], r"\.pyi?$")
1968 self.assertEqual(config["include"], r"\.py?$")
1970 def test_read_pyproject_toml(self) -> None:
1971 test_toml_file = THIS_DIR / "test.toml"
1972 fake_ctx = FakeContext()
1973 black.read_pyproject_toml(fake_ctx, FakeParameter(), str(test_toml_file))
1974 config = fake_ctx.default_map
1975 self.assertEqual(config["verbose"], "1")
1976 self.assertEqual(config["check"], "no")
1977 self.assertEqual(config["diff"], "y")
1978 self.assertEqual(config["color"], "True")
1979 self.assertEqual(config["line_length"], "79")
1980 self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
1981 self.assertEqual(config["exclude"], r"\.pyi?$")
1982 self.assertEqual(config["include"], r"\.py?$")
1984 def test_find_project_root(self) -> None:
1985 with TemporaryDirectory() as workspace:
1986 root = Path(workspace)
1987 test_dir = root / "test"
1990 src_dir = root / "src"
1993 root_pyproject = root / "pyproject.toml"
1994 root_pyproject.touch()
1995 src_pyproject = src_dir / "pyproject.toml"
1996 src_pyproject.touch()
1997 src_python = src_dir / "foo.py"
2001 black.find_project_root((src_dir, test_dir)), root.resolve()
2003 self.assertEqual(black.find_project_root((src_dir,)), src_dir.resolve())
2004 self.assertEqual(black.find_project_root((src_python,)), src_dir.resolve())
2007 "black.files.find_user_pyproject_toml",
2008 black.files.find_user_pyproject_toml.__wrapped__,
2010 def test_find_user_pyproject_toml_linux(self) -> None:
2011 if system() == "Windows":
2014 # Test if XDG_CONFIG_HOME is checked
2015 with TemporaryDirectory() as workspace:
2016 tmp_user_config = Path(workspace) / "black"
2017 with patch.dict("os.environ", {"XDG_CONFIG_HOME": workspace}):
2019 black.files.find_user_pyproject_toml(), tmp_user_config.resolve()
2022 # Test fallback for XDG_CONFIG_HOME
2023 with patch.dict("os.environ"):
2024 os.environ.pop("XDG_CONFIG_HOME", None)
2025 fallback_user_config = Path("~/.config").expanduser() / "black"
2027 black.files.find_user_pyproject_toml(), fallback_user_config.resolve()
2030 def test_find_user_pyproject_toml_windows(self) -> None:
2031 if system() != "Windows":
2034 user_config_path = Path.home() / ".black"
2036 black.files.find_user_pyproject_toml(), user_config_path.resolve()
2039 def test_bpo_33660_workaround(self) -> None:
2040 if system() == "Windows":
2043 # https://bugs.python.org/issue33660
2045 old_cwd = Path.cwd()
2049 path = Path("workspace") / "project"
2050 report = black.Report(verbose=True)
2051 normalized_path = black.normalize_path_maybe_ignore(path, root, report)
2052 self.assertEqual(normalized_path, "workspace/project")
2054 os.chdir(str(old_cwd))
2056 def test_newline_comment_interaction(self) -> None:
2057 source = "class A:\\\r\n# type: ignore\n pass\n"
2058 output = black.format_str(source, mode=DEFAULT_MODE)
2059 black.assert_stable(source, output, mode=DEFAULT_MODE)
2061 def test_bpo_2142_workaround(self) -> None:
2063 # https://bugs.python.org/issue2142
2065 source, _ = read_data("missing_final_newline.py")
2066 # read_data adds a trailing newline
2067 source = source.rstrip()
2068 expected, _ = read_data("missing_final_newline.diff")
2069 tmp_file = Path(black.dump_to_file(source, ensure_final_newline=False))
2070 diff_header = re.compile(
2071 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
2072 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
2075 result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
2076 self.assertEqual(result.exit_code, 0)
2079 actual = result.output
2080 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
2081 self.assertEqual(actual, expected)
2083 @pytest.mark.python2
2084 def test_docstring_reformat_for_py27(self) -> None:
2086 Check that stripping trailing whitespace from Python 2 docstrings
2087 doesn't trigger a "not equivalent to source" error
2090 b'def foo():\r\n """Testing\r\n Testing """\r\n print "Foo"\r\n'
2092 expected = 'def foo():\n """Testing\n Testing"""\n print "Foo"\n'
2094 result = CliRunner().invoke(
2096 ["-", "-q", "--target-version=py27"],
2097 input=BytesIO(source),
2100 self.assertEqual(result.exit_code, 0)
2101 actual = result.output
2102 self.assertFormatEqual(actual, expected)
2105 def compare_results(
2106 result: click.testing.Result, expected_value: str, expected_exit_code: int
2108 """Helper method to test the value and exit code of a click Result."""
2110 result.output == expected_value
2111 ), "The output did not match the expected value."
2112 assert result.exit_code == expected_exit_code, "The exit code is incorrect."
2114 def test_code_option(self) -> None:
2115 """Test the code option with no changes."""
2116 code = 'print("Hello world")\n'
2117 args = ["--code", code]
2118 result = CliRunner().invoke(black.main, args)
2120 self.compare_results(result, code, 0)
2122 def test_code_option_changed(self) -> None:
2123 """Test the code option when changes are required."""
2124 code = "print('hello world')"
2125 formatted = black.format_str(code, mode=DEFAULT_MODE)
2127 args = ["--code", code]
2128 result = CliRunner().invoke(black.main, args)
2130 self.compare_results(result, formatted, 0)
2132 def test_code_option_check(self) -> None:
2133 """Test the code option when check is passed."""
2134 args = ["--check", "--code", 'print("Hello world")\n']
2135 result = CliRunner().invoke(black.main, args)
2136 self.compare_results(result, "", 0)
2138 def test_code_option_check_changed(self) -> None:
2139 """Test the code option when changes are required, and check is passed."""
2140 args = ["--check", "--code", "print('hello world')"]
2141 result = CliRunner().invoke(black.main, args)
2142 self.compare_results(result, "", 1)
2144 def test_code_option_diff(self) -> None:
2145 """Test the code option when diff is passed."""
2146 code = "print('hello world')"
2147 formatted = black.format_str(code, mode=DEFAULT_MODE)
2148 result_diff = diff(code, formatted, "STDIN", "STDOUT")
2150 args = ["--diff", "--code", code]
2151 result = CliRunner().invoke(black.main, args)
2153 # Remove time from diff
2154 output = DIFF_TIME.sub("", result.output)
2156 assert output == result_diff, "The output did not match the expected value."
2157 assert result.exit_code == 0, "The exit code is incorrect."
2159 def test_code_option_color_diff(self) -> None:
2160 """Test the code option when color and diff are passed."""
2161 code = "print('hello world')"
2162 formatted = black.format_str(code, mode=DEFAULT_MODE)
2164 result_diff = diff(code, formatted, "STDIN", "STDOUT")
2165 result_diff = color_diff(result_diff)
2167 args = ["--diff", "--color", "--code", code]
2168 result = CliRunner().invoke(black.main, args)
2170 # Remove time from diff
2171 output = DIFF_TIME.sub("", result.output)
2173 assert output == result_diff, "The output did not match the expected value."
2174 assert result.exit_code == 0, "The exit code is incorrect."
2176 def test_code_option_safe(self) -> None:
2177 """Test that the code option throws an error when the sanity checks fail."""
2178 # Patch black.assert_equivalent to ensure the sanity checks fail
2179 with patch.object(black, "assert_equivalent", side_effect=AssertionError):
2180 code = 'print("Hello world")'
2181 error_msg = f"{code}\nerror: cannot format <string>: \n"
2183 args = ["--safe", "--code", code]
2184 result = CliRunner().invoke(black.main, args)
2186 self.compare_results(result, error_msg, 123)
2188 def test_code_option_fast(self) -> None:
2189 """Test that the code option ignores errors when the sanity checks fail."""
2190 # Patch black.assert_equivalent to ensure the sanity checks fail
2191 with patch.object(black, "assert_equivalent", side_effect=AssertionError):
2192 code = 'print("Hello world")'
2193 formatted = black.format_str(code, mode=DEFAULT_MODE)
2195 args = ["--fast", "--code", code]
2196 result = CliRunner().invoke(black.main, args)
2198 self.compare_results(result, formatted, 0)
2200 def test_code_option_config(self) -> None:
2202 Test that the code option finds the pyproject.toml in the current directory.
2204 with patch.object(black, "parse_pyproject_toml", return_value={}) as parse:
2205 # Make sure we are in the project root with the pyproject file
2206 if not Path("tests").exists():
2209 args = ["--code", "print"]
2210 CliRunner().invoke(black.main, args)
2212 pyproject_path = Path(Path().cwd(), "pyproject.toml").resolve()
2214 len(parse.mock_calls) >= 1
2215 ), "Expected config parse to be called with the current directory."
2217 _, call_args, _ = parse.mock_calls[0]
2219 call_args[0].lower() == str(pyproject_path).lower()
2220 ), "Incorrect config loaded."
2222 def test_code_option_parent_config(self) -> None:
2224 Test that the code option finds the pyproject.toml in the parent directory.
2226 with patch.object(black, "parse_pyproject_toml", return_value={}) as parse:
2227 # Make sure we are in the tests directory
2228 if Path("tests").exists():
2231 args = ["--code", "print"]
2232 CliRunner().invoke(black.main, args)
2234 pyproject_path = Path(Path().cwd().parent, "pyproject.toml").resolve()
2236 len(parse.mock_calls) >= 1
2237 ), "Expected config parse to be called with the current directory."
2239 _, call_args, _ = parse.mock_calls[0]
2241 call_args[0].lower() == str(pyproject_path).lower()
2242 ), "Incorrect config loaded."
2245 with open(black.__file__, "r", encoding="utf-8") as _bf:
2246 black_source_lines = _bf.readlines()
2249 def tracefunc(frame: types.FrameType, event: str, arg: Any) -> Callable:
2250 """Show function calls `from black/__init__.py` as they happen.
2252 Register this with `sys.settrace()` in a test you're debugging.
2257 stack = len(inspect.stack()) - 19
2259 filename = frame.f_code.co_filename
2260 lineno = frame.f_lineno
2261 func_sig_lineno = lineno - 1
2262 funcname = black_source_lines[func_sig_lineno].strip()
2263 while funcname.startswith("@"):
2264 func_sig_lineno += 1
2265 funcname = black_source_lines[func_sig_lineno].strip()
2266 if "black/__init__.py" in filename:
2267 print(f"{' ' * stack}{lineno}:{funcname}")
2271 if __name__ == "__main__":
2272 unittest.main(module="test_black")