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_trailing_comma_optional_parens_stability1_pass2(self) -> None:
259 source, _expected = read_data("trailing_comma_optional_parens1")
260 actual = fs(fs(source)) # this is what `format_file_contents` does with --safe
261 black.assert_stable(source, actual, DEFAULT_MODE)
263 @patch("black.dump_to_file", dump_to_stderr)
264 def test_trailing_comma_optional_parens_stability2_pass2(self) -> None:
265 source, _expected = read_data("trailing_comma_optional_parens2")
266 actual = fs(fs(source)) # this is what `format_file_contents` does with --safe
267 black.assert_stable(source, actual, DEFAULT_MODE)
269 @patch("black.dump_to_file", dump_to_stderr)
270 def test_trailing_comma_optional_parens_stability3_pass2(self) -> None:
271 source, _expected = read_data("trailing_comma_optional_parens3")
272 actual = fs(fs(source)) # this is what `format_file_contents` does with --safe
273 black.assert_stable(source, actual, DEFAULT_MODE)
275 @patch("black.dump_to_file", dump_to_stderr)
276 def test_pep_572(self) -> None:
277 source, expected = read_data("pep_572")
279 self.assertFormatEqual(expected, actual)
280 black.assert_stable(source, actual, DEFAULT_MODE)
281 if sys.version_info >= (3, 8):
282 black.assert_equivalent(source, actual)
284 @patch("black.dump_to_file", dump_to_stderr)
285 def test_pep_572_remove_parens(self) -> None:
286 source, expected = read_data("pep_572_remove_parens")
288 self.assertFormatEqual(expected, actual)
289 black.assert_stable(source, actual, DEFAULT_MODE)
290 if sys.version_info >= (3, 8):
291 black.assert_equivalent(source, actual)
293 @patch("black.dump_to_file", dump_to_stderr)
294 def test_pep_572_do_not_remove_parens(self) -> None:
295 source, expected = read_data("pep_572_do_not_remove_parens")
296 # the AST safety checks will fail, but that's expected, just make sure no
297 # parentheses are touched
298 actual = black.format_str(source, mode=DEFAULT_MODE)
299 self.assertFormatEqual(expected, actual)
301 def test_pep_572_version_detection(self) -> None:
302 source, _ = read_data("pep_572")
303 root = black.lib2to3_parse(source)
304 features = black.get_features_used(root)
305 self.assertIn(black.Feature.ASSIGNMENT_EXPRESSIONS, features)
306 versions = black.detect_target_versions(root)
307 self.assertIn(black.TargetVersion.PY38, versions)
309 def test_expression_ff(self) -> None:
310 source, expected = read_data("expression")
311 tmp_file = Path(black.dump_to_file(source))
313 self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
314 with open(tmp_file, encoding="utf8") as f:
318 self.assertFormatEqual(expected, actual)
319 with patch("black.dump_to_file", dump_to_stderr):
320 black.assert_equivalent(source, actual)
321 black.assert_stable(source, actual, DEFAULT_MODE)
323 def test_expression_diff(self) -> None:
324 source, _ = read_data("expression.py")
325 config = THIS_DIR / "data" / "empty_pyproject.toml"
326 expected, _ = read_data("expression.diff")
327 tmp_file = Path(black.dump_to_file(source))
328 diff_header = re.compile(
329 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
330 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
333 result = BlackRunner().invoke(
334 black.main, ["--diff", str(tmp_file), f"--config={config}"]
336 self.assertEqual(result.exit_code, 0)
339 actual = result.output
340 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
341 if expected != actual:
342 dump = black.dump_to_file(actual)
344 "Expected diff isn't equal to the actual. If you made changes to"
345 " expression.py and this is an anticipated difference, overwrite"
346 f" tests/data/expression.diff with {dump}"
348 self.assertEqual(expected, actual, msg)
350 def test_expression_diff_with_color(self) -> None:
351 source, _ = read_data("expression.py")
352 config = THIS_DIR / "data" / "empty_pyproject.toml"
353 expected, _ = read_data("expression.diff")
354 tmp_file = Path(black.dump_to_file(source))
356 result = BlackRunner().invoke(
357 black.main, ["--diff", "--color", str(tmp_file), f"--config={config}"]
361 actual = result.output
362 # We check the contents of the diff in `test_expression_diff`. All
363 # we need to check here is that color codes exist in the result.
364 self.assertIn("\033[1;37m", actual)
365 self.assertIn("\033[36m", actual)
366 self.assertIn("\033[32m", actual)
367 self.assertIn("\033[31m", actual)
368 self.assertIn("\033[0m", actual)
370 @patch("black.dump_to_file", dump_to_stderr)
371 def test_pep_570(self) -> None:
372 source, expected = read_data("pep_570")
374 self.assertFormatEqual(expected, actual)
375 black.assert_stable(source, actual, DEFAULT_MODE)
376 if sys.version_info >= (3, 8):
377 black.assert_equivalent(source, actual)
379 def test_detect_pos_only_arguments(self) -> None:
380 source, _ = read_data("pep_570")
381 root = black.lib2to3_parse(source)
382 features = black.get_features_used(root)
383 self.assertIn(black.Feature.POS_ONLY_ARGUMENTS, features)
384 versions = black.detect_target_versions(root)
385 self.assertIn(black.TargetVersion.PY38, versions)
387 @patch("black.dump_to_file", dump_to_stderr)
388 def test_string_quotes(self) -> None:
389 source, expected = read_data("string_quotes")
390 mode = black.Mode(experimental_string_processing=True)
391 actual = fs(source, mode=mode)
392 self.assertFormatEqual(expected, actual)
393 black.assert_equivalent(source, actual)
394 black.assert_stable(source, actual, mode)
395 mode = replace(mode, string_normalization=False)
396 not_normalized = fs(source, mode=mode)
397 self.assertFormatEqual(source.replace("\\\n", ""), not_normalized)
398 black.assert_equivalent(source, not_normalized)
399 black.assert_stable(source, not_normalized, mode=mode)
401 @patch("black.dump_to_file", dump_to_stderr)
402 def test_docstring_no_string_normalization(self) -> None:
403 """Like test_docstring but with string normalization off."""
404 source, expected = read_data("docstring_no_string_normalization")
405 mode = replace(DEFAULT_MODE, string_normalization=False)
406 actual = fs(source, mode=mode)
407 self.assertFormatEqual(expected, actual)
408 black.assert_equivalent(source, actual)
409 black.assert_stable(source, actual, mode)
411 def test_long_strings_flag_disabled(self) -> None:
412 """Tests for turning off the string processing logic."""
413 source, expected = read_data("long_strings_flag_disabled")
414 mode = replace(DEFAULT_MODE, experimental_string_processing=False)
415 actual = fs(source, mode=mode)
416 self.assertFormatEqual(expected, actual)
417 black.assert_stable(expected, actual, mode)
419 @patch("black.dump_to_file", dump_to_stderr)
420 def test_numeric_literals(self) -> None:
421 source, expected = read_data("numeric_literals")
422 mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
423 actual = fs(source, mode=mode)
424 self.assertFormatEqual(expected, actual)
425 black.assert_equivalent(source, actual)
426 black.assert_stable(source, actual, mode)
428 @patch("black.dump_to_file", dump_to_stderr)
429 def test_numeric_literals_ignoring_underscores(self) -> None:
430 source, expected = read_data("numeric_literals_skip_underscores")
431 mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
432 actual = fs(source, mode=mode)
433 self.assertFormatEqual(expected, actual)
434 black.assert_equivalent(source, actual)
435 black.assert_stable(source, actual, mode)
437 def test_skip_magic_trailing_comma(self) -> None:
438 source, _ = read_data("expression.py")
439 expected, _ = read_data("expression_skip_magic_trailing_comma.diff")
440 tmp_file = Path(black.dump_to_file(source))
441 diff_header = re.compile(
442 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
443 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
446 result = BlackRunner().invoke(black.main, ["-C", "--diff", str(tmp_file)])
447 self.assertEqual(result.exit_code, 0)
450 actual = result.output
451 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
452 actual = actual.rstrip() + "\n" # the diff output has a trailing space
453 if expected != actual:
454 dump = black.dump_to_file(actual)
456 "Expected diff isn't equal to the actual. If you made changes to"
457 " expression.py and this is an anticipated difference, overwrite"
458 f" tests/data/expression_skip_magic_trailing_comma.diff with {dump}"
460 self.assertEqual(expected, actual, msg)
462 @patch("black.dump_to_file", dump_to_stderr)
463 def test_python2_print_function(self) -> None:
464 source, expected = read_data("python2_print_function")
465 mode = replace(DEFAULT_MODE, target_versions={TargetVersion.PY27})
466 actual = fs(source, mode=mode)
467 self.assertFormatEqual(expected, actual)
468 black.assert_equivalent(source, actual)
469 black.assert_stable(source, actual, mode)
471 @patch("black.dump_to_file", dump_to_stderr)
472 def test_stub(self) -> None:
473 mode = replace(DEFAULT_MODE, is_pyi=True)
474 source, expected = read_data("stub.pyi")
475 actual = fs(source, mode=mode)
476 self.assertFormatEqual(expected, actual)
477 black.assert_stable(source, actual, mode)
479 @patch("black.dump_to_file", dump_to_stderr)
480 def test_async_as_identifier(self) -> None:
481 source_path = (THIS_DIR / "data" / "async_as_identifier.py").resolve()
482 source, expected = read_data("async_as_identifier")
484 self.assertFormatEqual(expected, actual)
485 major, minor = sys.version_info[:2]
486 if major < 3 or (major <= 3 and minor < 7):
487 black.assert_equivalent(source, actual)
488 black.assert_stable(source, actual, DEFAULT_MODE)
489 # ensure black can parse this when the target is 3.6
490 self.invokeBlack([str(source_path), "--target-version", "py36"])
491 # but not on 3.7, because async/await is no longer an identifier
492 self.invokeBlack([str(source_path), "--target-version", "py37"], exit_code=123)
494 @patch("black.dump_to_file", dump_to_stderr)
495 def test_python37(self) -> None:
496 source_path = (THIS_DIR / "data" / "python37.py").resolve()
497 source, expected = read_data("python37")
499 self.assertFormatEqual(expected, actual)
500 major, minor = sys.version_info[:2]
501 if major > 3 or (major == 3 and minor >= 7):
502 black.assert_equivalent(source, actual)
503 black.assert_stable(source, actual, DEFAULT_MODE)
504 # ensure black can parse this when the target is 3.7
505 self.invokeBlack([str(source_path), "--target-version", "py37"])
506 # but not on 3.6, because we use async as a reserved keyword
507 self.invokeBlack([str(source_path), "--target-version", "py36"], exit_code=123)
509 @patch("black.dump_to_file", dump_to_stderr)
510 def test_python38(self) -> None:
511 source, expected = read_data("python38")
513 self.assertFormatEqual(expected, actual)
514 major, minor = sys.version_info[:2]
515 if major > 3 or (major == 3 and minor >= 8):
516 black.assert_equivalent(source, actual)
517 black.assert_stable(source, actual, DEFAULT_MODE)
519 @patch("black.dump_to_file", dump_to_stderr)
520 def test_python39(self) -> None:
521 source, expected = read_data("python39")
523 self.assertFormatEqual(expected, actual)
524 major, minor = sys.version_info[:2]
525 if major > 3 or (major == 3 and minor >= 9):
526 black.assert_equivalent(source, actual)
527 black.assert_stable(source, actual, DEFAULT_MODE)
529 def test_tab_comment_indentation(self) -> None:
530 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
531 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
532 self.assertFormatEqual(contents_spc, fs(contents_spc))
533 self.assertFormatEqual(contents_spc, fs(contents_tab))
535 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
536 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
537 self.assertFormatEqual(contents_spc, fs(contents_spc))
538 self.assertFormatEqual(contents_spc, fs(contents_tab))
540 # mixed tabs and spaces (valid Python 2 code)
541 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t# comment\n pass\n"
542 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
543 self.assertFormatEqual(contents_spc, fs(contents_spc))
544 self.assertFormatEqual(contents_spc, fs(contents_tab))
546 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t\t# comment\n pass\n"
547 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
548 self.assertFormatEqual(contents_spc, fs(contents_spc))
549 self.assertFormatEqual(contents_spc, fs(contents_tab))
551 def test_report_verbose(self) -> None:
552 report = black.Report(verbose=True)
556 def out(msg: str, **kwargs: Any) -> None:
557 out_lines.append(msg)
559 def err(msg: str, **kwargs: Any) -> None:
560 err_lines.append(msg)
562 with patch("black.out", out), patch("black.err", err):
563 report.done(Path("f1"), black.Changed.NO)
564 self.assertEqual(len(out_lines), 1)
565 self.assertEqual(len(err_lines), 0)
566 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
567 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
568 self.assertEqual(report.return_code, 0)
569 report.done(Path("f2"), black.Changed.YES)
570 self.assertEqual(len(out_lines), 2)
571 self.assertEqual(len(err_lines), 0)
572 self.assertEqual(out_lines[-1], "reformatted f2")
574 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
576 report.done(Path("f3"), black.Changed.CACHED)
577 self.assertEqual(len(out_lines), 3)
578 self.assertEqual(len(err_lines), 0)
580 out_lines[-1], "f3 wasn't modified on disk since last run."
583 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
585 self.assertEqual(report.return_code, 0)
587 self.assertEqual(report.return_code, 1)
589 report.failed(Path("e1"), "boom")
590 self.assertEqual(len(out_lines), 3)
591 self.assertEqual(len(err_lines), 1)
592 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
594 unstyle(str(report)),
595 "1 file reformatted, 2 files left unchanged, 1 file failed to"
598 self.assertEqual(report.return_code, 123)
599 report.done(Path("f3"), black.Changed.YES)
600 self.assertEqual(len(out_lines), 4)
601 self.assertEqual(len(err_lines), 1)
602 self.assertEqual(out_lines[-1], "reformatted f3")
604 unstyle(str(report)),
605 "2 files reformatted, 2 files left unchanged, 1 file failed to"
608 self.assertEqual(report.return_code, 123)
609 report.failed(Path("e2"), "boom")
610 self.assertEqual(len(out_lines), 4)
611 self.assertEqual(len(err_lines), 2)
612 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
614 unstyle(str(report)),
615 "2 files reformatted, 2 files left unchanged, 2 files failed to"
618 self.assertEqual(report.return_code, 123)
619 report.path_ignored(Path("wat"), "no match")
620 self.assertEqual(len(out_lines), 5)
621 self.assertEqual(len(err_lines), 2)
622 self.assertEqual(out_lines[-1], "wat ignored: no match")
624 unstyle(str(report)),
625 "2 files reformatted, 2 files left unchanged, 2 files failed to"
628 self.assertEqual(report.return_code, 123)
629 report.done(Path("f4"), black.Changed.NO)
630 self.assertEqual(len(out_lines), 6)
631 self.assertEqual(len(err_lines), 2)
632 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
634 unstyle(str(report)),
635 "2 files reformatted, 3 files left unchanged, 2 files failed to"
638 self.assertEqual(report.return_code, 123)
641 unstyle(str(report)),
642 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
643 " would fail to reformat.",
648 unstyle(str(report)),
649 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
650 " would fail to reformat.",
653 def test_report_quiet(self) -> None:
654 report = black.Report(quiet=True)
658 def out(msg: str, **kwargs: Any) -> None:
659 out_lines.append(msg)
661 def err(msg: str, **kwargs: Any) -> None:
662 err_lines.append(msg)
664 with patch("black.out", out), patch("black.err", err):
665 report.done(Path("f1"), black.Changed.NO)
666 self.assertEqual(len(out_lines), 0)
667 self.assertEqual(len(err_lines), 0)
668 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
669 self.assertEqual(report.return_code, 0)
670 report.done(Path("f2"), black.Changed.YES)
671 self.assertEqual(len(out_lines), 0)
672 self.assertEqual(len(err_lines), 0)
674 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
676 report.done(Path("f3"), black.Changed.CACHED)
677 self.assertEqual(len(out_lines), 0)
678 self.assertEqual(len(err_lines), 0)
680 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
682 self.assertEqual(report.return_code, 0)
684 self.assertEqual(report.return_code, 1)
686 report.failed(Path("e1"), "boom")
687 self.assertEqual(len(out_lines), 0)
688 self.assertEqual(len(err_lines), 1)
689 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
691 unstyle(str(report)),
692 "1 file reformatted, 2 files left unchanged, 1 file failed to"
695 self.assertEqual(report.return_code, 123)
696 report.done(Path("f3"), black.Changed.YES)
697 self.assertEqual(len(out_lines), 0)
698 self.assertEqual(len(err_lines), 1)
700 unstyle(str(report)),
701 "2 files reformatted, 2 files left unchanged, 1 file failed to"
704 self.assertEqual(report.return_code, 123)
705 report.failed(Path("e2"), "boom")
706 self.assertEqual(len(out_lines), 0)
707 self.assertEqual(len(err_lines), 2)
708 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
710 unstyle(str(report)),
711 "2 files reformatted, 2 files left unchanged, 2 files failed to"
714 self.assertEqual(report.return_code, 123)
715 report.path_ignored(Path("wat"), "no match")
716 self.assertEqual(len(out_lines), 0)
717 self.assertEqual(len(err_lines), 2)
719 unstyle(str(report)),
720 "2 files reformatted, 2 files left unchanged, 2 files failed to"
723 self.assertEqual(report.return_code, 123)
724 report.done(Path("f4"), black.Changed.NO)
725 self.assertEqual(len(out_lines), 0)
726 self.assertEqual(len(err_lines), 2)
728 unstyle(str(report)),
729 "2 files reformatted, 3 files left unchanged, 2 files failed to"
732 self.assertEqual(report.return_code, 123)
735 unstyle(str(report)),
736 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
737 " would fail to reformat.",
742 unstyle(str(report)),
743 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
744 " would fail to reformat.",
747 def test_report_normal(self) -> None:
748 report = black.Report()
752 def out(msg: str, **kwargs: Any) -> None:
753 out_lines.append(msg)
755 def err(msg: str, **kwargs: Any) -> None:
756 err_lines.append(msg)
758 with patch("black.out", out), patch("black.err", err):
759 report.done(Path("f1"), black.Changed.NO)
760 self.assertEqual(len(out_lines), 0)
761 self.assertEqual(len(err_lines), 0)
762 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
763 self.assertEqual(report.return_code, 0)
764 report.done(Path("f2"), black.Changed.YES)
765 self.assertEqual(len(out_lines), 1)
766 self.assertEqual(len(err_lines), 0)
767 self.assertEqual(out_lines[-1], "reformatted f2")
769 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
771 report.done(Path("f3"), black.Changed.CACHED)
772 self.assertEqual(len(out_lines), 1)
773 self.assertEqual(len(err_lines), 0)
774 self.assertEqual(out_lines[-1], "reformatted f2")
776 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
778 self.assertEqual(report.return_code, 0)
780 self.assertEqual(report.return_code, 1)
782 report.failed(Path("e1"), "boom")
783 self.assertEqual(len(out_lines), 1)
784 self.assertEqual(len(err_lines), 1)
785 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
787 unstyle(str(report)),
788 "1 file reformatted, 2 files left unchanged, 1 file failed to"
791 self.assertEqual(report.return_code, 123)
792 report.done(Path("f3"), black.Changed.YES)
793 self.assertEqual(len(out_lines), 2)
794 self.assertEqual(len(err_lines), 1)
795 self.assertEqual(out_lines[-1], "reformatted f3")
797 unstyle(str(report)),
798 "2 files reformatted, 2 files left unchanged, 1 file failed to"
801 self.assertEqual(report.return_code, 123)
802 report.failed(Path("e2"), "boom")
803 self.assertEqual(len(out_lines), 2)
804 self.assertEqual(len(err_lines), 2)
805 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
807 unstyle(str(report)),
808 "2 files reformatted, 2 files left unchanged, 2 files failed to"
811 self.assertEqual(report.return_code, 123)
812 report.path_ignored(Path("wat"), "no match")
813 self.assertEqual(len(out_lines), 2)
814 self.assertEqual(len(err_lines), 2)
816 unstyle(str(report)),
817 "2 files reformatted, 2 files left unchanged, 2 files failed to"
820 self.assertEqual(report.return_code, 123)
821 report.done(Path("f4"), black.Changed.NO)
822 self.assertEqual(len(out_lines), 2)
823 self.assertEqual(len(err_lines), 2)
825 unstyle(str(report)),
826 "2 files reformatted, 3 files left unchanged, 2 files failed to"
829 self.assertEqual(report.return_code, 123)
832 unstyle(str(report)),
833 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
834 " would fail to reformat.",
839 unstyle(str(report)),
840 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
841 " would fail to reformat.",
844 def test_lib2to3_parse(self) -> None:
845 with self.assertRaises(black.InvalidInput):
846 black.lib2to3_parse("invalid syntax")
849 black.lib2to3_parse(straddling)
850 black.lib2to3_parse(straddling, {TargetVersion.PY27})
851 black.lib2to3_parse(straddling, {TargetVersion.PY36})
852 black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
855 black.lib2to3_parse(py2_only)
856 black.lib2to3_parse(py2_only, {TargetVersion.PY27})
857 with self.assertRaises(black.InvalidInput):
858 black.lib2to3_parse(py2_only, {TargetVersion.PY36})
859 with self.assertRaises(black.InvalidInput):
860 black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
862 py3_only = "exec(x, end=y)"
863 black.lib2to3_parse(py3_only)
864 with self.assertRaises(black.InvalidInput):
865 black.lib2to3_parse(py3_only, {TargetVersion.PY27})
866 black.lib2to3_parse(py3_only, {TargetVersion.PY36})
867 black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
869 def test_get_features_used_decorator(self) -> None:
870 # Test the feature detection of new decorator syntax
871 # since this makes some test cases of test_get_features_used()
872 # fails if it fails, this is tested first so that a useful case
874 simples, relaxed = read_data("decorators")
875 # skip explanation comments at the top of the file
876 for simple_test in simples.split("##")[1:]:
877 node = black.lib2to3_parse(simple_test)
878 decorator = str(node.children[0].children[0]).strip()
880 Feature.RELAXED_DECORATORS,
881 black.get_features_used(node),
883 f"decorator '{decorator}' follows python<=3.8 syntax"
884 "but is detected as 3.9+"
885 # f"The full node is\n{node!r}"
888 # skip the '# output' comment at the top of the output part
889 for relaxed_test in relaxed.split("##")[1:]:
890 node = black.lib2to3_parse(relaxed_test)
891 decorator = str(node.children[0].children[0]).strip()
893 Feature.RELAXED_DECORATORS,
894 black.get_features_used(node),
896 f"decorator '{decorator}' uses python3.9+ syntax"
897 "but is detected as python<=3.8"
898 # f"The full node is\n{node!r}"
902 def test_get_features_used(self) -> None:
903 node = black.lib2to3_parse("def f(*, arg): ...\n")
904 self.assertEqual(black.get_features_used(node), set())
905 node = black.lib2to3_parse("def f(*, arg,): ...\n")
906 self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
907 node = black.lib2to3_parse("f(*arg,)\n")
909 black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
911 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
912 self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
913 node = black.lib2to3_parse("123_456\n")
914 self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
915 node = black.lib2to3_parse("123456\n")
916 self.assertEqual(black.get_features_used(node), set())
917 source, expected = read_data("function")
918 node = black.lib2to3_parse(source)
919 expected_features = {
920 Feature.TRAILING_COMMA_IN_CALL,
921 Feature.TRAILING_COMMA_IN_DEF,
924 self.assertEqual(black.get_features_used(node), expected_features)
925 node = black.lib2to3_parse(expected)
926 self.assertEqual(black.get_features_used(node), expected_features)
927 source, expected = read_data("expression")
928 node = black.lib2to3_parse(source)
929 self.assertEqual(black.get_features_used(node), set())
930 node = black.lib2to3_parse(expected)
931 self.assertEqual(black.get_features_used(node), set())
933 def test_get_future_imports(self) -> None:
934 node = black.lib2to3_parse("\n")
935 self.assertEqual(set(), black.get_future_imports(node))
936 node = black.lib2to3_parse("from __future__ import black\n")
937 self.assertEqual({"black"}, black.get_future_imports(node))
938 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
939 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
940 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
941 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
942 node = black.lib2to3_parse(
943 "from __future__ import multiple\nfrom __future__ import imports\n"
945 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
946 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
947 self.assertEqual({"black"}, black.get_future_imports(node))
948 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
949 self.assertEqual({"black"}, black.get_future_imports(node))
950 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
951 self.assertEqual(set(), black.get_future_imports(node))
952 node = black.lib2to3_parse("from some.module import black\n")
953 self.assertEqual(set(), black.get_future_imports(node))
954 node = black.lib2to3_parse(
955 "from __future__ import unicode_literals as _unicode_literals"
957 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
958 node = black.lib2to3_parse(
959 "from __future__ import unicode_literals as _lol, print"
961 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
963 def test_debug_visitor(self) -> None:
964 source, _ = read_data("debug_visitor.py")
965 expected, _ = read_data("debug_visitor.out")
969 def out(msg: str, **kwargs: Any) -> None:
970 out_lines.append(msg)
972 def err(msg: str, **kwargs: Any) -> None:
973 err_lines.append(msg)
975 with patch("black.out", out), patch("black.err", err):
976 black.DebugVisitor.show(source)
977 actual = "\n".join(out_lines) + "\n"
979 if expected != actual:
980 log_name = black.dump_to_file(*out_lines)
984 f"AST print out is different. Actual version dumped to {log_name}",
987 def test_format_file_contents(self) -> None:
990 with self.assertRaises(black.NothingChanged):
991 black.format_file_contents(empty, mode=mode, fast=False)
993 with self.assertRaises(black.NothingChanged):
994 black.format_file_contents(just_nl, mode=mode, fast=False)
995 same = "j = [1, 2, 3]\n"
996 with self.assertRaises(black.NothingChanged):
997 black.format_file_contents(same, mode=mode, fast=False)
998 different = "j = [1,2,3]"
1000 actual = black.format_file_contents(different, mode=mode, fast=False)
1001 self.assertEqual(expected, actual)
1002 invalid = "return if you can"
1003 with self.assertRaises(black.InvalidInput) as e:
1004 black.format_file_contents(invalid, mode=mode, fast=False)
1005 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
1007 def test_endmarker(self) -> None:
1008 n = black.lib2to3_parse("\n")
1009 self.assertEqual(n.type, black.syms.file_input)
1010 self.assertEqual(len(n.children), 1)
1011 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
1013 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
1014 def test_assertFormatEqual(self) -> None:
1018 def out(msg: str, **kwargs: Any) -> None:
1019 out_lines.append(msg)
1021 def err(msg: str, **kwargs: Any) -> None:
1022 err_lines.append(msg)
1024 with patch("black.out", out), patch("black.err", err):
1025 with self.assertRaises(AssertionError):
1026 self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")
1028 out_str = "".join(out_lines)
1029 self.assertTrue("Expected tree:" in out_str)
1030 self.assertTrue("Actual tree:" in out_str)
1031 self.assertEqual("".join(err_lines), "")
1033 def test_cache_broken_file(self) -> None:
1035 with cache_dir() as workspace:
1036 cache_file = black.get_cache_file(mode)
1037 with cache_file.open("w") as fobj:
1038 fobj.write("this is not a pickle")
1039 self.assertEqual(black.read_cache(mode), {})
1040 src = (workspace / "test.py").resolve()
1041 with src.open("w") as fobj:
1042 fobj.write("print('hello')")
1043 self.invokeBlack([str(src)])
1044 cache = black.read_cache(mode)
1045 self.assertIn(str(src), cache)
1047 def test_cache_single_file_already_cached(self) -> None:
1049 with cache_dir() as workspace:
1050 src = (workspace / "test.py").resolve()
1051 with src.open("w") as fobj:
1052 fobj.write("print('hello')")
1053 black.write_cache({}, [src], mode)
1054 self.invokeBlack([str(src)])
1055 with src.open("r") as fobj:
1056 self.assertEqual(fobj.read(), "print('hello')")
1059 def test_cache_multiple_files(self) -> None:
1061 with cache_dir() as workspace, patch(
1062 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1064 one = (workspace / "one.py").resolve()
1065 with one.open("w") as fobj:
1066 fobj.write("print('hello')")
1067 two = (workspace / "two.py").resolve()
1068 with two.open("w") as fobj:
1069 fobj.write("print('hello')")
1070 black.write_cache({}, [one], mode)
1071 self.invokeBlack([str(workspace)])
1072 with one.open("r") as fobj:
1073 self.assertEqual(fobj.read(), "print('hello')")
1074 with two.open("r") as fobj:
1075 self.assertEqual(fobj.read(), 'print("hello")\n')
1076 cache = black.read_cache(mode)
1077 self.assertIn(str(one), cache)
1078 self.assertIn(str(two), cache)
1080 def test_no_cache_when_writeback_diff(self) -> None:
1082 with cache_dir() as workspace:
1083 src = (workspace / "test.py").resolve()
1084 with src.open("w") as fobj:
1085 fobj.write("print('hello')")
1086 with patch("black.read_cache") as read_cache, patch(
1089 self.invokeBlack([str(src), "--diff"])
1090 cache_file = black.get_cache_file(mode)
1091 self.assertFalse(cache_file.exists())
1092 write_cache.assert_not_called()
1093 read_cache.assert_not_called()
1095 def test_no_cache_when_writeback_color_diff(self) -> None:
1097 with cache_dir() as workspace:
1098 src = (workspace / "test.py").resolve()
1099 with src.open("w") as fobj:
1100 fobj.write("print('hello')")
1101 with patch("black.read_cache") as read_cache, patch(
1104 self.invokeBlack([str(src), "--diff", "--color"])
1105 cache_file = black.get_cache_file(mode)
1106 self.assertFalse(cache_file.exists())
1107 write_cache.assert_not_called()
1108 read_cache.assert_not_called()
1111 def test_output_locking_when_writeback_diff(self) -> None:
1112 with cache_dir() as workspace:
1113 for tag in range(0, 4):
1114 src = (workspace / f"test{tag}.py").resolve()
1115 with src.open("w") as fobj:
1116 fobj.write("print('hello')")
1117 with patch("black.Manager", wraps=multiprocessing.Manager) as mgr:
1118 self.invokeBlack(["--diff", str(workspace)], exit_code=0)
1119 # this isn't quite doing what we want, but if it _isn't_
1120 # called then we cannot be using the lock it provides
1124 def test_output_locking_when_writeback_color_diff(self) -> None:
1125 with cache_dir() as workspace:
1126 for tag in range(0, 4):
1127 src = (workspace / f"test{tag}.py").resolve()
1128 with src.open("w") as fobj:
1129 fobj.write("print('hello')")
1130 with patch("black.Manager", wraps=multiprocessing.Manager) as mgr:
1131 self.invokeBlack(["--diff", "--color", str(workspace)], exit_code=0)
1132 # this isn't quite doing what we want, but if it _isn't_
1133 # called then we cannot be using the lock it provides
1136 def test_no_cache_when_stdin(self) -> None:
1139 result = CliRunner().invoke(
1140 black.main, ["-"], input=BytesIO(b"print('hello')")
1142 self.assertEqual(result.exit_code, 0)
1143 cache_file = black.get_cache_file(mode)
1144 self.assertFalse(cache_file.exists())
1146 def test_read_cache_no_cachefile(self) -> None:
1149 self.assertEqual(black.read_cache(mode), {})
1151 def test_write_cache_read_cache(self) -> None:
1153 with cache_dir() as workspace:
1154 src = (workspace / "test.py").resolve()
1156 black.write_cache({}, [src], mode)
1157 cache = black.read_cache(mode)
1158 self.assertIn(str(src), cache)
1159 self.assertEqual(cache[str(src)], black.get_cache_info(src))
1161 def test_filter_cached(self) -> None:
1162 with TemporaryDirectory() as workspace:
1163 path = Path(workspace)
1164 uncached = (path / "uncached").resolve()
1165 cached = (path / "cached").resolve()
1166 cached_but_changed = (path / "changed").resolve()
1169 cached_but_changed.touch()
1171 str(cached): black.get_cache_info(cached),
1172 str(cached_but_changed): (0.0, 0),
1174 todo, done = black.filter_cached(
1175 cache, {uncached, cached, cached_but_changed}
1177 self.assertEqual(todo, {uncached, cached_but_changed})
1178 self.assertEqual(done, {cached})
1180 def test_write_cache_creates_directory_if_needed(self) -> None:
1182 with cache_dir(exists=False) as workspace:
1183 self.assertFalse(workspace.exists())
1184 black.write_cache({}, [], mode)
1185 self.assertTrue(workspace.exists())
1188 def test_failed_formatting_does_not_get_cached(self) -> None:
1190 with cache_dir() as workspace, patch(
1191 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1193 failing = (workspace / "failing.py").resolve()
1194 with failing.open("w") as fobj:
1195 fobj.write("not actually python")
1196 clean = (workspace / "clean.py").resolve()
1197 with clean.open("w") as fobj:
1198 fobj.write('print("hello")\n')
1199 self.invokeBlack([str(workspace)], exit_code=123)
1200 cache = black.read_cache(mode)
1201 self.assertNotIn(str(failing), cache)
1202 self.assertIn(str(clean), cache)
1204 def test_write_cache_write_fail(self) -> None:
1206 with cache_dir(), patch.object(Path, "open") as mock:
1207 mock.side_effect = OSError
1208 black.write_cache({}, [], mode)
1211 @patch("black.ProcessPoolExecutor", MagicMock(side_effect=OSError))
1212 def test_works_in_mono_process_only_environment(self) -> None:
1213 with cache_dir() as workspace:
1215 (workspace / "one.py").resolve(),
1216 (workspace / "two.py").resolve(),
1218 f.write_text('print("hello")\n')
1219 self.invokeBlack([str(workspace)])
1222 def test_check_diff_use_together(self) -> None:
1224 # Files which will be reformatted.
1225 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1226 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1227 # Files which will not be reformatted.
1228 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1229 self.invokeBlack([str(src2), "--diff", "--check"])
1230 # Multi file command.
1231 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1233 def test_no_files(self) -> None:
1235 # Without an argument, black exits with error code 0.
1236 self.invokeBlack([])
1238 def test_broken_symlink(self) -> None:
1239 with cache_dir() as workspace:
1240 symlink = workspace / "broken_link.py"
1242 symlink.symlink_to("nonexistent.py")
1243 except OSError as e:
1244 self.skipTest(f"Can't create symlinks: {e}")
1245 self.invokeBlack([str(workspace.resolve())])
1247 def test_read_cache_line_lengths(self) -> None:
1249 short_mode = replace(DEFAULT_MODE, line_length=1)
1250 with cache_dir() as workspace:
1251 path = (workspace / "file.py").resolve()
1253 black.write_cache({}, [path], mode)
1254 one = black.read_cache(mode)
1255 self.assertIn(str(path), one)
1256 two = black.read_cache(short_mode)
1257 self.assertNotIn(str(path), two)
1259 def test_single_file_force_pyi(self) -> None:
1260 pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
1261 contents, expected = read_data("force_pyi")
1262 with cache_dir() as workspace:
1263 path = (workspace / "file.py").resolve()
1264 with open(path, "w") as fh:
1266 self.invokeBlack([str(path), "--pyi"])
1267 with open(path, "r") as fh:
1269 # verify cache with --pyi is separate
1270 pyi_cache = black.read_cache(pyi_mode)
1271 self.assertIn(str(path), pyi_cache)
1272 normal_cache = black.read_cache(DEFAULT_MODE)
1273 self.assertNotIn(str(path), normal_cache)
1274 self.assertFormatEqual(expected, actual)
1275 black.assert_equivalent(contents, actual)
1276 black.assert_stable(contents, actual, pyi_mode)
1279 def test_multi_file_force_pyi(self) -> None:
1280 reg_mode = DEFAULT_MODE
1281 pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
1282 contents, expected = read_data("force_pyi")
1283 with cache_dir() as workspace:
1285 (workspace / "file1.py").resolve(),
1286 (workspace / "file2.py").resolve(),
1289 with open(path, "w") as fh:
1291 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1293 with open(path, "r") as fh:
1295 self.assertEqual(actual, expected)
1296 # verify cache with --pyi is separate
1297 pyi_cache = black.read_cache(pyi_mode)
1298 normal_cache = black.read_cache(reg_mode)
1300 self.assertIn(str(path), pyi_cache)
1301 self.assertNotIn(str(path), normal_cache)
1303 def test_pipe_force_pyi(self) -> None:
1304 source, expected = read_data("force_pyi")
1305 result = CliRunner().invoke(
1306 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1308 self.assertEqual(result.exit_code, 0)
1309 actual = result.output
1310 self.assertFormatEqual(actual, expected)
1312 def test_single_file_force_py36(self) -> None:
1313 reg_mode = DEFAULT_MODE
1314 py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
1315 source, expected = read_data("force_py36")
1316 with cache_dir() as workspace:
1317 path = (workspace / "file.py").resolve()
1318 with open(path, "w") as fh:
1320 self.invokeBlack([str(path), *PY36_ARGS])
1321 with open(path, "r") as fh:
1323 # verify cache with --target-version is separate
1324 py36_cache = black.read_cache(py36_mode)
1325 self.assertIn(str(path), py36_cache)
1326 normal_cache = black.read_cache(reg_mode)
1327 self.assertNotIn(str(path), normal_cache)
1328 self.assertEqual(actual, expected)
1331 def test_multi_file_force_py36(self) -> None:
1332 reg_mode = DEFAULT_MODE
1333 py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
1334 source, expected = read_data("force_py36")
1335 with cache_dir() as workspace:
1337 (workspace / "file1.py").resolve(),
1338 (workspace / "file2.py").resolve(),
1341 with open(path, "w") as fh:
1343 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1345 with open(path, "r") as fh:
1347 self.assertEqual(actual, expected)
1348 # verify cache with --target-version is separate
1349 pyi_cache = black.read_cache(py36_mode)
1350 normal_cache = black.read_cache(reg_mode)
1352 self.assertIn(str(path), pyi_cache)
1353 self.assertNotIn(str(path), normal_cache)
1355 def test_pipe_force_py36(self) -> None:
1356 source, expected = read_data("force_py36")
1357 result = CliRunner().invoke(
1359 ["-", "-q", "--target-version=py36"],
1360 input=BytesIO(source.encode("utf8")),
1362 self.assertEqual(result.exit_code, 0)
1363 actual = result.output
1364 self.assertFormatEqual(actual, expected)
1366 def test_include_exclude(self) -> None:
1367 path = THIS_DIR / "data" / "include_exclude_tests"
1368 include = re.compile(r"\.pyi?$")
1369 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1370 report = black.Report()
1371 gitignore = PathSpec.from_lines("gitwildmatch", [])
1372 sources: List[Path] = []
1374 Path(path / "b/dont_exclude/a.py"),
1375 Path(path / "b/dont_exclude/a.pyi"),
1377 this_abs = THIS_DIR.resolve()
1379 black.gen_python_files(
1390 self.assertEqual(sorted(expected), sorted(sources))
1392 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1393 def test_exclude_for_issue_1572(self) -> None:
1394 # Exclude shouldn't touch files that were explicitly given to Black through the
1395 # CLI. Exclude is supposed to only apply to the recursive discovery of files.
1396 # https://github.com/psf/black/issues/1572
1397 path = THIS_DIR / "data" / "include_exclude_tests"
1399 exclude = r"/exclude/|a\.py"
1400 src = str(path / "b/exclude/a.py")
1401 report = black.Report()
1402 expected = [Path(path / "b/exclude/a.py")]
1409 include=re.compile(include),
1410 exclude=re.compile(exclude),
1411 extend_exclude=None,
1414 stdin_filename=None,
1417 self.assertEqual(sorted(expected), sorted(sources))
1419 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1420 def test_get_sources_with_stdin(self) -> None:
1422 exclude = r"/exclude/|a\.py"
1424 report = black.Report()
1425 expected = [Path("-")]
1432 include=re.compile(include),
1433 exclude=re.compile(exclude),
1434 extend_exclude=None,
1437 stdin_filename=None,
1440 self.assertEqual(sorted(expected), sorted(sources))
1442 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1443 def test_get_sources_with_stdin_filename(self) -> None:
1445 exclude = r"/exclude/|a\.py"
1447 report = black.Report()
1448 stdin_filename = str(THIS_DIR / "data/collections.py")
1449 expected = [Path(f"__BLACK_STDIN_FILENAME__{stdin_filename}")]
1456 include=re.compile(include),
1457 exclude=re.compile(exclude),
1458 extend_exclude=None,
1461 stdin_filename=stdin_filename,
1464 self.assertEqual(sorted(expected), sorted(sources))
1466 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1467 def test_get_sources_with_stdin_filename_and_exclude(self) -> None:
1468 # Exclude shouldn't exclude stdin_filename since it is mimicing the
1469 # file being passed directly. This is the same as
1470 # test_exclude_for_issue_1572
1471 path = THIS_DIR / "data" / "include_exclude_tests"
1473 exclude = r"/exclude/|a\.py"
1475 report = black.Report()
1476 stdin_filename = str(path / "b/exclude/a.py")
1477 expected = [Path(f"__BLACK_STDIN_FILENAME__{stdin_filename}")]
1484 include=re.compile(include),
1485 exclude=re.compile(exclude),
1486 extend_exclude=None,
1489 stdin_filename=stdin_filename,
1492 self.assertEqual(sorted(expected), sorted(sources))
1494 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1495 def test_get_sources_with_stdin_filename_and_extend_exclude(self) -> None:
1496 # Extend exclude shouldn't exclude stdin_filename since it is mimicking the
1497 # file being passed directly. This is the same as
1498 # test_exclude_for_issue_1572
1499 path = THIS_DIR / "data" / "include_exclude_tests"
1501 extend_exclude = r"/exclude/|a\.py"
1503 report = black.Report()
1504 stdin_filename = str(path / "b/exclude/a.py")
1505 expected = [Path(f"__BLACK_STDIN_FILENAME__{stdin_filename}")]
1512 include=re.compile(include),
1513 exclude=re.compile(""),
1514 extend_exclude=re.compile(extend_exclude),
1517 stdin_filename=stdin_filename,
1520 self.assertEqual(sorted(expected), sorted(sources))
1522 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1523 def test_get_sources_with_stdin_filename_and_force_exclude(self) -> None:
1524 # Force exclude should exclude the file when passing it through
1526 path = THIS_DIR / "data" / "include_exclude_tests"
1528 force_exclude = r"/exclude/|a\.py"
1530 report = black.Report()
1531 stdin_filename = str(path / "b/exclude/a.py")
1538 include=re.compile(include),
1539 exclude=re.compile(""),
1540 extend_exclude=None,
1541 force_exclude=re.compile(force_exclude),
1543 stdin_filename=stdin_filename,
1546 self.assertEqual([], sorted(sources))
1548 def test_reformat_one_with_stdin(self) -> None:
1550 "black.format_stdin_to_stdout",
1551 return_value=lambda *args, **kwargs: black.Changed.YES,
1553 report = MagicMock()
1558 write_back=black.WriteBack.YES,
1562 fsts.assert_called_once()
1563 report.done.assert_called_with(path, black.Changed.YES)
1565 def test_reformat_one_with_stdin_filename(self) -> None:
1567 "black.format_stdin_to_stdout",
1568 return_value=lambda *args, **kwargs: black.Changed.YES,
1570 report = MagicMock()
1572 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1577 write_back=black.WriteBack.YES,
1581 fsts.assert_called_once()
1582 # __BLACK_STDIN_FILENAME__ should have been striped
1583 report.done.assert_called_with(expected, black.Changed.YES)
1585 def test_reformat_one_with_stdin_and_existing_path(self) -> None:
1587 "black.format_stdin_to_stdout",
1588 return_value=lambda *args, **kwargs: black.Changed.YES,
1590 report = MagicMock()
1591 # Even with an existing file, since we are forcing stdin, black
1592 # should output to stdout and not modify the file inplace
1593 p = Path(str(THIS_DIR / "data/collections.py"))
1594 # Make sure is_file actually returns True
1595 self.assertTrue(p.is_file())
1596 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1601 write_back=black.WriteBack.YES,
1605 fsts.assert_called_once()
1606 # __BLACK_STDIN_FILENAME__ should have been striped
1607 report.done.assert_called_with(expected, black.Changed.YES)
1609 def test_gitignore_exclude(self) -> None:
1610 path = THIS_DIR / "data" / "include_exclude_tests"
1611 include = re.compile(r"\.pyi?$")
1612 exclude = re.compile(r"")
1613 report = black.Report()
1614 gitignore = PathSpec.from_lines(
1615 "gitwildmatch", ["exclude/", ".definitely_exclude"]
1617 sources: List[Path] = []
1619 Path(path / "b/dont_exclude/a.py"),
1620 Path(path / "b/dont_exclude/a.pyi"),
1622 this_abs = THIS_DIR.resolve()
1624 black.gen_python_files(
1635 self.assertEqual(sorted(expected), sorted(sources))
1637 def test_empty_include(self) -> None:
1638 path = THIS_DIR / "data" / "include_exclude_tests"
1639 report = black.Report()
1640 gitignore = PathSpec.from_lines("gitwildmatch", [])
1641 empty = re.compile(r"")
1642 sources: List[Path] = []
1644 Path(path / "b/exclude/a.pie"),
1645 Path(path / "b/exclude/a.py"),
1646 Path(path / "b/exclude/a.pyi"),
1647 Path(path / "b/dont_exclude/a.pie"),
1648 Path(path / "b/dont_exclude/a.py"),
1649 Path(path / "b/dont_exclude/a.pyi"),
1650 Path(path / "b/.definitely_exclude/a.pie"),
1651 Path(path / "b/.definitely_exclude/a.py"),
1652 Path(path / "b/.definitely_exclude/a.pyi"),
1654 this_abs = THIS_DIR.resolve()
1656 black.gen_python_files(
1660 re.compile(black.DEFAULT_EXCLUDES),
1667 self.assertEqual(sorted(expected), sorted(sources))
1669 def test_extend_exclude(self) -> None:
1670 path = THIS_DIR / "data" / "include_exclude_tests"
1671 report = black.Report()
1672 gitignore = PathSpec.from_lines("gitwildmatch", [])
1673 sources: List[Path] = []
1675 Path(path / "b/exclude/a.py"),
1676 Path(path / "b/dont_exclude/a.py"),
1678 this_abs = THIS_DIR.resolve()
1680 black.gen_python_files(
1683 re.compile(black.DEFAULT_INCLUDES),
1684 re.compile(r"\.pyi$"),
1685 re.compile(r"\.definitely_exclude"),
1691 self.assertEqual(sorted(expected), sorted(sources))
1693 def test_invalid_cli_regex(self) -> None:
1694 for option in ["--include", "--exclude", "--extend-exclude", "--force-exclude"]:
1695 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1697 def test_preserves_line_endings(self) -> None:
1698 with TemporaryDirectory() as workspace:
1699 test_file = Path(workspace) / "test.py"
1700 for nl in ["\n", "\r\n"]:
1701 contents = nl.join(["def f( ):", " pass"])
1702 test_file.write_bytes(contents.encode())
1703 ff(test_file, write_back=black.WriteBack.YES)
1704 updated_contents: bytes = test_file.read_bytes()
1705 self.assertIn(nl.encode(), updated_contents)
1707 self.assertNotIn(b"\r\n", updated_contents)
1709 def test_preserves_line_endings_via_stdin(self) -> None:
1710 for nl in ["\n", "\r\n"]:
1711 contents = nl.join(["def f( ):", " pass"])
1712 runner = BlackRunner()
1713 result = runner.invoke(
1714 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1716 self.assertEqual(result.exit_code, 0)
1717 output = runner.stdout_bytes
1718 self.assertIn(nl.encode("utf8"), output)
1720 self.assertNotIn(b"\r\n", output)
1722 def test_assert_equivalent_different_asts(self) -> None:
1723 with self.assertRaises(AssertionError):
1724 black.assert_equivalent("{}", "None")
1726 def test_symlink_out_of_root_directory(self) -> None:
1728 root = THIS_DIR.resolve()
1730 include = re.compile(black.DEFAULT_INCLUDES)
1731 exclude = re.compile(black.DEFAULT_EXCLUDES)
1732 report = black.Report()
1733 gitignore = PathSpec.from_lines("gitwildmatch", [])
1734 # `child` should behave like a symlink which resolved path is clearly
1735 # outside of the `root` directory.
1736 path.iterdir.return_value = [child]
1737 child.resolve.return_value = Path("/a/b/c")
1738 child.as_posix.return_value = "/a/b/c"
1739 child.is_symlink.return_value = True
1742 black.gen_python_files(
1753 except ValueError as ve:
1754 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1755 path.iterdir.assert_called_once()
1756 child.resolve.assert_called_once()
1757 child.is_symlink.assert_called_once()
1758 # `child` should behave like a strange file which resolved path is clearly
1759 # outside of the `root` directory.
1760 child.is_symlink.return_value = False
1761 with self.assertRaises(ValueError):
1763 black.gen_python_files(
1774 path.iterdir.assert_called()
1775 self.assertEqual(path.iterdir.call_count, 2)
1776 child.resolve.assert_called()
1777 self.assertEqual(child.resolve.call_count, 2)
1778 child.is_symlink.assert_called()
1779 self.assertEqual(child.is_symlink.call_count, 2)
1781 def test_shhh_click(self) -> None:
1783 from click import _unicodefun # type: ignore
1784 except ModuleNotFoundError:
1785 self.skipTest("Incompatible Click version")
1786 if not hasattr(_unicodefun, "_verify_python3_env"):
1787 self.skipTest("Incompatible Click version")
1788 # First, let's see if Click is crashing with a preferred ASCII charset.
1789 with patch("locale.getpreferredencoding") as gpe:
1790 gpe.return_value = "ASCII"
1791 with self.assertRaises(RuntimeError):
1792 _unicodefun._verify_python3_env()
1793 # Now, let's silence Click...
1795 # ...and confirm it's silent.
1796 with patch("locale.getpreferredencoding") as gpe:
1797 gpe.return_value = "ASCII"
1799 _unicodefun._verify_python3_env()
1800 except RuntimeError as re:
1801 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1803 def test_root_logger_not_used_directly(self) -> None:
1804 def fail(*args: Any, **kwargs: Any) -> None:
1805 self.fail("Record created with root logger")
1807 with patch.multiple(
1818 def test_invalid_config_return_code(self) -> None:
1819 tmp_file = Path(black.dump_to_file())
1821 tmp_config = Path(black.dump_to_file())
1823 args = ["--config", str(tmp_config), str(tmp_file)]
1824 self.invokeBlack(args, exit_code=2, ignore_config=False)
1828 def test_parse_pyproject_toml(self) -> None:
1829 test_toml_file = THIS_DIR / "test.toml"
1830 config = black.parse_pyproject_toml(str(test_toml_file))
1831 self.assertEqual(config["verbose"], 1)
1832 self.assertEqual(config["check"], "no")
1833 self.assertEqual(config["diff"], "y")
1834 self.assertEqual(config["color"], True)
1835 self.assertEqual(config["line_length"], 79)
1836 self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
1837 self.assertEqual(config["exclude"], r"\.pyi?$")
1838 self.assertEqual(config["include"], r"\.py?$")
1840 def test_read_pyproject_toml(self) -> None:
1841 test_toml_file = THIS_DIR / "test.toml"
1842 fake_ctx = FakeContext()
1843 black.read_pyproject_toml(fake_ctx, FakeParameter(), str(test_toml_file))
1844 config = fake_ctx.default_map
1845 self.assertEqual(config["verbose"], "1")
1846 self.assertEqual(config["check"], "no")
1847 self.assertEqual(config["diff"], "y")
1848 self.assertEqual(config["color"], "True")
1849 self.assertEqual(config["line_length"], "79")
1850 self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
1851 self.assertEqual(config["exclude"], r"\.pyi?$")
1852 self.assertEqual(config["include"], r"\.py?$")
1854 def test_find_project_root(self) -> None:
1855 with TemporaryDirectory() as workspace:
1856 root = Path(workspace)
1857 test_dir = root / "test"
1860 src_dir = root / "src"
1863 root_pyproject = root / "pyproject.toml"
1864 root_pyproject.touch()
1865 src_pyproject = src_dir / "pyproject.toml"
1866 src_pyproject.touch()
1867 src_python = src_dir / "foo.py"
1871 black.find_project_root((src_dir, test_dir)), root.resolve()
1873 self.assertEqual(black.find_project_root((src_dir,)), src_dir.resolve())
1874 self.assertEqual(black.find_project_root((src_python,)), src_dir.resolve())
1876 @patch("black.find_user_pyproject_toml", black.find_user_pyproject_toml.__wrapped__)
1877 def test_find_user_pyproject_toml_linux(self) -> None:
1878 if system() == "Windows":
1881 # Test if XDG_CONFIG_HOME is checked
1882 with TemporaryDirectory() as workspace:
1883 tmp_user_config = Path(workspace) / "black"
1884 with patch.dict("os.environ", {"XDG_CONFIG_HOME": workspace}):
1886 black.find_user_pyproject_toml(), tmp_user_config.resolve()
1889 # Test fallback for XDG_CONFIG_HOME
1890 with patch.dict("os.environ"):
1891 os.environ.pop("XDG_CONFIG_HOME", None)
1892 fallback_user_config = Path("~/.config").expanduser() / "black"
1894 black.find_user_pyproject_toml(), fallback_user_config.resolve()
1897 def test_find_user_pyproject_toml_windows(self) -> None:
1898 if system() != "Windows":
1901 user_config_path = Path.home() / ".black"
1902 self.assertEqual(black.find_user_pyproject_toml(), user_config_path.resolve())
1904 def test_bpo_33660_workaround(self) -> None:
1905 if system() == "Windows":
1908 # https://bugs.python.org/issue33660
1910 old_cwd = Path.cwd()
1914 path = Path("workspace") / "project"
1915 report = black.Report(verbose=True)
1916 normalized_path = black.normalize_path_maybe_ignore(path, root, report)
1917 self.assertEqual(normalized_path, "workspace/project")
1919 os.chdir(str(old_cwd))
1921 def test_newline_comment_interaction(self) -> None:
1922 source = "class A:\\\r\n# type: ignore\n pass\n"
1923 output = black.format_str(source, mode=DEFAULT_MODE)
1924 black.assert_stable(source, output, mode=DEFAULT_MODE)
1926 def test_bpo_2142_workaround(self) -> None:
1928 # https://bugs.python.org/issue2142
1930 source, _ = read_data("missing_final_newline.py")
1931 # read_data adds a trailing newline
1932 source = source.rstrip()
1933 expected, _ = read_data("missing_final_newline.diff")
1934 tmp_file = Path(black.dump_to_file(source, ensure_final_newline=False))
1935 diff_header = re.compile(
1936 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
1937 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
1940 result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
1941 self.assertEqual(result.exit_code, 0)
1944 actual = result.output
1945 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
1946 self.assertEqual(actual, expected)
1948 def test_docstring_reformat_for_py27(self) -> None:
1950 Check that stripping trailing whitespace from Python 2 docstrings
1951 doesn't trigger a "not equivalent to source" error
1954 b'def foo():\r\n """Testing\r\n Testing """\r\n print "Foo"\r\n'
1956 expected = 'def foo():\n """Testing\n Testing"""\n print "Foo"\n'
1958 result = CliRunner().invoke(
1960 ["-", "-q", "--target-version=py27"],
1961 input=BytesIO(source),
1964 self.assertEqual(result.exit_code, 0)
1965 actual = result.output
1966 self.assertFormatEqual(actual, expected)
1969 with open(black.__file__, "r", encoding="utf-8") as _bf:
1970 black_source_lines = _bf.readlines()
1973 def tracefunc(frame: types.FrameType, event: str, arg: Any) -> Callable:
1974 """Show function calls `from black/__init__.py` as they happen.
1976 Register this with `sys.settrace()` in a test you're debugging.
1981 stack = len(inspect.stack()) - 19
1983 filename = frame.f_code.co_filename
1984 lineno = frame.f_lineno
1985 func_sig_lineno = lineno - 1
1986 funcname = black_source_lines[func_sig_lineno].strip()
1987 while funcname.startswith("@"):
1988 func_sig_lineno += 1
1989 funcname = black_source_lines[func_sig_lineno].strip()
1990 if "black/__init__.py" in filename:
1991 print(f"{' ' * stack}{lineno}:{funcname}")
1995 if __name__ == "__main__":
1996 unittest.main(module="test_black")