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)
455 @patch("black.dump_to_file", dump_to_stderr)
456 def test_python2_print_function(self) -> None:
457 source, expected = read_data("python2_print_function")
458 mode = replace(DEFAULT_MODE, target_versions={TargetVersion.PY27})
459 actual = fs(source, mode=mode)
460 self.assertFormatEqual(expected, actual)
461 black.assert_equivalent(source, actual)
462 black.assert_stable(source, actual, mode)
464 @patch("black.dump_to_file", dump_to_stderr)
465 def test_stub(self) -> None:
466 mode = replace(DEFAULT_MODE, is_pyi=True)
467 source, expected = read_data("stub.pyi")
468 actual = fs(source, mode=mode)
469 self.assertFormatEqual(expected, actual)
470 black.assert_stable(source, actual, mode)
472 @patch("black.dump_to_file", dump_to_stderr)
473 def test_async_as_identifier(self) -> None:
474 source_path = (THIS_DIR / "data" / "async_as_identifier.py").resolve()
475 source, expected = read_data("async_as_identifier")
477 self.assertFormatEqual(expected, actual)
478 major, minor = sys.version_info[:2]
479 if major < 3 or (major <= 3 and minor < 7):
480 black.assert_equivalent(source, actual)
481 black.assert_stable(source, actual, DEFAULT_MODE)
482 # ensure black can parse this when the target is 3.6
483 self.invokeBlack([str(source_path), "--target-version", "py36"])
484 # but not on 3.7, because async/await is no longer an identifier
485 self.invokeBlack([str(source_path), "--target-version", "py37"], exit_code=123)
487 @patch("black.dump_to_file", dump_to_stderr)
488 def test_python37(self) -> None:
489 source_path = (THIS_DIR / "data" / "python37.py").resolve()
490 source, expected = read_data("python37")
492 self.assertFormatEqual(expected, actual)
493 major, minor = sys.version_info[:2]
494 if major > 3 or (major == 3 and minor >= 7):
495 black.assert_equivalent(source, actual)
496 black.assert_stable(source, actual, DEFAULT_MODE)
497 # ensure black can parse this when the target is 3.7
498 self.invokeBlack([str(source_path), "--target-version", "py37"])
499 # but not on 3.6, because we use async as a reserved keyword
500 self.invokeBlack([str(source_path), "--target-version", "py36"], exit_code=123)
502 @patch("black.dump_to_file", dump_to_stderr)
503 def test_python38(self) -> None:
504 source, expected = read_data("python38")
506 self.assertFormatEqual(expected, actual)
507 major, minor = sys.version_info[:2]
508 if major > 3 or (major == 3 and minor >= 8):
509 black.assert_equivalent(source, actual)
510 black.assert_stable(source, actual, DEFAULT_MODE)
512 @patch("black.dump_to_file", dump_to_stderr)
513 def test_python39(self) -> None:
514 source, expected = read_data("python39")
516 self.assertFormatEqual(expected, actual)
517 major, minor = sys.version_info[:2]
518 if major > 3 or (major == 3 and minor >= 9):
519 black.assert_equivalent(source, actual)
520 black.assert_stable(source, actual, DEFAULT_MODE)
522 def test_tab_comment_indentation(self) -> None:
523 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
524 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
525 self.assertFormatEqual(contents_spc, fs(contents_spc))
526 self.assertFormatEqual(contents_spc, fs(contents_tab))
528 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
529 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
530 self.assertFormatEqual(contents_spc, fs(contents_spc))
531 self.assertFormatEqual(contents_spc, fs(contents_tab))
533 # mixed tabs and spaces (valid Python 2 code)
534 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t# comment\n pass\n"
535 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
536 self.assertFormatEqual(contents_spc, fs(contents_spc))
537 self.assertFormatEqual(contents_spc, fs(contents_tab))
539 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t\t# comment\n pass\n"
540 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
541 self.assertFormatEqual(contents_spc, fs(contents_spc))
542 self.assertFormatEqual(contents_spc, fs(contents_tab))
544 def test_report_verbose(self) -> None:
545 report = Report(verbose=True)
549 def out(msg: str, **kwargs: Any) -> None:
550 out_lines.append(msg)
552 def err(msg: str, **kwargs: Any) -> None:
553 err_lines.append(msg)
555 with patch("black.output._out", out), patch("black.output._err", err):
556 report.done(Path("f1"), black.Changed.NO)
557 self.assertEqual(len(out_lines), 1)
558 self.assertEqual(len(err_lines), 0)
559 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
560 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
561 self.assertEqual(report.return_code, 0)
562 report.done(Path("f2"), black.Changed.YES)
563 self.assertEqual(len(out_lines), 2)
564 self.assertEqual(len(err_lines), 0)
565 self.assertEqual(out_lines[-1], "reformatted f2")
567 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
569 report.done(Path("f3"), black.Changed.CACHED)
570 self.assertEqual(len(out_lines), 3)
571 self.assertEqual(len(err_lines), 0)
573 out_lines[-1], "f3 wasn't modified on disk since last run."
576 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
578 self.assertEqual(report.return_code, 0)
580 self.assertEqual(report.return_code, 1)
582 report.failed(Path("e1"), "boom")
583 self.assertEqual(len(out_lines), 3)
584 self.assertEqual(len(err_lines), 1)
585 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
587 unstyle(str(report)),
588 "1 file reformatted, 2 files left unchanged, 1 file failed to"
591 self.assertEqual(report.return_code, 123)
592 report.done(Path("f3"), black.Changed.YES)
593 self.assertEqual(len(out_lines), 4)
594 self.assertEqual(len(err_lines), 1)
595 self.assertEqual(out_lines[-1], "reformatted f3")
597 unstyle(str(report)),
598 "2 files reformatted, 2 files left unchanged, 1 file failed to"
601 self.assertEqual(report.return_code, 123)
602 report.failed(Path("e2"), "boom")
603 self.assertEqual(len(out_lines), 4)
604 self.assertEqual(len(err_lines), 2)
605 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
607 unstyle(str(report)),
608 "2 files reformatted, 2 files left unchanged, 2 files failed to"
611 self.assertEqual(report.return_code, 123)
612 report.path_ignored(Path("wat"), "no match")
613 self.assertEqual(len(out_lines), 5)
614 self.assertEqual(len(err_lines), 2)
615 self.assertEqual(out_lines[-1], "wat ignored: no match")
617 unstyle(str(report)),
618 "2 files reformatted, 2 files left unchanged, 2 files failed to"
621 self.assertEqual(report.return_code, 123)
622 report.done(Path("f4"), black.Changed.NO)
623 self.assertEqual(len(out_lines), 6)
624 self.assertEqual(len(err_lines), 2)
625 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
627 unstyle(str(report)),
628 "2 files reformatted, 3 files left unchanged, 2 files failed to"
631 self.assertEqual(report.return_code, 123)
634 unstyle(str(report)),
635 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
636 " would fail to reformat.",
641 unstyle(str(report)),
642 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
643 " would fail to reformat.",
646 def test_report_quiet(self) -> None:
647 report = Report(quiet=True)
651 def out(msg: str, **kwargs: Any) -> None:
652 out_lines.append(msg)
654 def err(msg: str, **kwargs: Any) -> None:
655 err_lines.append(msg)
657 with patch("black.output._out", out), patch("black.output._err", err):
658 report.done(Path("f1"), black.Changed.NO)
659 self.assertEqual(len(out_lines), 0)
660 self.assertEqual(len(err_lines), 0)
661 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
662 self.assertEqual(report.return_code, 0)
663 report.done(Path("f2"), black.Changed.YES)
664 self.assertEqual(len(out_lines), 0)
665 self.assertEqual(len(err_lines), 0)
667 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
669 report.done(Path("f3"), black.Changed.CACHED)
670 self.assertEqual(len(out_lines), 0)
671 self.assertEqual(len(err_lines), 0)
673 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
675 self.assertEqual(report.return_code, 0)
677 self.assertEqual(report.return_code, 1)
679 report.failed(Path("e1"), "boom")
680 self.assertEqual(len(out_lines), 0)
681 self.assertEqual(len(err_lines), 1)
682 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
684 unstyle(str(report)),
685 "1 file reformatted, 2 files left unchanged, 1 file failed to"
688 self.assertEqual(report.return_code, 123)
689 report.done(Path("f3"), black.Changed.YES)
690 self.assertEqual(len(out_lines), 0)
691 self.assertEqual(len(err_lines), 1)
693 unstyle(str(report)),
694 "2 files reformatted, 2 files left unchanged, 1 file failed to"
697 self.assertEqual(report.return_code, 123)
698 report.failed(Path("e2"), "boom")
699 self.assertEqual(len(out_lines), 0)
700 self.assertEqual(len(err_lines), 2)
701 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
703 unstyle(str(report)),
704 "2 files reformatted, 2 files left unchanged, 2 files failed to"
707 self.assertEqual(report.return_code, 123)
708 report.path_ignored(Path("wat"), "no match")
709 self.assertEqual(len(out_lines), 0)
710 self.assertEqual(len(err_lines), 2)
712 unstyle(str(report)),
713 "2 files reformatted, 2 files left unchanged, 2 files failed to"
716 self.assertEqual(report.return_code, 123)
717 report.done(Path("f4"), black.Changed.NO)
718 self.assertEqual(len(out_lines), 0)
719 self.assertEqual(len(err_lines), 2)
721 unstyle(str(report)),
722 "2 files reformatted, 3 files left unchanged, 2 files failed to"
725 self.assertEqual(report.return_code, 123)
728 unstyle(str(report)),
729 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
730 " would fail to reformat.",
735 unstyle(str(report)),
736 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
737 " would fail to reformat.",
740 def test_report_normal(self) -> None:
741 report = black.Report()
745 def out(msg: str, **kwargs: Any) -> None:
746 out_lines.append(msg)
748 def err(msg: str, **kwargs: Any) -> None:
749 err_lines.append(msg)
751 with patch("black.output._out", out), patch("black.output._err", err):
752 report.done(Path("f1"), black.Changed.NO)
753 self.assertEqual(len(out_lines), 0)
754 self.assertEqual(len(err_lines), 0)
755 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
756 self.assertEqual(report.return_code, 0)
757 report.done(Path("f2"), black.Changed.YES)
758 self.assertEqual(len(out_lines), 1)
759 self.assertEqual(len(err_lines), 0)
760 self.assertEqual(out_lines[-1], "reformatted f2")
762 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
764 report.done(Path("f3"), black.Changed.CACHED)
765 self.assertEqual(len(out_lines), 1)
766 self.assertEqual(len(err_lines), 0)
767 self.assertEqual(out_lines[-1], "reformatted f2")
769 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
771 self.assertEqual(report.return_code, 0)
773 self.assertEqual(report.return_code, 1)
775 report.failed(Path("e1"), "boom")
776 self.assertEqual(len(out_lines), 1)
777 self.assertEqual(len(err_lines), 1)
778 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
780 unstyle(str(report)),
781 "1 file reformatted, 2 files left unchanged, 1 file failed to"
784 self.assertEqual(report.return_code, 123)
785 report.done(Path("f3"), black.Changed.YES)
786 self.assertEqual(len(out_lines), 2)
787 self.assertEqual(len(err_lines), 1)
788 self.assertEqual(out_lines[-1], "reformatted f3")
790 unstyle(str(report)),
791 "2 files reformatted, 2 files left unchanged, 1 file failed to"
794 self.assertEqual(report.return_code, 123)
795 report.failed(Path("e2"), "boom")
796 self.assertEqual(len(out_lines), 2)
797 self.assertEqual(len(err_lines), 2)
798 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
800 unstyle(str(report)),
801 "2 files reformatted, 2 files left unchanged, 2 files failed to"
804 self.assertEqual(report.return_code, 123)
805 report.path_ignored(Path("wat"), "no match")
806 self.assertEqual(len(out_lines), 2)
807 self.assertEqual(len(err_lines), 2)
809 unstyle(str(report)),
810 "2 files reformatted, 2 files left unchanged, 2 files failed to"
813 self.assertEqual(report.return_code, 123)
814 report.done(Path("f4"), black.Changed.NO)
815 self.assertEqual(len(out_lines), 2)
816 self.assertEqual(len(err_lines), 2)
818 unstyle(str(report)),
819 "2 files reformatted, 3 files left unchanged, 2 files failed to"
822 self.assertEqual(report.return_code, 123)
825 unstyle(str(report)),
826 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
827 " would fail to reformat.",
832 unstyle(str(report)),
833 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
834 " would fail to reformat.",
837 def test_lib2to3_parse(self) -> None:
838 with self.assertRaises(black.InvalidInput):
839 black.lib2to3_parse("invalid syntax")
842 black.lib2to3_parse(straddling)
843 black.lib2to3_parse(straddling, {TargetVersion.PY27})
844 black.lib2to3_parse(straddling, {TargetVersion.PY36})
845 black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
848 black.lib2to3_parse(py2_only)
849 black.lib2to3_parse(py2_only, {TargetVersion.PY27})
850 with self.assertRaises(black.InvalidInput):
851 black.lib2to3_parse(py2_only, {TargetVersion.PY36})
852 with self.assertRaises(black.InvalidInput):
853 black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
855 py3_only = "exec(x, end=y)"
856 black.lib2to3_parse(py3_only)
857 with self.assertRaises(black.InvalidInput):
858 black.lib2to3_parse(py3_only, {TargetVersion.PY27})
859 black.lib2to3_parse(py3_only, {TargetVersion.PY36})
860 black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
862 def test_get_features_used_decorator(self) -> None:
863 # Test the feature detection of new decorator syntax
864 # since this makes some test cases of test_get_features_used()
865 # fails if it fails, this is tested first so that a useful case
867 simples, relaxed = read_data("decorators")
868 # skip explanation comments at the top of the file
869 for simple_test in simples.split("##")[1:]:
870 node = black.lib2to3_parse(simple_test)
871 decorator = str(node.children[0].children[0]).strip()
873 Feature.RELAXED_DECORATORS,
874 black.get_features_used(node),
876 f"decorator '{decorator}' follows python<=3.8 syntax"
877 "but is detected as 3.9+"
878 # f"The full node is\n{node!r}"
881 # skip the '# output' comment at the top of the output part
882 for relaxed_test in relaxed.split("##")[1:]:
883 node = black.lib2to3_parse(relaxed_test)
884 decorator = str(node.children[0].children[0]).strip()
886 Feature.RELAXED_DECORATORS,
887 black.get_features_used(node),
889 f"decorator '{decorator}' uses python3.9+ syntax"
890 "but is detected as python<=3.8"
891 # f"The full node is\n{node!r}"
895 def test_get_features_used(self) -> None:
896 node = black.lib2to3_parse("def f(*, arg): ...\n")
897 self.assertEqual(black.get_features_used(node), set())
898 node = black.lib2to3_parse("def f(*, arg,): ...\n")
899 self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
900 node = black.lib2to3_parse("f(*arg,)\n")
902 black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
904 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
905 self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
906 node = black.lib2to3_parse("123_456\n")
907 self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
908 node = black.lib2to3_parse("123456\n")
909 self.assertEqual(black.get_features_used(node), set())
910 source, expected = read_data("function")
911 node = black.lib2to3_parse(source)
912 expected_features = {
913 Feature.TRAILING_COMMA_IN_CALL,
914 Feature.TRAILING_COMMA_IN_DEF,
917 self.assertEqual(black.get_features_used(node), expected_features)
918 node = black.lib2to3_parse(expected)
919 self.assertEqual(black.get_features_used(node), expected_features)
920 source, expected = read_data("expression")
921 node = black.lib2to3_parse(source)
922 self.assertEqual(black.get_features_used(node), set())
923 node = black.lib2to3_parse(expected)
924 self.assertEqual(black.get_features_used(node), set())
926 def test_get_future_imports(self) -> None:
927 node = black.lib2to3_parse("\n")
928 self.assertEqual(set(), black.get_future_imports(node))
929 node = black.lib2to3_parse("from __future__ import black\n")
930 self.assertEqual({"black"}, black.get_future_imports(node))
931 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
932 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
933 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
934 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
935 node = black.lib2to3_parse(
936 "from __future__ import multiple\nfrom __future__ import imports\n"
938 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
939 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
940 self.assertEqual({"black"}, black.get_future_imports(node))
941 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
942 self.assertEqual({"black"}, black.get_future_imports(node))
943 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
944 self.assertEqual(set(), black.get_future_imports(node))
945 node = black.lib2to3_parse("from some.module import black\n")
946 self.assertEqual(set(), black.get_future_imports(node))
947 node = black.lib2to3_parse(
948 "from __future__ import unicode_literals as _unicode_literals"
950 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
951 node = black.lib2to3_parse(
952 "from __future__ import unicode_literals as _lol, print"
954 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
956 def test_debug_visitor(self) -> None:
957 source, _ = read_data("debug_visitor.py")
958 expected, _ = read_data("debug_visitor.out")
962 def out(msg: str, **kwargs: Any) -> None:
963 out_lines.append(msg)
965 def err(msg: str, **kwargs: Any) -> None:
966 err_lines.append(msg)
968 with patch("black.debug.out", out):
969 DebugVisitor.show(source)
970 actual = "\n".join(out_lines) + "\n"
972 if expected != actual:
973 log_name = black.dump_to_file(*out_lines)
977 f"AST print out is different. Actual version dumped to {log_name}",
980 def test_format_file_contents(self) -> None:
983 with self.assertRaises(black.NothingChanged):
984 black.format_file_contents(empty, mode=mode, fast=False)
986 with self.assertRaises(black.NothingChanged):
987 black.format_file_contents(just_nl, mode=mode, fast=False)
988 same = "j = [1, 2, 3]\n"
989 with self.assertRaises(black.NothingChanged):
990 black.format_file_contents(same, mode=mode, fast=False)
991 different = "j = [1,2,3]"
993 actual = black.format_file_contents(different, mode=mode, fast=False)
994 self.assertEqual(expected, actual)
995 invalid = "return if you can"
996 with self.assertRaises(black.InvalidInput) as e:
997 black.format_file_contents(invalid, mode=mode, fast=False)
998 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
1000 def test_endmarker(self) -> None:
1001 n = black.lib2to3_parse("\n")
1002 self.assertEqual(n.type, black.syms.file_input)
1003 self.assertEqual(len(n.children), 1)
1004 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
1006 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
1007 def test_assertFormatEqual(self) -> None:
1011 def out(msg: str, **kwargs: Any) -> None:
1012 out_lines.append(msg)
1014 def err(msg: str, **kwargs: Any) -> None:
1015 err_lines.append(msg)
1017 with patch("black.output._out", out), patch("black.output._err", err):
1018 with self.assertRaises(AssertionError):
1019 self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")
1021 out_str = "".join(out_lines)
1022 self.assertTrue("Expected tree:" in out_str)
1023 self.assertTrue("Actual tree:" in out_str)
1024 self.assertEqual("".join(err_lines), "")
1026 def test_cache_broken_file(self) -> None:
1028 with cache_dir() as workspace:
1029 cache_file = get_cache_file(mode)
1030 with cache_file.open("w") as fobj:
1031 fobj.write("this is not a pickle")
1032 self.assertEqual(black.read_cache(mode), {})
1033 src = (workspace / "test.py").resolve()
1034 with src.open("w") as fobj:
1035 fobj.write("print('hello')")
1036 self.invokeBlack([str(src)])
1037 cache = black.read_cache(mode)
1038 self.assertIn(str(src), cache)
1040 def test_cache_single_file_already_cached(self) -> None:
1042 with cache_dir() as workspace:
1043 src = (workspace / "test.py").resolve()
1044 with src.open("w") as fobj:
1045 fobj.write("print('hello')")
1046 black.write_cache({}, [src], mode)
1047 self.invokeBlack([str(src)])
1048 with src.open("r") as fobj:
1049 self.assertEqual(fobj.read(), "print('hello')")
1052 def test_cache_multiple_files(self) -> None:
1054 with cache_dir() as workspace, patch(
1055 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1057 one = (workspace / "one.py").resolve()
1058 with one.open("w") as fobj:
1059 fobj.write("print('hello')")
1060 two = (workspace / "two.py").resolve()
1061 with two.open("w") as fobj:
1062 fobj.write("print('hello')")
1063 black.write_cache({}, [one], mode)
1064 self.invokeBlack([str(workspace)])
1065 with one.open("r") as fobj:
1066 self.assertEqual(fobj.read(), "print('hello')")
1067 with two.open("r") as fobj:
1068 self.assertEqual(fobj.read(), 'print("hello")\n')
1069 cache = black.read_cache(mode)
1070 self.assertIn(str(one), cache)
1071 self.assertIn(str(two), cache)
1073 def test_no_cache_when_writeback_diff(self) -> None:
1075 with cache_dir() as workspace:
1076 src = (workspace / "test.py").resolve()
1077 with src.open("w") as fobj:
1078 fobj.write("print('hello')")
1079 with patch("black.read_cache") as read_cache, patch(
1082 self.invokeBlack([str(src), "--diff"])
1083 cache_file = get_cache_file(mode)
1084 self.assertFalse(cache_file.exists())
1085 write_cache.assert_not_called()
1086 read_cache.assert_not_called()
1088 def test_no_cache_when_writeback_color_diff(self) -> None:
1090 with cache_dir() as workspace:
1091 src = (workspace / "test.py").resolve()
1092 with src.open("w") as fobj:
1093 fobj.write("print('hello')")
1094 with patch("black.read_cache") as read_cache, patch(
1097 self.invokeBlack([str(src), "--diff", "--color"])
1098 cache_file = get_cache_file(mode)
1099 self.assertFalse(cache_file.exists())
1100 write_cache.assert_not_called()
1101 read_cache.assert_not_called()
1104 def test_output_locking_when_writeback_diff(self) -> None:
1105 with cache_dir() as workspace:
1106 for tag in range(0, 4):
1107 src = (workspace / f"test{tag}.py").resolve()
1108 with src.open("w") as fobj:
1109 fobj.write("print('hello')")
1110 with patch("black.Manager", wraps=multiprocessing.Manager) as mgr:
1111 self.invokeBlack(["--diff", str(workspace)], exit_code=0)
1112 # this isn't quite doing what we want, but if it _isn't_
1113 # called then we cannot be using the lock it provides
1117 def test_output_locking_when_writeback_color_diff(self) -> None:
1118 with cache_dir() as workspace:
1119 for tag in range(0, 4):
1120 src = (workspace / f"test{tag}.py").resolve()
1121 with src.open("w") as fobj:
1122 fobj.write("print('hello')")
1123 with patch("black.Manager", wraps=multiprocessing.Manager) as mgr:
1124 self.invokeBlack(["--diff", "--color", str(workspace)], exit_code=0)
1125 # this isn't quite doing what we want, but if it _isn't_
1126 # called then we cannot be using the lock it provides
1129 def test_no_cache_when_stdin(self) -> None:
1132 result = CliRunner().invoke(
1133 black.main, ["-"], input=BytesIO(b"print('hello')")
1135 self.assertEqual(result.exit_code, 0)
1136 cache_file = get_cache_file(mode)
1137 self.assertFalse(cache_file.exists())
1139 def test_read_cache_no_cachefile(self) -> None:
1142 self.assertEqual(black.read_cache(mode), {})
1144 def test_write_cache_read_cache(self) -> None:
1146 with cache_dir() as workspace:
1147 src = (workspace / "test.py").resolve()
1149 black.write_cache({}, [src], mode)
1150 cache = black.read_cache(mode)
1151 self.assertIn(str(src), cache)
1152 self.assertEqual(cache[str(src)], black.get_cache_info(src))
1154 def test_filter_cached(self) -> None:
1155 with TemporaryDirectory() as workspace:
1156 path = Path(workspace)
1157 uncached = (path / "uncached").resolve()
1158 cached = (path / "cached").resolve()
1159 cached_but_changed = (path / "changed").resolve()
1162 cached_but_changed.touch()
1164 str(cached): black.get_cache_info(cached),
1165 str(cached_but_changed): (0.0, 0),
1167 todo, done = black.filter_cached(
1168 cache, {uncached, cached, cached_but_changed}
1170 self.assertEqual(todo, {uncached, cached_but_changed})
1171 self.assertEqual(done, {cached})
1173 def test_write_cache_creates_directory_if_needed(self) -> None:
1175 with cache_dir(exists=False) as workspace:
1176 self.assertFalse(workspace.exists())
1177 black.write_cache({}, [], mode)
1178 self.assertTrue(workspace.exists())
1181 def test_failed_formatting_does_not_get_cached(self) -> None:
1183 with cache_dir() as workspace, patch(
1184 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1186 failing = (workspace / "failing.py").resolve()
1187 with failing.open("w") as fobj:
1188 fobj.write("not actually python")
1189 clean = (workspace / "clean.py").resolve()
1190 with clean.open("w") as fobj:
1191 fobj.write('print("hello")\n')
1192 self.invokeBlack([str(workspace)], exit_code=123)
1193 cache = black.read_cache(mode)
1194 self.assertNotIn(str(failing), cache)
1195 self.assertIn(str(clean), cache)
1197 def test_write_cache_write_fail(self) -> None:
1199 with cache_dir(), patch.object(Path, "open") as mock:
1200 mock.side_effect = OSError
1201 black.write_cache({}, [], mode)
1204 @patch("black.ProcessPoolExecutor", MagicMock(side_effect=OSError))
1205 def test_works_in_mono_process_only_environment(self) -> None:
1206 with cache_dir() as workspace:
1208 (workspace / "one.py").resolve(),
1209 (workspace / "two.py").resolve(),
1211 f.write_text('print("hello")\n')
1212 self.invokeBlack([str(workspace)])
1215 def test_check_diff_use_together(self) -> None:
1217 # Files which will be reformatted.
1218 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1219 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1220 # Files which will not be reformatted.
1221 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1222 self.invokeBlack([str(src2), "--diff", "--check"])
1223 # Multi file command.
1224 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1226 def test_no_files(self) -> None:
1228 # Without an argument, black exits with error code 0.
1229 self.invokeBlack([])
1231 def test_broken_symlink(self) -> None:
1232 with cache_dir() as workspace:
1233 symlink = workspace / "broken_link.py"
1235 symlink.symlink_to("nonexistent.py")
1236 except OSError as e:
1237 self.skipTest(f"Can't create symlinks: {e}")
1238 self.invokeBlack([str(workspace.resolve())])
1240 def test_read_cache_line_lengths(self) -> None:
1242 short_mode = replace(DEFAULT_MODE, line_length=1)
1243 with cache_dir() as workspace:
1244 path = (workspace / "file.py").resolve()
1246 black.write_cache({}, [path], mode)
1247 one = black.read_cache(mode)
1248 self.assertIn(str(path), one)
1249 two = black.read_cache(short_mode)
1250 self.assertNotIn(str(path), two)
1252 def test_single_file_force_pyi(self) -> None:
1253 pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
1254 contents, expected = read_data("force_pyi")
1255 with cache_dir() as workspace:
1256 path = (workspace / "file.py").resolve()
1257 with open(path, "w") as fh:
1259 self.invokeBlack([str(path), "--pyi"])
1260 with open(path, "r") as fh:
1262 # verify cache with --pyi is separate
1263 pyi_cache = black.read_cache(pyi_mode)
1264 self.assertIn(str(path), pyi_cache)
1265 normal_cache = black.read_cache(DEFAULT_MODE)
1266 self.assertNotIn(str(path), normal_cache)
1267 self.assertFormatEqual(expected, actual)
1268 black.assert_equivalent(contents, actual)
1269 black.assert_stable(contents, actual, pyi_mode)
1272 def test_multi_file_force_pyi(self) -> None:
1273 reg_mode = DEFAULT_MODE
1274 pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
1275 contents, expected = read_data("force_pyi")
1276 with cache_dir() as workspace:
1278 (workspace / "file1.py").resolve(),
1279 (workspace / "file2.py").resolve(),
1282 with open(path, "w") as fh:
1284 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1286 with open(path, "r") as fh:
1288 self.assertEqual(actual, expected)
1289 # verify cache with --pyi is separate
1290 pyi_cache = black.read_cache(pyi_mode)
1291 normal_cache = black.read_cache(reg_mode)
1293 self.assertIn(str(path), pyi_cache)
1294 self.assertNotIn(str(path), normal_cache)
1296 def test_pipe_force_pyi(self) -> None:
1297 source, expected = read_data("force_pyi")
1298 result = CliRunner().invoke(
1299 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1301 self.assertEqual(result.exit_code, 0)
1302 actual = result.output
1303 self.assertFormatEqual(actual, expected)
1305 def test_single_file_force_py36(self) -> None:
1306 reg_mode = DEFAULT_MODE
1307 py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
1308 source, expected = read_data("force_py36")
1309 with cache_dir() as workspace:
1310 path = (workspace / "file.py").resolve()
1311 with open(path, "w") as fh:
1313 self.invokeBlack([str(path), *PY36_ARGS])
1314 with open(path, "r") as fh:
1316 # verify cache with --target-version is separate
1317 py36_cache = black.read_cache(py36_mode)
1318 self.assertIn(str(path), py36_cache)
1319 normal_cache = black.read_cache(reg_mode)
1320 self.assertNotIn(str(path), normal_cache)
1321 self.assertEqual(actual, expected)
1324 def test_multi_file_force_py36(self) -> None:
1325 reg_mode = DEFAULT_MODE
1326 py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
1327 source, expected = read_data("force_py36")
1328 with cache_dir() as workspace:
1330 (workspace / "file1.py").resolve(),
1331 (workspace / "file2.py").resolve(),
1334 with open(path, "w") as fh:
1336 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1338 with open(path, "r") as fh:
1340 self.assertEqual(actual, expected)
1341 # verify cache with --target-version is separate
1342 pyi_cache = black.read_cache(py36_mode)
1343 normal_cache = black.read_cache(reg_mode)
1345 self.assertIn(str(path), pyi_cache)
1346 self.assertNotIn(str(path), normal_cache)
1348 def test_pipe_force_py36(self) -> None:
1349 source, expected = read_data("force_py36")
1350 result = CliRunner().invoke(
1352 ["-", "-q", "--target-version=py36"],
1353 input=BytesIO(source.encode("utf8")),
1355 self.assertEqual(result.exit_code, 0)
1356 actual = result.output
1357 self.assertFormatEqual(actual, expected)
1359 def test_include_exclude(self) -> None:
1360 path = THIS_DIR / "data" / "include_exclude_tests"
1361 include = re.compile(r"\.pyi?$")
1362 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1363 report = black.Report()
1364 gitignore = PathSpec.from_lines("gitwildmatch", [])
1365 sources: List[Path] = []
1367 Path(path / "b/dont_exclude/a.py"),
1368 Path(path / "b/dont_exclude/a.pyi"),
1370 this_abs = THIS_DIR.resolve()
1372 black.gen_python_files(
1383 self.assertEqual(sorted(expected), sorted(sources))
1385 def test_gitignore_used_as_default(self) -> None:
1386 path = Path(THIS_DIR / "data" / "include_exclude_tests")
1387 include = re.compile(r"\.pyi?$")
1388 extend_exclude = re.compile(r"/exclude/")
1389 src = str(path / "b/")
1390 report = black.Report()
1391 expected: List[Path] = [
1392 path / "b/.definitely_exclude/a.py",
1393 path / "b/.definitely_exclude/a.pyi",
1403 extend_exclude=extend_exclude,
1406 stdin_filename=None,
1409 self.assertEqual(sorted(expected), sorted(sources))
1411 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1412 def test_exclude_for_issue_1572(self) -> None:
1413 # Exclude shouldn't touch files that were explicitly given to Black through the
1414 # CLI. Exclude is supposed to only apply to the recursive discovery of files.
1415 # https://github.com/psf/black/issues/1572
1416 path = THIS_DIR / "data" / "include_exclude_tests"
1418 exclude = r"/exclude/|a\.py"
1419 src = str(path / "b/exclude/a.py")
1420 report = black.Report()
1421 expected = [Path(path / "b/exclude/a.py")]
1428 include=re.compile(include),
1429 exclude=re.compile(exclude),
1430 extend_exclude=None,
1433 stdin_filename=None,
1436 self.assertEqual(sorted(expected), sorted(sources))
1438 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1439 def test_get_sources_with_stdin(self) -> None:
1441 exclude = r"/exclude/|a\.py"
1443 report = black.Report()
1444 expected = [Path("-")]
1451 include=re.compile(include),
1452 exclude=re.compile(exclude),
1453 extend_exclude=None,
1456 stdin_filename=None,
1459 self.assertEqual(sorted(expected), sorted(sources))
1461 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1462 def test_get_sources_with_stdin_filename(self) -> None:
1464 exclude = r"/exclude/|a\.py"
1466 report = black.Report()
1467 stdin_filename = str(THIS_DIR / "data/collections.py")
1468 expected = [Path(f"__BLACK_STDIN_FILENAME__{stdin_filename}")]
1475 include=re.compile(include),
1476 exclude=re.compile(exclude),
1477 extend_exclude=None,
1480 stdin_filename=stdin_filename,
1483 self.assertEqual(sorted(expected), sorted(sources))
1485 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1486 def test_get_sources_with_stdin_filename_and_exclude(self) -> None:
1487 # Exclude shouldn't exclude stdin_filename since it is mimicking the
1488 # file being passed directly. This is the same as
1489 # test_exclude_for_issue_1572
1490 path = THIS_DIR / "data" / "include_exclude_tests"
1492 exclude = r"/exclude/|a\.py"
1494 report = black.Report()
1495 stdin_filename = str(path / "b/exclude/a.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_extend_exclude(self) -> None:
1515 # Extend 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 extend_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(""),
1533 extend_exclude=re.compile(extend_exclude),
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_force_exclude(self) -> None:
1543 # Force exclude should exclude the file when passing it through
1545 path = THIS_DIR / "data" / "include_exclude_tests"
1547 force_exclude = r"/exclude/|a\.py"
1549 report = black.Report()
1550 stdin_filename = str(path / "b/exclude/a.py")
1557 include=re.compile(include),
1558 exclude=re.compile(""),
1559 extend_exclude=None,
1560 force_exclude=re.compile(force_exclude),
1562 stdin_filename=stdin_filename,
1565 self.assertEqual([], sorted(sources))
1567 def test_reformat_one_with_stdin(self) -> None:
1569 "black.format_stdin_to_stdout",
1570 return_value=lambda *args, **kwargs: black.Changed.YES,
1572 report = MagicMock()
1577 write_back=black.WriteBack.YES,
1581 fsts.assert_called_once()
1582 report.done.assert_called_with(path, black.Changed.YES)
1584 def test_reformat_one_with_stdin_filename(self) -> None:
1586 "black.format_stdin_to_stdout",
1587 return_value=lambda *args, **kwargs: black.Changed.YES,
1589 report = MagicMock()
1591 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1596 write_back=black.WriteBack.YES,
1600 fsts.assert_called_once_with(
1601 fast=True, write_back=black.WriteBack.YES, mode=DEFAULT_MODE
1603 # __BLACK_STDIN_FILENAME__ should have been stripped
1604 report.done.assert_called_with(expected, black.Changed.YES)
1606 def test_reformat_one_with_stdin_filename_pyi(self) -> None:
1608 "black.format_stdin_to_stdout",
1609 return_value=lambda *args, **kwargs: black.Changed.YES,
1611 report = MagicMock()
1613 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1618 write_back=black.WriteBack.YES,
1622 fsts.assert_called_once_with(
1624 write_back=black.WriteBack.YES,
1625 mode=replace(DEFAULT_MODE, is_pyi=True),
1627 # __BLACK_STDIN_FILENAME__ should have been stripped
1628 report.done.assert_called_with(expected, black.Changed.YES)
1630 def test_reformat_one_with_stdin_and_existing_path(self) -> None:
1632 "black.format_stdin_to_stdout",
1633 return_value=lambda *args, **kwargs: black.Changed.YES,
1635 report = MagicMock()
1636 # Even with an existing file, since we are forcing stdin, black
1637 # should output to stdout and not modify the file inplace
1638 p = Path(str(THIS_DIR / "data/collections.py"))
1639 # Make sure is_file actually returns True
1640 self.assertTrue(p.is_file())
1641 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1646 write_back=black.WriteBack.YES,
1650 fsts.assert_called_once()
1651 # __BLACK_STDIN_FILENAME__ should have been stripped
1652 report.done.assert_called_with(expected, black.Changed.YES)
1654 def test_reformat_one_with_stdin_empty(self) -> None:
1655 output = io.StringIO()
1656 with patch("io.TextIOWrapper", lambda *args, **kwargs: output):
1658 black.format_stdin_to_stdout(
1661 write_back=black.WriteBack.YES,
1664 except io.UnsupportedOperation:
1665 pass # StringIO does not support detach
1666 assert output.getvalue() == ""
1668 def test_gitignore_exclude(self) -> None:
1669 path = THIS_DIR / "data" / "include_exclude_tests"
1670 include = re.compile(r"\.pyi?$")
1671 exclude = re.compile(r"")
1672 report = black.Report()
1673 gitignore = PathSpec.from_lines(
1674 "gitwildmatch", ["exclude/", ".definitely_exclude"]
1676 sources: List[Path] = []
1678 Path(path / "b/dont_exclude/a.py"),
1679 Path(path / "b/dont_exclude/a.pyi"),
1681 this_abs = THIS_DIR.resolve()
1683 black.gen_python_files(
1694 self.assertEqual(sorted(expected), sorted(sources))
1696 def test_nested_gitignore(self) -> None:
1697 path = Path(THIS_DIR / "data" / "nested_gitignore_tests")
1698 include = re.compile(r"\.pyi?$")
1699 exclude = re.compile(r"")
1700 root_gitignore = black.files.get_gitignore(path)
1701 report = black.Report()
1702 expected: List[Path] = [
1703 Path(path / "x.py"),
1704 Path(path / "root/b.py"),
1705 Path(path / "root/c.py"),
1706 Path(path / "root/child/c.py"),
1708 this_abs = THIS_DIR.resolve()
1710 black.gen_python_files(
1721 self.assertEqual(sorted(expected), sorted(sources))
1723 def test_empty_include(self) -> None:
1724 path = THIS_DIR / "data" / "include_exclude_tests"
1725 report = black.Report()
1726 gitignore = PathSpec.from_lines("gitwildmatch", [])
1727 empty = re.compile(r"")
1728 sources: List[Path] = []
1730 Path(path / "b/exclude/a.pie"),
1731 Path(path / "b/exclude/a.py"),
1732 Path(path / "b/exclude/a.pyi"),
1733 Path(path / "b/dont_exclude/a.pie"),
1734 Path(path / "b/dont_exclude/a.py"),
1735 Path(path / "b/dont_exclude/a.pyi"),
1736 Path(path / "b/.definitely_exclude/a.pie"),
1737 Path(path / "b/.definitely_exclude/a.py"),
1738 Path(path / "b/.definitely_exclude/a.pyi"),
1739 Path(path / ".gitignore"),
1740 Path(path / "pyproject.toml"),
1742 this_abs = THIS_DIR.resolve()
1744 black.gen_python_files(
1748 re.compile(black.DEFAULT_EXCLUDES),
1755 self.assertEqual(sorted(expected), sorted(sources))
1757 def test_extend_exclude(self) -> None:
1758 path = THIS_DIR / "data" / "include_exclude_tests"
1759 report = black.Report()
1760 gitignore = PathSpec.from_lines("gitwildmatch", [])
1761 sources: List[Path] = []
1763 Path(path / "b/exclude/a.py"),
1764 Path(path / "b/dont_exclude/a.py"),
1766 this_abs = THIS_DIR.resolve()
1768 black.gen_python_files(
1771 re.compile(black.DEFAULT_INCLUDES),
1772 re.compile(r"\.pyi$"),
1773 re.compile(r"\.definitely_exclude"),
1779 self.assertEqual(sorted(expected), sorted(sources))
1781 def test_invalid_cli_regex(self) -> None:
1782 for option in ["--include", "--exclude", "--extend-exclude", "--force-exclude"]:
1783 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1785 def test_required_version_matches_version(self) -> None:
1787 ["--required-version", black.__version__], exit_code=0, ignore_config=True
1790 def test_required_version_does_not_match_version(self) -> None:
1792 ["--required-version", "20.99b"], exit_code=1, ignore_config=True
1795 def test_preserves_line_endings(self) -> None:
1796 with TemporaryDirectory() as workspace:
1797 test_file = Path(workspace) / "test.py"
1798 for nl in ["\n", "\r\n"]:
1799 contents = nl.join(["def f( ):", " pass"])
1800 test_file.write_bytes(contents.encode())
1801 ff(test_file, write_back=black.WriteBack.YES)
1802 updated_contents: bytes = test_file.read_bytes()
1803 self.assertIn(nl.encode(), updated_contents)
1805 self.assertNotIn(b"\r\n", updated_contents)
1807 def test_preserves_line_endings_via_stdin(self) -> None:
1808 for nl in ["\n", "\r\n"]:
1809 contents = nl.join(["def f( ):", " pass"])
1810 runner = BlackRunner()
1811 result = runner.invoke(
1812 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1814 self.assertEqual(result.exit_code, 0)
1815 output = result.stdout_bytes
1816 self.assertIn(nl.encode("utf8"), output)
1818 self.assertNotIn(b"\r\n", output)
1820 def test_assert_equivalent_different_asts(self) -> None:
1821 with self.assertRaises(AssertionError):
1822 black.assert_equivalent("{}", "None")
1824 def test_symlink_out_of_root_directory(self) -> None:
1826 root = THIS_DIR.resolve()
1828 include = re.compile(black.DEFAULT_INCLUDES)
1829 exclude = re.compile(black.DEFAULT_EXCLUDES)
1830 report = black.Report()
1831 gitignore = PathSpec.from_lines("gitwildmatch", [])
1832 # `child` should behave like a symlink which resolved path is clearly
1833 # outside of the `root` directory.
1834 path.iterdir.return_value = [child]
1835 child.resolve.return_value = Path("/a/b/c")
1836 child.as_posix.return_value = "/a/b/c"
1837 child.is_symlink.return_value = True
1840 black.gen_python_files(
1851 except ValueError as ve:
1852 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1853 path.iterdir.assert_called_once()
1854 child.resolve.assert_called_once()
1855 child.is_symlink.assert_called_once()
1856 # `child` should behave like a strange file which resolved path is clearly
1857 # outside of the `root` directory.
1858 child.is_symlink.return_value = False
1859 with self.assertRaises(ValueError):
1861 black.gen_python_files(
1872 path.iterdir.assert_called()
1873 self.assertEqual(path.iterdir.call_count, 2)
1874 child.resolve.assert_called()
1875 self.assertEqual(child.resolve.call_count, 2)
1876 child.is_symlink.assert_called()
1877 self.assertEqual(child.is_symlink.call_count, 2)
1879 def test_shhh_click(self) -> None:
1881 from click import _unicodefun
1882 except ModuleNotFoundError:
1883 self.skipTest("Incompatible Click version")
1884 if not hasattr(_unicodefun, "_verify_python3_env"):
1885 self.skipTest("Incompatible Click version")
1886 # First, let's see if Click is crashing with a preferred ASCII charset.
1887 with patch("locale.getpreferredencoding") as gpe:
1888 gpe.return_value = "ASCII"
1889 with self.assertRaises(RuntimeError):
1890 _unicodefun._verify_python3_env() # type: ignore
1891 # Now, let's silence Click...
1893 # ...and confirm it's silent.
1894 with patch("locale.getpreferredencoding") as gpe:
1895 gpe.return_value = "ASCII"
1897 _unicodefun._verify_python3_env() # type: ignore
1898 except RuntimeError as re:
1899 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1901 def test_root_logger_not_used_directly(self) -> None:
1902 def fail(*args: Any, **kwargs: Any) -> None:
1903 self.fail("Record created with root logger")
1905 with patch.multiple(
1914 ff(THIS_DIR / "util.py")
1916 def test_invalid_config_return_code(self) -> None:
1917 tmp_file = Path(black.dump_to_file())
1919 tmp_config = Path(black.dump_to_file())
1921 args = ["--config", str(tmp_config), str(tmp_file)]
1922 self.invokeBlack(args, exit_code=2, ignore_config=False)
1926 def test_parse_pyproject_toml(self) -> None:
1927 test_toml_file = THIS_DIR / "test.toml"
1928 config = black.parse_pyproject_toml(str(test_toml_file))
1929 self.assertEqual(config["verbose"], 1)
1930 self.assertEqual(config["check"], "no")
1931 self.assertEqual(config["diff"], "y")
1932 self.assertEqual(config["color"], True)
1933 self.assertEqual(config["line_length"], 79)
1934 self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
1935 self.assertEqual(config["exclude"], r"\.pyi?$")
1936 self.assertEqual(config["include"], r"\.py?$")
1938 def test_read_pyproject_toml(self) -> None:
1939 test_toml_file = THIS_DIR / "test.toml"
1940 fake_ctx = FakeContext()
1941 black.read_pyproject_toml(fake_ctx, FakeParameter(), str(test_toml_file))
1942 config = fake_ctx.default_map
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_find_project_root(self) -> None:
1953 with TemporaryDirectory() as workspace:
1954 root = Path(workspace)
1955 test_dir = root / "test"
1958 src_dir = root / "src"
1961 root_pyproject = root / "pyproject.toml"
1962 root_pyproject.touch()
1963 src_pyproject = src_dir / "pyproject.toml"
1964 src_pyproject.touch()
1965 src_python = src_dir / "foo.py"
1969 black.find_project_root((src_dir, test_dir)), root.resolve()
1971 self.assertEqual(black.find_project_root((src_dir,)), src_dir.resolve())
1972 self.assertEqual(black.find_project_root((src_python,)), src_dir.resolve())
1975 "black.files.find_user_pyproject_toml",
1976 black.files.find_user_pyproject_toml.__wrapped__,
1978 def test_find_user_pyproject_toml_linux(self) -> None:
1979 if system() == "Windows":
1982 # Test if XDG_CONFIG_HOME is checked
1983 with TemporaryDirectory() as workspace:
1984 tmp_user_config = Path(workspace) / "black"
1985 with patch.dict("os.environ", {"XDG_CONFIG_HOME": workspace}):
1987 black.files.find_user_pyproject_toml(), tmp_user_config.resolve()
1990 # Test fallback for XDG_CONFIG_HOME
1991 with patch.dict("os.environ"):
1992 os.environ.pop("XDG_CONFIG_HOME", None)
1993 fallback_user_config = Path("~/.config").expanduser() / "black"
1995 black.files.find_user_pyproject_toml(), fallback_user_config.resolve()
1998 def test_find_user_pyproject_toml_windows(self) -> None:
1999 if system() != "Windows":
2002 user_config_path = Path.home() / ".black"
2004 black.files.find_user_pyproject_toml(), user_config_path.resolve()
2007 def test_bpo_33660_workaround(self) -> None:
2008 if system() == "Windows":
2011 # https://bugs.python.org/issue33660
2013 old_cwd = Path.cwd()
2017 path = Path("workspace") / "project"
2018 report = black.Report(verbose=True)
2019 normalized_path = black.normalize_path_maybe_ignore(path, root, report)
2020 self.assertEqual(normalized_path, "workspace/project")
2022 os.chdir(str(old_cwd))
2024 def test_newline_comment_interaction(self) -> None:
2025 source = "class A:\\\r\n# type: ignore\n pass\n"
2026 output = black.format_str(source, mode=DEFAULT_MODE)
2027 black.assert_stable(source, output, mode=DEFAULT_MODE)
2029 def test_bpo_2142_workaround(self) -> None:
2031 # https://bugs.python.org/issue2142
2033 source, _ = read_data("missing_final_newline.py")
2034 # read_data adds a trailing newline
2035 source = source.rstrip()
2036 expected, _ = read_data("missing_final_newline.diff")
2037 tmp_file = Path(black.dump_to_file(source, ensure_final_newline=False))
2038 diff_header = re.compile(
2039 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
2040 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
2043 result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
2044 self.assertEqual(result.exit_code, 0)
2047 actual = result.output
2048 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
2049 self.assertEqual(actual, expected)
2051 @pytest.mark.python2
2052 def test_docstring_reformat_for_py27(self) -> None:
2054 Check that stripping trailing whitespace from Python 2 docstrings
2055 doesn't trigger a "not equivalent to source" error
2058 b'def foo():\r\n """Testing\r\n Testing """\r\n print "Foo"\r\n'
2060 expected = 'def foo():\n """Testing\n Testing"""\n print "Foo"\n'
2062 result = CliRunner().invoke(
2064 ["-", "-q", "--target-version=py27"],
2065 input=BytesIO(source),
2068 self.assertEqual(result.exit_code, 0)
2069 actual = result.output
2070 self.assertFormatEqual(actual, expected)
2073 def compare_results(
2074 result: click.testing.Result, expected_value: str, expected_exit_code: int
2076 """Helper method to test the value and exit code of a click Result."""
2078 result.output == expected_value
2079 ), "The output did not match the expected value."
2080 assert result.exit_code == expected_exit_code, "The exit code is incorrect."
2082 def test_code_option(self) -> None:
2083 """Test the code option with no changes."""
2084 code = 'print("Hello world")\n'
2085 args = ["--code", code]
2086 result = CliRunner().invoke(black.main, args)
2088 self.compare_results(result, code, 0)
2090 def test_code_option_changed(self) -> None:
2091 """Test the code option when changes are required."""
2092 code = "print('hello world')"
2093 formatted = black.format_str(code, mode=DEFAULT_MODE)
2095 args = ["--code", code]
2096 result = CliRunner().invoke(black.main, args)
2098 self.compare_results(result, formatted, 0)
2100 def test_code_option_check(self) -> None:
2101 """Test the code option when check is passed."""
2102 args = ["--check", "--code", 'print("Hello world")\n']
2103 result = CliRunner().invoke(black.main, args)
2104 self.compare_results(result, "", 0)
2106 def test_code_option_check_changed(self) -> None:
2107 """Test the code option when changes are required, and check is passed."""
2108 args = ["--check", "--code", "print('hello world')"]
2109 result = CliRunner().invoke(black.main, args)
2110 self.compare_results(result, "", 1)
2112 def test_code_option_diff(self) -> None:
2113 """Test the code option when diff is passed."""
2114 code = "print('hello world')"
2115 formatted = black.format_str(code, mode=DEFAULT_MODE)
2116 result_diff = diff(code, formatted, "STDIN", "STDOUT")
2118 args = ["--diff", "--code", code]
2119 result = CliRunner().invoke(black.main, args)
2121 # Remove time from diff
2122 output = DIFF_TIME.sub("", result.output)
2124 assert output == result_diff, "The output did not match the expected value."
2125 assert result.exit_code == 0, "The exit code is incorrect."
2127 def test_code_option_color_diff(self) -> None:
2128 """Test the code option when color and diff are passed."""
2129 code = "print('hello world')"
2130 formatted = black.format_str(code, mode=DEFAULT_MODE)
2132 result_diff = diff(code, formatted, "STDIN", "STDOUT")
2133 result_diff = color_diff(result_diff)
2135 args = ["--diff", "--color", "--code", code]
2136 result = CliRunner().invoke(black.main, args)
2138 # Remove time from diff
2139 output = DIFF_TIME.sub("", result.output)
2141 assert output == result_diff, "The output did not match the expected value."
2142 assert result.exit_code == 0, "The exit code is incorrect."
2144 def test_code_option_safe(self) -> None:
2145 """Test that the code option throws an error when the sanity checks fail."""
2146 # Patch black.assert_equivalent to ensure the sanity checks fail
2147 with patch.object(black, "assert_equivalent", side_effect=AssertionError):
2148 code = 'print("Hello world")'
2149 error_msg = f"{code}\nerror: cannot format <string>: \n"
2151 args = ["--safe", "--code", code]
2152 result = CliRunner().invoke(black.main, args)
2154 self.compare_results(result, error_msg, 123)
2156 def test_code_option_fast(self) -> None:
2157 """Test that the code option ignores errors when the sanity checks fail."""
2158 # Patch black.assert_equivalent to ensure the sanity checks fail
2159 with patch.object(black, "assert_equivalent", side_effect=AssertionError):
2160 code = 'print("Hello world")'
2161 formatted = black.format_str(code, mode=DEFAULT_MODE)
2163 args = ["--fast", "--code", code]
2164 result = CliRunner().invoke(black.main, args)
2166 self.compare_results(result, formatted, 0)
2168 def test_code_option_config(self) -> None:
2170 Test that the code option finds the pyproject.toml in the current directory.
2172 with patch.object(black, "parse_pyproject_toml", return_value={}) as parse:
2173 # Make sure we are in the project root with the pyproject file
2174 if not Path("tests").exists():
2177 args = ["--code", "print"]
2178 CliRunner().invoke(black.main, args)
2180 pyproject_path = Path(Path().cwd(), "pyproject.toml").resolve()
2182 len(parse.mock_calls) >= 1
2183 ), "Expected config parse to be called with the current directory."
2185 _, call_args, _ = parse.mock_calls[0]
2187 call_args[0].lower() == str(pyproject_path).lower()
2188 ), "Incorrect config loaded."
2190 def test_code_option_parent_config(self) -> None:
2192 Test that the code option finds the pyproject.toml in the parent directory.
2194 with patch.object(black, "parse_pyproject_toml", return_value={}) as parse:
2195 # Make sure we are in the tests directory
2196 if Path("tests").exists():
2199 args = ["--code", "print"]
2200 CliRunner().invoke(black.main, args)
2202 pyproject_path = Path(Path().cwd().parent, "pyproject.toml").resolve()
2204 len(parse.mock_calls) >= 1
2205 ), "Expected config parse to be called with the current directory."
2207 _, call_args, _ = parse.mock_calls[0]
2209 call_args[0].lower() == str(pyproject_path).lower()
2210 ), "Incorrect config loaded."
2213 with open(black.__file__, "r", encoding="utf-8") as _bf:
2214 black_source_lines = _bf.readlines()
2217 def tracefunc(frame: types.FrameType, event: str, arg: Any) -> Callable:
2218 """Show function calls `from black/__init__.py` as they happen.
2220 Register this with `sys.settrace()` in a test you're debugging.
2225 stack = len(inspect.stack()) - 19
2227 filename = frame.f_code.co_filename
2228 lineno = frame.f_lineno
2229 func_sig_lineno = lineno - 1
2230 funcname = black_source_lines[func_sig_lineno].strip()
2231 while funcname.startswith("@"):
2232 func_sig_lineno += 1
2233 funcname = black_source_lines[func_sig_lineno].strip()
2234 if "black/__init__.py" in filename:
2235 print(f"{' ' * stack}{lineno}:{funcname}")
2239 if __name__ == "__main__":
2240 unittest.main(module="test_black")