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 def test_pep_572_version_detection(self) -> None:
294 source, _ = read_data("pep_572")
295 root = black.lib2to3_parse(source)
296 features = black.get_features_used(root)
297 self.assertIn(black.Feature.ASSIGNMENT_EXPRESSIONS, features)
298 versions = black.detect_target_versions(root)
299 self.assertIn(black.TargetVersion.PY38, versions)
301 def test_expression_ff(self) -> None:
302 source, expected = read_data("expression")
303 tmp_file = Path(black.dump_to_file(source))
305 self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
306 with open(tmp_file, encoding="utf8") as f:
310 self.assertFormatEqual(expected, actual)
311 with patch("black.dump_to_file", dump_to_stderr):
312 black.assert_equivalent(source, actual)
313 black.assert_stable(source, actual, DEFAULT_MODE)
315 def test_expression_diff(self) -> None:
316 source, _ = read_data("expression.py")
317 config = THIS_DIR / "data" / "empty_pyproject.toml"
318 expected, _ = read_data("expression.diff")
319 tmp_file = Path(black.dump_to_file(source))
320 diff_header = re.compile(
321 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
322 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
325 result = BlackRunner().invoke(
326 black.main, ["--diff", str(tmp_file), f"--config={config}"]
328 self.assertEqual(result.exit_code, 0)
331 actual = result.output
332 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
333 if expected != actual:
334 dump = black.dump_to_file(actual)
336 "Expected diff isn't equal to the actual. If you made changes to"
337 " expression.py and this is an anticipated difference, overwrite"
338 f" tests/data/expression.diff with {dump}"
340 self.assertEqual(expected, actual, msg)
342 def test_expression_diff_with_color(self) -> None:
343 source, _ = read_data("expression.py")
344 config = THIS_DIR / "data" / "empty_pyproject.toml"
345 expected, _ = read_data("expression.diff")
346 tmp_file = Path(black.dump_to_file(source))
348 result = BlackRunner().invoke(
349 black.main, ["--diff", "--color", str(tmp_file), f"--config={config}"]
353 actual = result.output
354 # We check the contents of the diff in `test_expression_diff`. All
355 # we need to check here is that color codes exist in the result.
356 self.assertIn("\033[1;37m", actual)
357 self.assertIn("\033[36m", actual)
358 self.assertIn("\033[32m", actual)
359 self.assertIn("\033[31m", actual)
360 self.assertIn("\033[0m", actual)
362 @patch("black.dump_to_file", dump_to_stderr)
363 def test_pep_570(self) -> None:
364 source, expected = read_data("pep_570")
366 self.assertFormatEqual(expected, actual)
367 black.assert_stable(source, actual, DEFAULT_MODE)
368 if sys.version_info >= (3, 8):
369 black.assert_equivalent(source, actual)
371 def test_detect_pos_only_arguments(self) -> None:
372 source, _ = read_data("pep_570")
373 root = black.lib2to3_parse(source)
374 features = black.get_features_used(root)
375 self.assertIn(black.Feature.POS_ONLY_ARGUMENTS, features)
376 versions = black.detect_target_versions(root)
377 self.assertIn(black.TargetVersion.PY38, versions)
379 @patch("black.dump_to_file", dump_to_stderr)
380 def test_string_quotes(self) -> None:
381 source, expected = read_data("string_quotes")
382 mode = black.Mode(experimental_string_processing=True)
383 actual = fs(source, mode=mode)
384 self.assertFormatEqual(expected, actual)
385 black.assert_equivalent(source, actual)
386 black.assert_stable(source, actual, mode)
387 mode = replace(mode, string_normalization=False)
388 not_normalized = fs(source, mode=mode)
389 self.assertFormatEqual(source.replace("\\\n", ""), not_normalized)
390 black.assert_equivalent(source, not_normalized)
391 black.assert_stable(source, not_normalized, mode=mode)
393 @patch("black.dump_to_file", dump_to_stderr)
394 def test_docstring_no_string_normalization(self) -> None:
395 """Like test_docstring but with string normalization off."""
396 source, expected = read_data("docstring_no_string_normalization")
397 mode = replace(DEFAULT_MODE, string_normalization=False)
398 actual = fs(source, mode=mode)
399 self.assertFormatEqual(expected, actual)
400 black.assert_equivalent(source, actual)
401 black.assert_stable(source, actual, mode)
403 def test_long_strings_flag_disabled(self) -> None:
404 """Tests for turning off the string processing logic."""
405 source, expected = read_data("long_strings_flag_disabled")
406 mode = replace(DEFAULT_MODE, experimental_string_processing=False)
407 actual = fs(source, mode=mode)
408 self.assertFormatEqual(expected, actual)
409 black.assert_stable(expected, actual, mode)
411 @patch("black.dump_to_file", dump_to_stderr)
412 def test_numeric_literals(self) -> None:
413 source, expected = read_data("numeric_literals")
414 mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
415 actual = fs(source, mode=mode)
416 self.assertFormatEqual(expected, actual)
417 black.assert_equivalent(source, actual)
418 black.assert_stable(source, actual, mode)
420 @patch("black.dump_to_file", dump_to_stderr)
421 def test_numeric_literals_ignoring_underscores(self) -> None:
422 source, expected = read_data("numeric_literals_skip_underscores")
423 mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
424 actual = fs(source, mode=mode)
425 self.assertFormatEqual(expected, actual)
426 black.assert_equivalent(source, actual)
427 black.assert_stable(source, actual, mode)
429 def test_skip_magic_trailing_comma(self) -> None:
430 source, _ = read_data("expression.py")
431 expected, _ = read_data("expression_skip_magic_trailing_comma.diff")
432 tmp_file = Path(black.dump_to_file(source))
433 diff_header = re.compile(
434 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
435 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
438 result = BlackRunner().invoke(black.main, ["-C", "--diff", str(tmp_file)])
439 self.assertEqual(result.exit_code, 0)
442 actual = result.output
443 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
444 actual = actual.rstrip() + "\n" # the diff output has a trailing space
445 if expected != actual:
446 dump = black.dump_to_file(actual)
448 "Expected diff isn't equal to the actual. If you made changes to"
449 " expression.py and this is an anticipated difference, overwrite"
450 f" tests/data/expression_skip_magic_trailing_comma.diff with {dump}"
452 self.assertEqual(expected, actual, msg)
454 @patch("black.dump_to_file", dump_to_stderr)
455 def test_python2_print_function(self) -> None:
456 source, expected = read_data("python2_print_function")
457 mode = replace(DEFAULT_MODE, target_versions={TargetVersion.PY27})
458 actual = fs(source, mode=mode)
459 self.assertFormatEqual(expected, actual)
460 black.assert_equivalent(source, actual)
461 black.assert_stable(source, actual, mode)
463 @patch("black.dump_to_file", dump_to_stderr)
464 def test_stub(self) -> None:
465 mode = replace(DEFAULT_MODE, is_pyi=True)
466 source, expected = read_data("stub.pyi")
467 actual = fs(source, mode=mode)
468 self.assertFormatEqual(expected, actual)
469 black.assert_stable(source, actual, mode)
471 @patch("black.dump_to_file", dump_to_stderr)
472 def test_async_as_identifier(self) -> None:
473 source_path = (THIS_DIR / "data" / "async_as_identifier.py").resolve()
474 source, expected = read_data("async_as_identifier")
476 self.assertFormatEqual(expected, actual)
477 major, minor = sys.version_info[:2]
478 if major < 3 or (major <= 3 and minor < 7):
479 black.assert_equivalent(source, actual)
480 black.assert_stable(source, actual, DEFAULT_MODE)
481 # ensure black can parse this when the target is 3.6
482 self.invokeBlack([str(source_path), "--target-version", "py36"])
483 # but not on 3.7, because async/await is no longer an identifier
484 self.invokeBlack([str(source_path), "--target-version", "py37"], exit_code=123)
486 @patch("black.dump_to_file", dump_to_stderr)
487 def test_python37(self) -> None:
488 source_path = (THIS_DIR / "data" / "python37.py").resolve()
489 source, expected = read_data("python37")
491 self.assertFormatEqual(expected, actual)
492 major, minor = sys.version_info[:2]
493 if major > 3 or (major == 3 and minor >= 7):
494 black.assert_equivalent(source, actual)
495 black.assert_stable(source, actual, DEFAULT_MODE)
496 # ensure black can parse this when the target is 3.7
497 self.invokeBlack([str(source_path), "--target-version", "py37"])
498 # but not on 3.6, because we use async as a reserved keyword
499 self.invokeBlack([str(source_path), "--target-version", "py36"], exit_code=123)
501 @patch("black.dump_to_file", dump_to_stderr)
502 def test_python38(self) -> None:
503 source, expected = read_data("python38")
505 self.assertFormatEqual(expected, actual)
506 major, minor = sys.version_info[:2]
507 if major > 3 or (major == 3 and minor >= 8):
508 black.assert_equivalent(source, actual)
509 black.assert_stable(source, actual, DEFAULT_MODE)
511 @patch("black.dump_to_file", dump_to_stderr)
512 def test_python39(self) -> None:
513 source, expected = read_data("python39")
515 self.assertFormatEqual(expected, actual)
516 major, minor = sys.version_info[:2]
517 if major > 3 or (major == 3 and minor >= 9):
518 black.assert_equivalent(source, actual)
519 black.assert_stable(source, actual, DEFAULT_MODE)
521 def test_tab_comment_indentation(self) -> None:
522 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
523 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
524 self.assertFormatEqual(contents_spc, fs(contents_spc))
525 self.assertFormatEqual(contents_spc, fs(contents_tab))
527 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
528 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
529 self.assertFormatEqual(contents_spc, fs(contents_spc))
530 self.assertFormatEqual(contents_spc, fs(contents_tab))
532 # mixed tabs and spaces (valid Python 2 code)
533 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t# comment\n pass\n"
534 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
535 self.assertFormatEqual(contents_spc, fs(contents_spc))
536 self.assertFormatEqual(contents_spc, fs(contents_tab))
538 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t\t# comment\n pass\n"
539 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
540 self.assertFormatEqual(contents_spc, fs(contents_spc))
541 self.assertFormatEqual(contents_spc, fs(contents_tab))
543 def test_report_verbose(self) -> None:
544 report = black.Report(verbose=True)
548 def out(msg: str, **kwargs: Any) -> None:
549 out_lines.append(msg)
551 def err(msg: str, **kwargs: Any) -> None:
552 err_lines.append(msg)
554 with patch("black.out", out), patch("black.err", err):
555 report.done(Path("f1"), black.Changed.NO)
556 self.assertEqual(len(out_lines), 1)
557 self.assertEqual(len(err_lines), 0)
558 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
559 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
560 self.assertEqual(report.return_code, 0)
561 report.done(Path("f2"), black.Changed.YES)
562 self.assertEqual(len(out_lines), 2)
563 self.assertEqual(len(err_lines), 0)
564 self.assertEqual(out_lines[-1], "reformatted f2")
566 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
568 report.done(Path("f3"), black.Changed.CACHED)
569 self.assertEqual(len(out_lines), 3)
570 self.assertEqual(len(err_lines), 0)
572 out_lines[-1], "f3 wasn't modified on disk since last run."
575 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
577 self.assertEqual(report.return_code, 0)
579 self.assertEqual(report.return_code, 1)
581 report.failed(Path("e1"), "boom")
582 self.assertEqual(len(out_lines), 3)
583 self.assertEqual(len(err_lines), 1)
584 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
586 unstyle(str(report)),
587 "1 file reformatted, 2 files left unchanged, 1 file failed to"
590 self.assertEqual(report.return_code, 123)
591 report.done(Path("f3"), black.Changed.YES)
592 self.assertEqual(len(out_lines), 4)
593 self.assertEqual(len(err_lines), 1)
594 self.assertEqual(out_lines[-1], "reformatted f3")
596 unstyle(str(report)),
597 "2 files reformatted, 2 files left unchanged, 1 file failed to"
600 self.assertEqual(report.return_code, 123)
601 report.failed(Path("e2"), "boom")
602 self.assertEqual(len(out_lines), 4)
603 self.assertEqual(len(err_lines), 2)
604 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
606 unstyle(str(report)),
607 "2 files reformatted, 2 files left unchanged, 2 files failed to"
610 self.assertEqual(report.return_code, 123)
611 report.path_ignored(Path("wat"), "no match")
612 self.assertEqual(len(out_lines), 5)
613 self.assertEqual(len(err_lines), 2)
614 self.assertEqual(out_lines[-1], "wat ignored: no match")
616 unstyle(str(report)),
617 "2 files reformatted, 2 files left unchanged, 2 files failed to"
620 self.assertEqual(report.return_code, 123)
621 report.done(Path("f4"), black.Changed.NO)
622 self.assertEqual(len(out_lines), 6)
623 self.assertEqual(len(err_lines), 2)
624 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
626 unstyle(str(report)),
627 "2 files reformatted, 3 files left unchanged, 2 files failed to"
630 self.assertEqual(report.return_code, 123)
633 unstyle(str(report)),
634 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
635 " would fail to reformat.",
640 unstyle(str(report)),
641 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
642 " would fail to reformat.",
645 def test_report_quiet(self) -> None:
646 report = black.Report(quiet=True)
650 def out(msg: str, **kwargs: Any) -> None:
651 out_lines.append(msg)
653 def err(msg: str, **kwargs: Any) -> None:
654 err_lines.append(msg)
656 with patch("black.out", out), patch("black.err", err):
657 report.done(Path("f1"), black.Changed.NO)
658 self.assertEqual(len(out_lines), 0)
659 self.assertEqual(len(err_lines), 0)
660 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
661 self.assertEqual(report.return_code, 0)
662 report.done(Path("f2"), black.Changed.YES)
663 self.assertEqual(len(out_lines), 0)
664 self.assertEqual(len(err_lines), 0)
666 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
668 report.done(Path("f3"), black.Changed.CACHED)
669 self.assertEqual(len(out_lines), 0)
670 self.assertEqual(len(err_lines), 0)
672 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
674 self.assertEqual(report.return_code, 0)
676 self.assertEqual(report.return_code, 1)
678 report.failed(Path("e1"), "boom")
679 self.assertEqual(len(out_lines), 0)
680 self.assertEqual(len(err_lines), 1)
681 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
683 unstyle(str(report)),
684 "1 file reformatted, 2 files left unchanged, 1 file failed to"
687 self.assertEqual(report.return_code, 123)
688 report.done(Path("f3"), black.Changed.YES)
689 self.assertEqual(len(out_lines), 0)
690 self.assertEqual(len(err_lines), 1)
692 unstyle(str(report)),
693 "2 files reformatted, 2 files left unchanged, 1 file failed to"
696 self.assertEqual(report.return_code, 123)
697 report.failed(Path("e2"), "boom")
698 self.assertEqual(len(out_lines), 0)
699 self.assertEqual(len(err_lines), 2)
700 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
702 unstyle(str(report)),
703 "2 files reformatted, 2 files left unchanged, 2 files failed to"
706 self.assertEqual(report.return_code, 123)
707 report.path_ignored(Path("wat"), "no match")
708 self.assertEqual(len(out_lines), 0)
709 self.assertEqual(len(err_lines), 2)
711 unstyle(str(report)),
712 "2 files reformatted, 2 files left unchanged, 2 files failed to"
715 self.assertEqual(report.return_code, 123)
716 report.done(Path("f4"), black.Changed.NO)
717 self.assertEqual(len(out_lines), 0)
718 self.assertEqual(len(err_lines), 2)
720 unstyle(str(report)),
721 "2 files reformatted, 3 files left unchanged, 2 files failed to"
724 self.assertEqual(report.return_code, 123)
727 unstyle(str(report)),
728 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
729 " would fail to reformat.",
734 unstyle(str(report)),
735 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
736 " would fail to reformat.",
739 def test_report_normal(self) -> None:
740 report = black.Report()
744 def out(msg: str, **kwargs: Any) -> None:
745 out_lines.append(msg)
747 def err(msg: str, **kwargs: Any) -> None:
748 err_lines.append(msg)
750 with patch("black.out", out), patch("black.err", err):
751 report.done(Path("f1"), black.Changed.NO)
752 self.assertEqual(len(out_lines), 0)
753 self.assertEqual(len(err_lines), 0)
754 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
755 self.assertEqual(report.return_code, 0)
756 report.done(Path("f2"), black.Changed.YES)
757 self.assertEqual(len(out_lines), 1)
758 self.assertEqual(len(err_lines), 0)
759 self.assertEqual(out_lines[-1], "reformatted f2")
761 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
763 report.done(Path("f3"), black.Changed.CACHED)
764 self.assertEqual(len(out_lines), 1)
765 self.assertEqual(len(err_lines), 0)
766 self.assertEqual(out_lines[-1], "reformatted f2")
768 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
770 self.assertEqual(report.return_code, 0)
772 self.assertEqual(report.return_code, 1)
774 report.failed(Path("e1"), "boom")
775 self.assertEqual(len(out_lines), 1)
776 self.assertEqual(len(err_lines), 1)
777 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
779 unstyle(str(report)),
780 "1 file reformatted, 2 files left unchanged, 1 file failed to"
783 self.assertEqual(report.return_code, 123)
784 report.done(Path("f3"), black.Changed.YES)
785 self.assertEqual(len(out_lines), 2)
786 self.assertEqual(len(err_lines), 1)
787 self.assertEqual(out_lines[-1], "reformatted f3")
789 unstyle(str(report)),
790 "2 files reformatted, 2 files left unchanged, 1 file failed to"
793 self.assertEqual(report.return_code, 123)
794 report.failed(Path("e2"), "boom")
795 self.assertEqual(len(out_lines), 2)
796 self.assertEqual(len(err_lines), 2)
797 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
799 unstyle(str(report)),
800 "2 files reformatted, 2 files left unchanged, 2 files failed to"
803 self.assertEqual(report.return_code, 123)
804 report.path_ignored(Path("wat"), "no match")
805 self.assertEqual(len(out_lines), 2)
806 self.assertEqual(len(err_lines), 2)
808 unstyle(str(report)),
809 "2 files reformatted, 2 files left unchanged, 2 files failed to"
812 self.assertEqual(report.return_code, 123)
813 report.done(Path("f4"), black.Changed.NO)
814 self.assertEqual(len(out_lines), 2)
815 self.assertEqual(len(err_lines), 2)
817 unstyle(str(report)),
818 "2 files reformatted, 3 files left unchanged, 2 files failed to"
821 self.assertEqual(report.return_code, 123)
824 unstyle(str(report)),
825 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
826 " would fail to reformat.",
831 unstyle(str(report)),
832 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
833 " would fail to reformat.",
836 def test_lib2to3_parse(self) -> None:
837 with self.assertRaises(black.InvalidInput):
838 black.lib2to3_parse("invalid syntax")
841 black.lib2to3_parse(straddling)
842 black.lib2to3_parse(straddling, {TargetVersion.PY27})
843 black.lib2to3_parse(straddling, {TargetVersion.PY36})
844 black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
847 black.lib2to3_parse(py2_only)
848 black.lib2to3_parse(py2_only, {TargetVersion.PY27})
849 with self.assertRaises(black.InvalidInput):
850 black.lib2to3_parse(py2_only, {TargetVersion.PY36})
851 with self.assertRaises(black.InvalidInput):
852 black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
854 py3_only = "exec(x, end=y)"
855 black.lib2to3_parse(py3_only)
856 with self.assertRaises(black.InvalidInput):
857 black.lib2to3_parse(py3_only, {TargetVersion.PY27})
858 black.lib2to3_parse(py3_only, {TargetVersion.PY36})
859 black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
861 def test_get_features_used_decorator(self) -> None:
862 # Test the feature detection of new decorator syntax
863 # since this makes some test cases of test_get_features_used()
864 # fails if it fails, this is tested first so that a useful case
866 simples, relaxed = read_data("decorators")
867 # skip explanation comments at the top of the file
868 for simple_test in simples.split("##")[1:]:
869 node = black.lib2to3_parse(simple_test)
870 decorator = str(node.children[0].children[0]).strip()
872 Feature.RELAXED_DECORATORS,
873 black.get_features_used(node),
875 f"decorator '{decorator}' follows python<=3.8 syntax"
876 "but is detected as 3.9+"
877 # f"The full node is\n{node!r}"
880 # skip the '# output' comment at the top of the output part
881 for relaxed_test in relaxed.split("##")[1:]:
882 node = black.lib2to3_parse(relaxed_test)
883 decorator = str(node.children[0].children[0]).strip()
885 Feature.RELAXED_DECORATORS,
886 black.get_features_used(node),
888 f"decorator '{decorator}' uses python3.9+ syntax"
889 "but is detected as python<=3.8"
890 # f"The full node is\n{node!r}"
894 def test_get_features_used(self) -> None:
895 node = black.lib2to3_parse("def f(*, arg): ...\n")
896 self.assertEqual(black.get_features_used(node), set())
897 node = black.lib2to3_parse("def f(*, arg,): ...\n")
898 self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
899 node = black.lib2to3_parse("f(*arg,)\n")
901 black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
903 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
904 self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
905 node = black.lib2to3_parse("123_456\n")
906 self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
907 node = black.lib2to3_parse("123456\n")
908 self.assertEqual(black.get_features_used(node), set())
909 source, expected = read_data("function")
910 node = black.lib2to3_parse(source)
911 expected_features = {
912 Feature.TRAILING_COMMA_IN_CALL,
913 Feature.TRAILING_COMMA_IN_DEF,
916 self.assertEqual(black.get_features_used(node), expected_features)
917 node = black.lib2to3_parse(expected)
918 self.assertEqual(black.get_features_used(node), expected_features)
919 source, expected = read_data("expression")
920 node = black.lib2to3_parse(source)
921 self.assertEqual(black.get_features_used(node), set())
922 node = black.lib2to3_parse(expected)
923 self.assertEqual(black.get_features_used(node), set())
925 def test_get_future_imports(self) -> None:
926 node = black.lib2to3_parse("\n")
927 self.assertEqual(set(), black.get_future_imports(node))
928 node = black.lib2to3_parse("from __future__ import black\n")
929 self.assertEqual({"black"}, black.get_future_imports(node))
930 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
931 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
932 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
933 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
934 node = black.lib2to3_parse(
935 "from __future__ import multiple\nfrom __future__ import imports\n"
937 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
938 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
939 self.assertEqual({"black"}, black.get_future_imports(node))
940 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
941 self.assertEqual({"black"}, black.get_future_imports(node))
942 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
943 self.assertEqual(set(), black.get_future_imports(node))
944 node = black.lib2to3_parse("from some.module import black\n")
945 self.assertEqual(set(), black.get_future_imports(node))
946 node = black.lib2to3_parse(
947 "from __future__ import unicode_literals as _unicode_literals"
949 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
950 node = black.lib2to3_parse(
951 "from __future__ import unicode_literals as _lol, print"
953 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
955 def test_debug_visitor(self) -> None:
956 source, _ = read_data("debug_visitor.py")
957 expected, _ = read_data("debug_visitor.out")
961 def out(msg: str, **kwargs: Any) -> None:
962 out_lines.append(msg)
964 def err(msg: str, **kwargs: Any) -> None:
965 err_lines.append(msg)
967 with patch("black.out", out), patch("black.err", err):
968 black.DebugVisitor.show(source)
969 actual = "\n".join(out_lines) + "\n"
971 if expected != actual:
972 log_name = black.dump_to_file(*out_lines)
976 f"AST print out is different. Actual version dumped to {log_name}",
979 def test_format_file_contents(self) -> None:
982 with self.assertRaises(black.NothingChanged):
983 black.format_file_contents(empty, mode=mode, fast=False)
985 with self.assertRaises(black.NothingChanged):
986 black.format_file_contents(just_nl, mode=mode, fast=False)
987 same = "j = [1, 2, 3]\n"
988 with self.assertRaises(black.NothingChanged):
989 black.format_file_contents(same, mode=mode, fast=False)
990 different = "j = [1,2,3]"
992 actual = black.format_file_contents(different, mode=mode, fast=False)
993 self.assertEqual(expected, actual)
994 invalid = "return if you can"
995 with self.assertRaises(black.InvalidInput) as e:
996 black.format_file_contents(invalid, mode=mode, fast=False)
997 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
999 def test_endmarker(self) -> None:
1000 n = black.lib2to3_parse("\n")
1001 self.assertEqual(n.type, black.syms.file_input)
1002 self.assertEqual(len(n.children), 1)
1003 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
1005 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
1006 def test_assertFormatEqual(self) -> None:
1010 def out(msg: str, **kwargs: Any) -> None:
1011 out_lines.append(msg)
1013 def err(msg: str, **kwargs: Any) -> None:
1014 err_lines.append(msg)
1016 with patch("black.out", out), patch("black.err", err):
1017 with self.assertRaises(AssertionError):
1018 self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")
1020 out_str = "".join(out_lines)
1021 self.assertTrue("Expected tree:" in out_str)
1022 self.assertTrue("Actual tree:" in out_str)
1023 self.assertEqual("".join(err_lines), "")
1025 def test_cache_broken_file(self) -> None:
1027 with cache_dir() as workspace:
1028 cache_file = black.get_cache_file(mode)
1029 with cache_file.open("w") as fobj:
1030 fobj.write("this is not a pickle")
1031 self.assertEqual(black.read_cache(mode), {})
1032 src = (workspace / "test.py").resolve()
1033 with src.open("w") as fobj:
1034 fobj.write("print('hello')")
1035 self.invokeBlack([str(src)])
1036 cache = black.read_cache(mode)
1037 self.assertIn(str(src), cache)
1039 def test_cache_single_file_already_cached(self) -> None:
1041 with cache_dir() as workspace:
1042 src = (workspace / "test.py").resolve()
1043 with src.open("w") as fobj:
1044 fobj.write("print('hello')")
1045 black.write_cache({}, [src], mode)
1046 self.invokeBlack([str(src)])
1047 with src.open("r") as fobj:
1048 self.assertEqual(fobj.read(), "print('hello')")
1051 def test_cache_multiple_files(self) -> None:
1053 with cache_dir() as workspace, patch(
1054 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1056 one = (workspace / "one.py").resolve()
1057 with one.open("w") as fobj:
1058 fobj.write("print('hello')")
1059 two = (workspace / "two.py").resolve()
1060 with two.open("w") as fobj:
1061 fobj.write("print('hello')")
1062 black.write_cache({}, [one], mode)
1063 self.invokeBlack([str(workspace)])
1064 with one.open("r") as fobj:
1065 self.assertEqual(fobj.read(), "print('hello')")
1066 with two.open("r") as fobj:
1067 self.assertEqual(fobj.read(), 'print("hello")\n')
1068 cache = black.read_cache(mode)
1069 self.assertIn(str(one), cache)
1070 self.assertIn(str(two), cache)
1072 def test_no_cache_when_writeback_diff(self) -> None:
1074 with cache_dir() as workspace:
1075 src = (workspace / "test.py").resolve()
1076 with src.open("w") as fobj:
1077 fobj.write("print('hello')")
1078 with patch("black.read_cache") as read_cache, patch(
1081 self.invokeBlack([str(src), "--diff"])
1082 cache_file = black.get_cache_file(mode)
1083 self.assertFalse(cache_file.exists())
1084 write_cache.assert_not_called()
1085 read_cache.assert_not_called()
1087 def test_no_cache_when_writeback_color_diff(self) -> None:
1089 with cache_dir() as workspace:
1090 src = (workspace / "test.py").resolve()
1091 with src.open("w") as fobj:
1092 fobj.write("print('hello')")
1093 with patch("black.read_cache") as read_cache, patch(
1096 self.invokeBlack([str(src), "--diff", "--color"])
1097 cache_file = black.get_cache_file(mode)
1098 self.assertFalse(cache_file.exists())
1099 write_cache.assert_not_called()
1100 read_cache.assert_not_called()
1103 def test_output_locking_when_writeback_diff(self) -> None:
1104 with cache_dir() as workspace:
1105 for tag in range(0, 4):
1106 src = (workspace / f"test{tag}.py").resolve()
1107 with src.open("w") as fobj:
1108 fobj.write("print('hello')")
1109 with patch("black.Manager", wraps=multiprocessing.Manager) as mgr:
1110 self.invokeBlack(["--diff", str(workspace)], exit_code=0)
1111 # this isn't quite doing what we want, but if it _isn't_
1112 # called then we cannot be using the lock it provides
1116 def test_output_locking_when_writeback_color_diff(self) -> None:
1117 with cache_dir() as workspace:
1118 for tag in range(0, 4):
1119 src = (workspace / f"test{tag}.py").resolve()
1120 with src.open("w") as fobj:
1121 fobj.write("print('hello')")
1122 with patch("black.Manager", wraps=multiprocessing.Manager) as mgr:
1123 self.invokeBlack(["--diff", "--color", str(workspace)], exit_code=0)
1124 # this isn't quite doing what we want, but if it _isn't_
1125 # called then we cannot be using the lock it provides
1128 def test_no_cache_when_stdin(self) -> None:
1131 result = CliRunner().invoke(
1132 black.main, ["-"], input=BytesIO(b"print('hello')")
1134 self.assertEqual(result.exit_code, 0)
1135 cache_file = black.get_cache_file(mode)
1136 self.assertFalse(cache_file.exists())
1138 def test_read_cache_no_cachefile(self) -> None:
1141 self.assertEqual(black.read_cache(mode), {})
1143 def test_write_cache_read_cache(self) -> None:
1145 with cache_dir() as workspace:
1146 src = (workspace / "test.py").resolve()
1148 black.write_cache({}, [src], mode)
1149 cache = black.read_cache(mode)
1150 self.assertIn(str(src), cache)
1151 self.assertEqual(cache[str(src)], black.get_cache_info(src))
1153 def test_filter_cached(self) -> None:
1154 with TemporaryDirectory() as workspace:
1155 path = Path(workspace)
1156 uncached = (path / "uncached").resolve()
1157 cached = (path / "cached").resolve()
1158 cached_but_changed = (path / "changed").resolve()
1161 cached_but_changed.touch()
1163 str(cached): black.get_cache_info(cached),
1164 str(cached_but_changed): (0.0, 0),
1166 todo, done = black.filter_cached(
1167 cache, {uncached, cached, cached_but_changed}
1169 self.assertEqual(todo, {uncached, cached_but_changed})
1170 self.assertEqual(done, {cached})
1172 def test_write_cache_creates_directory_if_needed(self) -> None:
1174 with cache_dir(exists=False) as workspace:
1175 self.assertFalse(workspace.exists())
1176 black.write_cache({}, [], mode)
1177 self.assertTrue(workspace.exists())
1180 def test_failed_formatting_does_not_get_cached(self) -> None:
1182 with cache_dir() as workspace, patch(
1183 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1185 failing = (workspace / "failing.py").resolve()
1186 with failing.open("w") as fobj:
1187 fobj.write("not actually python")
1188 clean = (workspace / "clean.py").resolve()
1189 with clean.open("w") as fobj:
1190 fobj.write('print("hello")\n')
1191 self.invokeBlack([str(workspace)], exit_code=123)
1192 cache = black.read_cache(mode)
1193 self.assertNotIn(str(failing), cache)
1194 self.assertIn(str(clean), cache)
1196 def test_write_cache_write_fail(self) -> None:
1198 with cache_dir(), patch.object(Path, "open") as mock:
1199 mock.side_effect = OSError
1200 black.write_cache({}, [], mode)
1203 @patch("black.ProcessPoolExecutor", MagicMock(side_effect=OSError))
1204 def test_works_in_mono_process_only_environment(self) -> None:
1205 with cache_dir() as workspace:
1207 (workspace / "one.py").resolve(),
1208 (workspace / "two.py").resolve(),
1210 f.write_text('print("hello")\n')
1211 self.invokeBlack([str(workspace)])
1214 def test_check_diff_use_together(self) -> None:
1216 # Files which will be reformatted.
1217 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1218 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1219 # Files which will not be reformatted.
1220 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1221 self.invokeBlack([str(src2), "--diff", "--check"])
1222 # Multi file command.
1223 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1225 def test_no_files(self) -> None:
1227 # Without an argument, black exits with error code 0.
1228 self.invokeBlack([])
1230 def test_broken_symlink(self) -> None:
1231 with cache_dir() as workspace:
1232 symlink = workspace / "broken_link.py"
1234 symlink.symlink_to("nonexistent.py")
1235 except OSError as e:
1236 self.skipTest(f"Can't create symlinks: {e}")
1237 self.invokeBlack([str(workspace.resolve())])
1239 def test_read_cache_line_lengths(self) -> None:
1241 short_mode = replace(DEFAULT_MODE, line_length=1)
1242 with cache_dir() as workspace:
1243 path = (workspace / "file.py").resolve()
1245 black.write_cache({}, [path], mode)
1246 one = black.read_cache(mode)
1247 self.assertIn(str(path), one)
1248 two = black.read_cache(short_mode)
1249 self.assertNotIn(str(path), two)
1251 def test_single_file_force_pyi(self) -> None:
1252 pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
1253 contents, expected = read_data("force_pyi")
1254 with cache_dir() as workspace:
1255 path = (workspace / "file.py").resolve()
1256 with open(path, "w") as fh:
1258 self.invokeBlack([str(path), "--pyi"])
1259 with open(path, "r") as fh:
1261 # verify cache with --pyi is separate
1262 pyi_cache = black.read_cache(pyi_mode)
1263 self.assertIn(str(path), pyi_cache)
1264 normal_cache = black.read_cache(DEFAULT_MODE)
1265 self.assertNotIn(str(path), normal_cache)
1266 self.assertFormatEqual(expected, actual)
1267 black.assert_equivalent(contents, actual)
1268 black.assert_stable(contents, actual, pyi_mode)
1271 def test_multi_file_force_pyi(self) -> None:
1272 reg_mode = DEFAULT_MODE
1273 pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
1274 contents, expected = read_data("force_pyi")
1275 with cache_dir() as workspace:
1277 (workspace / "file1.py").resolve(),
1278 (workspace / "file2.py").resolve(),
1281 with open(path, "w") as fh:
1283 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1285 with open(path, "r") as fh:
1287 self.assertEqual(actual, expected)
1288 # verify cache with --pyi is separate
1289 pyi_cache = black.read_cache(pyi_mode)
1290 normal_cache = black.read_cache(reg_mode)
1292 self.assertIn(str(path), pyi_cache)
1293 self.assertNotIn(str(path), normal_cache)
1295 def test_pipe_force_pyi(self) -> None:
1296 source, expected = read_data("force_pyi")
1297 result = CliRunner().invoke(
1298 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1300 self.assertEqual(result.exit_code, 0)
1301 actual = result.output
1302 self.assertFormatEqual(actual, expected)
1304 def test_single_file_force_py36(self) -> None:
1305 reg_mode = DEFAULT_MODE
1306 py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
1307 source, expected = read_data("force_py36")
1308 with cache_dir() as workspace:
1309 path = (workspace / "file.py").resolve()
1310 with open(path, "w") as fh:
1312 self.invokeBlack([str(path), *PY36_ARGS])
1313 with open(path, "r") as fh:
1315 # verify cache with --target-version is separate
1316 py36_cache = black.read_cache(py36_mode)
1317 self.assertIn(str(path), py36_cache)
1318 normal_cache = black.read_cache(reg_mode)
1319 self.assertNotIn(str(path), normal_cache)
1320 self.assertEqual(actual, expected)
1323 def test_multi_file_force_py36(self) -> None:
1324 reg_mode = DEFAULT_MODE
1325 py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
1326 source, expected = read_data("force_py36")
1327 with cache_dir() as workspace:
1329 (workspace / "file1.py").resolve(),
1330 (workspace / "file2.py").resolve(),
1333 with open(path, "w") as fh:
1335 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1337 with open(path, "r") as fh:
1339 self.assertEqual(actual, expected)
1340 # verify cache with --target-version is separate
1341 pyi_cache = black.read_cache(py36_mode)
1342 normal_cache = black.read_cache(reg_mode)
1344 self.assertIn(str(path), pyi_cache)
1345 self.assertNotIn(str(path), normal_cache)
1347 def test_pipe_force_py36(self) -> None:
1348 source, expected = read_data("force_py36")
1349 result = CliRunner().invoke(
1351 ["-", "-q", "--target-version=py36"],
1352 input=BytesIO(source.encode("utf8")),
1354 self.assertEqual(result.exit_code, 0)
1355 actual = result.output
1356 self.assertFormatEqual(actual, expected)
1358 def test_include_exclude(self) -> None:
1359 path = THIS_DIR / "data" / "include_exclude_tests"
1360 include = re.compile(r"\.pyi?$")
1361 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1362 report = black.Report()
1363 gitignore = PathSpec.from_lines("gitwildmatch", [])
1364 sources: List[Path] = []
1366 Path(path / "b/dont_exclude/a.py"),
1367 Path(path / "b/dont_exclude/a.pyi"),
1369 this_abs = THIS_DIR.resolve()
1371 black.gen_python_files(
1382 self.assertEqual(sorted(expected), sorted(sources))
1384 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1385 def test_exclude_for_issue_1572(self) -> None:
1386 # Exclude shouldn't touch files that were explicitly given to Black through the
1387 # CLI. Exclude is supposed to only apply to the recursive discovery of files.
1388 # https://github.com/psf/black/issues/1572
1389 path = THIS_DIR / "data" / "include_exclude_tests"
1391 exclude = r"/exclude/|a\.py"
1392 src = str(path / "b/exclude/a.py")
1393 report = black.Report()
1394 expected = [Path(path / "b/exclude/a.py")]
1401 include=re.compile(include),
1402 exclude=re.compile(exclude),
1403 extend_exclude=None,
1406 stdin_filename=None,
1409 self.assertEqual(sorted(expected), sorted(sources))
1411 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1412 def test_get_sources_with_stdin(self) -> None:
1414 exclude = r"/exclude/|a\.py"
1416 report = black.Report()
1417 expected = [Path("-")]
1424 include=re.compile(include),
1425 exclude=re.compile(exclude),
1426 extend_exclude=None,
1429 stdin_filename=None,
1432 self.assertEqual(sorted(expected), sorted(sources))
1434 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1435 def test_get_sources_with_stdin_filename(self) -> None:
1437 exclude = r"/exclude/|a\.py"
1439 report = black.Report()
1440 stdin_filename = str(THIS_DIR / "data/collections.py")
1441 expected = [Path(f"__BLACK_STDIN_FILENAME__{stdin_filename}")]
1448 include=re.compile(include),
1449 exclude=re.compile(exclude),
1450 extend_exclude=None,
1453 stdin_filename=stdin_filename,
1456 self.assertEqual(sorted(expected), sorted(sources))
1458 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1459 def test_get_sources_with_stdin_filename_and_exclude(self) -> None:
1460 # Exclude shouldn't exclude stdin_filename since it is mimicing the
1461 # file being passed directly. This is the same as
1462 # test_exclude_for_issue_1572
1463 path = THIS_DIR / "data" / "include_exclude_tests"
1465 exclude = r"/exclude/|a\.py"
1467 report = black.Report()
1468 stdin_filename = str(path / "b/exclude/a.py")
1469 expected = [Path(f"__BLACK_STDIN_FILENAME__{stdin_filename}")]
1476 include=re.compile(include),
1477 exclude=re.compile(exclude),
1478 extend_exclude=None,
1481 stdin_filename=stdin_filename,
1484 self.assertEqual(sorted(expected), sorted(sources))
1486 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1487 def test_get_sources_with_stdin_filename_and_extend_exclude(self) -> None:
1488 # Extend exclude shouldn't exclude stdin_filename since it is mimicking the
1489 # file being passed directly. This is the same as
1490 # test_exclude_for_issue_1572
1491 path = THIS_DIR / "data" / "include_exclude_tests"
1493 extend_exclude = r"/exclude/|a\.py"
1495 report = black.Report()
1496 stdin_filename = str(path / "b/exclude/a.py")
1497 expected = [Path(f"__BLACK_STDIN_FILENAME__{stdin_filename}")]
1504 include=re.compile(include),
1505 exclude=re.compile(""),
1506 extend_exclude=re.compile(extend_exclude),
1509 stdin_filename=stdin_filename,
1512 self.assertEqual(sorted(expected), sorted(sources))
1514 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1515 def test_get_sources_with_stdin_filename_and_force_exclude(self) -> None:
1516 # Force exclude should exclude the file when passing it through
1518 path = THIS_DIR / "data" / "include_exclude_tests"
1520 force_exclude = r"/exclude/|a\.py"
1522 report = black.Report()
1523 stdin_filename = str(path / "b/exclude/a.py")
1530 include=re.compile(include),
1531 exclude=re.compile(""),
1532 extend_exclude=None,
1533 force_exclude=re.compile(force_exclude),
1535 stdin_filename=stdin_filename,
1538 self.assertEqual([], sorted(sources))
1540 def test_reformat_one_with_stdin(self) -> None:
1542 "black.format_stdin_to_stdout",
1543 return_value=lambda *args, **kwargs: black.Changed.YES,
1545 report = MagicMock()
1550 write_back=black.WriteBack.YES,
1554 fsts.assert_called_once()
1555 report.done.assert_called_with(path, black.Changed.YES)
1557 def test_reformat_one_with_stdin_filename(self) -> None:
1559 "black.format_stdin_to_stdout",
1560 return_value=lambda *args, **kwargs: black.Changed.YES,
1562 report = MagicMock()
1564 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1569 write_back=black.WriteBack.YES,
1573 fsts.assert_called_once()
1574 # __BLACK_STDIN_FILENAME__ should have been striped
1575 report.done.assert_called_with(expected, black.Changed.YES)
1577 def test_reformat_one_with_stdin_and_existing_path(self) -> None:
1579 "black.format_stdin_to_stdout",
1580 return_value=lambda *args, **kwargs: black.Changed.YES,
1582 report = MagicMock()
1583 # Even with an existing file, since we are forcing stdin, black
1584 # should output to stdout and not modify the file inplace
1585 p = Path(str(THIS_DIR / "data/collections.py"))
1586 # Make sure is_file actually returns True
1587 self.assertTrue(p.is_file())
1588 path = Path(f"__BLACK_STDIN_FILENAME__{p}")
1593 write_back=black.WriteBack.YES,
1597 fsts.assert_called_once()
1598 # __BLACK_STDIN_FILENAME__ should have been striped
1599 report.done.assert_called_with(expected, black.Changed.YES)
1601 def test_gitignore_exclude(self) -> None:
1602 path = THIS_DIR / "data" / "include_exclude_tests"
1603 include = re.compile(r"\.pyi?$")
1604 exclude = re.compile(r"")
1605 report = black.Report()
1606 gitignore = PathSpec.from_lines(
1607 "gitwildmatch", ["exclude/", ".definitely_exclude"]
1609 sources: List[Path] = []
1611 Path(path / "b/dont_exclude/a.py"),
1612 Path(path / "b/dont_exclude/a.pyi"),
1614 this_abs = THIS_DIR.resolve()
1616 black.gen_python_files(
1627 self.assertEqual(sorted(expected), sorted(sources))
1629 def test_empty_include(self) -> None:
1630 path = THIS_DIR / "data" / "include_exclude_tests"
1631 report = black.Report()
1632 gitignore = PathSpec.from_lines("gitwildmatch", [])
1633 empty = re.compile(r"")
1634 sources: List[Path] = []
1636 Path(path / "b/exclude/a.pie"),
1637 Path(path / "b/exclude/a.py"),
1638 Path(path / "b/exclude/a.pyi"),
1639 Path(path / "b/dont_exclude/a.pie"),
1640 Path(path / "b/dont_exclude/a.py"),
1641 Path(path / "b/dont_exclude/a.pyi"),
1642 Path(path / "b/.definitely_exclude/a.pie"),
1643 Path(path / "b/.definitely_exclude/a.py"),
1644 Path(path / "b/.definitely_exclude/a.pyi"),
1646 this_abs = THIS_DIR.resolve()
1648 black.gen_python_files(
1652 re.compile(black.DEFAULT_EXCLUDES),
1659 self.assertEqual(sorted(expected), sorted(sources))
1661 def test_extend_exclude(self) -> None:
1662 path = THIS_DIR / "data" / "include_exclude_tests"
1663 report = black.Report()
1664 gitignore = PathSpec.from_lines("gitwildmatch", [])
1665 sources: List[Path] = []
1667 Path(path / "b/exclude/a.py"),
1668 Path(path / "b/dont_exclude/a.py"),
1670 this_abs = THIS_DIR.resolve()
1672 black.gen_python_files(
1675 re.compile(black.DEFAULT_INCLUDES),
1676 re.compile(r"\.pyi$"),
1677 re.compile(r"\.definitely_exclude"),
1683 self.assertEqual(sorted(expected), sorted(sources))
1685 def test_invalid_cli_regex(self) -> None:
1686 for option in ["--include", "--exclude", "--extend-exclude", "--force-exclude"]:
1687 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1689 def test_preserves_line_endings(self) -> None:
1690 with TemporaryDirectory() as workspace:
1691 test_file = Path(workspace) / "test.py"
1692 for nl in ["\n", "\r\n"]:
1693 contents = nl.join(["def f( ):", " pass"])
1694 test_file.write_bytes(contents.encode())
1695 ff(test_file, write_back=black.WriteBack.YES)
1696 updated_contents: bytes = test_file.read_bytes()
1697 self.assertIn(nl.encode(), updated_contents)
1699 self.assertNotIn(b"\r\n", updated_contents)
1701 def test_preserves_line_endings_via_stdin(self) -> None:
1702 for nl in ["\n", "\r\n"]:
1703 contents = nl.join(["def f( ):", " pass"])
1704 runner = BlackRunner()
1705 result = runner.invoke(
1706 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1708 self.assertEqual(result.exit_code, 0)
1709 output = runner.stdout_bytes
1710 self.assertIn(nl.encode("utf8"), output)
1712 self.assertNotIn(b"\r\n", output)
1714 def test_assert_equivalent_different_asts(self) -> None:
1715 with self.assertRaises(AssertionError):
1716 black.assert_equivalent("{}", "None")
1718 def test_symlink_out_of_root_directory(self) -> None:
1720 root = THIS_DIR.resolve()
1722 include = re.compile(black.DEFAULT_INCLUDES)
1723 exclude = re.compile(black.DEFAULT_EXCLUDES)
1724 report = black.Report()
1725 gitignore = PathSpec.from_lines("gitwildmatch", [])
1726 # `child` should behave like a symlink which resolved path is clearly
1727 # outside of the `root` directory.
1728 path.iterdir.return_value = [child]
1729 child.resolve.return_value = Path("/a/b/c")
1730 child.as_posix.return_value = "/a/b/c"
1731 child.is_symlink.return_value = True
1734 black.gen_python_files(
1745 except ValueError as ve:
1746 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1747 path.iterdir.assert_called_once()
1748 child.resolve.assert_called_once()
1749 child.is_symlink.assert_called_once()
1750 # `child` should behave like a strange file which resolved path is clearly
1751 # outside of the `root` directory.
1752 child.is_symlink.return_value = False
1753 with self.assertRaises(ValueError):
1755 black.gen_python_files(
1766 path.iterdir.assert_called()
1767 self.assertEqual(path.iterdir.call_count, 2)
1768 child.resolve.assert_called()
1769 self.assertEqual(child.resolve.call_count, 2)
1770 child.is_symlink.assert_called()
1771 self.assertEqual(child.is_symlink.call_count, 2)
1773 def test_shhh_click(self) -> None:
1775 from click import _unicodefun # type: ignore
1776 except ModuleNotFoundError:
1777 self.skipTest("Incompatible Click version")
1778 if not hasattr(_unicodefun, "_verify_python3_env"):
1779 self.skipTest("Incompatible Click version")
1780 # First, let's see if Click is crashing with a preferred ASCII charset.
1781 with patch("locale.getpreferredencoding") as gpe:
1782 gpe.return_value = "ASCII"
1783 with self.assertRaises(RuntimeError):
1784 _unicodefun._verify_python3_env()
1785 # Now, let's silence Click...
1787 # ...and confirm it's silent.
1788 with patch("locale.getpreferredencoding") as gpe:
1789 gpe.return_value = "ASCII"
1791 _unicodefun._verify_python3_env()
1792 except RuntimeError as re:
1793 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1795 def test_root_logger_not_used_directly(self) -> None:
1796 def fail(*args: Any, **kwargs: Any) -> None:
1797 self.fail("Record created with root logger")
1799 with patch.multiple(
1810 def test_invalid_config_return_code(self) -> None:
1811 tmp_file = Path(black.dump_to_file())
1813 tmp_config = Path(black.dump_to_file())
1815 args = ["--config", str(tmp_config), str(tmp_file)]
1816 self.invokeBlack(args, exit_code=2, ignore_config=False)
1820 def test_parse_pyproject_toml(self) -> None:
1821 test_toml_file = THIS_DIR / "test.toml"
1822 config = black.parse_pyproject_toml(str(test_toml_file))
1823 self.assertEqual(config["verbose"], 1)
1824 self.assertEqual(config["check"], "no")
1825 self.assertEqual(config["diff"], "y")
1826 self.assertEqual(config["color"], True)
1827 self.assertEqual(config["line_length"], 79)
1828 self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
1829 self.assertEqual(config["exclude"], r"\.pyi?$")
1830 self.assertEqual(config["include"], r"\.py?$")
1832 def test_read_pyproject_toml(self) -> None:
1833 test_toml_file = THIS_DIR / "test.toml"
1834 fake_ctx = FakeContext()
1835 black.read_pyproject_toml(fake_ctx, FakeParameter(), str(test_toml_file))
1836 config = fake_ctx.default_map
1837 self.assertEqual(config["verbose"], "1")
1838 self.assertEqual(config["check"], "no")
1839 self.assertEqual(config["diff"], "y")
1840 self.assertEqual(config["color"], "True")
1841 self.assertEqual(config["line_length"], "79")
1842 self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
1843 self.assertEqual(config["exclude"], r"\.pyi?$")
1844 self.assertEqual(config["include"], r"\.py?$")
1846 def test_find_project_root(self) -> None:
1847 with TemporaryDirectory() as workspace:
1848 root = Path(workspace)
1849 test_dir = root / "test"
1852 src_dir = root / "src"
1855 root_pyproject = root / "pyproject.toml"
1856 root_pyproject.touch()
1857 src_pyproject = src_dir / "pyproject.toml"
1858 src_pyproject.touch()
1859 src_python = src_dir / "foo.py"
1863 black.find_project_root((src_dir, test_dir)), root.resolve()
1865 self.assertEqual(black.find_project_root((src_dir,)), src_dir.resolve())
1866 self.assertEqual(black.find_project_root((src_python,)), src_dir.resolve())
1868 @patch("black.find_user_pyproject_toml", black.find_user_pyproject_toml.__wrapped__)
1869 def test_find_user_pyproject_toml_linux(self) -> None:
1870 if system() == "Windows":
1873 # Test if XDG_CONFIG_HOME is checked
1874 with TemporaryDirectory() as workspace:
1875 tmp_user_config = Path(workspace) / "black"
1876 with patch.dict("os.environ", {"XDG_CONFIG_HOME": workspace}):
1878 black.find_user_pyproject_toml(), tmp_user_config.resolve()
1881 # Test fallback for XDG_CONFIG_HOME
1882 with patch.dict("os.environ"):
1883 os.environ.pop("XDG_CONFIG_HOME", None)
1884 fallback_user_config = Path("~/.config").expanduser() / "black"
1886 black.find_user_pyproject_toml(), fallback_user_config.resolve()
1889 def test_find_user_pyproject_toml_windows(self) -> None:
1890 if system() != "Windows":
1893 user_config_path = Path.home() / ".black"
1894 self.assertEqual(black.find_user_pyproject_toml(), user_config_path.resolve())
1896 def test_bpo_33660_workaround(self) -> None:
1897 if system() == "Windows":
1900 # https://bugs.python.org/issue33660
1902 old_cwd = Path.cwd()
1906 path = Path("workspace") / "project"
1907 report = black.Report(verbose=True)
1908 normalized_path = black.normalize_path_maybe_ignore(path, root, report)
1909 self.assertEqual(normalized_path, "workspace/project")
1911 os.chdir(str(old_cwd))
1913 def test_newline_comment_interaction(self) -> None:
1914 source = "class A:\\\r\n# type: ignore\n pass\n"
1915 output = black.format_str(source, mode=DEFAULT_MODE)
1916 black.assert_stable(source, output, mode=DEFAULT_MODE)
1918 def test_bpo_2142_workaround(self) -> None:
1920 # https://bugs.python.org/issue2142
1922 source, _ = read_data("missing_final_newline.py")
1923 # read_data adds a trailing newline
1924 source = source.rstrip()
1925 expected, _ = read_data("missing_final_newline.diff")
1926 tmp_file = Path(black.dump_to_file(source, ensure_final_newline=False))
1927 diff_header = re.compile(
1928 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
1929 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
1932 result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
1933 self.assertEqual(result.exit_code, 0)
1936 actual = result.output
1937 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
1938 self.assertEqual(actual, expected)
1940 def test_docstring_reformat_for_py27(self) -> None:
1942 Check that stripping trailing whitespace from Python 2 docstrings
1943 doesn't trigger a "not equivalent to source" error
1946 b'def foo():\r\n """Testing\r\n Testing """\r\n print "Foo"\r\n'
1948 expected = 'def foo():\n """Testing\n Testing"""\n print "Foo"\n'
1950 result = CliRunner().invoke(
1952 ["-", "-q", "--target-version=py27"],
1953 input=BytesIO(source),
1956 self.assertEqual(result.exit_code, 0)
1957 actual = result.output
1958 self.assertFormatEqual(actual, expected)
1961 with open(black.__file__, "r", encoding="utf-8") as _bf:
1962 black_source_lines = _bf.readlines()
1965 def tracefunc(frame: types.FrameType, event: str, arg: Any) -> Callable:
1966 """Show function calls `from black/__init__.py` as they happen.
1968 Register this with `sys.settrace()` in a test you're debugging.
1973 stack = len(inspect.stack()) - 19
1975 filename = frame.f_code.co_filename
1976 lineno = frame.f_lineno
1977 func_sig_lineno = lineno - 1
1978 funcname = black_source_lines[func_sig_lineno].strip()
1979 while funcname.startswith("@"):
1980 func_sig_lineno += 1
1981 funcname = black_source_lines[func_sig_lineno].strip()
1982 if "black/__init__.py" in filename:
1983 print(f"{' ' * stack}{lineno}:{funcname}")
1987 if __name__ == "__main__":
1988 unittest.main(module="test_black")