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(str(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(str(one), cache)
1039 self.assertIn(str(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(str(src), cache)
1120 self.assertEqual(cache[str(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()
1132 str(cached): black.get_cache_info(cached),
1133 str(cached_but_changed): (0.0, 0),
1135 todo, done = black.filter_cached(
1136 cache, {uncached, cached, cached_but_changed}
1138 self.assertEqual(todo, {uncached, cached_but_changed})
1139 self.assertEqual(done, {cached})
1141 def test_write_cache_creates_directory_if_needed(self) -> None:
1143 with cache_dir(exists=False) as workspace:
1144 self.assertFalse(workspace.exists())
1145 black.write_cache({}, [], mode)
1146 self.assertTrue(workspace.exists())
1149 def test_failed_formatting_does_not_get_cached(self) -> None:
1151 with cache_dir() as workspace, patch(
1152 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1154 failing = (workspace / "failing.py").resolve()
1155 with failing.open("w") as fobj:
1156 fobj.write("not actually python")
1157 clean = (workspace / "clean.py").resolve()
1158 with clean.open("w") as fobj:
1159 fobj.write('print("hello")\n')
1160 self.invokeBlack([str(workspace)], exit_code=123)
1161 cache = black.read_cache(mode)
1162 self.assertNotIn(str(failing), cache)
1163 self.assertIn(str(clean), cache)
1165 def test_write_cache_write_fail(self) -> None:
1167 with cache_dir(), patch.object(Path, "open") as mock:
1168 mock.side_effect = OSError
1169 black.write_cache({}, [], mode)
1172 @patch("black.ProcessPoolExecutor", MagicMock(side_effect=OSError))
1173 def test_works_in_mono_process_only_environment(self) -> None:
1174 with cache_dir() as workspace:
1176 (workspace / "one.py").resolve(),
1177 (workspace / "two.py").resolve(),
1179 f.write_text('print("hello")\n')
1180 self.invokeBlack([str(workspace)])
1183 def test_check_diff_use_together(self) -> None:
1185 # Files which will be reformatted.
1186 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1187 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1188 # Files which will not be reformatted.
1189 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1190 self.invokeBlack([str(src2), "--diff", "--check"])
1191 # Multi file command.
1192 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1194 def test_no_files(self) -> None:
1196 # Without an argument, black exits with error code 0.
1197 self.invokeBlack([])
1199 def test_broken_symlink(self) -> None:
1200 with cache_dir() as workspace:
1201 symlink = workspace / "broken_link.py"
1203 symlink.symlink_to("nonexistent.py")
1204 except OSError as e:
1205 self.skipTest(f"Can't create symlinks: {e}")
1206 self.invokeBlack([str(workspace.resolve())])
1208 def test_read_cache_line_lengths(self) -> None:
1210 short_mode = replace(DEFAULT_MODE, line_length=1)
1211 with cache_dir() as workspace:
1212 path = (workspace / "file.py").resolve()
1214 black.write_cache({}, [path], mode)
1215 one = black.read_cache(mode)
1216 self.assertIn(str(path), one)
1217 two = black.read_cache(short_mode)
1218 self.assertNotIn(str(path), two)
1220 def test_single_file_force_pyi(self) -> None:
1221 pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
1222 contents, expected = read_data("force_pyi")
1223 with cache_dir() as workspace:
1224 path = (workspace / "file.py").resolve()
1225 with open(path, "w") as fh:
1227 self.invokeBlack([str(path), "--pyi"])
1228 with open(path, "r") as fh:
1230 # verify cache with --pyi is separate
1231 pyi_cache = black.read_cache(pyi_mode)
1232 self.assertIn(str(path), pyi_cache)
1233 normal_cache = black.read_cache(DEFAULT_MODE)
1234 self.assertNotIn(str(path), normal_cache)
1235 self.assertFormatEqual(expected, actual)
1236 black.assert_equivalent(contents, actual)
1237 black.assert_stable(contents, actual, pyi_mode)
1240 def test_multi_file_force_pyi(self) -> None:
1241 reg_mode = DEFAULT_MODE
1242 pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
1243 contents, expected = read_data("force_pyi")
1244 with cache_dir() as workspace:
1246 (workspace / "file1.py").resolve(),
1247 (workspace / "file2.py").resolve(),
1250 with open(path, "w") as fh:
1252 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1254 with open(path, "r") as fh:
1256 self.assertEqual(actual, expected)
1257 # verify cache with --pyi is separate
1258 pyi_cache = black.read_cache(pyi_mode)
1259 normal_cache = black.read_cache(reg_mode)
1261 self.assertIn(str(path), pyi_cache)
1262 self.assertNotIn(str(path), normal_cache)
1264 def test_pipe_force_pyi(self) -> None:
1265 source, expected = read_data("force_pyi")
1266 result = CliRunner().invoke(
1267 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1269 self.assertEqual(result.exit_code, 0)
1270 actual = result.output
1271 self.assertFormatEqual(actual, expected)
1273 def test_single_file_force_py36(self) -> None:
1274 reg_mode = DEFAULT_MODE
1275 py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
1276 source, expected = read_data("force_py36")
1277 with cache_dir() as workspace:
1278 path = (workspace / "file.py").resolve()
1279 with open(path, "w") as fh:
1281 self.invokeBlack([str(path), *PY36_ARGS])
1282 with open(path, "r") as fh:
1284 # verify cache with --target-version is separate
1285 py36_cache = black.read_cache(py36_mode)
1286 self.assertIn(str(path), py36_cache)
1287 normal_cache = black.read_cache(reg_mode)
1288 self.assertNotIn(str(path), normal_cache)
1289 self.assertEqual(actual, expected)
1292 def test_multi_file_force_py36(self) -> None:
1293 reg_mode = DEFAULT_MODE
1294 py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
1295 source, expected = read_data("force_py36")
1296 with cache_dir() as workspace:
1298 (workspace / "file1.py").resolve(),
1299 (workspace / "file2.py").resolve(),
1302 with open(path, "w") as fh:
1304 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1306 with open(path, "r") as fh:
1308 self.assertEqual(actual, expected)
1309 # verify cache with --target-version is separate
1310 pyi_cache = black.read_cache(py36_mode)
1311 normal_cache = black.read_cache(reg_mode)
1313 self.assertIn(str(path), pyi_cache)
1314 self.assertNotIn(str(path), normal_cache)
1316 def test_pipe_force_py36(self) -> None:
1317 source, expected = read_data("force_py36")
1318 result = CliRunner().invoke(
1320 ["-", "-q", "--target-version=py36"],
1321 input=BytesIO(source.encode("utf8")),
1323 self.assertEqual(result.exit_code, 0)
1324 actual = result.output
1325 self.assertFormatEqual(actual, expected)
1327 def test_include_exclude(self) -> None:
1328 path = THIS_DIR / "data" / "include_exclude_tests"
1329 include = re.compile(r"\.pyi?$")
1330 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1331 report = black.Report()
1332 gitignore = PathSpec.from_lines("gitwildmatch", [])
1333 sources: List[Path] = []
1335 Path(path / "b/dont_exclude/a.py"),
1336 Path(path / "b/dont_exclude/a.pyi"),
1338 this_abs = THIS_DIR.resolve()
1340 black.gen_python_files(
1341 path.iterdir(), this_abs, include, exclude, None, report, gitignore
1344 self.assertEqual(sorted(expected), sorted(sources))
1346 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1347 def test_exclude_for_issue_1572(self) -> None:
1348 # Exclude shouldn't touch files that were explicitly given to Black through the
1349 # CLI. Exclude is supposed to only apply to the recursive discovery of files.
1350 # https://github.com/psf/black/issues/1572
1351 path = THIS_DIR / "data" / "include_exclude_tests"
1353 exclude = r"/exclude/|a\.py"
1354 src = str(path / "b/exclude/a.py")
1355 report = black.Report()
1356 expected = [Path(path / "b/exclude/a.py")]
1367 stdin_filename=None,
1370 self.assertEqual(sorted(expected), sorted(sources))
1372 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1373 def test_get_sources_with_stdin(self) -> None:
1375 exclude = r"/exclude/|a\.py"
1377 report = black.Report()
1378 expected = [Path("-")]
1389 stdin_filename=None,
1392 self.assertEqual(sorted(expected), sorted(sources))
1394 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1395 def test_get_sources_with_stdin_filename(self) -> None:
1397 exclude = r"/exclude/|a\.py"
1399 report = black.Report()
1400 stdin_filename = str(THIS_DIR / "data/collections.py")
1401 expected = [Path(f"__BLACK_STDIN_FILENAME__{stdin_filename}")]
1412 stdin_filename=stdin_filename,
1415 self.assertEqual(sorted(expected), sorted(sources))
1417 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1418 def test_get_sources_with_stdin_filename_and_exclude(self) -> None:
1419 # Exclude shouldn't exclude stdin_filename since it is mimicing the
1420 # file being passed directly. This is the same as
1421 # test_exclude_for_issue_1572
1422 path = THIS_DIR / "data" / "include_exclude_tests"
1424 exclude = r"/exclude/|a\.py"
1426 report = black.Report()
1427 stdin_filename = str(path / "b/exclude/a.py")
1428 expected = [Path(f"__BLACK_STDIN_FILENAME__{stdin_filename}")]
1439 stdin_filename=stdin_filename,
1442 self.assertEqual(sorted(expected), sorted(sources))
1444 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1445 def test_get_sources_with_stdin_filename_and_force_exclude(self) -> None:
1446 # Force exclude should exclude the file when passing it through
1448 path = THIS_DIR / "data" / "include_exclude_tests"
1450 force_exclude = r"/exclude/|a\.py"
1452 report = black.Report()
1453 stdin_filename = str(path / "b/exclude/a.py")
1462 force_exclude=force_exclude,
1464 stdin_filename=stdin_filename,
1467 self.assertEqual([], sorted(sources))
1469 def test_reformat_one_with_stdin(self) -> None:
1471 "black.format_stdin_to_stdout",
1472 return_value=lambda *args, **kwargs: black.Changed.YES,
1474 report = MagicMock()
1479 write_back=black.WriteBack.YES,
1483 fsts.assert_called_once()
1484 report.done.assert_called_with(path, black.Changed.YES)
1486 def test_reformat_one_with_stdin_filename(self) -> None:
1488 "black.format_stdin_to_stdout",
1489 return_value=lambda *args, **kwargs: black.Changed.YES,
1491 report = MagicMock()
1493 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1498 write_back=black.WriteBack.YES,
1502 fsts.assert_called_once()
1503 # __BLACK_STDIN_FILENAME__ should have been striped
1504 report.done.assert_called_with(expected, black.Changed.YES)
1506 def test_reformat_one_with_stdin_and_existing_path(self) -> None:
1508 "black.format_stdin_to_stdout",
1509 return_value=lambda *args, **kwargs: black.Changed.YES,
1511 report = MagicMock()
1512 # Even with an existing file, since we are forcing stdin, black
1513 # should output to stdout and not modify the file inplace
1514 p = Path(str(THIS_DIR / "data/collections.py"))
1515 # Make sure is_file actually returns True
1516 self.assertTrue(p.is_file())
1517 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1522 write_back=black.WriteBack.YES,
1526 fsts.assert_called_once()
1527 # __BLACK_STDIN_FILENAME__ should have been striped
1528 report.done.assert_called_with(expected, black.Changed.YES)
1530 def test_gitignore_exclude(self) -> None:
1531 path = THIS_DIR / "data" / "include_exclude_tests"
1532 include = re.compile(r"\.pyi?$")
1533 exclude = re.compile(r"")
1534 report = black.Report()
1535 gitignore = PathSpec.from_lines(
1536 "gitwildmatch", ["exclude/", ".definitely_exclude"]
1538 sources: List[Path] = []
1540 Path(path / "b/dont_exclude/a.py"),
1541 Path(path / "b/dont_exclude/a.pyi"),
1543 this_abs = THIS_DIR.resolve()
1545 black.gen_python_files(
1546 path.iterdir(), this_abs, include, exclude, None, report, gitignore
1549 self.assertEqual(sorted(expected), sorted(sources))
1551 def test_empty_include(self) -> None:
1552 path = THIS_DIR / "data" / "include_exclude_tests"
1553 report = black.Report()
1554 gitignore = PathSpec.from_lines("gitwildmatch", [])
1555 empty = re.compile(r"")
1556 sources: List[Path] = []
1558 Path(path / "b/exclude/a.pie"),
1559 Path(path / "b/exclude/a.py"),
1560 Path(path / "b/exclude/a.pyi"),
1561 Path(path / "b/dont_exclude/a.pie"),
1562 Path(path / "b/dont_exclude/a.py"),
1563 Path(path / "b/dont_exclude/a.pyi"),
1564 Path(path / "b/.definitely_exclude/a.pie"),
1565 Path(path / "b/.definitely_exclude/a.py"),
1566 Path(path / "b/.definitely_exclude/a.pyi"),
1568 this_abs = THIS_DIR.resolve()
1570 black.gen_python_files(
1574 re.compile(black.DEFAULT_EXCLUDES),
1580 self.assertEqual(sorted(expected), sorted(sources))
1582 def test_empty_exclude(self) -> None:
1583 path = THIS_DIR / "data" / "include_exclude_tests"
1584 report = black.Report()
1585 gitignore = PathSpec.from_lines("gitwildmatch", [])
1586 empty = re.compile(r"")
1587 sources: List[Path] = []
1589 Path(path / "b/dont_exclude/a.py"),
1590 Path(path / "b/dont_exclude/a.pyi"),
1591 Path(path / "b/exclude/a.py"),
1592 Path(path / "b/exclude/a.pyi"),
1593 Path(path / "b/.definitely_exclude/a.py"),
1594 Path(path / "b/.definitely_exclude/a.pyi"),
1596 this_abs = THIS_DIR.resolve()
1598 black.gen_python_files(
1601 re.compile(black.DEFAULT_INCLUDES),
1608 self.assertEqual(sorted(expected), sorted(sources))
1610 def test_invalid_include_exclude(self) -> None:
1611 for option in ["--include", "--exclude"]:
1612 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1614 def test_preserves_line_endings(self) -> None:
1615 with TemporaryDirectory() as workspace:
1616 test_file = Path(workspace) / "test.py"
1617 for nl in ["\n", "\r\n"]:
1618 contents = nl.join(["def f( ):", " pass"])
1619 test_file.write_bytes(contents.encode())
1620 ff(test_file, write_back=black.WriteBack.YES)
1621 updated_contents: bytes = test_file.read_bytes()
1622 self.assertIn(nl.encode(), updated_contents)
1624 self.assertNotIn(b"\r\n", updated_contents)
1626 def test_preserves_line_endings_via_stdin(self) -> None:
1627 for nl in ["\n", "\r\n"]:
1628 contents = nl.join(["def f( ):", " pass"])
1629 runner = BlackRunner()
1630 result = runner.invoke(
1631 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1633 self.assertEqual(result.exit_code, 0)
1634 output = runner.stdout_bytes
1635 self.assertIn(nl.encode("utf8"), output)
1637 self.assertNotIn(b"\r\n", output)
1639 def test_assert_equivalent_different_asts(self) -> None:
1640 with self.assertRaises(AssertionError):
1641 black.assert_equivalent("{}", "None")
1643 def test_symlink_out_of_root_directory(self) -> None:
1645 root = THIS_DIR.resolve()
1647 include = re.compile(black.DEFAULT_INCLUDES)
1648 exclude = re.compile(black.DEFAULT_EXCLUDES)
1649 report = black.Report()
1650 gitignore = PathSpec.from_lines("gitwildmatch", [])
1651 # `child` should behave like a symlink which resolved path is clearly
1652 # outside of the `root` directory.
1653 path.iterdir.return_value = [child]
1654 child.resolve.return_value = Path("/a/b/c")
1655 child.as_posix.return_value = "/a/b/c"
1656 child.is_symlink.return_value = True
1659 black.gen_python_files(
1660 path.iterdir(), root, include, exclude, None, report, gitignore
1663 except ValueError as ve:
1664 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1665 path.iterdir.assert_called_once()
1666 child.resolve.assert_called_once()
1667 child.is_symlink.assert_called_once()
1668 # `child` should behave like a strange file which resolved path is clearly
1669 # outside of the `root` directory.
1670 child.is_symlink.return_value = False
1671 with self.assertRaises(ValueError):
1673 black.gen_python_files(
1674 path.iterdir(), root, include, exclude, None, report, gitignore
1677 path.iterdir.assert_called()
1678 self.assertEqual(path.iterdir.call_count, 2)
1679 child.resolve.assert_called()
1680 self.assertEqual(child.resolve.call_count, 2)
1681 child.is_symlink.assert_called()
1682 self.assertEqual(child.is_symlink.call_count, 2)
1684 def test_shhh_click(self) -> None:
1686 from click import _unicodefun # type: ignore
1687 except ModuleNotFoundError:
1688 self.skipTest("Incompatible Click version")
1689 if not hasattr(_unicodefun, "_verify_python3_env"):
1690 self.skipTest("Incompatible Click version")
1691 # First, let's see if Click is crashing with a preferred ASCII charset.
1692 with patch("locale.getpreferredencoding") as gpe:
1693 gpe.return_value = "ASCII"
1694 with self.assertRaises(RuntimeError):
1695 _unicodefun._verify_python3_env()
1696 # Now, let's silence Click...
1698 # ...and confirm it's silent.
1699 with patch("locale.getpreferredencoding") as gpe:
1700 gpe.return_value = "ASCII"
1702 _unicodefun._verify_python3_env()
1703 except RuntimeError as re:
1704 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1706 def test_root_logger_not_used_directly(self) -> None:
1707 def fail(*args: Any, **kwargs: Any) -> None:
1708 self.fail("Record created with root logger")
1710 with patch.multiple(
1721 def test_invalid_config_return_code(self) -> None:
1722 tmp_file = Path(black.dump_to_file())
1724 tmp_config = Path(black.dump_to_file())
1726 args = ["--config", str(tmp_config), str(tmp_file)]
1727 self.invokeBlack(args, exit_code=2, ignore_config=False)
1731 def test_parse_pyproject_toml(self) -> None:
1732 test_toml_file = THIS_DIR / "test.toml"
1733 config = black.parse_pyproject_toml(str(test_toml_file))
1734 self.assertEqual(config["verbose"], 1)
1735 self.assertEqual(config["check"], "no")
1736 self.assertEqual(config["diff"], "y")
1737 self.assertEqual(config["color"], True)
1738 self.assertEqual(config["line_length"], 79)
1739 self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
1740 self.assertEqual(config["exclude"], r"\.pyi?$")
1741 self.assertEqual(config["include"], r"\.py?$")
1743 def test_read_pyproject_toml(self) -> None:
1744 test_toml_file = THIS_DIR / "test.toml"
1745 fake_ctx = FakeContext()
1746 black.read_pyproject_toml(fake_ctx, FakeParameter(), str(test_toml_file))
1747 config = fake_ctx.default_map
1748 self.assertEqual(config["verbose"], "1")
1749 self.assertEqual(config["check"], "no")
1750 self.assertEqual(config["diff"], "y")
1751 self.assertEqual(config["color"], "True")
1752 self.assertEqual(config["line_length"], "79")
1753 self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
1754 self.assertEqual(config["exclude"], r"\.pyi?$")
1755 self.assertEqual(config["include"], r"\.py?$")
1757 def test_find_project_root(self) -> None:
1758 with TemporaryDirectory() as workspace:
1759 root = Path(workspace)
1760 test_dir = root / "test"
1763 src_dir = root / "src"
1766 root_pyproject = root / "pyproject.toml"
1767 root_pyproject.touch()
1768 src_pyproject = src_dir / "pyproject.toml"
1769 src_pyproject.touch()
1770 src_python = src_dir / "foo.py"
1774 black.find_project_root((src_dir, test_dir)), root.resolve()
1776 self.assertEqual(black.find_project_root((src_dir,)), src_dir.resolve())
1777 self.assertEqual(black.find_project_root((src_python,)), src_dir.resolve())
1779 def test_bpo_33660_workaround(self) -> None:
1780 if system() == "Windows":
1783 # https://bugs.python.org/issue33660
1785 old_cwd = Path.cwd()
1789 path = Path("workspace") / "project"
1790 report = black.Report(verbose=True)
1791 normalized_path = black.normalize_path_maybe_ignore(path, root, report)
1792 self.assertEqual(normalized_path, "workspace/project")
1794 os.chdir(str(old_cwd))
1797 with open(black.__file__, "r", encoding="utf-8") as _bf:
1798 black_source_lines = _bf.readlines()
1801 def tracefunc(frame: types.FrameType, event: str, arg: Any) -> Callable:
1802 """Show function calls `from black/__init__.py` as they happen.
1804 Register this with `sys.settrace()` in a test you're debugging.
1809 stack = len(inspect.stack()) - 19
1811 filename = frame.f_code.co_filename
1812 lineno = frame.f_lineno
1813 func_sig_lineno = lineno - 1
1814 funcname = black_source_lines[func_sig_lineno].strip()
1815 while funcname.startswith("@"):
1816 func_sig_lineno += 1
1817 funcname = black_source_lines[func_sig_lineno].strip()
1818 if "black/__init__.py" in filename:
1819 print(f"{' ' * stack}{lineno}:{funcname}")
1823 if __name__ == "__main__":
1824 unittest.main(module="test_black")