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
9 from io import BytesIO, TextIOWrapper
11 from pathlib import Path
12 from platform import system
15 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
37 from pathspec import PathSpec
39 # Import other test classes
40 from tests.util import (
50 from .test_primer import PrimerCLITests # noqa: F401
53 THIS_FILE = Path(__file__)
60 PY36_ARGS = [f"--target-version={version.name.lower()}" for version in PY36_VERSIONS]
66 def cache_dir(exists: bool = True) -> Iterator[Path]:
67 with TemporaryDirectory() as workspace:
68 cache_dir = Path(workspace)
70 cache_dir = cache_dir / "new"
71 with patch("black.CACHE_DIR", cache_dir):
76 def event_loop() -> Iterator[None]:
77 policy = asyncio.get_event_loop_policy()
78 loop = policy.new_event_loop()
79 asyncio.set_event_loop(loop)
87 class FakeContext(click.Context):
88 """A fake click Context for when calling functions that need it."""
90 def __init__(self) -> None:
91 self.default_map: Dict[str, Any] = {}
94 class FakeParameter(click.Parameter):
95 """A fake click Parameter for when calling functions that need it."""
97 def __init__(self) -> None:
101 class BlackRunner(CliRunner):
102 """Modify CliRunner so that stderr is not merged with stdout.
104 This is a hack that can be removed once we depend on Click 7.x"""
106 def __init__(self) -> None:
107 self.stderrbuf = BytesIO()
108 self.stdoutbuf = BytesIO()
109 self.stdout_bytes = b""
110 self.stderr_bytes = b""
114 def isolation(self, *args: Any, **kwargs: Any) -> Generator[BinaryIO, None, None]:
115 with super().isolation(*args, **kwargs) as output:
117 hold_stderr = sys.stderr
118 sys.stderr = TextIOWrapper(self.stderrbuf, encoding=self.charset)
121 self.stdout_bytes = sys.stdout.buffer.getvalue() # type: ignore
122 self.stderr_bytes = sys.stderr.buffer.getvalue() # type: ignore
123 sys.stderr = hold_stderr
126 class BlackTestCase(BlackBaseTestCase):
128 self, args: List[str], exit_code: int = 0, ignore_config: bool = True
130 runner = BlackRunner()
132 args = ["--verbose", "--config", str(THIS_DIR / "empty.toml"), *args]
133 result = runner.invoke(black.main, args)
138 f"Failed with args: {args}\n"
139 f"stdout: {runner.stdout_bytes.decode()!r}\n"
140 f"stderr: {runner.stderr_bytes.decode()!r}\n"
141 f"exception: {result.exception}"
145 @patch("black.dump_to_file", dump_to_stderr)
146 def test_empty(self) -> None:
147 source = expected = ""
149 self.assertFormatEqual(expected, actual)
150 black.assert_equivalent(source, actual)
151 black.assert_stable(source, actual, DEFAULT_MODE)
153 def test_empty_ff(self) -> None:
155 tmp_file = Path(black.dump_to_file())
157 self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES))
158 with open(tmp_file, encoding="utf8") as f:
162 self.assertFormatEqual(expected, actual)
164 def test_piping(self) -> None:
165 source, expected = read_data("src/black/__init__", data=False)
166 result = BlackRunner().invoke(
168 ["-", "--fast", f"--line-length={black.DEFAULT_LINE_LENGTH}"],
169 input=BytesIO(source.encode("utf8")),
171 self.assertEqual(result.exit_code, 0)
172 self.assertFormatEqual(expected, result.output)
173 black.assert_equivalent(source, result.output)
174 black.assert_stable(source, result.output, DEFAULT_MODE)
176 def test_piping_diff(self) -> None:
177 diff_header = re.compile(
178 r"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d\d\d\d "
181 source, _ = read_data("expression.py")
182 expected, _ = read_data("expression.diff")
183 config = THIS_DIR / "data" / "empty_pyproject.toml"
187 f"--line-length={black.DEFAULT_LINE_LENGTH}",
189 f"--config={config}",
191 result = BlackRunner().invoke(
192 black.main, args, input=BytesIO(source.encode("utf8"))
194 self.assertEqual(result.exit_code, 0)
195 actual = diff_header.sub(DETERMINISTIC_HEADER, result.output)
196 actual = actual.rstrip() + "\n" # the diff output has a trailing space
197 self.assertEqual(expected, actual)
199 def test_piping_diff_with_color(self) -> None:
200 source, _ = read_data("expression.py")
201 config = THIS_DIR / "data" / "empty_pyproject.toml"
205 f"--line-length={black.DEFAULT_LINE_LENGTH}",
208 f"--config={config}",
210 result = BlackRunner().invoke(
211 black.main, args, input=BytesIO(source.encode("utf8"))
213 actual = result.output
214 # Again, the contents are checked in a different test, so only look for colors.
215 self.assertIn("\033[1;37m", actual)
216 self.assertIn("\033[36m", actual)
217 self.assertIn("\033[32m", actual)
218 self.assertIn("\033[31m", actual)
219 self.assertIn("\033[0m", actual)
221 @patch("black.dump_to_file", dump_to_stderr)
222 def _test_wip(self) -> None:
223 source, expected = read_data("wip")
224 sys.settrace(tracefunc)
227 experimental_string_processing=False,
228 target_versions={black.TargetVersion.PY38},
230 actual = fs(source, mode=mode)
232 self.assertFormatEqual(expected, actual)
233 black.assert_equivalent(source, actual)
234 black.assert_stable(source, actual, black.FileMode())
236 @unittest.expectedFailure
237 @patch("black.dump_to_file", dump_to_stderr)
238 def test_trailing_comma_optional_parens_stability1(self) -> None:
239 source, _expected = read_data("trailing_comma_optional_parens1")
241 black.assert_stable(source, actual, DEFAULT_MODE)
243 @unittest.expectedFailure
244 @patch("black.dump_to_file", dump_to_stderr)
245 def test_trailing_comma_optional_parens_stability2(self) -> None:
246 source, _expected = read_data("trailing_comma_optional_parens2")
248 black.assert_stable(source, actual, DEFAULT_MODE)
250 @unittest.expectedFailure
251 @patch("black.dump_to_file", dump_to_stderr)
252 def test_trailing_comma_optional_parens_stability3(self) -> None:
253 source, _expected = read_data("trailing_comma_optional_parens3")
255 black.assert_stable(source, actual, DEFAULT_MODE)
257 @patch("black.dump_to_file", dump_to_stderr)
258 def test_pep_572(self) -> None:
259 source, expected = read_data("pep_572")
261 self.assertFormatEqual(expected, actual)
262 black.assert_stable(source, actual, DEFAULT_MODE)
263 if sys.version_info >= (3, 8):
264 black.assert_equivalent(source, actual)
266 def test_pep_572_version_detection(self) -> None:
267 source, _ = read_data("pep_572")
268 root = black.lib2to3_parse(source)
269 features = black.get_features_used(root)
270 self.assertIn(black.Feature.ASSIGNMENT_EXPRESSIONS, features)
271 versions = black.detect_target_versions(root)
272 self.assertIn(black.TargetVersion.PY38, versions)
274 def test_expression_ff(self) -> None:
275 source, expected = read_data("expression")
276 tmp_file = Path(black.dump_to_file(source))
278 self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
279 with open(tmp_file, encoding="utf8") as f:
283 self.assertFormatEqual(expected, actual)
284 with patch("black.dump_to_file", dump_to_stderr):
285 black.assert_equivalent(source, actual)
286 black.assert_stable(source, actual, DEFAULT_MODE)
288 def test_expression_diff(self) -> None:
289 source, _ = read_data("expression.py")
290 expected, _ = read_data("expression.diff")
291 tmp_file = Path(black.dump_to_file(source))
292 diff_header = re.compile(
293 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
294 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
297 result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
298 self.assertEqual(result.exit_code, 0)
301 actual = result.output
302 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
303 actual = actual.rstrip() + "\n" # the diff output has a trailing space
304 if expected != actual:
305 dump = black.dump_to_file(actual)
307 "Expected diff isn't equal to the actual. If you made changes to"
308 " expression.py and this is an anticipated difference, overwrite"
309 f" tests/data/expression.diff with {dump}"
311 self.assertEqual(expected, actual, msg)
313 def test_expression_diff_with_color(self) -> None:
314 source, _ = read_data("expression.py")
315 expected, _ = read_data("expression.diff")
316 tmp_file = Path(black.dump_to_file(source))
318 result = BlackRunner().invoke(
319 black.main, ["--diff", "--color", str(tmp_file)]
323 actual = result.output
324 # We check the contents of the diff in `test_expression_diff`. All
325 # we need to check here is that color codes exist in the result.
326 self.assertIn("\033[1;37m", actual)
327 self.assertIn("\033[36m", actual)
328 self.assertIn("\033[32m", actual)
329 self.assertIn("\033[31m", actual)
330 self.assertIn("\033[0m", actual)
332 @patch("black.dump_to_file", dump_to_stderr)
333 def test_pep_570(self) -> None:
334 source, expected = read_data("pep_570")
336 self.assertFormatEqual(expected, actual)
337 black.assert_stable(source, actual, DEFAULT_MODE)
338 if sys.version_info >= (3, 8):
339 black.assert_equivalent(source, actual)
341 def test_detect_pos_only_arguments(self) -> None:
342 source, _ = read_data("pep_570")
343 root = black.lib2to3_parse(source)
344 features = black.get_features_used(root)
345 self.assertIn(black.Feature.POS_ONLY_ARGUMENTS, features)
346 versions = black.detect_target_versions(root)
347 self.assertIn(black.TargetVersion.PY38, versions)
349 @patch("black.dump_to_file", dump_to_stderr)
350 def test_string_quotes(self) -> None:
351 source, expected = read_data("string_quotes")
353 self.assertFormatEqual(expected, actual)
354 black.assert_equivalent(source, actual)
355 black.assert_stable(source, actual, DEFAULT_MODE)
356 mode = replace(DEFAULT_MODE, string_normalization=False)
357 not_normalized = fs(source, mode=mode)
358 self.assertFormatEqual(source.replace("\\\n", ""), not_normalized)
359 black.assert_equivalent(source, not_normalized)
360 black.assert_stable(source, not_normalized, mode=mode)
362 @patch("black.dump_to_file", dump_to_stderr)
363 def test_docstring_no_string_normalization(self) -> None:
364 """Like test_docstring but with string normalization off."""
365 source, expected = read_data("docstring_no_string_normalization")
366 mode = replace(DEFAULT_MODE, string_normalization=False)
367 actual = fs(source, mode=mode)
368 self.assertFormatEqual(expected, actual)
369 black.assert_equivalent(source, actual)
370 black.assert_stable(source, actual, mode)
372 def test_long_strings_flag_disabled(self) -> None:
373 """Tests for turning off the string processing logic."""
374 source, expected = read_data("long_strings_flag_disabled")
375 mode = replace(DEFAULT_MODE, experimental_string_processing=False)
376 actual = fs(source, mode=mode)
377 self.assertFormatEqual(expected, actual)
378 black.assert_stable(expected, actual, mode)
380 @patch("black.dump_to_file", dump_to_stderr)
381 def test_numeric_literals(self) -> None:
382 source, expected = read_data("numeric_literals")
383 mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
384 actual = fs(source, mode=mode)
385 self.assertFormatEqual(expected, actual)
386 black.assert_equivalent(source, actual)
387 black.assert_stable(source, actual, mode)
389 @patch("black.dump_to_file", dump_to_stderr)
390 def test_numeric_literals_ignoring_underscores(self) -> None:
391 source, expected = read_data("numeric_literals_skip_underscores")
392 mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
393 actual = fs(source, mode=mode)
394 self.assertFormatEqual(expected, actual)
395 black.assert_equivalent(source, actual)
396 black.assert_stable(source, actual, mode)
398 def test_skip_magic_trailing_comma(self) -> None:
399 source, _ = read_data("expression.py")
400 expected, _ = read_data("expression_skip_magic_trailing_comma.diff")
401 tmp_file = Path(black.dump_to_file(source))
402 diff_header = re.compile(
403 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
404 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
407 result = BlackRunner().invoke(black.main, ["-C", "--diff", str(tmp_file)])
408 self.assertEqual(result.exit_code, 0)
411 actual = result.output
412 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
413 actual = actual.rstrip() + "\n" # the diff output has a trailing space
414 if expected != actual:
415 dump = black.dump_to_file(actual)
417 "Expected diff isn't equal to the actual. If you made changes to"
418 " expression.py and this is an anticipated difference, overwrite"
419 f" tests/data/expression_skip_magic_trailing_comma.diff with {dump}"
421 self.assertEqual(expected, actual, msg)
423 @patch("black.dump_to_file", dump_to_stderr)
424 def test_python2_print_function(self) -> None:
425 source, expected = read_data("python2_print_function")
426 mode = replace(DEFAULT_MODE, target_versions={TargetVersion.PY27})
427 actual = fs(source, mode=mode)
428 self.assertFormatEqual(expected, actual)
429 black.assert_equivalent(source, actual)
430 black.assert_stable(source, actual, mode)
432 @patch("black.dump_to_file", dump_to_stderr)
433 def test_stub(self) -> None:
434 mode = replace(DEFAULT_MODE, is_pyi=True)
435 source, expected = read_data("stub.pyi")
436 actual = fs(source, mode=mode)
437 self.assertFormatEqual(expected, actual)
438 black.assert_stable(source, actual, mode)
440 @patch("black.dump_to_file", dump_to_stderr)
441 def test_async_as_identifier(self) -> None:
442 source_path = (THIS_DIR / "data" / "async_as_identifier.py").resolve()
443 source, expected = read_data("async_as_identifier")
445 self.assertFormatEqual(expected, actual)
446 major, minor = sys.version_info[:2]
447 if major < 3 or (major <= 3 and minor < 7):
448 black.assert_equivalent(source, actual)
449 black.assert_stable(source, actual, DEFAULT_MODE)
450 # ensure black can parse this when the target is 3.6
451 self.invokeBlack([str(source_path), "--target-version", "py36"])
452 # but not on 3.7, because async/await is no longer an identifier
453 self.invokeBlack([str(source_path), "--target-version", "py37"], exit_code=123)
455 @patch("black.dump_to_file", dump_to_stderr)
456 def test_python37(self) -> None:
457 source_path = (THIS_DIR / "data" / "python37.py").resolve()
458 source, expected = read_data("python37")
460 self.assertFormatEqual(expected, actual)
461 major, minor = sys.version_info[:2]
462 if major > 3 or (major == 3 and minor >= 7):
463 black.assert_equivalent(source, actual)
464 black.assert_stable(source, actual, DEFAULT_MODE)
465 # ensure black can parse this when the target is 3.7
466 self.invokeBlack([str(source_path), "--target-version", "py37"])
467 # but not on 3.6, because we use async as a reserved keyword
468 self.invokeBlack([str(source_path), "--target-version", "py36"], exit_code=123)
470 @patch("black.dump_to_file", dump_to_stderr)
471 def test_python38(self) -> None:
472 source, expected = read_data("python38")
474 self.assertFormatEqual(expected, actual)
475 major, minor = sys.version_info[:2]
476 if major > 3 or (major == 3 and minor >= 8):
477 black.assert_equivalent(source, actual)
478 black.assert_stable(source, actual, DEFAULT_MODE)
480 @patch("black.dump_to_file", dump_to_stderr)
481 def test_python39(self) -> None:
482 source, expected = read_data("python39")
484 self.assertFormatEqual(expected, actual)
485 major, minor = sys.version_info[:2]
486 if major > 3 or (major == 3 and minor >= 9):
487 black.assert_equivalent(source, actual)
488 black.assert_stable(source, actual, DEFAULT_MODE)
490 def test_tab_comment_indentation(self) -> None:
491 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
492 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
493 self.assertFormatEqual(contents_spc, fs(contents_spc))
494 self.assertFormatEqual(contents_spc, fs(contents_tab))
496 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
497 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
498 self.assertFormatEqual(contents_spc, fs(contents_spc))
499 self.assertFormatEqual(contents_spc, fs(contents_tab))
501 # mixed tabs and spaces (valid Python 2 code)
502 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t# comment\n pass\n"
503 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
504 self.assertFormatEqual(contents_spc, fs(contents_spc))
505 self.assertFormatEqual(contents_spc, fs(contents_tab))
507 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t\t# comment\n pass\n"
508 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
509 self.assertFormatEqual(contents_spc, fs(contents_spc))
510 self.assertFormatEqual(contents_spc, fs(contents_tab))
512 def test_report_verbose(self) -> None:
513 report = black.Report(verbose=True)
517 def out(msg: str, **kwargs: Any) -> None:
518 out_lines.append(msg)
520 def err(msg: str, **kwargs: Any) -> None:
521 err_lines.append(msg)
523 with patch("black.out", out), patch("black.err", err):
524 report.done(Path("f1"), black.Changed.NO)
525 self.assertEqual(len(out_lines), 1)
526 self.assertEqual(len(err_lines), 0)
527 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
528 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
529 self.assertEqual(report.return_code, 0)
530 report.done(Path("f2"), black.Changed.YES)
531 self.assertEqual(len(out_lines), 2)
532 self.assertEqual(len(err_lines), 0)
533 self.assertEqual(out_lines[-1], "reformatted f2")
535 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
537 report.done(Path("f3"), black.Changed.CACHED)
538 self.assertEqual(len(out_lines), 3)
539 self.assertEqual(len(err_lines), 0)
541 out_lines[-1], "f3 wasn't modified on disk since last run."
544 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
546 self.assertEqual(report.return_code, 0)
548 self.assertEqual(report.return_code, 1)
550 report.failed(Path("e1"), "boom")
551 self.assertEqual(len(out_lines), 3)
552 self.assertEqual(len(err_lines), 1)
553 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
555 unstyle(str(report)),
556 "1 file reformatted, 2 files left unchanged, 1 file failed to"
559 self.assertEqual(report.return_code, 123)
560 report.done(Path("f3"), black.Changed.YES)
561 self.assertEqual(len(out_lines), 4)
562 self.assertEqual(len(err_lines), 1)
563 self.assertEqual(out_lines[-1], "reformatted f3")
565 unstyle(str(report)),
566 "2 files reformatted, 2 files left unchanged, 1 file failed to"
569 self.assertEqual(report.return_code, 123)
570 report.failed(Path("e2"), "boom")
571 self.assertEqual(len(out_lines), 4)
572 self.assertEqual(len(err_lines), 2)
573 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
575 unstyle(str(report)),
576 "2 files reformatted, 2 files left unchanged, 2 files failed to"
579 self.assertEqual(report.return_code, 123)
580 report.path_ignored(Path("wat"), "no match")
581 self.assertEqual(len(out_lines), 5)
582 self.assertEqual(len(err_lines), 2)
583 self.assertEqual(out_lines[-1], "wat ignored: no match")
585 unstyle(str(report)),
586 "2 files reformatted, 2 files left unchanged, 2 files failed to"
589 self.assertEqual(report.return_code, 123)
590 report.done(Path("f4"), black.Changed.NO)
591 self.assertEqual(len(out_lines), 6)
592 self.assertEqual(len(err_lines), 2)
593 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
595 unstyle(str(report)),
596 "2 files reformatted, 3 files left unchanged, 2 files failed to"
599 self.assertEqual(report.return_code, 123)
602 unstyle(str(report)),
603 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
604 " would fail to reformat.",
609 unstyle(str(report)),
610 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
611 " would fail to reformat.",
614 def test_report_quiet(self) -> None:
615 report = black.Report(quiet=True)
619 def out(msg: str, **kwargs: Any) -> None:
620 out_lines.append(msg)
622 def err(msg: str, **kwargs: Any) -> None:
623 err_lines.append(msg)
625 with patch("black.out", out), patch("black.err", err):
626 report.done(Path("f1"), black.Changed.NO)
627 self.assertEqual(len(out_lines), 0)
628 self.assertEqual(len(err_lines), 0)
629 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
630 self.assertEqual(report.return_code, 0)
631 report.done(Path("f2"), black.Changed.YES)
632 self.assertEqual(len(out_lines), 0)
633 self.assertEqual(len(err_lines), 0)
635 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
637 report.done(Path("f3"), black.Changed.CACHED)
638 self.assertEqual(len(out_lines), 0)
639 self.assertEqual(len(err_lines), 0)
641 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
643 self.assertEqual(report.return_code, 0)
645 self.assertEqual(report.return_code, 1)
647 report.failed(Path("e1"), "boom")
648 self.assertEqual(len(out_lines), 0)
649 self.assertEqual(len(err_lines), 1)
650 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
652 unstyle(str(report)),
653 "1 file reformatted, 2 files left unchanged, 1 file failed to"
656 self.assertEqual(report.return_code, 123)
657 report.done(Path("f3"), black.Changed.YES)
658 self.assertEqual(len(out_lines), 0)
659 self.assertEqual(len(err_lines), 1)
661 unstyle(str(report)),
662 "2 files reformatted, 2 files left unchanged, 1 file failed to"
665 self.assertEqual(report.return_code, 123)
666 report.failed(Path("e2"), "boom")
667 self.assertEqual(len(out_lines), 0)
668 self.assertEqual(len(err_lines), 2)
669 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
671 unstyle(str(report)),
672 "2 files reformatted, 2 files left unchanged, 2 files failed to"
675 self.assertEqual(report.return_code, 123)
676 report.path_ignored(Path("wat"), "no match")
677 self.assertEqual(len(out_lines), 0)
678 self.assertEqual(len(err_lines), 2)
680 unstyle(str(report)),
681 "2 files reformatted, 2 files left unchanged, 2 files failed to"
684 self.assertEqual(report.return_code, 123)
685 report.done(Path("f4"), black.Changed.NO)
686 self.assertEqual(len(out_lines), 0)
687 self.assertEqual(len(err_lines), 2)
689 unstyle(str(report)),
690 "2 files reformatted, 3 files left unchanged, 2 files failed to"
693 self.assertEqual(report.return_code, 123)
696 unstyle(str(report)),
697 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
698 " would fail to reformat.",
703 unstyle(str(report)),
704 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
705 " would fail to reformat.",
708 def test_report_normal(self) -> None:
709 report = black.Report()
713 def out(msg: str, **kwargs: Any) -> None:
714 out_lines.append(msg)
716 def err(msg: str, **kwargs: Any) -> None:
717 err_lines.append(msg)
719 with patch("black.out", out), patch("black.err", err):
720 report.done(Path("f1"), black.Changed.NO)
721 self.assertEqual(len(out_lines), 0)
722 self.assertEqual(len(err_lines), 0)
723 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
724 self.assertEqual(report.return_code, 0)
725 report.done(Path("f2"), black.Changed.YES)
726 self.assertEqual(len(out_lines), 1)
727 self.assertEqual(len(err_lines), 0)
728 self.assertEqual(out_lines[-1], "reformatted f2")
730 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
732 report.done(Path("f3"), black.Changed.CACHED)
733 self.assertEqual(len(out_lines), 1)
734 self.assertEqual(len(err_lines), 0)
735 self.assertEqual(out_lines[-1], "reformatted f2")
737 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
739 self.assertEqual(report.return_code, 0)
741 self.assertEqual(report.return_code, 1)
743 report.failed(Path("e1"), "boom")
744 self.assertEqual(len(out_lines), 1)
745 self.assertEqual(len(err_lines), 1)
746 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
748 unstyle(str(report)),
749 "1 file reformatted, 2 files left unchanged, 1 file failed to"
752 self.assertEqual(report.return_code, 123)
753 report.done(Path("f3"), black.Changed.YES)
754 self.assertEqual(len(out_lines), 2)
755 self.assertEqual(len(err_lines), 1)
756 self.assertEqual(out_lines[-1], "reformatted f3")
758 unstyle(str(report)),
759 "2 files reformatted, 2 files left unchanged, 1 file failed to"
762 self.assertEqual(report.return_code, 123)
763 report.failed(Path("e2"), "boom")
764 self.assertEqual(len(out_lines), 2)
765 self.assertEqual(len(err_lines), 2)
766 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
768 unstyle(str(report)),
769 "2 files reformatted, 2 files left unchanged, 2 files failed to"
772 self.assertEqual(report.return_code, 123)
773 report.path_ignored(Path("wat"), "no match")
774 self.assertEqual(len(out_lines), 2)
775 self.assertEqual(len(err_lines), 2)
777 unstyle(str(report)),
778 "2 files reformatted, 2 files left unchanged, 2 files failed to"
781 self.assertEqual(report.return_code, 123)
782 report.done(Path("f4"), black.Changed.NO)
783 self.assertEqual(len(out_lines), 2)
784 self.assertEqual(len(err_lines), 2)
786 unstyle(str(report)),
787 "2 files reformatted, 3 files left unchanged, 2 files failed to"
790 self.assertEqual(report.return_code, 123)
793 unstyle(str(report)),
794 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
795 " would fail to reformat.",
800 unstyle(str(report)),
801 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
802 " would fail to reformat.",
805 def test_lib2to3_parse(self) -> None:
806 with self.assertRaises(black.InvalidInput):
807 black.lib2to3_parse("invalid syntax")
810 black.lib2to3_parse(straddling)
811 black.lib2to3_parse(straddling, {TargetVersion.PY27})
812 black.lib2to3_parse(straddling, {TargetVersion.PY36})
813 black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
816 black.lib2to3_parse(py2_only)
817 black.lib2to3_parse(py2_only, {TargetVersion.PY27})
818 with self.assertRaises(black.InvalidInput):
819 black.lib2to3_parse(py2_only, {TargetVersion.PY36})
820 with self.assertRaises(black.InvalidInput):
821 black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
823 py3_only = "exec(x, end=y)"
824 black.lib2to3_parse(py3_only)
825 with self.assertRaises(black.InvalidInput):
826 black.lib2to3_parse(py3_only, {TargetVersion.PY27})
827 black.lib2to3_parse(py3_only, {TargetVersion.PY36})
828 black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
830 def test_get_features_used_decorator(self) -> None:
831 # Test the feature detection of new decorator syntax
832 # since this makes some test cases of test_get_features_used()
833 # fails if it fails, this is tested first so that a useful case
835 simples, relaxed = read_data("decorators")
836 # skip explanation comments at the top of the file
837 for simple_test in simples.split("##")[1:]:
838 node = black.lib2to3_parse(simple_test)
839 decorator = str(node.children[0].children[0]).strip()
841 Feature.RELAXED_DECORATORS,
842 black.get_features_used(node),
844 f"decorator '{decorator}' follows python<=3.8 syntax"
845 "but is detected as 3.9+"
846 # f"The full node is\n{node!r}"
849 # skip the '# output' comment at the top of the output part
850 for relaxed_test in relaxed.split("##")[1:]:
851 node = black.lib2to3_parse(relaxed_test)
852 decorator = str(node.children[0].children[0]).strip()
854 Feature.RELAXED_DECORATORS,
855 black.get_features_used(node),
857 f"decorator '{decorator}' uses python3.9+ syntax"
858 "but is detected as python<=3.8"
859 # f"The full node is\n{node!r}"
863 def test_get_features_used(self) -> None:
864 node = black.lib2to3_parse("def f(*, arg): ...\n")
865 self.assertEqual(black.get_features_used(node), set())
866 node = black.lib2to3_parse("def f(*, arg,): ...\n")
867 self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
868 node = black.lib2to3_parse("f(*arg,)\n")
870 black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
872 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
873 self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
874 node = black.lib2to3_parse("123_456\n")
875 self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
876 node = black.lib2to3_parse("123456\n")
877 self.assertEqual(black.get_features_used(node), set())
878 source, expected = read_data("function")
879 node = black.lib2to3_parse(source)
880 expected_features = {
881 Feature.TRAILING_COMMA_IN_CALL,
882 Feature.TRAILING_COMMA_IN_DEF,
885 self.assertEqual(black.get_features_used(node), expected_features)
886 node = black.lib2to3_parse(expected)
887 self.assertEqual(black.get_features_used(node), expected_features)
888 source, expected = read_data("expression")
889 node = black.lib2to3_parse(source)
890 self.assertEqual(black.get_features_used(node), set())
891 node = black.lib2to3_parse(expected)
892 self.assertEqual(black.get_features_used(node), set())
894 def test_get_future_imports(self) -> None:
895 node = black.lib2to3_parse("\n")
896 self.assertEqual(set(), black.get_future_imports(node))
897 node = black.lib2to3_parse("from __future__ import black\n")
898 self.assertEqual({"black"}, black.get_future_imports(node))
899 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
900 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
901 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
902 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
903 node = black.lib2to3_parse(
904 "from __future__ import multiple\nfrom __future__ import imports\n"
906 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
907 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
908 self.assertEqual({"black"}, black.get_future_imports(node))
909 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
910 self.assertEqual({"black"}, black.get_future_imports(node))
911 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
912 self.assertEqual(set(), black.get_future_imports(node))
913 node = black.lib2to3_parse("from some.module import black\n")
914 self.assertEqual(set(), black.get_future_imports(node))
915 node = black.lib2to3_parse(
916 "from __future__ import unicode_literals as _unicode_literals"
918 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
919 node = black.lib2to3_parse(
920 "from __future__ import unicode_literals as _lol, print"
922 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
924 def test_debug_visitor(self) -> None:
925 source, _ = read_data("debug_visitor.py")
926 expected, _ = read_data("debug_visitor.out")
930 def out(msg: str, **kwargs: Any) -> None:
931 out_lines.append(msg)
933 def err(msg: str, **kwargs: Any) -> None:
934 err_lines.append(msg)
936 with patch("black.out", out), patch("black.err", err):
937 black.DebugVisitor.show(source)
938 actual = "\n".join(out_lines) + "\n"
940 if expected != actual:
941 log_name = black.dump_to_file(*out_lines)
945 f"AST print out is different. Actual version dumped to {log_name}",
948 def test_format_file_contents(self) -> None:
951 with self.assertRaises(black.NothingChanged):
952 black.format_file_contents(empty, mode=mode, fast=False)
954 with self.assertRaises(black.NothingChanged):
955 black.format_file_contents(just_nl, mode=mode, fast=False)
956 same = "j = [1, 2, 3]\n"
957 with self.assertRaises(black.NothingChanged):
958 black.format_file_contents(same, mode=mode, fast=False)
959 different = "j = [1,2,3]"
961 actual = black.format_file_contents(different, mode=mode, fast=False)
962 self.assertEqual(expected, actual)
963 invalid = "return if you can"
964 with self.assertRaises(black.InvalidInput) as e:
965 black.format_file_contents(invalid, mode=mode, fast=False)
966 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
968 def test_endmarker(self) -> None:
969 n = black.lib2to3_parse("\n")
970 self.assertEqual(n.type, black.syms.file_input)
971 self.assertEqual(len(n.children), 1)
972 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
974 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
975 def test_assertFormatEqual(self) -> None:
979 def out(msg: str, **kwargs: Any) -> None:
980 out_lines.append(msg)
982 def err(msg: str, **kwargs: Any) -> None:
983 err_lines.append(msg)
985 with patch("black.out", out), patch("black.err", err):
986 with self.assertRaises(AssertionError):
987 self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")
989 out_str = "".join(out_lines)
990 self.assertTrue("Expected tree:" in out_str)
991 self.assertTrue("Actual tree:" in out_str)
992 self.assertEqual("".join(err_lines), "")
994 def test_cache_broken_file(self) -> None:
996 with cache_dir() as workspace:
997 cache_file = black.get_cache_file(mode)
998 with cache_file.open("w") as fobj:
999 fobj.write("this is not a pickle")
1000 self.assertEqual(black.read_cache(mode), {})
1001 src = (workspace / "test.py").resolve()
1002 with src.open("w") as fobj:
1003 fobj.write("print('hello')")
1004 self.invokeBlack([str(src)])
1005 cache = black.read_cache(mode)
1006 self.assertIn(src, cache)
1008 def test_cache_single_file_already_cached(self) -> None:
1010 with cache_dir() as workspace:
1011 src = (workspace / "test.py").resolve()
1012 with src.open("w") as fobj:
1013 fobj.write("print('hello')")
1014 black.write_cache({}, [src], mode)
1015 self.invokeBlack([str(src)])
1016 with src.open("r") as fobj:
1017 self.assertEqual(fobj.read(), "print('hello')")
1020 def test_cache_multiple_files(self) -> None:
1022 with cache_dir() as workspace, patch(
1023 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1025 one = (workspace / "one.py").resolve()
1026 with one.open("w") as fobj:
1027 fobj.write("print('hello')")
1028 two = (workspace / "two.py").resolve()
1029 with two.open("w") as fobj:
1030 fobj.write("print('hello')")
1031 black.write_cache({}, [one], mode)
1032 self.invokeBlack([str(workspace)])
1033 with one.open("r") as fobj:
1034 self.assertEqual(fobj.read(), "print('hello')")
1035 with two.open("r") as fobj:
1036 self.assertEqual(fobj.read(), 'print("hello")\n')
1037 cache = black.read_cache(mode)
1038 self.assertIn(one, cache)
1039 self.assertIn(two, cache)
1041 def test_no_cache_when_writeback_diff(self) -> None:
1043 with cache_dir() as workspace:
1044 src = (workspace / "test.py").resolve()
1045 with src.open("w") as fobj:
1046 fobj.write("print('hello')")
1047 with patch("black.read_cache") as read_cache, patch(
1050 self.invokeBlack([str(src), "--diff"])
1051 cache_file = black.get_cache_file(mode)
1052 self.assertFalse(cache_file.exists())
1053 write_cache.assert_not_called()
1054 read_cache.assert_not_called()
1056 def test_no_cache_when_writeback_color_diff(self) -> None:
1058 with cache_dir() as workspace:
1059 src = (workspace / "test.py").resolve()
1060 with src.open("w") as fobj:
1061 fobj.write("print('hello')")
1062 with patch("black.read_cache") as read_cache, patch(
1065 self.invokeBlack([str(src), "--diff", "--color"])
1066 cache_file = black.get_cache_file(mode)
1067 self.assertFalse(cache_file.exists())
1068 write_cache.assert_not_called()
1069 read_cache.assert_not_called()
1072 def test_output_locking_when_writeback_diff(self) -> None:
1073 with cache_dir() as workspace:
1074 for tag in range(0, 4):
1075 src = (workspace / f"test{tag}.py").resolve()
1076 with src.open("w") as fobj:
1077 fobj.write("print('hello')")
1078 with patch("black.Manager", wraps=multiprocessing.Manager) as mgr:
1079 self.invokeBlack(["--diff", str(workspace)], exit_code=0)
1080 # this isn't quite doing what we want, but if it _isn't_
1081 # called then we cannot be using the lock it provides
1085 def test_output_locking_when_writeback_color_diff(self) -> None:
1086 with cache_dir() as workspace:
1087 for tag in range(0, 4):
1088 src = (workspace / f"test{tag}.py").resolve()
1089 with src.open("w") as fobj:
1090 fobj.write("print('hello')")
1091 with patch("black.Manager", wraps=multiprocessing.Manager) as mgr:
1092 self.invokeBlack(["--diff", "--color", str(workspace)], exit_code=0)
1093 # this isn't quite doing what we want, but if it _isn't_
1094 # called then we cannot be using the lock it provides
1097 def test_no_cache_when_stdin(self) -> None:
1100 result = CliRunner().invoke(
1101 black.main, ["-"], input=BytesIO(b"print('hello')")
1103 self.assertEqual(result.exit_code, 0)
1104 cache_file = black.get_cache_file(mode)
1105 self.assertFalse(cache_file.exists())
1107 def test_read_cache_no_cachefile(self) -> None:
1110 self.assertEqual(black.read_cache(mode), {})
1112 def test_write_cache_read_cache(self) -> None:
1114 with cache_dir() as workspace:
1115 src = (workspace / "test.py").resolve()
1117 black.write_cache({}, [src], mode)
1118 cache = black.read_cache(mode)
1119 self.assertIn(src, cache)
1120 self.assertEqual(cache[src], black.get_cache_info(src))
1122 def test_filter_cached(self) -> None:
1123 with TemporaryDirectory() as workspace:
1124 path = Path(workspace)
1125 uncached = (path / "uncached").resolve()
1126 cached = (path / "cached").resolve()
1127 cached_but_changed = (path / "changed").resolve()
1130 cached_but_changed.touch()
1131 cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
1132 todo, done = black.filter_cached(
1133 cache, {uncached, cached, cached_but_changed}
1135 self.assertEqual(todo, {uncached, cached_but_changed})
1136 self.assertEqual(done, {cached})
1138 def test_write_cache_creates_directory_if_needed(self) -> None:
1140 with cache_dir(exists=False) as workspace:
1141 self.assertFalse(workspace.exists())
1142 black.write_cache({}, [], mode)
1143 self.assertTrue(workspace.exists())
1146 def test_failed_formatting_does_not_get_cached(self) -> None:
1148 with cache_dir() as workspace, patch(
1149 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1151 failing = (workspace / "failing.py").resolve()
1152 with failing.open("w") as fobj:
1153 fobj.write("not actually python")
1154 clean = (workspace / "clean.py").resolve()
1155 with clean.open("w") as fobj:
1156 fobj.write('print("hello")\n')
1157 self.invokeBlack([str(workspace)], exit_code=123)
1158 cache = black.read_cache(mode)
1159 self.assertNotIn(failing, cache)
1160 self.assertIn(clean, cache)
1162 def test_write_cache_write_fail(self) -> None:
1164 with cache_dir(), patch.object(Path, "open") as mock:
1165 mock.side_effect = OSError
1166 black.write_cache({}, [], mode)
1169 @patch("black.ProcessPoolExecutor", MagicMock(side_effect=OSError))
1170 def test_works_in_mono_process_only_environment(self) -> None:
1171 with cache_dir() as workspace:
1173 (workspace / "one.py").resolve(),
1174 (workspace / "two.py").resolve(),
1176 f.write_text('print("hello")\n')
1177 self.invokeBlack([str(workspace)])
1180 def test_check_diff_use_together(self) -> None:
1182 # Files which will be reformatted.
1183 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1184 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1185 # Files which will not be reformatted.
1186 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1187 self.invokeBlack([str(src2), "--diff", "--check"])
1188 # Multi file command.
1189 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1191 def test_no_files(self) -> None:
1193 # Without an argument, black exits with error code 0.
1194 self.invokeBlack([])
1196 def test_broken_symlink(self) -> None:
1197 with cache_dir() as workspace:
1198 symlink = workspace / "broken_link.py"
1200 symlink.symlink_to("nonexistent.py")
1201 except OSError as e:
1202 self.skipTest(f"Can't create symlinks: {e}")
1203 self.invokeBlack([str(workspace.resolve())])
1205 def test_read_cache_line_lengths(self) -> None:
1207 short_mode = replace(DEFAULT_MODE, line_length=1)
1208 with cache_dir() as workspace:
1209 path = (workspace / "file.py").resolve()
1211 black.write_cache({}, [path], mode)
1212 one = black.read_cache(mode)
1213 self.assertIn(path, one)
1214 two = black.read_cache(short_mode)
1215 self.assertNotIn(path, two)
1217 def test_single_file_force_pyi(self) -> None:
1218 pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
1219 contents, expected = read_data("force_pyi")
1220 with cache_dir() as workspace:
1221 path = (workspace / "file.py").resolve()
1222 with open(path, "w") as fh:
1224 self.invokeBlack([str(path), "--pyi"])
1225 with open(path, "r") as fh:
1227 # verify cache with --pyi is separate
1228 pyi_cache = black.read_cache(pyi_mode)
1229 self.assertIn(path, pyi_cache)
1230 normal_cache = black.read_cache(DEFAULT_MODE)
1231 self.assertNotIn(path, normal_cache)
1232 self.assertFormatEqual(expected, actual)
1233 black.assert_equivalent(contents, actual)
1234 black.assert_stable(contents, actual, pyi_mode)
1237 def test_multi_file_force_pyi(self) -> None:
1238 reg_mode = DEFAULT_MODE
1239 pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
1240 contents, expected = read_data("force_pyi")
1241 with cache_dir() as workspace:
1243 (workspace / "file1.py").resolve(),
1244 (workspace / "file2.py").resolve(),
1247 with open(path, "w") as fh:
1249 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1251 with open(path, "r") as fh:
1253 self.assertEqual(actual, expected)
1254 # verify cache with --pyi is separate
1255 pyi_cache = black.read_cache(pyi_mode)
1256 normal_cache = black.read_cache(reg_mode)
1258 self.assertIn(path, pyi_cache)
1259 self.assertNotIn(path, normal_cache)
1261 def test_pipe_force_pyi(self) -> None:
1262 source, expected = read_data("force_pyi")
1263 result = CliRunner().invoke(
1264 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1266 self.assertEqual(result.exit_code, 0)
1267 actual = result.output
1268 self.assertFormatEqual(actual, expected)
1270 def test_single_file_force_py36(self) -> None:
1271 reg_mode = DEFAULT_MODE
1272 py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
1273 source, expected = read_data("force_py36")
1274 with cache_dir() as workspace:
1275 path = (workspace / "file.py").resolve()
1276 with open(path, "w") as fh:
1278 self.invokeBlack([str(path), *PY36_ARGS])
1279 with open(path, "r") as fh:
1281 # verify cache with --target-version is separate
1282 py36_cache = black.read_cache(py36_mode)
1283 self.assertIn(path, py36_cache)
1284 normal_cache = black.read_cache(reg_mode)
1285 self.assertNotIn(path, normal_cache)
1286 self.assertEqual(actual, expected)
1289 def test_multi_file_force_py36(self) -> None:
1290 reg_mode = DEFAULT_MODE
1291 py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
1292 source, expected = read_data("force_py36")
1293 with cache_dir() as workspace:
1295 (workspace / "file1.py").resolve(),
1296 (workspace / "file2.py").resolve(),
1299 with open(path, "w") as fh:
1301 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1303 with open(path, "r") as fh:
1305 self.assertEqual(actual, expected)
1306 # verify cache with --target-version is separate
1307 pyi_cache = black.read_cache(py36_mode)
1308 normal_cache = black.read_cache(reg_mode)
1310 self.assertIn(path, pyi_cache)
1311 self.assertNotIn(path, normal_cache)
1313 def test_pipe_force_py36(self) -> None:
1314 source, expected = read_data("force_py36")
1315 result = CliRunner().invoke(
1317 ["-", "-q", "--target-version=py36"],
1318 input=BytesIO(source.encode("utf8")),
1320 self.assertEqual(result.exit_code, 0)
1321 actual = result.output
1322 self.assertFormatEqual(actual, expected)
1324 def test_include_exclude(self) -> None:
1325 path = THIS_DIR / "data" / "include_exclude_tests"
1326 include = re.compile(r"\.pyi?$")
1327 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1328 report = black.Report()
1329 gitignore = PathSpec.from_lines("gitwildmatch", [])
1330 sources: List[Path] = []
1332 Path(path / "b/dont_exclude/a.py"),
1333 Path(path / "b/dont_exclude/a.pyi"),
1335 this_abs = THIS_DIR.resolve()
1337 black.gen_python_files(
1338 path.iterdir(), this_abs, include, exclude, None, report, gitignore
1341 self.assertEqual(sorted(expected), sorted(sources))
1343 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1344 def test_exclude_for_issue_1572(self) -> None:
1345 # Exclude shouldn't touch files that were explicitly given to Black through the
1346 # CLI. Exclude is supposed to only apply to the recursive discovery of files.
1347 # https://github.com/psf/black/issues/1572
1348 path = THIS_DIR / "data" / "include_exclude_tests"
1350 exclude = r"/exclude/|a\.py"
1351 src = str(path / "b/exclude/a.py")
1352 report = black.Report()
1353 expected = [Path(path / "b/exclude/a.py")]
1364 stdin_filename=None,
1367 self.assertEqual(sorted(expected), sorted(sources))
1369 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1370 def test_get_sources_with_stdin(self) -> None:
1372 exclude = r"/exclude/|a\.py"
1374 report = black.Report()
1375 expected = [Path("-")]
1386 stdin_filename=None,
1389 self.assertEqual(sorted(expected), sorted(sources))
1391 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1392 def test_get_sources_with_stdin_filename(self) -> None:
1394 exclude = r"/exclude/|a\.py"
1396 report = black.Report()
1397 stdin_filename = str(THIS_DIR / "data/collections.py")
1398 expected = [Path(f"__BLACK_STDIN_FILENAME__{stdin_filename}")]
1409 stdin_filename=stdin_filename,
1412 self.assertEqual(sorted(expected), sorted(sources))
1414 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1415 def test_get_sources_with_stdin_filename_and_exclude(self) -> None:
1416 # Exclude shouldn't exclude stdin_filename since it is mimicing the
1417 # file being passed directly. This is the same as
1418 # test_exclude_for_issue_1572
1419 path = THIS_DIR / "data" / "include_exclude_tests"
1421 exclude = r"/exclude/|a\.py"
1423 report = black.Report()
1424 stdin_filename = str(path / "b/exclude/a.py")
1425 expected = [Path(f"__BLACK_STDIN_FILENAME__{stdin_filename}")]
1436 stdin_filename=stdin_filename,
1439 self.assertEqual(sorted(expected), sorted(sources))
1441 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1442 def test_get_sources_with_stdin_filename_and_force_exclude(self) -> None:
1443 # Force exclude should exclude the file when passing it through
1445 path = THIS_DIR / "data" / "include_exclude_tests"
1447 force_exclude = r"/exclude/|a\.py"
1449 report = black.Report()
1450 stdin_filename = str(path / "b/exclude/a.py")
1459 force_exclude=force_exclude,
1461 stdin_filename=stdin_filename,
1464 self.assertEqual([], sorted(sources))
1466 def test_reformat_one_with_stdin(self) -> None:
1468 "black.format_stdin_to_stdout",
1469 return_value=lambda *args, **kwargs: black.Changed.YES,
1471 report = MagicMock()
1476 write_back=black.WriteBack.YES,
1480 fsts.assert_called_once()
1481 report.done.assert_called_with(path, black.Changed.YES)
1483 def test_reformat_one_with_stdin_filename(self) -> None:
1485 "black.format_stdin_to_stdout",
1486 return_value=lambda *args, **kwargs: black.Changed.YES,
1488 report = MagicMock()
1490 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1495 write_back=black.WriteBack.YES,
1499 fsts.assert_called_once()
1500 # __BLACK_STDIN_FILENAME__ should have been striped
1501 report.done.assert_called_with(expected, black.Changed.YES)
1503 def test_reformat_one_with_stdin_and_existing_path(self) -> None:
1505 "black.format_stdin_to_stdout",
1506 return_value=lambda *args, **kwargs: black.Changed.YES,
1508 report = MagicMock()
1509 # Even with an existing file, since we are forcing stdin, black
1510 # should output to stdout and not modify the file inplace
1511 p = Path(str(THIS_DIR / "data/collections.py"))
1512 # Make sure is_file actually returns True
1513 self.assertTrue(p.is_file())
1514 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1519 write_back=black.WriteBack.YES,
1523 fsts.assert_called_once()
1524 # __BLACK_STDIN_FILENAME__ should have been striped
1525 report.done.assert_called_with(expected, black.Changed.YES)
1527 def test_gitignore_exclude(self) -> None:
1528 path = THIS_DIR / "data" / "include_exclude_tests"
1529 include = re.compile(r"\.pyi?$")
1530 exclude = re.compile(r"")
1531 report = black.Report()
1532 gitignore = PathSpec.from_lines(
1533 "gitwildmatch", ["exclude/", ".definitely_exclude"]
1535 sources: List[Path] = []
1537 Path(path / "b/dont_exclude/a.py"),
1538 Path(path / "b/dont_exclude/a.pyi"),
1540 this_abs = THIS_DIR.resolve()
1542 black.gen_python_files(
1543 path.iterdir(), this_abs, include, exclude, None, report, gitignore
1546 self.assertEqual(sorted(expected), sorted(sources))
1548 def test_empty_include(self) -> None:
1549 path = THIS_DIR / "data" / "include_exclude_tests"
1550 report = black.Report()
1551 gitignore = PathSpec.from_lines("gitwildmatch", [])
1552 empty = re.compile(r"")
1553 sources: List[Path] = []
1555 Path(path / "b/exclude/a.pie"),
1556 Path(path / "b/exclude/a.py"),
1557 Path(path / "b/exclude/a.pyi"),
1558 Path(path / "b/dont_exclude/a.pie"),
1559 Path(path / "b/dont_exclude/a.py"),
1560 Path(path / "b/dont_exclude/a.pyi"),
1561 Path(path / "b/.definitely_exclude/a.pie"),
1562 Path(path / "b/.definitely_exclude/a.py"),
1563 Path(path / "b/.definitely_exclude/a.pyi"),
1565 this_abs = THIS_DIR.resolve()
1567 black.gen_python_files(
1571 re.compile(black.DEFAULT_EXCLUDES),
1577 self.assertEqual(sorted(expected), sorted(sources))
1579 def test_empty_exclude(self) -> None:
1580 path = THIS_DIR / "data" / "include_exclude_tests"
1581 report = black.Report()
1582 gitignore = PathSpec.from_lines("gitwildmatch", [])
1583 empty = re.compile(r"")
1584 sources: List[Path] = []
1586 Path(path / "b/dont_exclude/a.py"),
1587 Path(path / "b/dont_exclude/a.pyi"),
1588 Path(path / "b/exclude/a.py"),
1589 Path(path / "b/exclude/a.pyi"),
1590 Path(path / "b/.definitely_exclude/a.py"),
1591 Path(path / "b/.definitely_exclude/a.pyi"),
1593 this_abs = THIS_DIR.resolve()
1595 black.gen_python_files(
1598 re.compile(black.DEFAULT_INCLUDES),
1605 self.assertEqual(sorted(expected), sorted(sources))
1607 def test_invalid_include_exclude(self) -> None:
1608 for option in ["--include", "--exclude"]:
1609 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1611 def test_preserves_line_endings(self) -> None:
1612 with TemporaryDirectory() as workspace:
1613 test_file = Path(workspace) / "test.py"
1614 for nl in ["\n", "\r\n"]:
1615 contents = nl.join(["def f( ):", " pass"])
1616 test_file.write_bytes(contents.encode())
1617 ff(test_file, write_back=black.WriteBack.YES)
1618 updated_contents: bytes = test_file.read_bytes()
1619 self.assertIn(nl.encode(), updated_contents)
1621 self.assertNotIn(b"\r\n", updated_contents)
1623 def test_preserves_line_endings_via_stdin(self) -> None:
1624 for nl in ["\n", "\r\n"]:
1625 contents = nl.join(["def f( ):", " pass"])
1626 runner = BlackRunner()
1627 result = runner.invoke(
1628 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1630 self.assertEqual(result.exit_code, 0)
1631 output = runner.stdout_bytes
1632 self.assertIn(nl.encode("utf8"), output)
1634 self.assertNotIn(b"\r\n", output)
1636 def test_assert_equivalent_different_asts(self) -> None:
1637 with self.assertRaises(AssertionError):
1638 black.assert_equivalent("{}", "None")
1640 def test_symlink_out_of_root_directory(self) -> None:
1642 root = THIS_DIR.resolve()
1644 include = re.compile(black.DEFAULT_INCLUDES)
1645 exclude = re.compile(black.DEFAULT_EXCLUDES)
1646 report = black.Report()
1647 gitignore = PathSpec.from_lines("gitwildmatch", [])
1648 # `child` should behave like a symlink which resolved path is clearly
1649 # outside of the `root` directory.
1650 path.iterdir.return_value = [child]
1651 child.resolve.return_value = Path("/a/b/c")
1652 child.as_posix.return_value = "/a/b/c"
1653 child.is_symlink.return_value = True
1656 black.gen_python_files(
1657 path.iterdir(), root, include, exclude, None, report, gitignore
1660 except ValueError as ve:
1661 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1662 path.iterdir.assert_called_once()
1663 child.resolve.assert_called_once()
1664 child.is_symlink.assert_called_once()
1665 # `child` should behave like a strange file which resolved path is clearly
1666 # outside of the `root` directory.
1667 child.is_symlink.return_value = False
1668 with self.assertRaises(ValueError):
1670 black.gen_python_files(
1671 path.iterdir(), root, include, exclude, None, report, gitignore
1674 path.iterdir.assert_called()
1675 self.assertEqual(path.iterdir.call_count, 2)
1676 child.resolve.assert_called()
1677 self.assertEqual(child.resolve.call_count, 2)
1678 child.is_symlink.assert_called()
1679 self.assertEqual(child.is_symlink.call_count, 2)
1681 def test_shhh_click(self) -> None:
1683 from click import _unicodefun # type: ignore
1684 except ModuleNotFoundError:
1685 self.skipTest("Incompatible Click version")
1686 if not hasattr(_unicodefun, "_verify_python3_env"):
1687 self.skipTest("Incompatible Click version")
1688 # First, let's see if Click is crashing with a preferred ASCII charset.
1689 with patch("locale.getpreferredencoding") as gpe:
1690 gpe.return_value = "ASCII"
1691 with self.assertRaises(RuntimeError):
1692 _unicodefun._verify_python3_env()
1693 # Now, let's silence Click...
1695 # ...and confirm it's silent.
1696 with patch("locale.getpreferredencoding") as gpe:
1697 gpe.return_value = "ASCII"
1699 _unicodefun._verify_python3_env()
1700 except RuntimeError as re:
1701 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1703 def test_root_logger_not_used_directly(self) -> None:
1704 def fail(*args: Any, **kwargs: Any) -> None:
1705 self.fail("Record created with root logger")
1707 with patch.multiple(
1718 def test_invalid_config_return_code(self) -> None:
1719 tmp_file = Path(black.dump_to_file())
1721 tmp_config = Path(black.dump_to_file())
1723 args = ["--config", str(tmp_config), str(tmp_file)]
1724 self.invokeBlack(args, exit_code=2, ignore_config=False)
1728 def test_parse_pyproject_toml(self) -> None:
1729 test_toml_file = THIS_DIR / "test.toml"
1730 config = black.parse_pyproject_toml(str(test_toml_file))
1731 self.assertEqual(config["verbose"], 1)
1732 self.assertEqual(config["check"], "no")
1733 self.assertEqual(config["diff"], "y")
1734 self.assertEqual(config["color"], True)
1735 self.assertEqual(config["line_length"], 79)
1736 self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
1737 self.assertEqual(config["exclude"], r"\.pyi?$")
1738 self.assertEqual(config["include"], r"\.py?$")
1740 def test_read_pyproject_toml(self) -> None:
1741 test_toml_file = THIS_DIR / "test.toml"
1742 fake_ctx = FakeContext()
1743 black.read_pyproject_toml(fake_ctx, FakeParameter(), str(test_toml_file))
1744 config = fake_ctx.default_map
1745 self.assertEqual(config["verbose"], "1")
1746 self.assertEqual(config["check"], "no")
1747 self.assertEqual(config["diff"], "y")
1748 self.assertEqual(config["color"], "True")
1749 self.assertEqual(config["line_length"], "79")
1750 self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
1751 self.assertEqual(config["exclude"], r"\.pyi?$")
1752 self.assertEqual(config["include"], r"\.py?$")
1754 def test_find_project_root(self) -> None:
1755 with TemporaryDirectory() as workspace:
1756 root = Path(workspace)
1757 test_dir = root / "test"
1760 src_dir = root / "src"
1763 root_pyproject = root / "pyproject.toml"
1764 root_pyproject.touch()
1765 src_pyproject = src_dir / "pyproject.toml"
1766 src_pyproject.touch()
1767 src_python = src_dir / "foo.py"
1771 black.find_project_root((src_dir, test_dir)), root.resolve()
1773 self.assertEqual(black.find_project_root((src_dir,)), src_dir.resolve())
1774 self.assertEqual(black.find_project_root((src_python,)), src_dir.resolve())
1776 def test_bpo_33660_workaround(self) -> None:
1777 if system() == "Windows":
1780 # https://bugs.python.org/issue33660
1782 old_cwd = Path.cwd()
1786 path = Path("workspace") / "project"
1787 report = black.Report(verbose=True)
1788 normalized_path = black.normalize_path_maybe_ignore(path, root, report)
1789 self.assertEqual(normalized_path, "workspace/project")
1791 os.chdir(str(old_cwd))
1794 with open(black.__file__, "r", encoding="utf-8") as _bf:
1795 black_source_lines = _bf.readlines()
1798 def tracefunc(frame: types.FrameType, event: str, arg: Any) -> Callable:
1799 """Show function calls `from black/__init__.py` as they happen.
1801 Register this with `sys.settrace()` in a test you're debugging.
1806 stack = len(inspect.stack()) - 19
1808 filename = frame.f_code.co_filename
1809 lineno = frame.f_lineno
1810 func_sig_lineno = lineno - 1
1811 funcname = black_source_lines[func_sig_lineno].strip()
1812 while funcname.startswith("@"):
1813 func_sig_lineno += 1
1814 funcname = black_source_lines[func_sig_lineno].strip()
1815 if "black/__init__.py" in filename:
1816 print(f"{' ' * stack}{lineno}:{funcname}")
1820 if __name__ == "__main__":
1821 unittest.main(module="test_black")