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
29 from parameterized import parameterized
32 from click import unstyle
33 from click.testing import CliRunner
36 from black import Feature, TargetVersion
37 from black.cache import get_cache_file
38 from black.debug import DebugVisitor
39 from black.output import diff, color_diff
40 from black.report import Report
43 from pathspec import PathSpec
45 # Import other test classes
46 from tests.util import (
59 THIS_FILE = Path(__file__)
66 PY36_ARGS = [f"--target-version={version.name.lower()}" for version in PY36_VERSIONS]
70 # Match the time output in a diff, but nothing else
71 DIFF_TIME = re.compile(r"\t[\d-:+\. ]+")
75 def cache_dir(exists: bool = True) -> Iterator[Path]:
76 with TemporaryDirectory() as workspace:
77 cache_dir = Path(workspace)
79 cache_dir = cache_dir / "new"
80 with patch("black.cache.CACHE_DIR", cache_dir):
85 def event_loop() -> Iterator[None]:
86 policy = asyncio.get_event_loop_policy()
87 loop = policy.new_event_loop()
88 asyncio.set_event_loop(loop)
96 class FakeContext(click.Context):
97 """A fake click Context for when calling functions that need it."""
99 def __init__(self) -> None:
100 self.default_map: Dict[str, Any] = {}
103 class FakeParameter(click.Parameter):
104 """A fake click Parameter for when calling functions that need it."""
106 def __init__(self) -> None:
110 class BlackRunner(CliRunner):
111 """Make sure STDOUT and STDERR are kept separate when testing Black via its CLI."""
113 def __init__(self) -> None:
114 super().__init__(mix_stderr=False)
117 class BlackTestCase(BlackBaseTestCase):
119 self, args: List[str], exit_code: int = 0, ignore_config: bool = True
121 runner = BlackRunner()
123 args = ["--verbose", "--config", str(THIS_DIR / "empty.toml"), *args]
124 result = runner.invoke(black.main, args)
125 assert result.stdout_bytes is not None
126 assert result.stderr_bytes is not None
131 f"Failed with args: {args}\n"
132 f"stdout: {result.stdout_bytes.decode()!r}\n"
133 f"stderr: {result.stderr_bytes.decode()!r}\n"
134 f"exception: {result.exception}"
138 @patch("black.dump_to_file", dump_to_stderr)
139 def test_empty(self) -> None:
140 source = expected = ""
142 self.assertFormatEqual(expected, actual)
143 black.assert_equivalent(source, actual)
144 black.assert_stable(source, actual, DEFAULT_MODE)
146 def test_empty_ff(self) -> None:
148 tmp_file = Path(black.dump_to_file())
150 self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES))
151 with open(tmp_file, encoding="utf8") as f:
155 self.assertFormatEqual(expected, actual)
157 def test_piping(self) -> None:
158 source, expected = read_data("src/black/__init__", data=False)
159 result = BlackRunner().invoke(
161 ["-", "--fast", f"--line-length={black.DEFAULT_LINE_LENGTH}"],
162 input=BytesIO(source.encode("utf8")),
164 self.assertEqual(result.exit_code, 0)
165 self.assertFormatEqual(expected, result.output)
166 if source != result.output:
167 black.assert_equivalent(source, result.output)
168 black.assert_stable(source, result.output, DEFAULT_MODE)
170 def test_piping_diff(self) -> None:
171 diff_header = re.compile(
172 r"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d\d\d\d "
175 source, _ = read_data("expression.py")
176 expected, _ = read_data("expression.diff")
177 config = THIS_DIR / "data" / "empty_pyproject.toml"
181 f"--line-length={black.DEFAULT_LINE_LENGTH}",
183 f"--config={config}",
185 result = BlackRunner().invoke(
186 black.main, args, input=BytesIO(source.encode("utf8"))
188 self.assertEqual(result.exit_code, 0)
189 actual = diff_header.sub(DETERMINISTIC_HEADER, result.output)
190 actual = actual.rstrip() + "\n" # the diff output has a trailing space
191 self.assertEqual(expected, actual)
193 def test_piping_diff_with_color(self) -> None:
194 source, _ = read_data("expression.py")
195 config = THIS_DIR / "data" / "empty_pyproject.toml"
199 f"--line-length={black.DEFAULT_LINE_LENGTH}",
202 f"--config={config}",
204 result = BlackRunner().invoke(
205 black.main, args, input=BytesIO(source.encode("utf8"))
207 actual = result.output
208 # Again, the contents are checked in a different test, so only look for colors.
209 self.assertIn("\033[1;37m", actual)
210 self.assertIn("\033[36m", actual)
211 self.assertIn("\033[32m", actual)
212 self.assertIn("\033[31m", actual)
213 self.assertIn("\033[0m", actual)
215 @patch("black.dump_to_file", dump_to_stderr)
216 def _test_wip(self) -> None:
217 source, expected = read_data("wip")
218 sys.settrace(tracefunc)
221 experimental_string_processing=False,
222 target_versions={black.TargetVersion.PY38},
224 actual = fs(source, mode=mode)
226 self.assertFormatEqual(expected, actual)
227 black.assert_equivalent(source, actual)
228 black.assert_stable(source, actual, black.FileMode())
230 @unittest.expectedFailure
231 @patch("black.dump_to_file", dump_to_stderr)
232 def test_trailing_comma_optional_parens_stability1(self) -> None:
233 source, _expected = read_data("trailing_comma_optional_parens1")
235 black.assert_stable(source, actual, DEFAULT_MODE)
237 @unittest.expectedFailure
238 @patch("black.dump_to_file", dump_to_stderr)
239 def test_trailing_comma_optional_parens_stability2(self) -> None:
240 source, _expected = read_data("trailing_comma_optional_parens2")
242 black.assert_stable(source, actual, DEFAULT_MODE)
244 @unittest.expectedFailure
245 @patch("black.dump_to_file", dump_to_stderr)
246 def test_trailing_comma_optional_parens_stability3(self) -> None:
247 source, _expected = read_data("trailing_comma_optional_parens3")
249 black.assert_stable(source, actual, DEFAULT_MODE)
251 @patch("black.dump_to_file", dump_to_stderr)
252 def test_trailing_comma_optional_parens_stability1_pass2(self) -> None:
253 source, _expected = read_data("trailing_comma_optional_parens1")
254 actual = fs(fs(source)) # this is what `format_file_contents` does with --safe
255 black.assert_stable(source, actual, DEFAULT_MODE)
257 @patch("black.dump_to_file", dump_to_stderr)
258 def test_trailing_comma_optional_parens_stability2_pass2(self) -> None:
259 source, _expected = read_data("trailing_comma_optional_parens2")
260 actual = fs(fs(source)) # this is what `format_file_contents` does with --safe
261 black.assert_stable(source, actual, DEFAULT_MODE)
263 @patch("black.dump_to_file", dump_to_stderr)
264 def test_trailing_comma_optional_parens_stability3_pass2(self) -> None:
265 source, _expected = read_data("trailing_comma_optional_parens3")
266 actual = fs(fs(source)) # this is what `format_file_contents` does with --safe
267 black.assert_stable(source, actual, DEFAULT_MODE)
269 @patch("black.dump_to_file", dump_to_stderr)
270 def test_pep_572(self) -> None:
271 source, expected = read_data("pep_572")
273 self.assertFormatEqual(expected, actual)
274 black.assert_stable(source, actual, DEFAULT_MODE)
275 if sys.version_info >= (3, 8):
276 black.assert_equivalent(source, actual)
278 @patch("black.dump_to_file", dump_to_stderr)
279 def test_pep_572_remove_parens(self) -> None:
280 source, expected = read_data("pep_572_remove_parens")
282 self.assertFormatEqual(expected, actual)
283 black.assert_stable(source, actual, DEFAULT_MODE)
284 if sys.version_info >= (3, 8):
285 black.assert_equivalent(source, actual)
287 @patch("black.dump_to_file", dump_to_stderr)
288 def test_pep_572_do_not_remove_parens(self) -> None:
289 source, expected = read_data("pep_572_do_not_remove_parens")
290 # the AST safety checks will fail, but that's expected, just make sure no
291 # parentheses are touched
292 actual = black.format_str(source, mode=DEFAULT_MODE)
293 self.assertFormatEqual(expected, actual)
295 def test_pep_572_version_detection(self) -> None:
296 source, _ = read_data("pep_572")
297 root = black.lib2to3_parse(source)
298 features = black.get_features_used(root)
299 self.assertIn(black.Feature.ASSIGNMENT_EXPRESSIONS, features)
300 versions = black.detect_target_versions(root)
301 self.assertIn(black.TargetVersion.PY38, versions)
303 @parameterized.expand([(3, 9), (3, 10)])
304 def test_pep_572_newer_syntax(self, major: int, minor: int) -> None:
305 source, expected = read_data(f"pep_572_py{major}{minor}")
306 actual = fs(source, mode=DEFAULT_MODE)
307 self.assertFormatEqual(expected, actual)
308 if sys.version_info >= (major, minor):
309 black.assert_equivalent(source, actual)
311 def test_expression_ff(self) -> None:
312 source, expected = read_data("expression")
313 tmp_file = Path(black.dump_to_file(source))
315 self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
316 with open(tmp_file, encoding="utf8") as f:
320 self.assertFormatEqual(expected, actual)
321 with patch("black.dump_to_file", dump_to_stderr):
322 black.assert_equivalent(source, actual)
323 black.assert_stable(source, actual, DEFAULT_MODE)
325 def test_expression_diff(self) -> None:
326 source, _ = read_data("expression.py")
327 config = THIS_DIR / "data" / "empty_pyproject.toml"
328 expected, _ = read_data("expression.diff")
329 tmp_file = Path(black.dump_to_file(source))
330 diff_header = re.compile(
331 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
332 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
335 result = BlackRunner().invoke(
336 black.main, ["--diff", str(tmp_file), f"--config={config}"]
338 self.assertEqual(result.exit_code, 0)
341 actual = result.output
342 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
343 if expected != actual:
344 dump = black.dump_to_file(actual)
346 "Expected diff isn't equal to the actual. If you made changes to"
347 " expression.py and this is an anticipated difference, overwrite"
348 f" tests/data/expression.diff with {dump}"
350 self.assertEqual(expected, actual, msg)
352 def test_expression_diff_with_color(self) -> None:
353 source, _ = read_data("expression.py")
354 config = THIS_DIR / "data" / "empty_pyproject.toml"
355 expected, _ = read_data("expression.diff")
356 tmp_file = Path(black.dump_to_file(source))
358 result = BlackRunner().invoke(
359 black.main, ["--diff", "--color", str(tmp_file), f"--config={config}"]
363 actual = result.output
364 # We check the contents of the diff in `test_expression_diff`. All
365 # we need to check here is that color codes exist in the result.
366 self.assertIn("\033[1;37m", actual)
367 self.assertIn("\033[36m", actual)
368 self.assertIn("\033[32m", actual)
369 self.assertIn("\033[31m", actual)
370 self.assertIn("\033[0m", actual)
372 @patch("black.dump_to_file", dump_to_stderr)
373 def test_pep_570(self) -> None:
374 source, expected = read_data("pep_570")
376 self.assertFormatEqual(expected, actual)
377 black.assert_stable(source, actual, DEFAULT_MODE)
378 if sys.version_info >= (3, 8):
379 black.assert_equivalent(source, actual)
381 def test_detect_pos_only_arguments(self) -> None:
382 source, _ = read_data("pep_570")
383 root = black.lib2to3_parse(source)
384 features = black.get_features_used(root)
385 self.assertIn(black.Feature.POS_ONLY_ARGUMENTS, features)
386 versions = black.detect_target_versions(root)
387 self.assertIn(black.TargetVersion.PY38, versions)
389 @patch("black.dump_to_file", dump_to_stderr)
390 def test_string_quotes(self) -> None:
391 source, expected = read_data("string_quotes")
392 mode = black.Mode(experimental_string_processing=True)
393 actual = fs(source, mode=mode)
394 self.assertFormatEqual(expected, actual)
395 black.assert_equivalent(source, actual)
396 black.assert_stable(source, actual, mode)
397 mode = replace(mode, string_normalization=False)
398 not_normalized = fs(source, mode=mode)
399 self.assertFormatEqual(source.replace("\\\n", ""), not_normalized)
400 black.assert_equivalent(source, not_normalized)
401 black.assert_stable(source, not_normalized, mode=mode)
403 @patch("black.dump_to_file", dump_to_stderr)
404 def test_docstring_no_string_normalization(self) -> None:
405 """Like test_docstring but with string normalization off."""
406 source, expected = read_data("docstring_no_string_normalization")
407 mode = replace(DEFAULT_MODE, string_normalization=False)
408 actual = fs(source, mode=mode)
409 self.assertFormatEqual(expected, actual)
410 black.assert_equivalent(source, actual)
411 black.assert_stable(source, actual, mode)
413 def test_long_strings_flag_disabled(self) -> None:
414 """Tests for turning off the string processing logic."""
415 source, expected = read_data("long_strings_flag_disabled")
416 mode = replace(DEFAULT_MODE, experimental_string_processing=False)
417 actual = fs(source, mode=mode)
418 self.assertFormatEqual(expected, actual)
419 black.assert_stable(expected, actual, mode)
421 @patch("black.dump_to_file", dump_to_stderr)
422 def test_numeric_literals(self) -> None:
423 source, expected = read_data("numeric_literals")
424 mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
425 actual = fs(source, mode=mode)
426 self.assertFormatEqual(expected, actual)
427 black.assert_equivalent(source, actual)
428 black.assert_stable(source, actual, mode)
430 @patch("black.dump_to_file", dump_to_stderr)
431 def test_numeric_literals_ignoring_underscores(self) -> None:
432 source, expected = read_data("numeric_literals_skip_underscores")
433 mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
434 actual = fs(source, mode=mode)
435 self.assertFormatEqual(expected, actual)
436 black.assert_equivalent(source, actual)
437 black.assert_stable(source, actual, mode)
439 def test_skip_magic_trailing_comma(self) -> None:
440 source, _ = read_data("expression.py")
441 expected, _ = read_data("expression_skip_magic_trailing_comma.diff")
442 tmp_file = Path(black.dump_to_file(source))
443 diff_header = re.compile(
444 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
445 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
448 result = BlackRunner().invoke(black.main, ["-C", "--diff", str(tmp_file)])
449 self.assertEqual(result.exit_code, 0)
452 actual = result.output
453 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
454 actual = actual.rstrip() + "\n" # the diff output has a trailing space
455 if expected != actual:
456 dump = black.dump_to_file(actual)
458 "Expected diff isn't equal to the actual. If you made changes to"
459 " expression.py and this is an anticipated difference, overwrite"
460 f" tests/data/expression_skip_magic_trailing_comma.diff with {dump}"
462 self.assertEqual(expected, actual, msg)
465 @patch("black.dump_to_file", dump_to_stderr)
466 def test_python2_print_function(self) -> None:
467 source, expected = read_data("python2_print_function")
468 mode = replace(DEFAULT_MODE, target_versions={TargetVersion.PY27})
469 actual = fs(source, mode=mode)
470 self.assertFormatEqual(expected, actual)
471 black.assert_equivalent(source, actual)
472 black.assert_stable(source, actual, mode)
474 @patch("black.dump_to_file", dump_to_stderr)
475 def test_stub(self) -> None:
476 mode = replace(DEFAULT_MODE, is_pyi=True)
477 source, expected = read_data("stub.pyi")
478 actual = fs(source, mode=mode)
479 self.assertFormatEqual(expected, actual)
480 black.assert_stable(source, actual, mode)
482 @patch("black.dump_to_file", dump_to_stderr)
483 def test_async_as_identifier(self) -> None:
484 source_path = (THIS_DIR / "data" / "async_as_identifier.py").resolve()
485 source, expected = read_data("async_as_identifier")
487 self.assertFormatEqual(expected, actual)
488 major, minor = sys.version_info[:2]
489 if major < 3 or (major <= 3 and minor < 7):
490 black.assert_equivalent(source, actual)
491 black.assert_stable(source, actual, DEFAULT_MODE)
492 # ensure black can parse this when the target is 3.6
493 self.invokeBlack([str(source_path), "--target-version", "py36"])
494 # but not on 3.7, because async/await is no longer an identifier
495 self.invokeBlack([str(source_path), "--target-version", "py37"], exit_code=123)
497 @patch("black.dump_to_file", dump_to_stderr)
498 def test_python37(self) -> None:
499 source_path = (THIS_DIR / "data" / "python37.py").resolve()
500 source, expected = read_data("python37")
502 self.assertFormatEqual(expected, actual)
503 major, minor = sys.version_info[:2]
504 if major > 3 or (major == 3 and minor >= 7):
505 black.assert_equivalent(source, actual)
506 black.assert_stable(source, actual, DEFAULT_MODE)
507 # ensure black can parse this when the target is 3.7
508 self.invokeBlack([str(source_path), "--target-version", "py37"])
509 # but not on 3.6, because we use async as a reserved keyword
510 self.invokeBlack([str(source_path), "--target-version", "py36"], exit_code=123)
512 @patch("black.dump_to_file", dump_to_stderr)
513 def test_python38(self) -> None:
514 source, expected = read_data("python38")
516 self.assertFormatEqual(expected, actual)
517 major, minor = sys.version_info[:2]
518 if major > 3 or (major == 3 and minor >= 8):
519 black.assert_equivalent(source, actual)
520 black.assert_stable(source, actual, DEFAULT_MODE)
522 @patch("black.dump_to_file", dump_to_stderr)
523 def test_python39(self) -> None:
524 source, expected = read_data("python39")
526 self.assertFormatEqual(expected, actual)
527 major, minor = sys.version_info[:2]
528 if major > 3 or (major == 3 and minor >= 9):
529 black.assert_equivalent(source, actual)
530 black.assert_stable(source, actual, DEFAULT_MODE)
532 def test_tab_comment_indentation(self) -> None:
533 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
534 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
535 self.assertFormatEqual(contents_spc, fs(contents_spc))
536 self.assertFormatEqual(contents_spc, fs(contents_tab))
538 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
539 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
540 self.assertFormatEqual(contents_spc, fs(contents_spc))
541 self.assertFormatEqual(contents_spc, fs(contents_tab))
543 # mixed tabs and spaces (valid Python 2 code)
544 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t# comment\n pass\n"
545 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
546 self.assertFormatEqual(contents_spc, fs(contents_spc))
547 self.assertFormatEqual(contents_spc, fs(contents_tab))
549 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t\t# comment\n pass\n"
550 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
551 self.assertFormatEqual(contents_spc, fs(contents_spc))
552 self.assertFormatEqual(contents_spc, fs(contents_tab))
554 def test_report_verbose(self) -> None:
555 report = Report(verbose=True)
559 def out(msg: str, **kwargs: Any) -> None:
560 out_lines.append(msg)
562 def err(msg: str, **kwargs: Any) -> None:
563 err_lines.append(msg)
565 with patch("black.output._out", out), patch("black.output._err", err):
566 report.done(Path("f1"), black.Changed.NO)
567 self.assertEqual(len(out_lines), 1)
568 self.assertEqual(len(err_lines), 0)
569 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
570 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
571 self.assertEqual(report.return_code, 0)
572 report.done(Path("f2"), black.Changed.YES)
573 self.assertEqual(len(out_lines), 2)
574 self.assertEqual(len(err_lines), 0)
575 self.assertEqual(out_lines[-1], "reformatted f2")
577 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
579 report.done(Path("f3"), black.Changed.CACHED)
580 self.assertEqual(len(out_lines), 3)
581 self.assertEqual(len(err_lines), 0)
583 out_lines[-1], "f3 wasn't modified on disk since last run."
586 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
588 self.assertEqual(report.return_code, 0)
590 self.assertEqual(report.return_code, 1)
592 report.failed(Path("e1"), "boom")
593 self.assertEqual(len(out_lines), 3)
594 self.assertEqual(len(err_lines), 1)
595 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
597 unstyle(str(report)),
598 "1 file reformatted, 2 files left unchanged, 1 file failed to"
601 self.assertEqual(report.return_code, 123)
602 report.done(Path("f3"), black.Changed.YES)
603 self.assertEqual(len(out_lines), 4)
604 self.assertEqual(len(err_lines), 1)
605 self.assertEqual(out_lines[-1], "reformatted f3")
607 unstyle(str(report)),
608 "2 files reformatted, 2 files left unchanged, 1 file failed to"
611 self.assertEqual(report.return_code, 123)
612 report.failed(Path("e2"), "boom")
613 self.assertEqual(len(out_lines), 4)
614 self.assertEqual(len(err_lines), 2)
615 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
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.path_ignored(Path("wat"), "no match")
623 self.assertEqual(len(out_lines), 5)
624 self.assertEqual(len(err_lines), 2)
625 self.assertEqual(out_lines[-1], "wat ignored: no match")
627 unstyle(str(report)),
628 "2 files reformatted, 2 files left unchanged, 2 files failed to"
631 self.assertEqual(report.return_code, 123)
632 report.done(Path("f4"), black.Changed.NO)
633 self.assertEqual(len(out_lines), 6)
634 self.assertEqual(len(err_lines), 2)
635 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
637 unstyle(str(report)),
638 "2 files reformatted, 3 files left unchanged, 2 files failed to"
641 self.assertEqual(report.return_code, 123)
644 unstyle(str(report)),
645 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
646 " would fail to reformat.",
651 unstyle(str(report)),
652 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
653 " would fail to reformat.",
656 def test_report_quiet(self) -> None:
657 report = Report(quiet=True)
661 def out(msg: str, **kwargs: Any) -> None:
662 out_lines.append(msg)
664 def err(msg: str, **kwargs: Any) -> None:
665 err_lines.append(msg)
667 with patch("black.output._out", out), patch("black.output._err", err):
668 report.done(Path("f1"), black.Changed.NO)
669 self.assertEqual(len(out_lines), 0)
670 self.assertEqual(len(err_lines), 0)
671 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
672 self.assertEqual(report.return_code, 0)
673 report.done(Path("f2"), black.Changed.YES)
674 self.assertEqual(len(out_lines), 0)
675 self.assertEqual(len(err_lines), 0)
677 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
679 report.done(Path("f3"), black.Changed.CACHED)
680 self.assertEqual(len(out_lines), 0)
681 self.assertEqual(len(err_lines), 0)
683 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
685 self.assertEqual(report.return_code, 0)
687 self.assertEqual(report.return_code, 1)
689 report.failed(Path("e1"), "boom")
690 self.assertEqual(len(out_lines), 0)
691 self.assertEqual(len(err_lines), 1)
692 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
694 unstyle(str(report)),
695 "1 file reformatted, 2 files left unchanged, 1 file failed to"
698 self.assertEqual(report.return_code, 123)
699 report.done(Path("f3"), black.Changed.YES)
700 self.assertEqual(len(out_lines), 0)
701 self.assertEqual(len(err_lines), 1)
703 unstyle(str(report)),
704 "2 files reformatted, 2 files left unchanged, 1 file failed to"
707 self.assertEqual(report.return_code, 123)
708 report.failed(Path("e2"), "boom")
709 self.assertEqual(len(out_lines), 0)
710 self.assertEqual(len(err_lines), 2)
711 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
713 unstyle(str(report)),
714 "2 files reformatted, 2 files left unchanged, 2 files failed to"
717 self.assertEqual(report.return_code, 123)
718 report.path_ignored(Path("wat"), "no match")
719 self.assertEqual(len(out_lines), 0)
720 self.assertEqual(len(err_lines), 2)
722 unstyle(str(report)),
723 "2 files reformatted, 2 files left unchanged, 2 files failed to"
726 self.assertEqual(report.return_code, 123)
727 report.done(Path("f4"), black.Changed.NO)
728 self.assertEqual(len(out_lines), 0)
729 self.assertEqual(len(err_lines), 2)
731 unstyle(str(report)),
732 "2 files reformatted, 3 files left unchanged, 2 files failed to"
735 self.assertEqual(report.return_code, 123)
738 unstyle(str(report)),
739 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
740 " would fail to reformat.",
745 unstyle(str(report)),
746 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
747 " would fail to reformat.",
750 def test_report_normal(self) -> None:
751 report = black.Report()
755 def out(msg: str, **kwargs: Any) -> None:
756 out_lines.append(msg)
758 def err(msg: str, **kwargs: Any) -> None:
759 err_lines.append(msg)
761 with patch("black.output._out", out), patch("black.output._err", err):
762 report.done(Path("f1"), black.Changed.NO)
763 self.assertEqual(len(out_lines), 0)
764 self.assertEqual(len(err_lines), 0)
765 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
766 self.assertEqual(report.return_code, 0)
767 report.done(Path("f2"), black.Changed.YES)
768 self.assertEqual(len(out_lines), 1)
769 self.assertEqual(len(err_lines), 0)
770 self.assertEqual(out_lines[-1], "reformatted f2")
772 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
774 report.done(Path("f3"), black.Changed.CACHED)
775 self.assertEqual(len(out_lines), 1)
776 self.assertEqual(len(err_lines), 0)
777 self.assertEqual(out_lines[-1], "reformatted f2")
779 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
781 self.assertEqual(report.return_code, 0)
783 self.assertEqual(report.return_code, 1)
785 report.failed(Path("e1"), "boom")
786 self.assertEqual(len(out_lines), 1)
787 self.assertEqual(len(err_lines), 1)
788 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
790 unstyle(str(report)),
791 "1 file reformatted, 2 files left unchanged, 1 file failed to"
794 self.assertEqual(report.return_code, 123)
795 report.done(Path("f3"), black.Changed.YES)
796 self.assertEqual(len(out_lines), 2)
797 self.assertEqual(len(err_lines), 1)
798 self.assertEqual(out_lines[-1], "reformatted f3")
800 unstyle(str(report)),
801 "2 files reformatted, 2 files left unchanged, 1 file failed to"
804 self.assertEqual(report.return_code, 123)
805 report.failed(Path("e2"), "boom")
806 self.assertEqual(len(out_lines), 2)
807 self.assertEqual(len(err_lines), 2)
808 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
810 unstyle(str(report)),
811 "2 files reformatted, 2 files left unchanged, 2 files failed to"
814 self.assertEqual(report.return_code, 123)
815 report.path_ignored(Path("wat"), "no match")
816 self.assertEqual(len(out_lines), 2)
817 self.assertEqual(len(err_lines), 2)
819 unstyle(str(report)),
820 "2 files reformatted, 2 files left unchanged, 2 files failed to"
823 self.assertEqual(report.return_code, 123)
824 report.done(Path("f4"), black.Changed.NO)
825 self.assertEqual(len(out_lines), 2)
826 self.assertEqual(len(err_lines), 2)
828 unstyle(str(report)),
829 "2 files reformatted, 3 files left unchanged, 2 files failed to"
832 self.assertEqual(report.return_code, 123)
835 unstyle(str(report)),
836 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
837 " would fail to reformat.",
842 unstyle(str(report)),
843 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
844 " would fail to reformat.",
847 def test_lib2to3_parse(self) -> None:
848 with self.assertRaises(black.InvalidInput):
849 black.lib2to3_parse("invalid syntax")
852 black.lib2to3_parse(straddling)
853 black.lib2to3_parse(straddling, {TargetVersion.PY27})
854 black.lib2to3_parse(straddling, {TargetVersion.PY36})
855 black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
858 black.lib2to3_parse(py2_only)
859 black.lib2to3_parse(py2_only, {TargetVersion.PY27})
860 with self.assertRaises(black.InvalidInput):
861 black.lib2to3_parse(py2_only, {TargetVersion.PY36})
862 with self.assertRaises(black.InvalidInput):
863 black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
865 py3_only = "exec(x, end=y)"
866 black.lib2to3_parse(py3_only)
867 with self.assertRaises(black.InvalidInput):
868 black.lib2to3_parse(py3_only, {TargetVersion.PY27})
869 black.lib2to3_parse(py3_only, {TargetVersion.PY36})
870 black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
872 def test_get_features_used_decorator(self) -> None:
873 # Test the feature detection of new decorator syntax
874 # since this makes some test cases of test_get_features_used()
875 # fails if it fails, this is tested first so that a useful case
877 simples, relaxed = read_data("decorators")
878 # skip explanation comments at the top of the file
879 for simple_test in simples.split("##")[1:]:
880 node = black.lib2to3_parse(simple_test)
881 decorator = str(node.children[0].children[0]).strip()
883 Feature.RELAXED_DECORATORS,
884 black.get_features_used(node),
886 f"decorator '{decorator}' follows python<=3.8 syntax"
887 "but is detected as 3.9+"
888 # f"The full node is\n{node!r}"
891 # skip the '# output' comment at the top of the output part
892 for relaxed_test in relaxed.split("##")[1:]:
893 node = black.lib2to3_parse(relaxed_test)
894 decorator = str(node.children[0].children[0]).strip()
896 Feature.RELAXED_DECORATORS,
897 black.get_features_used(node),
899 f"decorator '{decorator}' uses python3.9+ syntax"
900 "but is detected as python<=3.8"
901 # f"The full node is\n{node!r}"
905 def test_get_features_used(self) -> None:
906 node = black.lib2to3_parse("def f(*, arg): ...\n")
907 self.assertEqual(black.get_features_used(node), set())
908 node = black.lib2to3_parse("def f(*, arg,): ...\n")
909 self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
910 node = black.lib2to3_parse("f(*arg,)\n")
912 black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
914 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
915 self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
916 node = black.lib2to3_parse("123_456\n")
917 self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
918 node = black.lib2to3_parse("123456\n")
919 self.assertEqual(black.get_features_used(node), set())
920 source, expected = read_data("function")
921 node = black.lib2to3_parse(source)
922 expected_features = {
923 Feature.TRAILING_COMMA_IN_CALL,
924 Feature.TRAILING_COMMA_IN_DEF,
927 self.assertEqual(black.get_features_used(node), expected_features)
928 node = black.lib2to3_parse(expected)
929 self.assertEqual(black.get_features_used(node), expected_features)
930 source, expected = read_data("expression")
931 node = black.lib2to3_parse(source)
932 self.assertEqual(black.get_features_used(node), set())
933 node = black.lib2to3_parse(expected)
934 self.assertEqual(black.get_features_used(node), set())
936 def test_get_future_imports(self) -> None:
937 node = black.lib2to3_parse("\n")
938 self.assertEqual(set(), black.get_future_imports(node))
939 node = black.lib2to3_parse("from __future__ import black\n")
940 self.assertEqual({"black"}, black.get_future_imports(node))
941 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
942 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
943 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
944 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
945 node = black.lib2to3_parse(
946 "from __future__ import multiple\nfrom __future__ import imports\n"
948 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
949 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
950 self.assertEqual({"black"}, black.get_future_imports(node))
951 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
952 self.assertEqual({"black"}, black.get_future_imports(node))
953 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
954 self.assertEqual(set(), black.get_future_imports(node))
955 node = black.lib2to3_parse("from some.module import black\n")
956 self.assertEqual(set(), black.get_future_imports(node))
957 node = black.lib2to3_parse(
958 "from __future__ import unicode_literals as _unicode_literals"
960 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
961 node = black.lib2to3_parse(
962 "from __future__ import unicode_literals as _lol, print"
964 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
966 def test_debug_visitor(self) -> None:
967 source, _ = read_data("debug_visitor.py")
968 expected, _ = read_data("debug_visitor.out")
972 def out(msg: str, **kwargs: Any) -> None:
973 out_lines.append(msg)
975 def err(msg: str, **kwargs: Any) -> None:
976 err_lines.append(msg)
978 with patch("black.debug.out", out):
979 DebugVisitor.show(source)
980 actual = "\n".join(out_lines) + "\n"
982 if expected != actual:
983 log_name = black.dump_to_file(*out_lines)
987 f"AST print out is different. Actual version dumped to {log_name}",
990 def test_format_file_contents(self) -> None:
993 with self.assertRaises(black.NothingChanged):
994 black.format_file_contents(empty, mode=mode, fast=False)
996 with self.assertRaises(black.NothingChanged):
997 black.format_file_contents(just_nl, mode=mode, fast=False)
998 same = "j = [1, 2, 3]\n"
999 with self.assertRaises(black.NothingChanged):
1000 black.format_file_contents(same, mode=mode, fast=False)
1001 different = "j = [1,2,3]"
1003 actual = black.format_file_contents(different, mode=mode, fast=False)
1004 self.assertEqual(expected, actual)
1005 invalid = "return if you can"
1006 with self.assertRaises(black.InvalidInput) as e:
1007 black.format_file_contents(invalid, mode=mode, fast=False)
1008 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
1010 def test_endmarker(self) -> None:
1011 n = black.lib2to3_parse("\n")
1012 self.assertEqual(n.type, black.syms.file_input)
1013 self.assertEqual(len(n.children), 1)
1014 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
1016 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
1017 def test_assertFormatEqual(self) -> None:
1021 def out(msg: str, **kwargs: Any) -> None:
1022 out_lines.append(msg)
1024 def err(msg: str, **kwargs: Any) -> None:
1025 err_lines.append(msg)
1027 with patch("black.output._out", out), patch("black.output._err", err):
1028 with self.assertRaises(AssertionError):
1029 self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")
1031 out_str = "".join(out_lines)
1032 self.assertTrue("Expected tree:" in out_str)
1033 self.assertTrue("Actual tree:" in out_str)
1034 self.assertEqual("".join(err_lines), "")
1036 def test_cache_broken_file(self) -> None:
1038 with cache_dir() as workspace:
1039 cache_file = get_cache_file(mode)
1040 with cache_file.open("w") as fobj:
1041 fobj.write("this is not a pickle")
1042 self.assertEqual(black.read_cache(mode), {})
1043 src = (workspace / "test.py").resolve()
1044 with src.open("w") as fobj:
1045 fobj.write("print('hello')")
1046 self.invokeBlack([str(src)])
1047 cache = black.read_cache(mode)
1048 self.assertIn(str(src), cache)
1050 def test_cache_single_file_already_cached(self) -> None:
1052 with cache_dir() as workspace:
1053 src = (workspace / "test.py").resolve()
1054 with src.open("w") as fobj:
1055 fobj.write("print('hello')")
1056 black.write_cache({}, [src], mode)
1057 self.invokeBlack([str(src)])
1058 with src.open("r") as fobj:
1059 self.assertEqual(fobj.read(), "print('hello')")
1062 def test_cache_multiple_files(self) -> None:
1064 with cache_dir() as workspace, patch(
1065 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1067 one = (workspace / "one.py").resolve()
1068 with one.open("w") as fobj:
1069 fobj.write("print('hello')")
1070 two = (workspace / "two.py").resolve()
1071 with two.open("w") as fobj:
1072 fobj.write("print('hello')")
1073 black.write_cache({}, [one], mode)
1074 self.invokeBlack([str(workspace)])
1075 with one.open("r") as fobj:
1076 self.assertEqual(fobj.read(), "print('hello')")
1077 with two.open("r") as fobj:
1078 self.assertEqual(fobj.read(), 'print("hello")\n')
1079 cache = black.read_cache(mode)
1080 self.assertIn(str(one), cache)
1081 self.assertIn(str(two), cache)
1083 def test_no_cache_when_writeback_diff(self) -> None:
1085 with cache_dir() as workspace:
1086 src = (workspace / "test.py").resolve()
1087 with src.open("w") as fobj:
1088 fobj.write("print('hello')")
1089 with patch("black.read_cache") as read_cache, patch(
1092 self.invokeBlack([str(src), "--diff"])
1093 cache_file = get_cache_file(mode)
1094 self.assertFalse(cache_file.exists())
1095 write_cache.assert_not_called()
1096 read_cache.assert_not_called()
1098 def test_no_cache_when_writeback_color_diff(self) -> None:
1100 with cache_dir() as workspace:
1101 src = (workspace / "test.py").resolve()
1102 with src.open("w") as fobj:
1103 fobj.write("print('hello')")
1104 with patch("black.read_cache") as read_cache, patch(
1107 self.invokeBlack([str(src), "--diff", "--color"])
1108 cache_file = get_cache_file(mode)
1109 self.assertFalse(cache_file.exists())
1110 write_cache.assert_not_called()
1111 read_cache.assert_not_called()
1114 def test_output_locking_when_writeback_diff(self) -> None:
1115 with cache_dir() as workspace:
1116 for tag in range(0, 4):
1117 src = (workspace / f"test{tag}.py").resolve()
1118 with src.open("w") as fobj:
1119 fobj.write("print('hello')")
1120 with patch("black.Manager", wraps=multiprocessing.Manager) as mgr:
1121 self.invokeBlack(["--diff", str(workspace)], exit_code=0)
1122 # this isn't quite doing what we want, but if it _isn't_
1123 # called then we cannot be using the lock it provides
1127 def test_output_locking_when_writeback_color_diff(self) -> None:
1128 with cache_dir() as workspace:
1129 for tag in range(0, 4):
1130 src = (workspace / f"test{tag}.py").resolve()
1131 with src.open("w") as fobj:
1132 fobj.write("print('hello')")
1133 with patch("black.Manager", wraps=multiprocessing.Manager) as mgr:
1134 self.invokeBlack(["--diff", "--color", str(workspace)], exit_code=0)
1135 # this isn't quite doing what we want, but if it _isn't_
1136 # called then we cannot be using the lock it provides
1139 def test_no_cache_when_stdin(self) -> None:
1142 result = CliRunner().invoke(
1143 black.main, ["-"], input=BytesIO(b"print('hello')")
1145 self.assertEqual(result.exit_code, 0)
1146 cache_file = get_cache_file(mode)
1147 self.assertFalse(cache_file.exists())
1149 def test_read_cache_no_cachefile(self) -> None:
1152 self.assertEqual(black.read_cache(mode), {})
1154 def test_write_cache_read_cache(self) -> None:
1156 with cache_dir() as workspace:
1157 src = (workspace / "test.py").resolve()
1159 black.write_cache({}, [src], mode)
1160 cache = black.read_cache(mode)
1161 self.assertIn(str(src), cache)
1162 self.assertEqual(cache[str(src)], black.get_cache_info(src))
1164 def test_filter_cached(self) -> None:
1165 with TemporaryDirectory() as workspace:
1166 path = Path(workspace)
1167 uncached = (path / "uncached").resolve()
1168 cached = (path / "cached").resolve()
1169 cached_but_changed = (path / "changed").resolve()
1172 cached_but_changed.touch()
1174 str(cached): black.get_cache_info(cached),
1175 str(cached_but_changed): (0.0, 0),
1177 todo, done = black.filter_cached(
1178 cache, {uncached, cached, cached_but_changed}
1180 self.assertEqual(todo, {uncached, cached_but_changed})
1181 self.assertEqual(done, {cached})
1183 def test_write_cache_creates_directory_if_needed(self) -> None:
1185 with cache_dir(exists=False) as workspace:
1186 self.assertFalse(workspace.exists())
1187 black.write_cache({}, [], mode)
1188 self.assertTrue(workspace.exists())
1191 def test_failed_formatting_does_not_get_cached(self) -> None:
1193 with cache_dir() as workspace, patch(
1194 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1196 failing = (workspace / "failing.py").resolve()
1197 with failing.open("w") as fobj:
1198 fobj.write("not actually python")
1199 clean = (workspace / "clean.py").resolve()
1200 with clean.open("w") as fobj:
1201 fobj.write('print("hello")\n')
1202 self.invokeBlack([str(workspace)], exit_code=123)
1203 cache = black.read_cache(mode)
1204 self.assertNotIn(str(failing), cache)
1205 self.assertIn(str(clean), cache)
1207 def test_write_cache_write_fail(self) -> None:
1209 with cache_dir(), patch.object(Path, "open") as mock:
1210 mock.side_effect = OSError
1211 black.write_cache({}, [], mode)
1214 @patch("black.ProcessPoolExecutor", MagicMock(side_effect=OSError))
1215 def test_works_in_mono_process_only_environment(self) -> None:
1216 with cache_dir() as workspace:
1218 (workspace / "one.py").resolve(),
1219 (workspace / "two.py").resolve(),
1221 f.write_text('print("hello")\n')
1222 self.invokeBlack([str(workspace)])
1225 def test_check_diff_use_together(self) -> None:
1227 # Files which will be reformatted.
1228 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1229 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1230 # Files which will not be reformatted.
1231 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1232 self.invokeBlack([str(src2), "--diff", "--check"])
1233 # Multi file command.
1234 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1236 def test_no_files(self) -> None:
1238 # Without an argument, black exits with error code 0.
1239 self.invokeBlack([])
1241 def test_broken_symlink(self) -> None:
1242 with cache_dir() as workspace:
1243 symlink = workspace / "broken_link.py"
1245 symlink.symlink_to("nonexistent.py")
1246 except OSError as e:
1247 self.skipTest(f"Can't create symlinks: {e}")
1248 self.invokeBlack([str(workspace.resolve())])
1250 def test_read_cache_line_lengths(self) -> None:
1252 short_mode = replace(DEFAULT_MODE, line_length=1)
1253 with cache_dir() as workspace:
1254 path = (workspace / "file.py").resolve()
1256 black.write_cache({}, [path], mode)
1257 one = black.read_cache(mode)
1258 self.assertIn(str(path), one)
1259 two = black.read_cache(short_mode)
1260 self.assertNotIn(str(path), two)
1262 def test_single_file_force_pyi(self) -> None:
1263 pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
1264 contents, expected = read_data("force_pyi")
1265 with cache_dir() as workspace:
1266 path = (workspace / "file.py").resolve()
1267 with open(path, "w") as fh:
1269 self.invokeBlack([str(path), "--pyi"])
1270 with open(path, "r") as fh:
1272 # verify cache with --pyi is separate
1273 pyi_cache = black.read_cache(pyi_mode)
1274 self.assertIn(str(path), pyi_cache)
1275 normal_cache = black.read_cache(DEFAULT_MODE)
1276 self.assertNotIn(str(path), normal_cache)
1277 self.assertFormatEqual(expected, actual)
1278 black.assert_equivalent(contents, actual)
1279 black.assert_stable(contents, actual, pyi_mode)
1282 def test_multi_file_force_pyi(self) -> None:
1283 reg_mode = DEFAULT_MODE
1284 pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
1285 contents, expected = read_data("force_pyi")
1286 with cache_dir() as workspace:
1288 (workspace / "file1.py").resolve(),
1289 (workspace / "file2.py").resolve(),
1292 with open(path, "w") as fh:
1294 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1296 with open(path, "r") as fh:
1298 self.assertEqual(actual, expected)
1299 # verify cache with --pyi is separate
1300 pyi_cache = black.read_cache(pyi_mode)
1301 normal_cache = black.read_cache(reg_mode)
1303 self.assertIn(str(path), pyi_cache)
1304 self.assertNotIn(str(path), normal_cache)
1306 def test_pipe_force_pyi(self) -> None:
1307 source, expected = read_data("force_pyi")
1308 result = CliRunner().invoke(
1309 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1311 self.assertEqual(result.exit_code, 0)
1312 actual = result.output
1313 self.assertFormatEqual(actual, expected)
1315 def test_single_file_force_py36(self) -> None:
1316 reg_mode = DEFAULT_MODE
1317 py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
1318 source, expected = read_data("force_py36")
1319 with cache_dir() as workspace:
1320 path = (workspace / "file.py").resolve()
1321 with open(path, "w") as fh:
1323 self.invokeBlack([str(path), *PY36_ARGS])
1324 with open(path, "r") as fh:
1326 # verify cache with --target-version is separate
1327 py36_cache = black.read_cache(py36_mode)
1328 self.assertIn(str(path), py36_cache)
1329 normal_cache = black.read_cache(reg_mode)
1330 self.assertNotIn(str(path), normal_cache)
1331 self.assertEqual(actual, expected)
1334 def test_multi_file_force_py36(self) -> None:
1335 reg_mode = DEFAULT_MODE
1336 py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
1337 source, expected = read_data("force_py36")
1338 with cache_dir() as workspace:
1340 (workspace / "file1.py").resolve(),
1341 (workspace / "file2.py").resolve(),
1344 with open(path, "w") as fh:
1346 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1348 with open(path, "r") as fh:
1350 self.assertEqual(actual, expected)
1351 # verify cache with --target-version is separate
1352 pyi_cache = black.read_cache(py36_mode)
1353 normal_cache = black.read_cache(reg_mode)
1355 self.assertIn(str(path), pyi_cache)
1356 self.assertNotIn(str(path), normal_cache)
1358 def test_pipe_force_py36(self) -> None:
1359 source, expected = read_data("force_py36")
1360 result = CliRunner().invoke(
1362 ["-", "-q", "--target-version=py36"],
1363 input=BytesIO(source.encode("utf8")),
1365 self.assertEqual(result.exit_code, 0)
1366 actual = result.output
1367 self.assertFormatEqual(actual, expected)
1369 def test_include_exclude(self) -> None:
1370 path = THIS_DIR / "data" / "include_exclude_tests"
1371 include = re.compile(r"\.pyi?$")
1372 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1373 report = black.Report()
1374 gitignore = PathSpec.from_lines("gitwildmatch", [])
1375 sources: List[Path] = []
1377 Path(path / "b/dont_exclude/a.py"),
1378 Path(path / "b/dont_exclude/a.pyi"),
1380 this_abs = THIS_DIR.resolve()
1382 black.gen_python_files(
1395 self.assertEqual(sorted(expected), sorted(sources))
1397 def test_gitignore_used_as_default(self) -> None:
1398 path = Path(THIS_DIR / "data" / "include_exclude_tests")
1399 include = re.compile(r"\.pyi?$")
1400 extend_exclude = re.compile(r"/exclude/")
1401 src = str(path / "b/")
1402 report = black.Report()
1403 expected: List[Path] = [
1404 path / "b/.definitely_exclude/a.py",
1405 path / "b/.definitely_exclude/a.pyi",
1415 extend_exclude=extend_exclude,
1418 stdin_filename=None,
1421 self.assertEqual(sorted(expected), sorted(sources))
1423 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1424 def test_exclude_for_issue_1572(self) -> None:
1425 # Exclude shouldn't touch files that were explicitly given to Black through the
1426 # CLI. Exclude is supposed to only apply to the recursive discovery of files.
1427 # https://github.com/psf/black/issues/1572
1428 path = THIS_DIR / "data" / "include_exclude_tests"
1430 exclude = r"/exclude/|a\.py"
1431 src = str(path / "b/exclude/a.py")
1432 report = black.Report()
1433 expected = [Path(path / "b/exclude/a.py")]
1440 include=re.compile(include),
1441 exclude=re.compile(exclude),
1442 extend_exclude=None,
1445 stdin_filename=None,
1448 self.assertEqual(sorted(expected), sorted(sources))
1450 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1451 def test_get_sources_with_stdin(self) -> None:
1453 exclude = r"/exclude/|a\.py"
1455 report = black.Report()
1456 expected = [Path("-")]
1463 include=re.compile(include),
1464 exclude=re.compile(exclude),
1465 extend_exclude=None,
1468 stdin_filename=None,
1471 self.assertEqual(sorted(expected), sorted(sources))
1473 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1474 def test_get_sources_with_stdin_filename(self) -> None:
1476 exclude = r"/exclude/|a\.py"
1478 report = black.Report()
1479 stdin_filename = str(THIS_DIR / "data/collections.py")
1480 expected = [Path(f"__BLACK_STDIN_FILENAME__{stdin_filename}")]
1487 include=re.compile(include),
1488 exclude=re.compile(exclude),
1489 extend_exclude=None,
1492 stdin_filename=stdin_filename,
1495 self.assertEqual(sorted(expected), sorted(sources))
1497 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1498 def test_get_sources_with_stdin_filename_and_exclude(self) -> None:
1499 # Exclude shouldn't exclude stdin_filename since it is mimicking the
1500 # file being passed directly. This is the same as
1501 # test_exclude_for_issue_1572
1502 path = THIS_DIR / "data" / "include_exclude_tests"
1504 exclude = r"/exclude/|a\.py"
1506 report = black.Report()
1507 stdin_filename = str(path / "b/exclude/a.py")
1508 expected = [Path(f"__BLACK_STDIN_FILENAME__{stdin_filename}")]
1515 include=re.compile(include),
1516 exclude=re.compile(exclude),
1517 extend_exclude=None,
1520 stdin_filename=stdin_filename,
1523 self.assertEqual(sorted(expected), sorted(sources))
1525 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1526 def test_get_sources_with_stdin_filename_and_extend_exclude(self) -> None:
1527 # Extend exclude shouldn't exclude stdin_filename since it is mimicking the
1528 # file being passed directly. This is the same as
1529 # test_exclude_for_issue_1572
1530 path = THIS_DIR / "data" / "include_exclude_tests"
1532 extend_exclude = r"/exclude/|a\.py"
1534 report = black.Report()
1535 stdin_filename = str(path / "b/exclude/a.py")
1536 expected = [Path(f"__BLACK_STDIN_FILENAME__{stdin_filename}")]
1543 include=re.compile(include),
1544 exclude=re.compile(""),
1545 extend_exclude=re.compile(extend_exclude),
1548 stdin_filename=stdin_filename,
1551 self.assertEqual(sorted(expected), sorted(sources))
1553 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1554 def test_get_sources_with_stdin_filename_and_force_exclude(self) -> None:
1555 # Force exclude should exclude the file when passing it through
1557 path = THIS_DIR / "data" / "include_exclude_tests"
1559 force_exclude = r"/exclude/|a\.py"
1561 report = black.Report()
1562 stdin_filename = str(path / "b/exclude/a.py")
1569 include=re.compile(include),
1570 exclude=re.compile(""),
1571 extend_exclude=None,
1572 force_exclude=re.compile(force_exclude),
1574 stdin_filename=stdin_filename,
1577 self.assertEqual([], sorted(sources))
1579 def test_reformat_one_with_stdin(self) -> None:
1581 "black.format_stdin_to_stdout",
1582 return_value=lambda *args, **kwargs: black.Changed.YES,
1584 report = MagicMock()
1589 write_back=black.WriteBack.YES,
1593 fsts.assert_called_once()
1594 report.done.assert_called_with(path, black.Changed.YES)
1596 def test_reformat_one_with_stdin_filename(self) -> None:
1598 "black.format_stdin_to_stdout",
1599 return_value=lambda *args, **kwargs: black.Changed.YES,
1601 report = MagicMock()
1603 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1608 write_back=black.WriteBack.YES,
1612 fsts.assert_called_once_with(
1613 fast=True, write_back=black.WriteBack.YES, mode=DEFAULT_MODE
1615 # __BLACK_STDIN_FILENAME__ should have been stripped
1616 report.done.assert_called_with(expected, black.Changed.YES)
1618 def test_reformat_one_with_stdin_filename_pyi(self) -> None:
1620 "black.format_stdin_to_stdout",
1621 return_value=lambda *args, **kwargs: black.Changed.YES,
1623 report = MagicMock()
1625 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1630 write_back=black.WriteBack.YES,
1634 fsts.assert_called_once_with(
1636 write_back=black.WriteBack.YES,
1637 mode=replace(DEFAULT_MODE, is_pyi=True),
1639 # __BLACK_STDIN_FILENAME__ should have been stripped
1640 report.done.assert_called_with(expected, black.Changed.YES)
1642 def test_reformat_one_with_stdin_filename_ipynb(self) -> None:
1644 "black.format_stdin_to_stdout",
1645 return_value=lambda *args, **kwargs: black.Changed.YES,
1647 report = MagicMock()
1649 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1654 write_back=black.WriteBack.YES,
1658 fsts.assert_called_once_with(
1660 write_back=black.WriteBack.YES,
1661 mode=replace(DEFAULT_MODE, is_ipynb=True),
1663 # __BLACK_STDIN_FILENAME__ should have been stripped
1664 report.done.assert_called_with(expected, black.Changed.YES)
1666 def test_reformat_one_with_stdin_and_existing_path(self) -> None:
1668 "black.format_stdin_to_stdout",
1669 return_value=lambda *args, **kwargs: black.Changed.YES,
1671 report = MagicMock()
1672 # Even with an existing file, since we are forcing stdin, black
1673 # should output to stdout and not modify the file inplace
1674 p = Path(str(THIS_DIR / "data/collections.py"))
1675 # Make sure is_file actually returns True
1676 self.assertTrue(p.is_file())
1677 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1682 write_back=black.WriteBack.YES,
1686 fsts.assert_called_once()
1687 # __BLACK_STDIN_FILENAME__ should have been stripped
1688 report.done.assert_called_with(expected, black.Changed.YES)
1690 def test_reformat_one_with_stdin_empty(self) -> None:
1691 output = io.StringIO()
1692 with patch("io.TextIOWrapper", lambda *args, **kwargs: output):
1694 black.format_stdin_to_stdout(
1697 write_back=black.WriteBack.YES,
1700 except io.UnsupportedOperation:
1701 pass # StringIO does not support detach
1702 assert output.getvalue() == ""
1704 def test_gitignore_exclude(self) -> None:
1705 path = THIS_DIR / "data" / "include_exclude_tests"
1706 include = re.compile(r"\.pyi?$")
1707 exclude = re.compile(r"")
1708 report = black.Report()
1709 gitignore = PathSpec.from_lines(
1710 "gitwildmatch", ["exclude/", ".definitely_exclude"]
1712 sources: List[Path] = []
1714 Path(path / "b/dont_exclude/a.py"),
1715 Path(path / "b/dont_exclude/a.pyi"),
1717 this_abs = THIS_DIR.resolve()
1719 black.gen_python_files(
1732 self.assertEqual(sorted(expected), sorted(sources))
1734 def test_nested_gitignore(self) -> None:
1735 path = Path(THIS_DIR / "data" / "nested_gitignore_tests")
1736 include = re.compile(r"\.pyi?$")
1737 exclude = re.compile(r"")
1738 root_gitignore = black.files.get_gitignore(path)
1739 report = black.Report()
1740 expected: List[Path] = [
1741 Path(path / "x.py"),
1742 Path(path / "root/b.py"),
1743 Path(path / "root/c.py"),
1744 Path(path / "root/child/c.py"),
1746 this_abs = THIS_DIR.resolve()
1748 black.gen_python_files(
1761 self.assertEqual(sorted(expected), sorted(sources))
1763 def test_invalid_gitignore(self) -> None:
1764 path = THIS_DIR / "data" / "invalid_gitignore_tests"
1765 empty_config = path / "pyproject.toml"
1766 result = BlackRunner().invoke(
1767 black.main, ["--verbose", "--config", str(empty_config), str(path)]
1769 assert result.exit_code == 1
1770 assert result.stderr_bytes is not None
1772 gitignore = path / ".gitignore"
1773 assert f"Could not parse {gitignore}" in result.stderr_bytes.decode()
1775 def test_invalid_nested_gitignore(self) -> None:
1776 path = THIS_DIR / "data" / "invalid_nested_gitignore_tests"
1777 empty_config = path / "pyproject.toml"
1778 result = BlackRunner().invoke(
1779 black.main, ["--verbose", "--config", str(empty_config), str(path)]
1781 assert result.exit_code == 1
1782 assert result.stderr_bytes is not None
1784 gitignore = path / "a" / ".gitignore"
1785 assert f"Could not parse {gitignore}" in result.stderr_bytes.decode()
1787 def test_empty_include(self) -> None:
1788 path = THIS_DIR / "data" / "include_exclude_tests"
1789 report = black.Report()
1790 gitignore = PathSpec.from_lines("gitwildmatch", [])
1791 empty = re.compile(r"")
1792 sources: List[Path] = []
1794 Path(path / "b/exclude/a.pie"),
1795 Path(path / "b/exclude/a.py"),
1796 Path(path / "b/exclude/a.pyi"),
1797 Path(path / "b/dont_exclude/a.pie"),
1798 Path(path / "b/dont_exclude/a.py"),
1799 Path(path / "b/dont_exclude/a.pyi"),
1800 Path(path / "b/.definitely_exclude/a.pie"),
1801 Path(path / "b/.definitely_exclude/a.py"),
1802 Path(path / "b/.definitely_exclude/a.pyi"),
1803 Path(path / ".gitignore"),
1804 Path(path / "pyproject.toml"),
1806 this_abs = THIS_DIR.resolve()
1808 black.gen_python_files(
1812 re.compile(black.DEFAULT_EXCLUDES),
1821 self.assertEqual(sorted(expected), sorted(sources))
1823 def test_extend_exclude(self) -> None:
1824 path = THIS_DIR / "data" / "include_exclude_tests"
1825 report = black.Report()
1826 gitignore = PathSpec.from_lines("gitwildmatch", [])
1827 sources: List[Path] = []
1829 Path(path / "b/exclude/a.py"),
1830 Path(path / "b/dont_exclude/a.py"),
1832 this_abs = THIS_DIR.resolve()
1834 black.gen_python_files(
1837 re.compile(black.DEFAULT_INCLUDES),
1838 re.compile(r"\.pyi$"),
1839 re.compile(r"\.definitely_exclude"),
1847 self.assertEqual(sorted(expected), sorted(sources))
1849 def test_invalid_cli_regex(self) -> None:
1850 for option in ["--include", "--exclude", "--extend-exclude", "--force-exclude"]:
1851 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1853 def test_required_version_matches_version(self) -> None:
1855 ["--required-version", black.__version__], exit_code=0, ignore_config=True
1858 def test_required_version_does_not_match_version(self) -> None:
1860 ["--required-version", "20.99b"], exit_code=1, ignore_config=True
1863 def test_preserves_line_endings(self) -> None:
1864 with TemporaryDirectory() as workspace:
1865 test_file = Path(workspace) / "test.py"
1866 for nl in ["\n", "\r\n"]:
1867 contents = nl.join(["def f( ):", " pass"])
1868 test_file.write_bytes(contents.encode())
1869 ff(test_file, write_back=black.WriteBack.YES)
1870 updated_contents: bytes = test_file.read_bytes()
1871 self.assertIn(nl.encode(), updated_contents)
1873 self.assertNotIn(b"\r\n", updated_contents)
1875 def test_preserves_line_endings_via_stdin(self) -> None:
1876 for nl in ["\n", "\r\n"]:
1877 contents = nl.join(["def f( ):", " pass"])
1878 runner = BlackRunner()
1879 result = runner.invoke(
1880 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1882 self.assertEqual(result.exit_code, 0)
1883 output = result.stdout_bytes
1884 self.assertIn(nl.encode("utf8"), output)
1886 self.assertNotIn(b"\r\n", output)
1888 def test_assert_equivalent_different_asts(self) -> None:
1889 with self.assertRaises(AssertionError):
1890 black.assert_equivalent("{}", "None")
1892 def test_symlink_out_of_root_directory(self) -> None:
1894 root = THIS_DIR.resolve()
1896 include = re.compile(black.DEFAULT_INCLUDES)
1897 exclude = re.compile(black.DEFAULT_EXCLUDES)
1898 report = black.Report()
1899 gitignore = PathSpec.from_lines("gitwildmatch", [])
1900 # `child` should behave like a symlink which resolved path is clearly
1901 # outside of the `root` directory.
1902 path.iterdir.return_value = [child]
1903 child.resolve.return_value = Path("/a/b/c")
1904 child.as_posix.return_value = "/a/b/c"
1905 child.is_symlink.return_value = True
1908 black.gen_python_files(
1921 except ValueError as ve:
1922 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1923 path.iterdir.assert_called_once()
1924 child.resolve.assert_called_once()
1925 child.is_symlink.assert_called_once()
1926 # `child` should behave like a strange file which resolved path is clearly
1927 # outside of the `root` directory.
1928 child.is_symlink.return_value = False
1929 with self.assertRaises(ValueError):
1931 black.gen_python_files(
1944 path.iterdir.assert_called()
1945 self.assertEqual(path.iterdir.call_count, 2)
1946 child.resolve.assert_called()
1947 self.assertEqual(child.resolve.call_count, 2)
1948 child.is_symlink.assert_called()
1949 self.assertEqual(child.is_symlink.call_count, 2)
1951 def test_shhh_click(self) -> None:
1953 from click import _unicodefun
1954 except ModuleNotFoundError:
1955 self.skipTest("Incompatible Click version")
1956 if not hasattr(_unicodefun, "_verify_python3_env"):
1957 self.skipTest("Incompatible Click version")
1958 # First, let's see if Click is crashing with a preferred ASCII charset.
1959 with patch("locale.getpreferredencoding") as gpe:
1960 gpe.return_value = "ASCII"
1961 with self.assertRaises(RuntimeError):
1962 _unicodefun._verify_python3_env() # type: ignore
1963 # Now, let's silence Click...
1965 # ...and confirm it's silent.
1966 with patch("locale.getpreferredencoding") as gpe:
1967 gpe.return_value = "ASCII"
1969 _unicodefun._verify_python3_env() # type: ignore
1970 except RuntimeError as re:
1971 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1973 def test_root_logger_not_used_directly(self) -> None:
1974 def fail(*args: Any, **kwargs: Any) -> None:
1975 self.fail("Record created with root logger")
1977 with patch.multiple(
1986 ff(THIS_DIR / "util.py")
1988 def test_invalid_config_return_code(self) -> None:
1989 tmp_file = Path(black.dump_to_file())
1991 tmp_config = Path(black.dump_to_file())
1993 args = ["--config", str(tmp_config), str(tmp_file)]
1994 self.invokeBlack(args, exit_code=2, ignore_config=False)
1998 def test_parse_pyproject_toml(self) -> None:
1999 test_toml_file = THIS_DIR / "test.toml"
2000 config = black.parse_pyproject_toml(str(test_toml_file))
2001 self.assertEqual(config["verbose"], 1)
2002 self.assertEqual(config["check"], "no")
2003 self.assertEqual(config["diff"], "y")
2004 self.assertEqual(config["color"], True)
2005 self.assertEqual(config["line_length"], 79)
2006 self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
2007 self.assertEqual(config["exclude"], r"\.pyi?$")
2008 self.assertEqual(config["include"], r"\.py?$")
2010 def test_read_pyproject_toml(self) -> None:
2011 test_toml_file = THIS_DIR / "test.toml"
2012 fake_ctx = FakeContext()
2013 black.read_pyproject_toml(fake_ctx, FakeParameter(), str(test_toml_file))
2014 config = fake_ctx.default_map
2015 self.assertEqual(config["verbose"], "1")
2016 self.assertEqual(config["check"], "no")
2017 self.assertEqual(config["diff"], "y")
2018 self.assertEqual(config["color"], "True")
2019 self.assertEqual(config["line_length"], "79")
2020 self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
2021 self.assertEqual(config["exclude"], r"\.pyi?$")
2022 self.assertEqual(config["include"], r"\.py?$")
2024 def test_find_project_root(self) -> None:
2025 with TemporaryDirectory() as workspace:
2026 root = Path(workspace)
2027 test_dir = root / "test"
2030 src_dir = root / "src"
2033 root_pyproject = root / "pyproject.toml"
2034 root_pyproject.touch()
2035 src_pyproject = src_dir / "pyproject.toml"
2036 src_pyproject.touch()
2037 src_python = src_dir / "foo.py"
2041 black.find_project_root((src_dir, test_dir)), root.resolve()
2043 self.assertEqual(black.find_project_root((src_dir,)), src_dir.resolve())
2044 self.assertEqual(black.find_project_root((src_python,)), src_dir.resolve())
2047 "black.files.find_user_pyproject_toml",
2048 black.files.find_user_pyproject_toml.__wrapped__,
2050 def test_find_user_pyproject_toml_linux(self) -> None:
2051 if system() == "Windows":
2054 # Test if XDG_CONFIG_HOME is checked
2055 with TemporaryDirectory() as workspace:
2056 tmp_user_config = Path(workspace) / "black"
2057 with patch.dict("os.environ", {"XDG_CONFIG_HOME": workspace}):
2059 black.files.find_user_pyproject_toml(), tmp_user_config.resolve()
2062 # Test fallback for XDG_CONFIG_HOME
2063 with patch.dict("os.environ"):
2064 os.environ.pop("XDG_CONFIG_HOME", None)
2065 fallback_user_config = Path("~/.config").expanduser() / "black"
2067 black.files.find_user_pyproject_toml(), fallback_user_config.resolve()
2070 def test_find_user_pyproject_toml_windows(self) -> None:
2071 if system() != "Windows":
2074 user_config_path = Path.home() / ".black"
2076 black.files.find_user_pyproject_toml(), user_config_path.resolve()
2079 def test_bpo_33660_workaround(self) -> None:
2080 if system() == "Windows":
2083 # https://bugs.python.org/issue33660
2085 with change_directory(root):
2086 path = Path("workspace") / "project"
2087 report = black.Report(verbose=True)
2088 normalized_path = black.normalize_path_maybe_ignore(path, root, report)
2089 self.assertEqual(normalized_path, "workspace/project")
2091 def test_newline_comment_interaction(self) -> None:
2092 source = "class A:\\\r\n# type: ignore\n pass\n"
2093 output = black.format_str(source, mode=DEFAULT_MODE)
2094 black.assert_stable(source, output, mode=DEFAULT_MODE)
2096 def test_bpo_2142_workaround(self) -> None:
2098 # https://bugs.python.org/issue2142
2100 source, _ = read_data("missing_final_newline.py")
2101 # read_data adds a trailing newline
2102 source = source.rstrip()
2103 expected, _ = read_data("missing_final_newline.diff")
2104 tmp_file = Path(black.dump_to_file(source, ensure_final_newline=False))
2105 diff_header = re.compile(
2106 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
2107 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
2110 result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
2111 self.assertEqual(result.exit_code, 0)
2114 actual = result.output
2115 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
2116 self.assertEqual(actual, expected)
2118 @pytest.mark.python2
2119 def test_docstring_reformat_for_py27(self) -> None:
2121 Check that stripping trailing whitespace from Python 2 docstrings
2122 doesn't trigger a "not equivalent to source" error
2125 b'def foo():\r\n """Testing\r\n Testing """\r\n print "Foo"\r\n'
2127 expected = 'def foo():\n """Testing\n Testing"""\n print "Foo"\n'
2129 result = CliRunner().invoke(
2131 ["-", "-q", "--target-version=py27"],
2132 input=BytesIO(source),
2135 self.assertEqual(result.exit_code, 0)
2136 actual = result.output
2137 self.assertFormatEqual(actual, expected)
2140 def compare_results(
2141 result: click.testing.Result, expected_value: str, expected_exit_code: int
2143 """Helper method to test the value and exit code of a click Result."""
2145 result.output == expected_value
2146 ), "The output did not match the expected value."
2147 assert result.exit_code == expected_exit_code, "The exit code is incorrect."
2149 def test_code_option(self) -> None:
2150 """Test the code option with no changes."""
2151 code = 'print("Hello world")\n'
2152 args = ["--code", code]
2153 result = CliRunner().invoke(black.main, args)
2155 self.compare_results(result, code, 0)
2157 def test_code_option_changed(self) -> None:
2158 """Test the code option when changes are required."""
2159 code = "print('hello world')"
2160 formatted = black.format_str(code, mode=DEFAULT_MODE)
2162 args = ["--code", code]
2163 result = CliRunner().invoke(black.main, args)
2165 self.compare_results(result, formatted, 0)
2167 def test_code_option_check(self) -> None:
2168 """Test the code option when check is passed."""
2169 args = ["--check", "--code", 'print("Hello world")\n']
2170 result = CliRunner().invoke(black.main, args)
2171 self.compare_results(result, "", 0)
2173 def test_code_option_check_changed(self) -> None:
2174 """Test the code option when changes are required, and check is passed."""
2175 args = ["--check", "--code", "print('hello world')"]
2176 result = CliRunner().invoke(black.main, args)
2177 self.compare_results(result, "", 1)
2179 def test_code_option_diff(self) -> None:
2180 """Test the code option when diff is passed."""
2181 code = "print('hello world')"
2182 formatted = black.format_str(code, mode=DEFAULT_MODE)
2183 result_diff = diff(code, formatted, "STDIN", "STDOUT")
2185 args = ["--diff", "--code", code]
2186 result = CliRunner().invoke(black.main, args)
2188 # Remove time from diff
2189 output = DIFF_TIME.sub("", result.output)
2191 assert output == result_diff, "The output did not match the expected value."
2192 assert result.exit_code == 0, "The exit code is incorrect."
2194 def test_code_option_color_diff(self) -> None:
2195 """Test the code option when color and diff are passed."""
2196 code = "print('hello world')"
2197 formatted = black.format_str(code, mode=DEFAULT_MODE)
2199 result_diff = diff(code, formatted, "STDIN", "STDOUT")
2200 result_diff = color_diff(result_diff)
2202 args = ["--diff", "--color", "--code", code]
2203 result = CliRunner().invoke(black.main, args)
2205 # Remove time from diff
2206 output = DIFF_TIME.sub("", result.output)
2208 assert output == result_diff, "The output did not match the expected value."
2209 assert result.exit_code == 0, "The exit code is incorrect."
2211 def test_code_option_safe(self) -> None:
2212 """Test that the code option throws an error when the sanity checks fail."""
2213 # Patch black.assert_equivalent to ensure the sanity checks fail
2214 with patch.object(black, "assert_equivalent", side_effect=AssertionError):
2215 code = 'print("Hello world")'
2216 error_msg = f"{code}\nerror: cannot format <string>: \n"
2218 args = ["--safe", "--code", code]
2219 result = CliRunner().invoke(black.main, args)
2221 self.compare_results(result, error_msg, 123)
2223 def test_code_option_fast(self) -> None:
2224 """Test that the code option ignores errors when the sanity checks fail."""
2225 # Patch black.assert_equivalent to ensure the sanity checks fail
2226 with patch.object(black, "assert_equivalent", side_effect=AssertionError):
2227 code = 'print("Hello world")'
2228 formatted = black.format_str(code, mode=DEFAULT_MODE)
2230 args = ["--fast", "--code", code]
2231 result = CliRunner().invoke(black.main, args)
2233 self.compare_results(result, formatted, 0)
2235 def test_code_option_config(self) -> None:
2237 Test that the code option finds the pyproject.toml in the current directory.
2239 with patch.object(black, "parse_pyproject_toml", return_value={}) as parse:
2240 args = ["--code", "print"]
2241 CliRunner().invoke(black.main, args)
2243 pyproject_path = Path(Path().cwd(), "pyproject.toml").resolve()
2245 len(parse.mock_calls) >= 1
2246 ), "Expected config parse to be called with the current directory."
2248 _, call_args, _ = parse.mock_calls[0]
2250 call_args[0].lower() == str(pyproject_path).lower()
2251 ), "Incorrect config loaded."
2253 def test_code_option_parent_config(self) -> None:
2255 Test that the code option finds the pyproject.toml in the parent directory.
2257 with patch.object(black, "parse_pyproject_toml", return_value={}) as parse:
2258 with change_directory(Path("tests")):
2259 args = ["--code", "print"]
2260 CliRunner().invoke(black.main, args)
2262 pyproject_path = Path(Path().cwd().parent, "pyproject.toml").resolve()
2264 len(parse.mock_calls) >= 1
2265 ), "Expected config parse to be called with the current directory."
2267 _, call_args, _ = parse.mock_calls[0]
2269 call_args[0].lower() == str(pyproject_path).lower()
2270 ), "Incorrect config loaded."
2273 with open(black.__file__, "r", encoding="utf-8") as _bf:
2274 black_source_lines = _bf.readlines()
2277 def tracefunc(frame: types.FrameType, event: str, arg: Any) -> Callable:
2278 """Show function calls `from black/__init__.py` as they happen.
2280 Register this with `sys.settrace()` in a test you're debugging.
2285 stack = len(inspect.stack()) - 19
2287 filename = frame.f_code.co_filename
2288 lineno = frame.f_lineno
2289 func_sig_lineno = lineno - 1
2290 funcname = black_source_lines[func_sig_lineno].strip()
2291 while funcname.startswith("@"):
2292 func_sig_lineno += 1
2293 funcname = black_source_lines[func_sig_lineno].strip()
2294 if "black/__init__.py" in filename:
2295 print(f"{' ' * stack}{lineno}:{funcname}")
2299 if __name__ == "__main__":
2300 unittest.main(module="test_black")