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)
126 f"Failed with args: {args}\n"
127 f"stdout: {result.stdout_bytes.decode()!r}\n"
128 f"stderr: {result.stderr_bytes.decode()!r}\n"
129 f"exception: {result.exception}"
133 @patch("black.dump_to_file", dump_to_stderr)
134 def test_empty(self) -> None:
135 source = expected = ""
137 self.assertFormatEqual(expected, actual)
138 black.assert_equivalent(source, actual)
139 black.assert_stable(source, actual, DEFAULT_MODE)
141 def test_empty_ff(self) -> None:
143 tmp_file = Path(black.dump_to_file())
145 self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES))
146 with open(tmp_file, encoding="utf8") as f:
150 self.assertFormatEqual(expected, actual)
152 def test_piping(self) -> None:
153 source, expected = read_data("src/black/__init__", data=False)
154 result = BlackRunner().invoke(
156 ["-", "--fast", f"--line-length={black.DEFAULT_LINE_LENGTH}"],
157 input=BytesIO(source.encode("utf8")),
159 self.assertEqual(result.exit_code, 0)
160 self.assertFormatEqual(expected, result.output)
161 if source != result.output:
162 black.assert_equivalent(source, result.output)
163 black.assert_stable(source, result.output, DEFAULT_MODE)
165 def test_piping_diff(self) -> None:
166 diff_header = re.compile(
167 r"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d\d\d\d "
170 source, _ = read_data("expression.py")
171 expected, _ = read_data("expression.diff")
172 config = THIS_DIR / "data" / "empty_pyproject.toml"
176 f"--line-length={black.DEFAULT_LINE_LENGTH}",
178 f"--config={config}",
180 result = BlackRunner().invoke(
181 black.main, args, input=BytesIO(source.encode("utf8"))
183 self.assertEqual(result.exit_code, 0)
184 actual = diff_header.sub(DETERMINISTIC_HEADER, result.output)
185 actual = actual.rstrip() + "\n" # the diff output has a trailing space
186 self.assertEqual(expected, actual)
188 def test_piping_diff_with_color(self) -> None:
189 source, _ = read_data("expression.py")
190 config = THIS_DIR / "data" / "empty_pyproject.toml"
194 f"--line-length={black.DEFAULT_LINE_LENGTH}",
197 f"--config={config}",
199 result = BlackRunner().invoke(
200 black.main, args, input=BytesIO(source.encode("utf8"))
202 actual = result.output
203 # Again, the contents are checked in a different test, so only look for colors.
204 self.assertIn("\033[1;37m", actual)
205 self.assertIn("\033[36m", actual)
206 self.assertIn("\033[32m", actual)
207 self.assertIn("\033[31m", actual)
208 self.assertIn("\033[0m", actual)
210 @patch("black.dump_to_file", dump_to_stderr)
211 def _test_wip(self) -> None:
212 source, expected = read_data("wip")
213 sys.settrace(tracefunc)
216 experimental_string_processing=False,
217 target_versions={black.TargetVersion.PY38},
219 actual = fs(source, mode=mode)
221 self.assertFormatEqual(expected, actual)
222 black.assert_equivalent(source, actual)
223 black.assert_stable(source, actual, black.FileMode())
225 @unittest.expectedFailure
226 @patch("black.dump_to_file", dump_to_stderr)
227 def test_trailing_comma_optional_parens_stability1(self) -> None:
228 source, _expected = read_data("trailing_comma_optional_parens1")
230 black.assert_stable(source, actual, DEFAULT_MODE)
232 @unittest.expectedFailure
233 @patch("black.dump_to_file", dump_to_stderr)
234 def test_trailing_comma_optional_parens_stability2(self) -> None:
235 source, _expected = read_data("trailing_comma_optional_parens2")
237 black.assert_stable(source, actual, DEFAULT_MODE)
239 @unittest.expectedFailure
240 @patch("black.dump_to_file", dump_to_stderr)
241 def test_trailing_comma_optional_parens_stability3(self) -> None:
242 source, _expected = read_data("trailing_comma_optional_parens3")
244 black.assert_stable(source, actual, DEFAULT_MODE)
246 @patch("black.dump_to_file", dump_to_stderr)
247 def test_trailing_comma_optional_parens_stability1_pass2(self) -> None:
248 source, _expected = read_data("trailing_comma_optional_parens1")
249 actual = fs(fs(source)) # this is what `format_file_contents` does with --safe
250 black.assert_stable(source, actual, DEFAULT_MODE)
252 @patch("black.dump_to_file", dump_to_stderr)
253 def test_trailing_comma_optional_parens_stability2_pass2(self) -> None:
254 source, _expected = read_data("trailing_comma_optional_parens2")
255 actual = fs(fs(source)) # this is what `format_file_contents` does with --safe
256 black.assert_stable(source, actual, DEFAULT_MODE)
258 @patch("black.dump_to_file", dump_to_stderr)
259 def test_trailing_comma_optional_parens_stability3_pass2(self) -> None:
260 source, _expected = read_data("trailing_comma_optional_parens3")
261 actual = fs(fs(source)) # this is what `format_file_contents` does with --safe
262 black.assert_stable(source, actual, DEFAULT_MODE)
264 @patch("black.dump_to_file", dump_to_stderr)
265 def test_pep_572(self) -> None:
266 source, expected = read_data("pep_572")
268 self.assertFormatEqual(expected, actual)
269 black.assert_stable(source, actual, DEFAULT_MODE)
270 if sys.version_info >= (3, 8):
271 black.assert_equivalent(source, actual)
273 @patch("black.dump_to_file", dump_to_stderr)
274 def test_pep_572_remove_parens(self) -> None:
275 source, expected = read_data("pep_572_remove_parens")
277 self.assertFormatEqual(expected, actual)
278 black.assert_stable(source, actual, DEFAULT_MODE)
279 if sys.version_info >= (3, 8):
280 black.assert_equivalent(source, actual)
282 @patch("black.dump_to_file", dump_to_stderr)
283 def test_pep_572_do_not_remove_parens(self) -> None:
284 source, expected = read_data("pep_572_do_not_remove_parens")
285 # the AST safety checks will fail, but that's expected, just make sure no
286 # parentheses are touched
287 actual = black.format_str(source, mode=DEFAULT_MODE)
288 self.assertFormatEqual(expected, actual)
290 def test_pep_572_version_detection(self) -> None:
291 source, _ = read_data("pep_572")
292 root = black.lib2to3_parse(source)
293 features = black.get_features_used(root)
294 self.assertIn(black.Feature.ASSIGNMENT_EXPRESSIONS, features)
295 versions = black.detect_target_versions(root)
296 self.assertIn(black.TargetVersion.PY38, versions)
298 def test_expression_ff(self) -> None:
299 source, expected = read_data("expression")
300 tmp_file = Path(black.dump_to_file(source))
302 self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
303 with open(tmp_file, encoding="utf8") as f:
307 self.assertFormatEqual(expected, actual)
308 with patch("black.dump_to_file", dump_to_stderr):
309 black.assert_equivalent(source, actual)
310 black.assert_stable(source, actual, DEFAULT_MODE)
312 def test_expression_diff(self) -> None:
313 source, _ = read_data("expression.py")
314 config = THIS_DIR / "data" / "empty_pyproject.toml"
315 expected, _ = read_data("expression.diff")
316 tmp_file = Path(black.dump_to_file(source))
317 diff_header = re.compile(
318 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
319 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
322 result = BlackRunner().invoke(
323 black.main, ["--diff", str(tmp_file), f"--config={config}"]
325 self.assertEqual(result.exit_code, 0)
328 actual = result.output
329 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
330 if expected != actual:
331 dump = black.dump_to_file(actual)
333 "Expected diff isn't equal to the actual. If you made changes to"
334 " expression.py and this is an anticipated difference, overwrite"
335 f" tests/data/expression.diff with {dump}"
337 self.assertEqual(expected, actual, msg)
339 def test_expression_diff_with_color(self) -> None:
340 source, _ = read_data("expression.py")
341 config = THIS_DIR / "data" / "empty_pyproject.toml"
342 expected, _ = read_data("expression.diff")
343 tmp_file = Path(black.dump_to_file(source))
345 result = BlackRunner().invoke(
346 black.main, ["--diff", "--color", str(tmp_file), f"--config={config}"]
350 actual = result.output
351 # We check the contents of the diff in `test_expression_diff`. All
352 # we need to check here is that color codes exist in the result.
353 self.assertIn("\033[1;37m", actual)
354 self.assertIn("\033[36m", actual)
355 self.assertIn("\033[32m", actual)
356 self.assertIn("\033[31m", actual)
357 self.assertIn("\033[0m", actual)
359 @patch("black.dump_to_file", dump_to_stderr)
360 def test_pep_570(self) -> None:
361 source, expected = read_data("pep_570")
363 self.assertFormatEqual(expected, actual)
364 black.assert_stable(source, actual, DEFAULT_MODE)
365 if sys.version_info >= (3, 8):
366 black.assert_equivalent(source, actual)
368 def test_detect_pos_only_arguments(self) -> None:
369 source, _ = read_data("pep_570")
370 root = black.lib2to3_parse(source)
371 features = black.get_features_used(root)
372 self.assertIn(black.Feature.POS_ONLY_ARGUMENTS, features)
373 versions = black.detect_target_versions(root)
374 self.assertIn(black.TargetVersion.PY38, versions)
376 @patch("black.dump_to_file", dump_to_stderr)
377 def test_string_quotes(self) -> None:
378 source, expected = read_data("string_quotes")
379 mode = black.Mode(experimental_string_processing=True)
380 actual = fs(source, mode=mode)
381 self.assertFormatEqual(expected, actual)
382 black.assert_equivalent(source, actual)
383 black.assert_stable(source, actual, mode)
384 mode = replace(mode, string_normalization=False)
385 not_normalized = fs(source, mode=mode)
386 self.assertFormatEqual(source.replace("\\\n", ""), not_normalized)
387 black.assert_equivalent(source, not_normalized)
388 black.assert_stable(source, not_normalized, mode=mode)
390 @patch("black.dump_to_file", dump_to_stderr)
391 def test_docstring_no_string_normalization(self) -> None:
392 """Like test_docstring but with string normalization off."""
393 source, expected = read_data("docstring_no_string_normalization")
394 mode = replace(DEFAULT_MODE, string_normalization=False)
395 actual = fs(source, mode=mode)
396 self.assertFormatEqual(expected, actual)
397 black.assert_equivalent(source, actual)
398 black.assert_stable(source, actual, mode)
400 def test_long_strings_flag_disabled(self) -> None:
401 """Tests for turning off the string processing logic."""
402 source, expected = read_data("long_strings_flag_disabled")
403 mode = replace(DEFAULT_MODE, experimental_string_processing=False)
404 actual = fs(source, mode=mode)
405 self.assertFormatEqual(expected, actual)
406 black.assert_stable(expected, actual, mode)
408 @patch("black.dump_to_file", dump_to_stderr)
409 def test_numeric_literals(self) -> None:
410 source, expected = read_data("numeric_literals")
411 mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
412 actual = fs(source, mode=mode)
413 self.assertFormatEqual(expected, actual)
414 black.assert_equivalent(source, actual)
415 black.assert_stable(source, actual, mode)
417 @patch("black.dump_to_file", dump_to_stderr)
418 def test_numeric_literals_ignoring_underscores(self) -> None:
419 source, expected = read_data("numeric_literals_skip_underscores")
420 mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
421 actual = fs(source, mode=mode)
422 self.assertFormatEqual(expected, actual)
423 black.assert_equivalent(source, actual)
424 black.assert_stable(source, actual, mode)
426 def test_skip_magic_trailing_comma(self) -> None:
427 source, _ = read_data("expression.py")
428 expected, _ = read_data("expression_skip_magic_trailing_comma.diff")
429 tmp_file = Path(black.dump_to_file(source))
430 diff_header = re.compile(
431 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
432 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
435 result = BlackRunner().invoke(black.main, ["-C", "--diff", str(tmp_file)])
436 self.assertEqual(result.exit_code, 0)
439 actual = result.output
440 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
441 actual = actual.rstrip() + "\n" # the diff output has a trailing space
442 if expected != actual:
443 dump = black.dump_to_file(actual)
445 "Expected diff isn't equal to the actual. If you made changes to"
446 " expression.py and this is an anticipated difference, overwrite"
447 f" tests/data/expression_skip_magic_trailing_comma.diff with {dump}"
449 self.assertEqual(expected, actual, msg)
451 @pytest.mark.no_python2
452 def test_python2_should_fail_without_optional_install(self) -> None:
453 if sys.version_info < (3, 8):
455 "Python 3.6 and 3.7 will install typed-ast to work and as such will be"
456 " able to parse Python 2 syntax without explicitly specifying the"
461 tmp_file = Path(black.dump_to_file(source))
463 runner = BlackRunner()
464 result = runner.invoke(black.main, [str(tmp_file)])
465 self.assertEqual(result.exit_code, 123)
469 result.stderr_bytes.decode()
476 "The requested source code has invalid Python 3 syntax."
477 "If you are trying to format Python 2 files please reinstall Black"
478 " with the 'python2' extra: `python3 -m pip install black[python2]`."
480 self.assertIn(msg, actual)
483 @patch("black.dump_to_file", dump_to_stderr)
484 def test_python2_print_function(self) -> None:
485 source, expected = read_data("python2_print_function")
486 mode = replace(DEFAULT_MODE, target_versions={TargetVersion.PY27})
487 actual = fs(source, mode=mode)
488 self.assertFormatEqual(expected, actual)
489 black.assert_equivalent(source, actual)
490 black.assert_stable(source, actual, mode)
492 @patch("black.dump_to_file", dump_to_stderr)
493 def test_stub(self) -> None:
494 mode = replace(DEFAULT_MODE, is_pyi=True)
495 source, expected = read_data("stub.pyi")
496 actual = fs(source, mode=mode)
497 self.assertFormatEqual(expected, actual)
498 black.assert_stable(source, actual, mode)
500 @patch("black.dump_to_file", dump_to_stderr)
501 def test_async_as_identifier(self) -> None:
502 source_path = (THIS_DIR / "data" / "async_as_identifier.py").resolve()
503 source, expected = read_data("async_as_identifier")
505 self.assertFormatEqual(expected, actual)
506 major, minor = sys.version_info[:2]
507 if major < 3 or (major <= 3 and minor < 7):
508 black.assert_equivalent(source, actual)
509 black.assert_stable(source, actual, DEFAULT_MODE)
510 # ensure black can parse this when the target is 3.6
511 self.invokeBlack([str(source_path), "--target-version", "py36"])
512 # but not on 3.7, because async/await is no longer an identifier
513 self.invokeBlack([str(source_path), "--target-version", "py37"], exit_code=123)
515 @patch("black.dump_to_file", dump_to_stderr)
516 def test_python37(self) -> None:
517 source_path = (THIS_DIR / "data" / "python37.py").resolve()
518 source, expected = read_data("python37")
520 self.assertFormatEqual(expected, actual)
521 major, minor = sys.version_info[:2]
522 if major > 3 or (major == 3 and minor >= 7):
523 black.assert_equivalent(source, actual)
524 black.assert_stable(source, actual, DEFAULT_MODE)
525 # ensure black can parse this when the target is 3.7
526 self.invokeBlack([str(source_path), "--target-version", "py37"])
527 # but not on 3.6, because we use async as a reserved keyword
528 self.invokeBlack([str(source_path), "--target-version", "py36"], exit_code=123)
530 @patch("black.dump_to_file", dump_to_stderr)
531 def test_python38(self) -> None:
532 source, expected = read_data("python38")
534 self.assertFormatEqual(expected, actual)
535 major, minor = sys.version_info[:2]
536 if major > 3 or (major == 3 and minor >= 8):
537 black.assert_equivalent(source, actual)
538 black.assert_stable(source, actual, DEFAULT_MODE)
540 @patch("black.dump_to_file", dump_to_stderr)
541 def test_python39(self) -> None:
542 source, expected = read_data("python39")
544 self.assertFormatEqual(expected, actual)
545 major, minor = sys.version_info[:2]
546 if major > 3 or (major == 3 and minor >= 9):
547 black.assert_equivalent(source, actual)
548 black.assert_stable(source, actual, DEFAULT_MODE)
550 def test_tab_comment_indentation(self) -> None:
551 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
552 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
553 self.assertFormatEqual(contents_spc, fs(contents_spc))
554 self.assertFormatEqual(contents_spc, fs(contents_tab))
556 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
557 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
558 self.assertFormatEqual(contents_spc, fs(contents_spc))
559 self.assertFormatEqual(contents_spc, fs(contents_tab))
561 # mixed tabs and spaces (valid Python 2 code)
562 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t# comment\n pass\n"
563 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
564 self.assertFormatEqual(contents_spc, fs(contents_spc))
565 self.assertFormatEqual(contents_spc, fs(contents_tab))
567 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t\t# comment\n pass\n"
568 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
569 self.assertFormatEqual(contents_spc, fs(contents_spc))
570 self.assertFormatEqual(contents_spc, fs(contents_tab))
572 def test_report_verbose(self) -> None:
573 report = Report(verbose=True)
577 def out(msg: str, **kwargs: Any) -> None:
578 out_lines.append(msg)
580 def err(msg: str, **kwargs: Any) -> None:
581 err_lines.append(msg)
583 with patch("black.output._out", out), patch("black.output._err", err):
584 report.done(Path("f1"), black.Changed.NO)
585 self.assertEqual(len(out_lines), 1)
586 self.assertEqual(len(err_lines), 0)
587 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
588 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
589 self.assertEqual(report.return_code, 0)
590 report.done(Path("f2"), black.Changed.YES)
591 self.assertEqual(len(out_lines), 2)
592 self.assertEqual(len(err_lines), 0)
593 self.assertEqual(out_lines[-1], "reformatted f2")
595 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
597 report.done(Path("f3"), black.Changed.CACHED)
598 self.assertEqual(len(out_lines), 3)
599 self.assertEqual(len(err_lines), 0)
601 out_lines[-1], "f3 wasn't modified on disk since last run."
604 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
606 self.assertEqual(report.return_code, 0)
608 self.assertEqual(report.return_code, 1)
610 report.failed(Path("e1"), "boom")
611 self.assertEqual(len(out_lines), 3)
612 self.assertEqual(len(err_lines), 1)
613 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
615 unstyle(str(report)),
616 "1 file reformatted, 2 files left unchanged, 1 file failed to"
619 self.assertEqual(report.return_code, 123)
620 report.done(Path("f3"), black.Changed.YES)
621 self.assertEqual(len(out_lines), 4)
622 self.assertEqual(len(err_lines), 1)
623 self.assertEqual(out_lines[-1], "reformatted f3")
625 unstyle(str(report)),
626 "2 files reformatted, 2 files left unchanged, 1 file failed to"
629 self.assertEqual(report.return_code, 123)
630 report.failed(Path("e2"), "boom")
631 self.assertEqual(len(out_lines), 4)
632 self.assertEqual(len(err_lines), 2)
633 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
635 unstyle(str(report)),
636 "2 files reformatted, 2 files left unchanged, 2 files failed to"
639 self.assertEqual(report.return_code, 123)
640 report.path_ignored(Path("wat"), "no match")
641 self.assertEqual(len(out_lines), 5)
642 self.assertEqual(len(err_lines), 2)
643 self.assertEqual(out_lines[-1], "wat ignored: no match")
645 unstyle(str(report)),
646 "2 files reformatted, 2 files left unchanged, 2 files failed to"
649 self.assertEqual(report.return_code, 123)
650 report.done(Path("f4"), black.Changed.NO)
651 self.assertEqual(len(out_lines), 6)
652 self.assertEqual(len(err_lines), 2)
653 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
655 unstyle(str(report)),
656 "2 files reformatted, 3 files left unchanged, 2 files failed to"
659 self.assertEqual(report.return_code, 123)
662 unstyle(str(report)),
663 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
664 " would fail to reformat.",
669 unstyle(str(report)),
670 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
671 " would fail to reformat.",
674 def test_report_quiet(self) -> None:
675 report = Report(quiet=True)
679 def out(msg: str, **kwargs: Any) -> None:
680 out_lines.append(msg)
682 def err(msg: str, **kwargs: Any) -> None:
683 err_lines.append(msg)
685 with patch("black.output._out", out), patch("black.output._err", err):
686 report.done(Path("f1"), black.Changed.NO)
687 self.assertEqual(len(out_lines), 0)
688 self.assertEqual(len(err_lines), 0)
689 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
690 self.assertEqual(report.return_code, 0)
691 report.done(Path("f2"), black.Changed.YES)
692 self.assertEqual(len(out_lines), 0)
693 self.assertEqual(len(err_lines), 0)
695 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
697 report.done(Path("f3"), black.Changed.CACHED)
698 self.assertEqual(len(out_lines), 0)
699 self.assertEqual(len(err_lines), 0)
701 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
703 self.assertEqual(report.return_code, 0)
705 self.assertEqual(report.return_code, 1)
707 report.failed(Path("e1"), "boom")
708 self.assertEqual(len(out_lines), 0)
709 self.assertEqual(len(err_lines), 1)
710 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
712 unstyle(str(report)),
713 "1 file reformatted, 2 files left unchanged, 1 file failed to"
716 self.assertEqual(report.return_code, 123)
717 report.done(Path("f3"), black.Changed.YES)
718 self.assertEqual(len(out_lines), 0)
719 self.assertEqual(len(err_lines), 1)
721 unstyle(str(report)),
722 "2 files reformatted, 2 files left unchanged, 1 file failed to"
725 self.assertEqual(report.return_code, 123)
726 report.failed(Path("e2"), "boom")
727 self.assertEqual(len(out_lines), 0)
728 self.assertEqual(len(err_lines), 2)
729 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
731 unstyle(str(report)),
732 "2 files reformatted, 2 files left unchanged, 2 files failed to"
735 self.assertEqual(report.return_code, 123)
736 report.path_ignored(Path("wat"), "no match")
737 self.assertEqual(len(out_lines), 0)
738 self.assertEqual(len(err_lines), 2)
740 unstyle(str(report)),
741 "2 files reformatted, 2 files left unchanged, 2 files failed to"
744 self.assertEqual(report.return_code, 123)
745 report.done(Path("f4"), black.Changed.NO)
746 self.assertEqual(len(out_lines), 0)
747 self.assertEqual(len(err_lines), 2)
749 unstyle(str(report)),
750 "2 files reformatted, 3 files left unchanged, 2 files failed to"
753 self.assertEqual(report.return_code, 123)
756 unstyle(str(report)),
757 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
758 " would fail to reformat.",
763 unstyle(str(report)),
764 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
765 " would fail to reformat.",
768 def test_report_normal(self) -> None:
769 report = black.Report()
773 def out(msg: str, **kwargs: Any) -> None:
774 out_lines.append(msg)
776 def err(msg: str, **kwargs: Any) -> None:
777 err_lines.append(msg)
779 with patch("black.output._out", out), patch("black.output._err", err):
780 report.done(Path("f1"), black.Changed.NO)
781 self.assertEqual(len(out_lines), 0)
782 self.assertEqual(len(err_lines), 0)
783 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
784 self.assertEqual(report.return_code, 0)
785 report.done(Path("f2"), black.Changed.YES)
786 self.assertEqual(len(out_lines), 1)
787 self.assertEqual(len(err_lines), 0)
788 self.assertEqual(out_lines[-1], "reformatted f2")
790 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
792 report.done(Path("f3"), black.Changed.CACHED)
793 self.assertEqual(len(out_lines), 1)
794 self.assertEqual(len(err_lines), 0)
795 self.assertEqual(out_lines[-1], "reformatted f2")
797 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
799 self.assertEqual(report.return_code, 0)
801 self.assertEqual(report.return_code, 1)
803 report.failed(Path("e1"), "boom")
804 self.assertEqual(len(out_lines), 1)
805 self.assertEqual(len(err_lines), 1)
806 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
808 unstyle(str(report)),
809 "1 file reformatted, 2 files left unchanged, 1 file failed to"
812 self.assertEqual(report.return_code, 123)
813 report.done(Path("f3"), black.Changed.YES)
814 self.assertEqual(len(out_lines), 2)
815 self.assertEqual(len(err_lines), 1)
816 self.assertEqual(out_lines[-1], "reformatted f3")
818 unstyle(str(report)),
819 "2 files reformatted, 2 files left unchanged, 1 file failed to"
822 self.assertEqual(report.return_code, 123)
823 report.failed(Path("e2"), "boom")
824 self.assertEqual(len(out_lines), 2)
825 self.assertEqual(len(err_lines), 2)
826 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
828 unstyle(str(report)),
829 "2 files reformatted, 2 files left unchanged, 2 files failed to"
832 self.assertEqual(report.return_code, 123)
833 report.path_ignored(Path("wat"), "no match")
834 self.assertEqual(len(out_lines), 2)
835 self.assertEqual(len(err_lines), 2)
837 unstyle(str(report)),
838 "2 files reformatted, 2 files left unchanged, 2 files failed to"
841 self.assertEqual(report.return_code, 123)
842 report.done(Path("f4"), black.Changed.NO)
843 self.assertEqual(len(out_lines), 2)
844 self.assertEqual(len(err_lines), 2)
846 unstyle(str(report)),
847 "2 files reformatted, 3 files left unchanged, 2 files failed to"
850 self.assertEqual(report.return_code, 123)
853 unstyle(str(report)),
854 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
855 " would fail to reformat.",
860 unstyle(str(report)),
861 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
862 " would fail to reformat.",
865 def test_lib2to3_parse(self) -> None:
866 with self.assertRaises(black.InvalidInput):
867 black.lib2to3_parse("invalid syntax")
870 black.lib2to3_parse(straddling)
871 black.lib2to3_parse(straddling, {TargetVersion.PY27})
872 black.lib2to3_parse(straddling, {TargetVersion.PY36})
873 black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
876 black.lib2to3_parse(py2_only)
877 black.lib2to3_parse(py2_only, {TargetVersion.PY27})
878 with self.assertRaises(black.InvalidInput):
879 black.lib2to3_parse(py2_only, {TargetVersion.PY36})
880 with self.assertRaises(black.InvalidInput):
881 black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
883 py3_only = "exec(x, end=y)"
884 black.lib2to3_parse(py3_only)
885 with self.assertRaises(black.InvalidInput):
886 black.lib2to3_parse(py3_only, {TargetVersion.PY27})
887 black.lib2to3_parse(py3_only, {TargetVersion.PY36})
888 black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
890 def test_get_features_used_decorator(self) -> None:
891 # Test the feature detection of new decorator syntax
892 # since this makes some test cases of test_get_features_used()
893 # fails if it fails, this is tested first so that a useful case
895 simples, relaxed = read_data("decorators")
896 # skip explanation comments at the top of the file
897 for simple_test in simples.split("##")[1:]:
898 node = black.lib2to3_parse(simple_test)
899 decorator = str(node.children[0].children[0]).strip()
901 Feature.RELAXED_DECORATORS,
902 black.get_features_used(node),
904 f"decorator '{decorator}' follows python<=3.8 syntax"
905 "but is detected as 3.9+"
906 # f"The full node is\n{node!r}"
909 # skip the '# output' comment at the top of the output part
910 for relaxed_test in relaxed.split("##")[1:]:
911 node = black.lib2to3_parse(relaxed_test)
912 decorator = str(node.children[0].children[0]).strip()
914 Feature.RELAXED_DECORATORS,
915 black.get_features_used(node),
917 f"decorator '{decorator}' uses python3.9+ syntax"
918 "but is detected as python<=3.8"
919 # f"The full node is\n{node!r}"
923 def test_get_features_used(self) -> None:
924 node = black.lib2to3_parse("def f(*, arg): ...\n")
925 self.assertEqual(black.get_features_used(node), set())
926 node = black.lib2to3_parse("def f(*, arg,): ...\n")
927 self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
928 node = black.lib2to3_parse("f(*arg,)\n")
930 black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
932 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
933 self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
934 node = black.lib2to3_parse("123_456\n")
935 self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
936 node = black.lib2to3_parse("123456\n")
937 self.assertEqual(black.get_features_used(node), set())
938 source, expected = read_data("function")
939 node = black.lib2to3_parse(source)
940 expected_features = {
941 Feature.TRAILING_COMMA_IN_CALL,
942 Feature.TRAILING_COMMA_IN_DEF,
945 self.assertEqual(black.get_features_used(node), expected_features)
946 node = black.lib2to3_parse(expected)
947 self.assertEqual(black.get_features_used(node), expected_features)
948 source, expected = read_data("expression")
949 node = black.lib2to3_parse(source)
950 self.assertEqual(black.get_features_used(node), set())
951 node = black.lib2to3_parse(expected)
952 self.assertEqual(black.get_features_used(node), set())
954 def test_get_future_imports(self) -> None:
955 node = black.lib2to3_parse("\n")
956 self.assertEqual(set(), black.get_future_imports(node))
957 node = black.lib2to3_parse("from __future__ import black\n")
958 self.assertEqual({"black"}, black.get_future_imports(node))
959 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
960 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
961 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
962 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
963 node = black.lib2to3_parse(
964 "from __future__ import multiple\nfrom __future__ import imports\n"
966 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
967 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
968 self.assertEqual({"black"}, black.get_future_imports(node))
969 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
970 self.assertEqual({"black"}, black.get_future_imports(node))
971 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
972 self.assertEqual(set(), black.get_future_imports(node))
973 node = black.lib2to3_parse("from some.module import black\n")
974 self.assertEqual(set(), black.get_future_imports(node))
975 node = black.lib2to3_parse(
976 "from __future__ import unicode_literals as _unicode_literals"
978 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
979 node = black.lib2to3_parse(
980 "from __future__ import unicode_literals as _lol, print"
982 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
984 def test_debug_visitor(self) -> None:
985 source, _ = read_data("debug_visitor.py")
986 expected, _ = read_data("debug_visitor.out")
990 def out(msg: str, **kwargs: Any) -> None:
991 out_lines.append(msg)
993 def err(msg: str, **kwargs: Any) -> None:
994 err_lines.append(msg)
996 with patch("black.debug.out", out):
997 DebugVisitor.show(source)
998 actual = "\n".join(out_lines) + "\n"
1000 if expected != actual:
1001 log_name = black.dump_to_file(*out_lines)
1005 f"AST print out is different. Actual version dumped to {log_name}",
1008 def test_format_file_contents(self) -> None:
1011 with self.assertRaises(black.NothingChanged):
1012 black.format_file_contents(empty, mode=mode, fast=False)
1014 with self.assertRaises(black.NothingChanged):
1015 black.format_file_contents(just_nl, mode=mode, fast=False)
1016 same = "j = [1, 2, 3]\n"
1017 with self.assertRaises(black.NothingChanged):
1018 black.format_file_contents(same, mode=mode, fast=False)
1019 different = "j = [1,2,3]"
1021 actual = black.format_file_contents(different, mode=mode, fast=False)
1022 self.assertEqual(expected, actual)
1023 invalid = "return if you can"
1024 with self.assertRaises(black.InvalidInput) as e:
1025 black.format_file_contents(invalid, mode=mode, fast=False)
1026 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
1028 def test_endmarker(self) -> None:
1029 n = black.lib2to3_parse("\n")
1030 self.assertEqual(n.type, black.syms.file_input)
1031 self.assertEqual(len(n.children), 1)
1032 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
1034 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
1035 def test_assertFormatEqual(self) -> None:
1039 def out(msg: str, **kwargs: Any) -> None:
1040 out_lines.append(msg)
1042 def err(msg: str, **kwargs: Any) -> None:
1043 err_lines.append(msg)
1045 with patch("black.output._out", out), patch("black.output._err", err):
1046 with self.assertRaises(AssertionError):
1047 self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")
1049 out_str = "".join(out_lines)
1050 self.assertTrue("Expected tree:" in out_str)
1051 self.assertTrue("Actual tree:" in out_str)
1052 self.assertEqual("".join(err_lines), "")
1054 def test_cache_broken_file(self) -> None:
1056 with cache_dir() as workspace:
1057 cache_file = get_cache_file(mode)
1058 with cache_file.open("w") as fobj:
1059 fobj.write("this is not a pickle")
1060 self.assertEqual(black.read_cache(mode), {})
1061 src = (workspace / "test.py").resolve()
1062 with src.open("w") as fobj:
1063 fobj.write("print('hello')")
1064 self.invokeBlack([str(src)])
1065 cache = black.read_cache(mode)
1066 self.assertIn(str(src), cache)
1068 def test_cache_single_file_already_cached(self) -> None:
1070 with cache_dir() as workspace:
1071 src = (workspace / "test.py").resolve()
1072 with src.open("w") as fobj:
1073 fobj.write("print('hello')")
1074 black.write_cache({}, [src], mode)
1075 self.invokeBlack([str(src)])
1076 with src.open("r") as fobj:
1077 self.assertEqual(fobj.read(), "print('hello')")
1080 def test_cache_multiple_files(self) -> None:
1082 with cache_dir() as workspace, patch(
1083 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1085 one = (workspace / "one.py").resolve()
1086 with one.open("w") as fobj:
1087 fobj.write("print('hello')")
1088 two = (workspace / "two.py").resolve()
1089 with two.open("w") as fobj:
1090 fobj.write("print('hello')")
1091 black.write_cache({}, [one], mode)
1092 self.invokeBlack([str(workspace)])
1093 with one.open("r") as fobj:
1094 self.assertEqual(fobj.read(), "print('hello')")
1095 with two.open("r") as fobj:
1096 self.assertEqual(fobj.read(), 'print("hello")\n')
1097 cache = black.read_cache(mode)
1098 self.assertIn(str(one), cache)
1099 self.assertIn(str(two), cache)
1101 def test_no_cache_when_writeback_diff(self) -> None:
1103 with cache_dir() as workspace:
1104 src = (workspace / "test.py").resolve()
1105 with src.open("w") as fobj:
1106 fobj.write("print('hello')")
1107 with patch("black.read_cache") as read_cache, patch(
1110 self.invokeBlack([str(src), "--diff"])
1111 cache_file = get_cache_file(mode)
1112 self.assertFalse(cache_file.exists())
1113 write_cache.assert_not_called()
1114 read_cache.assert_not_called()
1116 def test_no_cache_when_writeback_color_diff(self) -> None:
1118 with cache_dir() as workspace:
1119 src = (workspace / "test.py").resolve()
1120 with src.open("w") as fobj:
1121 fobj.write("print('hello')")
1122 with patch("black.read_cache") as read_cache, patch(
1125 self.invokeBlack([str(src), "--diff", "--color"])
1126 cache_file = get_cache_file(mode)
1127 self.assertFalse(cache_file.exists())
1128 write_cache.assert_not_called()
1129 read_cache.assert_not_called()
1132 def test_output_locking_when_writeback_diff(self) -> None:
1133 with cache_dir() as workspace:
1134 for tag in range(0, 4):
1135 src = (workspace / f"test{tag}.py").resolve()
1136 with src.open("w") as fobj:
1137 fobj.write("print('hello')")
1138 with patch("black.Manager", wraps=multiprocessing.Manager) as mgr:
1139 self.invokeBlack(["--diff", str(workspace)], exit_code=0)
1140 # this isn't quite doing what we want, but if it _isn't_
1141 # called then we cannot be using the lock it provides
1145 def test_output_locking_when_writeback_color_diff(self) -> None:
1146 with cache_dir() as workspace:
1147 for tag in range(0, 4):
1148 src = (workspace / f"test{tag}.py").resolve()
1149 with src.open("w") as fobj:
1150 fobj.write("print('hello')")
1151 with patch("black.Manager", wraps=multiprocessing.Manager) as mgr:
1152 self.invokeBlack(["--diff", "--color", str(workspace)], exit_code=0)
1153 # this isn't quite doing what we want, but if it _isn't_
1154 # called then we cannot be using the lock it provides
1157 def test_no_cache_when_stdin(self) -> None:
1160 result = CliRunner().invoke(
1161 black.main, ["-"], input=BytesIO(b"print('hello')")
1163 self.assertEqual(result.exit_code, 0)
1164 cache_file = get_cache_file(mode)
1165 self.assertFalse(cache_file.exists())
1167 def test_read_cache_no_cachefile(self) -> None:
1170 self.assertEqual(black.read_cache(mode), {})
1172 def test_write_cache_read_cache(self) -> None:
1174 with cache_dir() as workspace:
1175 src = (workspace / "test.py").resolve()
1177 black.write_cache({}, [src], mode)
1178 cache = black.read_cache(mode)
1179 self.assertIn(str(src), cache)
1180 self.assertEqual(cache[str(src)], black.get_cache_info(src))
1182 def test_filter_cached(self) -> None:
1183 with TemporaryDirectory() as workspace:
1184 path = Path(workspace)
1185 uncached = (path / "uncached").resolve()
1186 cached = (path / "cached").resolve()
1187 cached_but_changed = (path / "changed").resolve()
1190 cached_but_changed.touch()
1192 str(cached): black.get_cache_info(cached),
1193 str(cached_but_changed): (0.0, 0),
1195 todo, done = black.filter_cached(
1196 cache, {uncached, cached, cached_but_changed}
1198 self.assertEqual(todo, {uncached, cached_but_changed})
1199 self.assertEqual(done, {cached})
1201 def test_write_cache_creates_directory_if_needed(self) -> None:
1203 with cache_dir(exists=False) as workspace:
1204 self.assertFalse(workspace.exists())
1205 black.write_cache({}, [], mode)
1206 self.assertTrue(workspace.exists())
1209 def test_failed_formatting_does_not_get_cached(self) -> None:
1211 with cache_dir() as workspace, patch(
1212 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1214 failing = (workspace / "failing.py").resolve()
1215 with failing.open("w") as fobj:
1216 fobj.write("not actually python")
1217 clean = (workspace / "clean.py").resolve()
1218 with clean.open("w") as fobj:
1219 fobj.write('print("hello")\n')
1220 self.invokeBlack([str(workspace)], exit_code=123)
1221 cache = black.read_cache(mode)
1222 self.assertNotIn(str(failing), cache)
1223 self.assertIn(str(clean), cache)
1225 def test_write_cache_write_fail(self) -> None:
1227 with cache_dir(), patch.object(Path, "open") as mock:
1228 mock.side_effect = OSError
1229 black.write_cache({}, [], mode)
1232 @patch("black.ProcessPoolExecutor", MagicMock(side_effect=OSError))
1233 def test_works_in_mono_process_only_environment(self) -> None:
1234 with cache_dir() as workspace:
1236 (workspace / "one.py").resolve(),
1237 (workspace / "two.py").resolve(),
1239 f.write_text('print("hello")\n')
1240 self.invokeBlack([str(workspace)])
1243 def test_check_diff_use_together(self) -> None:
1245 # Files which will be reformatted.
1246 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1247 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1248 # Files which will not be reformatted.
1249 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1250 self.invokeBlack([str(src2), "--diff", "--check"])
1251 # Multi file command.
1252 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1254 def test_no_files(self) -> None:
1256 # Without an argument, black exits with error code 0.
1257 self.invokeBlack([])
1259 def test_broken_symlink(self) -> None:
1260 with cache_dir() as workspace:
1261 symlink = workspace / "broken_link.py"
1263 symlink.symlink_to("nonexistent.py")
1264 except OSError as e:
1265 self.skipTest(f"Can't create symlinks: {e}")
1266 self.invokeBlack([str(workspace.resolve())])
1268 def test_read_cache_line_lengths(self) -> None:
1270 short_mode = replace(DEFAULT_MODE, line_length=1)
1271 with cache_dir() as workspace:
1272 path = (workspace / "file.py").resolve()
1274 black.write_cache({}, [path], mode)
1275 one = black.read_cache(mode)
1276 self.assertIn(str(path), one)
1277 two = black.read_cache(short_mode)
1278 self.assertNotIn(str(path), two)
1280 def test_single_file_force_pyi(self) -> None:
1281 pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
1282 contents, expected = read_data("force_pyi")
1283 with cache_dir() as workspace:
1284 path = (workspace / "file.py").resolve()
1285 with open(path, "w") as fh:
1287 self.invokeBlack([str(path), "--pyi"])
1288 with open(path, "r") as fh:
1290 # verify cache with --pyi is separate
1291 pyi_cache = black.read_cache(pyi_mode)
1292 self.assertIn(str(path), pyi_cache)
1293 normal_cache = black.read_cache(DEFAULT_MODE)
1294 self.assertNotIn(str(path), normal_cache)
1295 self.assertFormatEqual(expected, actual)
1296 black.assert_equivalent(contents, actual)
1297 black.assert_stable(contents, actual, pyi_mode)
1300 def test_multi_file_force_pyi(self) -> None:
1301 reg_mode = DEFAULT_MODE
1302 pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
1303 contents, expected = read_data("force_pyi")
1304 with cache_dir() as workspace:
1306 (workspace / "file1.py").resolve(),
1307 (workspace / "file2.py").resolve(),
1310 with open(path, "w") as fh:
1312 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1314 with open(path, "r") as fh:
1316 self.assertEqual(actual, expected)
1317 # verify cache with --pyi is separate
1318 pyi_cache = black.read_cache(pyi_mode)
1319 normal_cache = black.read_cache(reg_mode)
1321 self.assertIn(str(path), pyi_cache)
1322 self.assertNotIn(str(path), normal_cache)
1324 def test_pipe_force_pyi(self) -> None:
1325 source, expected = read_data("force_pyi")
1326 result = CliRunner().invoke(
1327 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1329 self.assertEqual(result.exit_code, 0)
1330 actual = result.output
1331 self.assertFormatEqual(actual, expected)
1333 def test_single_file_force_py36(self) -> None:
1334 reg_mode = DEFAULT_MODE
1335 py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
1336 source, expected = read_data("force_py36")
1337 with cache_dir() as workspace:
1338 path = (workspace / "file.py").resolve()
1339 with open(path, "w") as fh:
1341 self.invokeBlack([str(path), *PY36_ARGS])
1342 with open(path, "r") as fh:
1344 # verify cache with --target-version is separate
1345 py36_cache = black.read_cache(py36_mode)
1346 self.assertIn(str(path), py36_cache)
1347 normal_cache = black.read_cache(reg_mode)
1348 self.assertNotIn(str(path), normal_cache)
1349 self.assertEqual(actual, expected)
1352 def test_multi_file_force_py36(self) -> None:
1353 reg_mode = DEFAULT_MODE
1354 py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
1355 source, expected = read_data("force_py36")
1356 with cache_dir() as workspace:
1358 (workspace / "file1.py").resolve(),
1359 (workspace / "file2.py").resolve(),
1362 with open(path, "w") as fh:
1364 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1366 with open(path, "r") as fh:
1368 self.assertEqual(actual, expected)
1369 # verify cache with --target-version is separate
1370 pyi_cache = black.read_cache(py36_mode)
1371 normal_cache = black.read_cache(reg_mode)
1373 self.assertIn(str(path), pyi_cache)
1374 self.assertNotIn(str(path), normal_cache)
1376 def test_pipe_force_py36(self) -> None:
1377 source, expected = read_data("force_py36")
1378 result = CliRunner().invoke(
1380 ["-", "-q", "--target-version=py36"],
1381 input=BytesIO(source.encode("utf8")),
1383 self.assertEqual(result.exit_code, 0)
1384 actual = result.output
1385 self.assertFormatEqual(actual, expected)
1387 def test_include_exclude(self) -> None:
1388 path = THIS_DIR / "data" / "include_exclude_tests"
1389 include = re.compile(r"\.pyi?$")
1390 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1391 report = black.Report()
1392 gitignore = PathSpec.from_lines("gitwildmatch", [])
1393 sources: List[Path] = []
1395 Path(path / "b/dont_exclude/a.py"),
1396 Path(path / "b/dont_exclude/a.pyi"),
1398 this_abs = THIS_DIR.resolve()
1400 black.gen_python_files(
1411 self.assertEqual(sorted(expected), sorted(sources))
1413 def test_gitignore_used_as_default(self) -> None:
1414 path = Path(THIS_DIR / "data" / "include_exclude_tests")
1415 include = re.compile(r"\.pyi?$")
1416 extend_exclude = re.compile(r"/exclude/")
1417 src = str(path / "b/")
1418 report = black.Report()
1419 expected: List[Path] = [
1420 path / "b/.definitely_exclude/a.py",
1421 path / "b/.definitely_exclude/a.pyi",
1431 extend_exclude=extend_exclude,
1434 stdin_filename=None,
1437 self.assertEqual(sorted(expected), sorted(sources))
1439 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1440 def test_exclude_for_issue_1572(self) -> None:
1441 # Exclude shouldn't touch files that were explicitly given to Black through the
1442 # CLI. Exclude is supposed to only apply to the recursive discovery of files.
1443 # https://github.com/psf/black/issues/1572
1444 path = THIS_DIR / "data" / "include_exclude_tests"
1446 exclude = r"/exclude/|a\.py"
1447 src = str(path / "b/exclude/a.py")
1448 report = black.Report()
1449 expected = [Path(path / "b/exclude/a.py")]
1456 include=re.compile(include),
1457 exclude=re.compile(exclude),
1458 extend_exclude=None,
1461 stdin_filename=None,
1464 self.assertEqual(sorted(expected), sorted(sources))
1466 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1467 def test_get_sources_with_stdin(self) -> None:
1469 exclude = r"/exclude/|a\.py"
1471 report = black.Report()
1472 expected = [Path("-")]
1479 include=re.compile(include),
1480 exclude=re.compile(exclude),
1481 extend_exclude=None,
1484 stdin_filename=None,
1487 self.assertEqual(sorted(expected), sorted(sources))
1489 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1490 def test_get_sources_with_stdin_filename(self) -> None:
1492 exclude = r"/exclude/|a\.py"
1494 report = black.Report()
1495 stdin_filename = str(THIS_DIR / "data/collections.py")
1496 expected = [Path(f"__BLACK_STDIN_FILENAME__{stdin_filename}")]
1503 include=re.compile(include),
1504 exclude=re.compile(exclude),
1505 extend_exclude=None,
1508 stdin_filename=stdin_filename,
1511 self.assertEqual(sorted(expected), sorted(sources))
1513 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1514 def test_get_sources_with_stdin_filename_and_exclude(self) -> None:
1515 # Exclude shouldn't exclude stdin_filename since it is mimicking the
1516 # file being passed directly. This is the same as
1517 # test_exclude_for_issue_1572
1518 path = THIS_DIR / "data" / "include_exclude_tests"
1520 exclude = r"/exclude/|a\.py"
1522 report = black.Report()
1523 stdin_filename = str(path / "b/exclude/a.py")
1524 expected = [Path(f"__BLACK_STDIN_FILENAME__{stdin_filename}")]
1531 include=re.compile(include),
1532 exclude=re.compile(exclude),
1533 extend_exclude=None,
1536 stdin_filename=stdin_filename,
1539 self.assertEqual(sorted(expected), sorted(sources))
1541 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1542 def test_get_sources_with_stdin_filename_and_extend_exclude(self) -> None:
1543 # Extend exclude shouldn't exclude stdin_filename since it is mimicking the
1544 # file being passed directly. This is the same as
1545 # test_exclude_for_issue_1572
1546 path = THIS_DIR / "data" / "include_exclude_tests"
1548 extend_exclude = r"/exclude/|a\.py"
1550 report = black.Report()
1551 stdin_filename = str(path / "b/exclude/a.py")
1552 expected = [Path(f"__BLACK_STDIN_FILENAME__{stdin_filename}")]
1559 include=re.compile(include),
1560 exclude=re.compile(""),
1561 extend_exclude=re.compile(extend_exclude),
1564 stdin_filename=stdin_filename,
1567 self.assertEqual(sorted(expected), sorted(sources))
1569 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1570 def test_get_sources_with_stdin_filename_and_force_exclude(self) -> None:
1571 # Force exclude should exclude the file when passing it through
1573 path = THIS_DIR / "data" / "include_exclude_tests"
1575 force_exclude = r"/exclude/|a\.py"
1577 report = black.Report()
1578 stdin_filename = str(path / "b/exclude/a.py")
1585 include=re.compile(include),
1586 exclude=re.compile(""),
1587 extend_exclude=None,
1588 force_exclude=re.compile(force_exclude),
1590 stdin_filename=stdin_filename,
1593 self.assertEqual([], sorted(sources))
1595 def test_reformat_one_with_stdin(self) -> None:
1597 "black.format_stdin_to_stdout",
1598 return_value=lambda *args, **kwargs: black.Changed.YES,
1600 report = MagicMock()
1605 write_back=black.WriteBack.YES,
1609 fsts.assert_called_once()
1610 report.done.assert_called_with(path, black.Changed.YES)
1612 def test_reformat_one_with_stdin_filename(self) -> None:
1614 "black.format_stdin_to_stdout",
1615 return_value=lambda *args, **kwargs: black.Changed.YES,
1617 report = MagicMock()
1619 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1624 write_back=black.WriteBack.YES,
1628 fsts.assert_called_once_with(
1629 fast=True, write_back=black.WriteBack.YES, mode=DEFAULT_MODE
1631 # __BLACK_STDIN_FILENAME__ should have been stripped
1632 report.done.assert_called_with(expected, black.Changed.YES)
1634 def test_reformat_one_with_stdin_filename_pyi(self) -> None:
1636 "black.format_stdin_to_stdout",
1637 return_value=lambda *args, **kwargs: black.Changed.YES,
1639 report = MagicMock()
1641 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1646 write_back=black.WriteBack.YES,
1650 fsts.assert_called_once_with(
1652 write_back=black.WriteBack.YES,
1653 mode=replace(DEFAULT_MODE, is_pyi=True),
1655 # __BLACK_STDIN_FILENAME__ should have been stripped
1656 report.done.assert_called_with(expected, black.Changed.YES)
1658 def test_reformat_one_with_stdin_and_existing_path(self) -> None:
1660 "black.format_stdin_to_stdout",
1661 return_value=lambda *args, **kwargs: black.Changed.YES,
1663 report = MagicMock()
1664 # Even with an existing file, since we are forcing stdin, black
1665 # should output to stdout and not modify the file inplace
1666 p = Path(str(THIS_DIR / "data/collections.py"))
1667 # Make sure is_file actually returns True
1668 self.assertTrue(p.is_file())
1669 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1674 write_back=black.WriteBack.YES,
1678 fsts.assert_called_once()
1679 # __BLACK_STDIN_FILENAME__ should have been stripped
1680 report.done.assert_called_with(expected, black.Changed.YES)
1682 def test_gitignore_exclude(self) -> None:
1683 path = THIS_DIR / "data" / "include_exclude_tests"
1684 include = re.compile(r"\.pyi?$")
1685 exclude = re.compile(r"")
1686 report = black.Report()
1687 gitignore = PathSpec.from_lines(
1688 "gitwildmatch", ["exclude/", ".definitely_exclude"]
1690 sources: List[Path] = []
1692 Path(path / "b/dont_exclude/a.py"),
1693 Path(path / "b/dont_exclude/a.pyi"),
1695 this_abs = THIS_DIR.resolve()
1697 black.gen_python_files(
1708 self.assertEqual(sorted(expected), sorted(sources))
1710 def test_nested_gitignore(self) -> None:
1711 path = Path(THIS_DIR / "data" / "nested_gitignore_tests")
1712 include = re.compile(r"\.pyi?$")
1713 exclude = re.compile(r"")
1714 root_gitignore = black.files.get_gitignore(path)
1715 report = black.Report()
1716 expected: List[Path] = [
1717 Path(path / "x.py"),
1718 Path(path / "root/b.py"),
1719 Path(path / "root/c.py"),
1720 Path(path / "root/child/c.py"),
1722 this_abs = THIS_DIR.resolve()
1724 black.gen_python_files(
1735 self.assertEqual(sorted(expected), sorted(sources))
1737 def test_empty_include(self) -> None:
1738 path = THIS_DIR / "data" / "include_exclude_tests"
1739 report = black.Report()
1740 gitignore = PathSpec.from_lines("gitwildmatch", [])
1741 empty = re.compile(r"")
1742 sources: List[Path] = []
1744 Path(path / "b/exclude/a.pie"),
1745 Path(path / "b/exclude/a.py"),
1746 Path(path / "b/exclude/a.pyi"),
1747 Path(path / "b/dont_exclude/a.pie"),
1748 Path(path / "b/dont_exclude/a.py"),
1749 Path(path / "b/dont_exclude/a.pyi"),
1750 Path(path / "b/.definitely_exclude/a.pie"),
1751 Path(path / "b/.definitely_exclude/a.py"),
1752 Path(path / "b/.definitely_exclude/a.pyi"),
1753 Path(path / ".gitignore"),
1754 Path(path / "pyproject.toml"),
1756 this_abs = THIS_DIR.resolve()
1758 black.gen_python_files(
1762 re.compile(black.DEFAULT_EXCLUDES),
1769 self.assertEqual(sorted(expected), sorted(sources))
1771 def test_extend_exclude(self) -> None:
1772 path = THIS_DIR / "data" / "include_exclude_tests"
1773 report = black.Report()
1774 gitignore = PathSpec.from_lines("gitwildmatch", [])
1775 sources: List[Path] = []
1777 Path(path / "b/exclude/a.py"),
1778 Path(path / "b/dont_exclude/a.py"),
1780 this_abs = THIS_DIR.resolve()
1782 black.gen_python_files(
1785 re.compile(black.DEFAULT_INCLUDES),
1786 re.compile(r"\.pyi$"),
1787 re.compile(r"\.definitely_exclude"),
1793 self.assertEqual(sorted(expected), sorted(sources))
1795 def test_invalid_cli_regex(self) -> None:
1796 for option in ["--include", "--exclude", "--extend-exclude", "--force-exclude"]:
1797 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1799 def test_required_version_matches_version(self) -> None:
1801 ["--required-version", black.__version__], exit_code=0, ignore_config=True
1804 def test_required_version_does_not_match_version(self) -> None:
1806 ["--required-version", "20.99b"], exit_code=1, ignore_config=True
1809 def test_preserves_line_endings(self) -> None:
1810 with TemporaryDirectory() as workspace:
1811 test_file = Path(workspace) / "test.py"
1812 for nl in ["\n", "\r\n"]:
1813 contents = nl.join(["def f( ):", " pass"])
1814 test_file.write_bytes(contents.encode())
1815 ff(test_file, write_back=black.WriteBack.YES)
1816 updated_contents: bytes = test_file.read_bytes()
1817 self.assertIn(nl.encode(), updated_contents)
1819 self.assertNotIn(b"\r\n", updated_contents)
1821 def test_preserves_line_endings_via_stdin(self) -> None:
1822 for nl in ["\n", "\r\n"]:
1823 contents = nl.join(["def f( ):", " pass"])
1824 runner = BlackRunner()
1825 result = runner.invoke(
1826 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1828 self.assertEqual(result.exit_code, 0)
1829 output = result.stdout_bytes
1830 self.assertIn(nl.encode("utf8"), output)
1832 self.assertNotIn(b"\r\n", output)
1834 def test_assert_equivalent_different_asts(self) -> None:
1835 with self.assertRaises(AssertionError):
1836 black.assert_equivalent("{}", "None")
1838 def test_symlink_out_of_root_directory(self) -> None:
1840 root = THIS_DIR.resolve()
1842 include = re.compile(black.DEFAULT_INCLUDES)
1843 exclude = re.compile(black.DEFAULT_EXCLUDES)
1844 report = black.Report()
1845 gitignore = PathSpec.from_lines("gitwildmatch", [])
1846 # `child` should behave like a symlink which resolved path is clearly
1847 # outside of the `root` directory.
1848 path.iterdir.return_value = [child]
1849 child.resolve.return_value = Path("/a/b/c")
1850 child.as_posix.return_value = "/a/b/c"
1851 child.is_symlink.return_value = True
1854 black.gen_python_files(
1865 except ValueError as ve:
1866 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1867 path.iterdir.assert_called_once()
1868 child.resolve.assert_called_once()
1869 child.is_symlink.assert_called_once()
1870 # `child` should behave like a strange file which resolved path is clearly
1871 # outside of the `root` directory.
1872 child.is_symlink.return_value = False
1873 with self.assertRaises(ValueError):
1875 black.gen_python_files(
1886 path.iterdir.assert_called()
1887 self.assertEqual(path.iterdir.call_count, 2)
1888 child.resolve.assert_called()
1889 self.assertEqual(child.resolve.call_count, 2)
1890 child.is_symlink.assert_called()
1891 self.assertEqual(child.is_symlink.call_count, 2)
1893 def test_shhh_click(self) -> None:
1895 from click import _unicodefun # type: ignore
1896 except ModuleNotFoundError:
1897 self.skipTest("Incompatible Click version")
1898 if not hasattr(_unicodefun, "_verify_python3_env"):
1899 self.skipTest("Incompatible Click version")
1900 # First, let's see if Click is crashing with a preferred ASCII charset.
1901 with patch("locale.getpreferredencoding") as gpe:
1902 gpe.return_value = "ASCII"
1903 with self.assertRaises(RuntimeError):
1904 _unicodefun._verify_python3_env()
1905 # Now, let's silence Click...
1907 # ...and confirm it's silent.
1908 with patch("locale.getpreferredencoding") as gpe:
1909 gpe.return_value = "ASCII"
1911 _unicodefun._verify_python3_env()
1912 except RuntimeError as re:
1913 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1915 def test_root_logger_not_used_directly(self) -> None:
1916 def fail(*args: Any, **kwargs: Any) -> None:
1917 self.fail("Record created with root logger")
1919 with patch.multiple(
1928 ff(THIS_DIR / "util.py")
1930 def test_invalid_config_return_code(self) -> None:
1931 tmp_file = Path(black.dump_to_file())
1933 tmp_config = Path(black.dump_to_file())
1935 args = ["--config", str(tmp_config), str(tmp_file)]
1936 self.invokeBlack(args, exit_code=2, ignore_config=False)
1940 def test_parse_pyproject_toml(self) -> None:
1941 test_toml_file = THIS_DIR / "test.toml"
1942 config = black.parse_pyproject_toml(str(test_toml_file))
1943 self.assertEqual(config["verbose"], 1)
1944 self.assertEqual(config["check"], "no")
1945 self.assertEqual(config["diff"], "y")
1946 self.assertEqual(config["color"], True)
1947 self.assertEqual(config["line_length"], 79)
1948 self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
1949 self.assertEqual(config["exclude"], r"\.pyi?$")
1950 self.assertEqual(config["include"], r"\.py?$")
1952 def test_read_pyproject_toml(self) -> None:
1953 test_toml_file = THIS_DIR / "test.toml"
1954 fake_ctx = FakeContext()
1955 black.read_pyproject_toml(fake_ctx, FakeParameter(), str(test_toml_file))
1956 config = fake_ctx.default_map
1957 self.assertEqual(config["verbose"], "1")
1958 self.assertEqual(config["check"], "no")
1959 self.assertEqual(config["diff"], "y")
1960 self.assertEqual(config["color"], "True")
1961 self.assertEqual(config["line_length"], "79")
1962 self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
1963 self.assertEqual(config["exclude"], r"\.pyi?$")
1964 self.assertEqual(config["include"], r"\.py?$")
1966 def test_find_project_root(self) -> None:
1967 with TemporaryDirectory() as workspace:
1968 root = Path(workspace)
1969 test_dir = root / "test"
1972 src_dir = root / "src"
1975 root_pyproject = root / "pyproject.toml"
1976 root_pyproject.touch()
1977 src_pyproject = src_dir / "pyproject.toml"
1978 src_pyproject.touch()
1979 src_python = src_dir / "foo.py"
1983 black.find_project_root((src_dir, test_dir)), root.resolve()
1985 self.assertEqual(black.find_project_root((src_dir,)), src_dir.resolve())
1986 self.assertEqual(black.find_project_root((src_python,)), src_dir.resolve())
1989 "black.files.find_user_pyproject_toml",
1990 black.files.find_user_pyproject_toml.__wrapped__,
1992 def test_find_user_pyproject_toml_linux(self) -> None:
1993 if system() == "Windows":
1996 # Test if XDG_CONFIG_HOME is checked
1997 with TemporaryDirectory() as workspace:
1998 tmp_user_config = Path(workspace) / "black"
1999 with patch.dict("os.environ", {"XDG_CONFIG_HOME": workspace}):
2001 black.files.find_user_pyproject_toml(), tmp_user_config.resolve()
2004 # Test fallback for XDG_CONFIG_HOME
2005 with patch.dict("os.environ"):
2006 os.environ.pop("XDG_CONFIG_HOME", None)
2007 fallback_user_config = Path("~/.config").expanduser() / "black"
2009 black.files.find_user_pyproject_toml(), fallback_user_config.resolve()
2012 def test_find_user_pyproject_toml_windows(self) -> None:
2013 if system() != "Windows":
2016 user_config_path = Path.home() / ".black"
2018 black.files.find_user_pyproject_toml(), user_config_path.resolve()
2021 def test_bpo_33660_workaround(self) -> None:
2022 if system() == "Windows":
2025 # https://bugs.python.org/issue33660
2027 old_cwd = Path.cwd()
2031 path = Path("workspace") / "project"
2032 report = black.Report(verbose=True)
2033 normalized_path = black.normalize_path_maybe_ignore(path, root, report)
2034 self.assertEqual(normalized_path, "workspace/project")
2036 os.chdir(str(old_cwd))
2038 def test_newline_comment_interaction(self) -> None:
2039 source = "class A:\\\r\n# type: ignore\n pass\n"
2040 output = black.format_str(source, mode=DEFAULT_MODE)
2041 black.assert_stable(source, output, mode=DEFAULT_MODE)
2043 def test_bpo_2142_workaround(self) -> None:
2045 # https://bugs.python.org/issue2142
2047 source, _ = read_data("missing_final_newline.py")
2048 # read_data adds a trailing newline
2049 source = source.rstrip()
2050 expected, _ = read_data("missing_final_newline.diff")
2051 tmp_file = Path(black.dump_to_file(source, ensure_final_newline=False))
2052 diff_header = re.compile(
2053 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
2054 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
2057 result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
2058 self.assertEqual(result.exit_code, 0)
2061 actual = result.output
2062 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
2063 self.assertEqual(actual, expected)
2065 @pytest.mark.python2
2066 def test_docstring_reformat_for_py27(self) -> None:
2068 Check that stripping trailing whitespace from Python 2 docstrings
2069 doesn't trigger a "not equivalent to source" error
2072 b'def foo():\r\n """Testing\r\n Testing """\r\n print "Foo"\r\n'
2074 expected = 'def foo():\n """Testing\n Testing"""\n print "Foo"\n'
2076 result = CliRunner().invoke(
2078 ["-", "-q", "--target-version=py27"],
2079 input=BytesIO(source),
2082 self.assertEqual(result.exit_code, 0)
2083 actual = result.output
2084 self.assertFormatEqual(actual, expected)
2087 def compare_results(
2088 result: click.testing.Result, expected_value: str, expected_exit_code: int
2090 """Helper method to test the value and exit code of a click Result."""
2092 result.output == expected_value
2093 ), "The output did not match the expected value."
2094 assert result.exit_code == expected_exit_code, "The exit code is incorrect."
2096 def test_code_option(self) -> None:
2097 """Test the code option with no changes."""
2098 code = 'print("Hello world")\n'
2099 args = ["--code", code]
2100 result = CliRunner().invoke(black.main, args)
2102 self.compare_results(result, code, 0)
2104 def test_code_option_changed(self) -> None:
2105 """Test the code option when changes are required."""
2106 code = "print('hello world')"
2107 formatted = black.format_str(code, mode=DEFAULT_MODE)
2109 args = ["--code", code]
2110 result = CliRunner().invoke(black.main, args)
2112 self.compare_results(result, formatted, 0)
2114 def test_code_option_check(self) -> None:
2115 """Test the code option when check is passed."""
2116 args = ["--check", "--code", 'print("Hello world")\n']
2117 result = CliRunner().invoke(black.main, args)
2118 self.compare_results(result, "", 0)
2120 def test_code_option_check_changed(self) -> None:
2121 """Test the code option when changes are required, and check is passed."""
2122 args = ["--check", "--code", "print('hello world')"]
2123 result = CliRunner().invoke(black.main, args)
2124 self.compare_results(result, "", 1)
2126 def test_code_option_diff(self) -> None:
2127 """Test the code option when diff is passed."""
2128 code = "print('hello world')"
2129 formatted = black.format_str(code, mode=DEFAULT_MODE)
2130 result_diff = diff(code, formatted, "STDIN", "STDOUT")
2132 args = ["--diff", "--code", code]
2133 result = CliRunner().invoke(black.main, args)
2135 # Remove time from diff
2136 output = DIFF_TIME.sub("", result.output)
2138 assert output == result_diff, "The output did not match the expected value."
2139 assert result.exit_code == 0, "The exit code is incorrect."
2141 def test_code_option_color_diff(self) -> None:
2142 """Test the code option when color and diff are passed."""
2143 code = "print('hello world')"
2144 formatted = black.format_str(code, mode=DEFAULT_MODE)
2146 result_diff = diff(code, formatted, "STDIN", "STDOUT")
2147 result_diff = color_diff(result_diff)
2149 args = ["--diff", "--color", "--code", code]
2150 result = CliRunner().invoke(black.main, args)
2152 # Remove time from diff
2153 output = DIFF_TIME.sub("", result.output)
2155 assert output == result_diff, "The output did not match the expected value."
2156 assert result.exit_code == 0, "The exit code is incorrect."
2158 def test_code_option_safe(self) -> None:
2159 """Test that the code option throws an error when the sanity checks fail."""
2160 # Patch black.assert_equivalent to ensure the sanity checks fail
2161 with patch.object(black, "assert_equivalent", side_effect=AssertionError):
2162 code = 'print("Hello world")'
2163 error_msg = f"{code}\nerror: cannot format <string>: \n"
2165 args = ["--safe", "--code", code]
2166 result = CliRunner().invoke(black.main, args)
2168 self.compare_results(result, error_msg, 123)
2170 def test_code_option_fast(self) -> None:
2171 """Test that the code option ignores errors when the sanity checks fail."""
2172 # Patch black.assert_equivalent to ensure the sanity checks fail
2173 with patch.object(black, "assert_equivalent", side_effect=AssertionError):
2174 code = 'print("Hello world")'
2175 formatted = black.format_str(code, mode=DEFAULT_MODE)
2177 args = ["--fast", "--code", code]
2178 result = CliRunner().invoke(black.main, args)
2180 self.compare_results(result, formatted, 0)
2182 def test_code_option_config(self) -> None:
2184 Test that the code option finds the pyproject.toml in the current directory.
2186 with patch.object(black, "parse_pyproject_toml", return_value={}) as parse:
2187 # Make sure we are in the project root with the pyproject file
2188 if not Path("tests").exists():
2191 args = ["--code", "print"]
2192 CliRunner().invoke(black.main, args)
2194 pyproject_path = Path(Path().cwd(), "pyproject.toml").resolve()
2196 len(parse.mock_calls) >= 1
2197 ), "Expected config parse to be called with the current directory."
2199 _, call_args, _ = parse.mock_calls[0]
2201 call_args[0].lower() == str(pyproject_path).lower()
2202 ), "Incorrect config loaded."
2204 def test_code_option_parent_config(self) -> None:
2206 Test that the code option finds the pyproject.toml in the parent directory.
2208 with patch.object(black, "parse_pyproject_toml", return_value={}) as parse:
2209 # Make sure we are in the tests directory
2210 if Path("tests").exists():
2213 args = ["--code", "print"]
2214 CliRunner().invoke(black.main, args)
2216 pyproject_path = Path(Path().cwd().parent, "pyproject.toml").resolve()
2218 len(parse.mock_calls) >= 1
2219 ), "Expected config parse to be called with the current directory."
2221 _, call_args, _ = parse.mock_calls[0]
2223 call_args[0].lower() == str(pyproject_path).lower()
2224 ), "Incorrect config loaded."
2227 with open(black.__file__, "r", encoding="utf-8") as _bf:
2228 black_source_lines = _bf.readlines()
2231 def tracefunc(frame: types.FrameType, event: str, arg: Any) -> Callable:
2232 """Show function calls `from black/__init__.py` as they happen.
2234 Register this with `sys.settrace()` in a test you're debugging.
2239 stack = len(inspect.stack()) - 19
2241 filename = frame.f_code.co_filename
2242 lineno = frame.f_lineno
2243 func_sig_lineno = lineno - 1
2244 funcname = black_source_lines[func_sig_lineno].strip()
2245 while funcname.startswith("@"):
2246 func_sig_lineno += 1
2247 funcname = black_source_lines[func_sig_lineno].strip()
2248 if "black/__init__.py" in filename:
2249 print(f"{' ' * stack}{lineno}:{funcname}")
2253 if __name__ == "__main__":
2254 unittest.main(module="test_black")