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
8 from functools import partial
10 from io import BytesIO, TextIOWrapper
12 from pathlib import Path
13 from platform import system
16 from tempfile import TemporaryDirectory
29 from unittest.mock import patch, MagicMock
32 from click import unstyle
33 from click.testing import CliRunner
36 from black import Feature, TargetVersion
38 from pathspec import PathSpec
40 # Import other test classes
41 from tests.util import THIS_DIR, read_data, DETERMINISTIC_HEADER
42 from .test_primer import PrimerCLITests # noqa: F401
45 DEFAULT_MODE = black.FileMode(experimental_string_processing=True)
46 ff = partial(black.format_file_in_place, mode=DEFAULT_MODE, fast=True)
47 fs = partial(black.format_str, mode=DEFAULT_MODE)
48 THIS_FILE = Path(__file__)
55 PY36_ARGS = [f"--target-version={version.name.lower()}" for version in PY36_VERSIONS]
60 def dump_to_stderr(*output: str) -> str:
61 return "\n" + "\n".join(output) + "\n"
65 def cache_dir(exists: bool = True) -> Iterator[Path]:
66 with TemporaryDirectory() as workspace:
67 cache_dir = Path(workspace)
69 cache_dir = cache_dir / "new"
70 with patch("black.CACHE_DIR", cache_dir):
75 def event_loop() -> Iterator[None]:
76 policy = asyncio.get_event_loop_policy()
77 loop = policy.new_event_loop()
78 asyncio.set_event_loop(loop)
86 class FakeContext(click.Context):
87 """A fake click Context for when calling functions that need it."""
89 def __init__(self) -> None:
90 self.default_map: Dict[str, Any] = {}
93 class FakeParameter(click.Parameter):
94 """A fake click Parameter for when calling functions that need it."""
96 def __init__(self) -> None:
100 class BlackRunner(CliRunner):
101 """Modify CliRunner so that stderr is not merged with stdout.
103 This is a hack that can be removed once we depend on Click 7.x"""
105 def __init__(self) -> None:
106 self.stderrbuf = BytesIO()
107 self.stdoutbuf = BytesIO()
108 self.stdout_bytes = b""
109 self.stderr_bytes = b""
113 def isolation(self, *args: Any, **kwargs: Any) -> Generator[BinaryIO, None, None]:
114 with super().isolation(*args, **kwargs) as output:
116 hold_stderr = sys.stderr
117 sys.stderr = TextIOWrapper(self.stderrbuf, encoding=self.charset)
120 self.stdout_bytes = sys.stdout.buffer.getvalue() # type: ignore
121 self.stderr_bytes = sys.stderr.buffer.getvalue() # type: ignore
122 sys.stderr = hold_stderr
125 class BlackTestCase(unittest.TestCase):
127 _diffThreshold = 2 ** 20
129 def assertFormatEqual(self, expected: str, actual: str) -> None:
130 if actual != expected and not os.environ.get("SKIP_AST_PRINT"):
131 bdv: black.DebugVisitor[Any]
132 black.out("Expected tree:", fg="green")
134 exp_node = black.lib2to3_parse(expected)
135 bdv = black.DebugVisitor()
136 list(bdv.visit(exp_node))
137 except Exception as ve:
139 black.out("Actual tree:", fg="red")
141 exp_node = black.lib2to3_parse(actual)
142 bdv = black.DebugVisitor()
143 list(bdv.visit(exp_node))
144 except Exception as ve:
146 self.assertMultiLineEqual(expected, actual)
149 self, args: List[str], exit_code: int = 0, ignore_config: bool = True
151 runner = BlackRunner()
153 args = ["--verbose", "--config", str(THIS_DIR / "empty.toml"), *args]
154 result = runner.invoke(black.main, args)
159 f"Failed with args: {args}\n"
160 f"stdout: {runner.stdout_bytes.decode()!r}\n"
161 f"stderr: {runner.stderr_bytes.decode()!r}\n"
162 f"exception: {result.exception}"
166 @patch("black.dump_to_file", dump_to_stderr)
167 def checkSourceFile(self, name: str, mode: black.FileMode = DEFAULT_MODE) -> None:
168 path = THIS_DIR.parent / name
169 source, expected = read_data(str(path), data=False)
170 actual = fs(source, mode=mode)
171 self.assertFormatEqual(expected, actual)
172 black.assert_equivalent(source, actual)
173 black.assert_stable(source, actual, mode)
174 self.assertFalse(ff(path))
176 @patch("black.dump_to_file", dump_to_stderr)
177 def test_empty(self) -> None:
178 source = expected = ""
180 self.assertFormatEqual(expected, actual)
181 black.assert_equivalent(source, actual)
182 black.assert_stable(source, actual, DEFAULT_MODE)
184 def test_empty_ff(self) -> None:
186 tmp_file = Path(black.dump_to_file())
188 self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES))
189 with open(tmp_file, encoding="utf8") as f:
193 self.assertFormatEqual(expected, actual)
195 def test_run_on_test_black(self) -> None:
196 self.checkSourceFile("tests/test_black.py")
198 def test_run_on_test_blackd(self) -> None:
199 self.checkSourceFile("tests/test_blackd.py")
201 def test_black(self) -> None:
202 self.checkSourceFile("src/black/__init__.py")
204 def test_pygram(self) -> None:
205 self.checkSourceFile("src/blib2to3/pygram.py")
207 def test_pytree(self) -> None:
208 self.checkSourceFile("src/blib2to3/pytree.py")
210 def test_conv(self) -> None:
211 self.checkSourceFile("src/blib2to3/pgen2/conv.py")
213 def test_driver(self) -> None:
214 self.checkSourceFile("src/blib2to3/pgen2/driver.py")
216 def test_grammar(self) -> None:
217 self.checkSourceFile("src/blib2to3/pgen2/grammar.py")
219 def test_literals(self) -> None:
220 self.checkSourceFile("src/blib2to3/pgen2/literals.py")
222 def test_parse(self) -> None:
223 self.checkSourceFile("src/blib2to3/pgen2/parse.py")
225 def test_pgen(self) -> None:
226 self.checkSourceFile("src/blib2to3/pgen2/pgen.py")
228 def test_tokenize(self) -> None:
229 self.checkSourceFile("src/blib2to3/pgen2/tokenize.py")
231 def test_token(self) -> None:
232 self.checkSourceFile("src/blib2to3/pgen2/token.py")
234 def test_setup(self) -> None:
235 self.checkSourceFile("setup.py")
237 def test_piping(self) -> None:
238 source, expected = read_data("src/black/__init__", data=False)
239 result = BlackRunner().invoke(
241 ["-", "--fast", f"--line-length={black.DEFAULT_LINE_LENGTH}"],
242 input=BytesIO(source.encode("utf8")),
244 self.assertEqual(result.exit_code, 0)
245 self.assertFormatEqual(expected, result.output)
246 black.assert_equivalent(source, result.output)
247 black.assert_stable(source, result.output, DEFAULT_MODE)
249 def test_piping_diff(self) -> None:
250 diff_header = re.compile(
251 r"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d\d\d\d "
254 source, _ = read_data("expression.py")
255 expected, _ = read_data("expression.diff")
256 config = THIS_DIR / "data" / "empty_pyproject.toml"
260 f"--line-length={black.DEFAULT_LINE_LENGTH}",
262 f"--config={config}",
264 result = BlackRunner().invoke(
265 black.main, args, input=BytesIO(source.encode("utf8"))
267 self.assertEqual(result.exit_code, 0)
268 actual = diff_header.sub(DETERMINISTIC_HEADER, result.output)
269 actual = actual.rstrip() + "\n" # the diff output has a trailing space
270 self.assertEqual(expected, actual)
272 def test_piping_diff_with_color(self) -> None:
273 source, _ = read_data("expression.py")
274 config = THIS_DIR / "data" / "empty_pyproject.toml"
278 f"--line-length={black.DEFAULT_LINE_LENGTH}",
281 f"--config={config}",
283 result = BlackRunner().invoke(
284 black.main, args, input=BytesIO(source.encode("utf8"))
286 actual = result.output
287 # Again, the contents are checked in a different test, so only look for colors.
288 self.assertIn("\033[1;37m", actual)
289 self.assertIn("\033[36m", actual)
290 self.assertIn("\033[32m", actual)
291 self.assertIn("\033[31m", actual)
292 self.assertIn("\033[0m", actual)
294 @patch("black.dump_to_file", dump_to_stderr)
295 def test_function(self) -> None:
296 source, expected = read_data("function")
298 self.assertFormatEqual(expected, actual)
299 black.assert_equivalent(source, actual)
300 black.assert_stable(source, actual, DEFAULT_MODE)
302 @patch("black.dump_to_file", dump_to_stderr)
303 def test_function2(self) -> None:
304 source, expected = read_data("function2")
306 self.assertFormatEqual(expected, actual)
307 black.assert_equivalent(source, actual)
308 black.assert_stable(source, actual, DEFAULT_MODE)
310 @patch("black.dump_to_file", dump_to_stderr)
311 def _test_wip(self) -> None:
312 source, expected = read_data("wip")
313 sys.settrace(tracefunc)
316 experimental_string_processing=False,
317 target_versions={black.TargetVersion.PY38},
319 actual = fs(source, mode=mode)
321 self.assertFormatEqual(expected, actual)
322 black.assert_equivalent(source, actual)
323 black.assert_stable(source, actual, black.FileMode())
325 @patch("black.dump_to_file", dump_to_stderr)
326 def test_function_trailing_comma(self) -> None:
327 source, expected = read_data("function_trailing_comma")
329 self.assertFormatEqual(expected, actual)
330 black.assert_equivalent(source, actual)
331 black.assert_stable(source, actual, DEFAULT_MODE)
333 @unittest.expectedFailure
334 @patch("black.dump_to_file", dump_to_stderr)
335 def test_trailing_comma_optional_parens_stability1(self) -> None:
336 source, _expected = read_data("trailing_comma_optional_parens1")
338 black.assert_stable(source, actual, DEFAULT_MODE)
340 @unittest.expectedFailure
341 @patch("black.dump_to_file", dump_to_stderr)
342 def test_trailing_comma_optional_parens_stability2(self) -> None:
343 source, _expected = read_data("trailing_comma_optional_parens2")
345 black.assert_stable(source, actual, DEFAULT_MODE)
347 @unittest.expectedFailure
348 @patch("black.dump_to_file", dump_to_stderr)
349 def test_trailing_comma_optional_parens_stability3(self) -> None:
350 source, _expected = read_data("trailing_comma_optional_parens3")
352 black.assert_stable(source, actual, DEFAULT_MODE)
354 @patch("black.dump_to_file", dump_to_stderr)
355 def test_expression(self) -> None:
356 source, expected = read_data("expression")
358 self.assertFormatEqual(expected, actual)
359 black.assert_equivalent(source, actual)
360 black.assert_stable(source, actual, DEFAULT_MODE)
362 @patch("black.dump_to_file", dump_to_stderr)
363 def test_pep_572(self) -> None:
364 source, expected = read_data("pep_572")
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_pep_572_version_detection(self) -> None:
372 source, _ = read_data("pep_572")
373 root = black.lib2to3_parse(source)
374 features = black.get_features_used(root)
375 self.assertIn(black.Feature.ASSIGNMENT_EXPRESSIONS, features)
376 versions = black.detect_target_versions(root)
377 self.assertIn(black.TargetVersion.PY38, versions)
379 def test_expression_ff(self) -> None:
380 source, expected = read_data("expression")
381 tmp_file = Path(black.dump_to_file(source))
383 self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
384 with open(tmp_file, encoding="utf8") as f:
388 self.assertFormatEqual(expected, actual)
389 with patch("black.dump_to_file", dump_to_stderr):
390 black.assert_equivalent(source, actual)
391 black.assert_stable(source, actual, DEFAULT_MODE)
393 def test_expression_diff(self) -> None:
394 source, _ = read_data("expression.py")
395 expected, _ = read_data("expression.diff")
396 tmp_file = Path(black.dump_to_file(source))
397 diff_header = re.compile(
398 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
399 r"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
402 result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
403 self.assertEqual(result.exit_code, 0)
406 actual = result.output
407 actual = diff_header.sub(DETERMINISTIC_HEADER, actual)
408 actual = actual.rstrip() + "\n" # the diff output has a trailing space
409 if expected != actual:
410 dump = black.dump_to_file(actual)
412 "Expected diff isn't equal to the actual. If you made changes to"
413 " expression.py and this is an anticipated difference, overwrite"
414 f" tests/data/expression.diff with {dump}"
416 self.assertEqual(expected, actual, msg)
418 def test_expression_diff_with_color(self) -> None:
419 source, _ = read_data("expression.py")
420 expected, _ = read_data("expression.diff")
421 tmp_file = Path(black.dump_to_file(source))
423 result = BlackRunner().invoke(
424 black.main, ["--diff", "--color", str(tmp_file)]
428 actual = result.output
429 # We check the contents of the diff in `test_expression_diff`. All
430 # we need to check here is that color codes exist in the result.
431 self.assertIn("\033[1;37m", actual)
432 self.assertIn("\033[36m", actual)
433 self.assertIn("\033[32m", actual)
434 self.assertIn("\033[31m", actual)
435 self.assertIn("\033[0m", actual)
437 @patch("black.dump_to_file", dump_to_stderr)
438 def test_fstring(self) -> None:
439 source, expected = read_data("fstring")
441 self.assertFormatEqual(expected, actual)
442 black.assert_equivalent(source, actual)
443 black.assert_stable(source, actual, DEFAULT_MODE)
445 @patch("black.dump_to_file", dump_to_stderr)
446 def test_pep_570(self) -> None:
447 source, expected = read_data("pep_570")
449 self.assertFormatEqual(expected, actual)
450 black.assert_stable(source, actual, DEFAULT_MODE)
451 if sys.version_info >= (3, 8):
452 black.assert_equivalent(source, actual)
454 def test_detect_pos_only_arguments(self) -> None:
455 source, _ = read_data("pep_570")
456 root = black.lib2to3_parse(source)
457 features = black.get_features_used(root)
458 self.assertIn(black.Feature.POS_ONLY_ARGUMENTS, features)
459 versions = black.detect_target_versions(root)
460 self.assertIn(black.TargetVersion.PY38, versions)
462 @patch("black.dump_to_file", dump_to_stderr)
463 def test_string_quotes(self) -> None:
464 source, expected = read_data("string_quotes")
466 self.assertFormatEqual(expected, actual)
467 black.assert_equivalent(source, actual)
468 black.assert_stable(source, actual, DEFAULT_MODE)
469 mode = replace(DEFAULT_MODE, string_normalization=False)
470 not_normalized = fs(source, mode=mode)
471 self.assertFormatEqual(source.replace("\\\n", ""), not_normalized)
472 black.assert_equivalent(source, not_normalized)
473 black.assert_stable(source, not_normalized, mode=mode)
475 @patch("black.dump_to_file", dump_to_stderr)
476 def test_docstring(self) -> None:
477 source, expected = read_data("docstring")
479 self.assertFormatEqual(expected, actual)
480 black.assert_equivalent(source, actual)
481 black.assert_stable(source, actual, DEFAULT_MODE)
483 @patch("black.dump_to_file", dump_to_stderr)
484 def test_docstring_no_string_normalization(self) -> None:
485 """Like test_docstring but with string normalization off."""
486 source, expected = read_data("docstring_no_string_normalization")
487 mode = replace(DEFAULT_MODE, string_normalization=False)
488 actual = fs(source, mode=mode)
489 self.assertFormatEqual(expected, actual)
490 black.assert_equivalent(source, actual)
491 black.assert_stable(source, actual, mode)
493 def test_long_strings(self) -> None:
494 """Tests for splitting long strings."""
495 source, expected = read_data("long_strings")
497 self.assertFormatEqual(expected, actual)
498 black.assert_equivalent(source, actual)
499 black.assert_stable(source, actual, DEFAULT_MODE)
501 def test_long_strings_flag_disabled(self) -> None:
502 """Tests for turning off the string processing logic."""
503 source, expected = read_data("long_strings_flag_disabled")
504 mode = replace(DEFAULT_MODE, experimental_string_processing=False)
505 actual = fs(source, mode=mode)
506 self.assertFormatEqual(expected, actual)
507 black.assert_stable(expected, actual, mode)
509 @patch("black.dump_to_file", dump_to_stderr)
510 def test_long_strings__edge_case(self) -> None:
511 """Edge-case tests for splitting long strings."""
512 source, expected = read_data("long_strings__edge_case")
514 self.assertFormatEqual(expected, actual)
515 black.assert_equivalent(source, actual)
516 black.assert_stable(source, actual, DEFAULT_MODE)
518 @patch("black.dump_to_file", dump_to_stderr)
519 def test_long_strings__regression(self) -> None:
520 """Regression tests for splitting long strings."""
521 source, expected = read_data("long_strings__regression")
523 self.assertFormatEqual(expected, actual)
524 black.assert_equivalent(source, actual)
525 black.assert_stable(source, actual, DEFAULT_MODE)
527 @patch("black.dump_to_file", dump_to_stderr)
528 def test_slices(self) -> None:
529 source, expected = read_data("slices")
531 self.assertFormatEqual(expected, actual)
532 black.assert_equivalent(source, actual)
533 black.assert_stable(source, actual, DEFAULT_MODE)
535 @patch("black.dump_to_file", dump_to_stderr)
536 def test_percent_precedence(self) -> None:
537 source, expected = read_data("percent_precedence")
539 self.assertFormatEqual(expected, actual)
540 black.assert_equivalent(source, actual)
541 black.assert_stable(source, actual, DEFAULT_MODE)
543 @patch("black.dump_to_file", dump_to_stderr)
544 def test_comments(self) -> None:
545 source, expected = read_data("comments")
547 self.assertFormatEqual(expected, actual)
548 black.assert_equivalent(source, actual)
549 black.assert_stable(source, actual, DEFAULT_MODE)
551 @patch("black.dump_to_file", dump_to_stderr)
552 def test_comments2(self) -> None:
553 source, expected = read_data("comments2")
555 self.assertFormatEqual(expected, actual)
556 black.assert_equivalent(source, actual)
557 black.assert_stable(source, actual, DEFAULT_MODE)
559 @patch("black.dump_to_file", dump_to_stderr)
560 def test_comments3(self) -> None:
561 source, expected = read_data("comments3")
563 self.assertFormatEqual(expected, actual)
564 black.assert_equivalent(source, actual)
565 black.assert_stable(source, actual, DEFAULT_MODE)
567 @patch("black.dump_to_file", dump_to_stderr)
568 def test_comments4(self) -> None:
569 source, expected = read_data("comments4")
571 self.assertFormatEqual(expected, actual)
572 black.assert_equivalent(source, actual)
573 black.assert_stable(source, actual, DEFAULT_MODE)
575 @patch("black.dump_to_file", dump_to_stderr)
576 def test_comments5(self) -> None:
577 source, expected = read_data("comments5")
579 self.assertFormatEqual(expected, actual)
580 black.assert_equivalent(source, actual)
581 black.assert_stable(source, actual, DEFAULT_MODE)
583 @patch("black.dump_to_file", dump_to_stderr)
584 def test_comments6(self) -> None:
585 source, expected = read_data("comments6")
587 self.assertFormatEqual(expected, actual)
588 black.assert_equivalent(source, actual)
589 black.assert_stable(source, actual, DEFAULT_MODE)
591 @patch("black.dump_to_file", dump_to_stderr)
592 def test_comments7(self) -> None:
593 source, expected = read_data("comments7")
594 mode = replace(DEFAULT_MODE, target_versions={black.TargetVersion.PY38})
595 actual = fs(source, mode=mode)
596 self.assertFormatEqual(expected, actual)
597 black.assert_equivalent(source, actual)
598 black.assert_stable(source, actual, DEFAULT_MODE)
600 @patch("black.dump_to_file", dump_to_stderr)
601 def test_comment_after_escaped_newline(self) -> None:
602 source, expected = read_data("comment_after_escaped_newline")
604 self.assertFormatEqual(expected, actual)
605 black.assert_equivalent(source, actual)
606 black.assert_stable(source, actual, DEFAULT_MODE)
608 @patch("black.dump_to_file", dump_to_stderr)
609 def test_cantfit(self) -> None:
610 source, expected = read_data("cantfit")
612 self.assertFormatEqual(expected, actual)
613 black.assert_equivalent(source, actual)
614 black.assert_stable(source, actual, DEFAULT_MODE)
616 @patch("black.dump_to_file", dump_to_stderr)
617 def test_import_spacing(self) -> None:
618 source, expected = read_data("import_spacing")
620 self.assertFormatEqual(expected, actual)
621 black.assert_equivalent(source, actual)
622 black.assert_stable(source, actual, DEFAULT_MODE)
624 @patch("black.dump_to_file", dump_to_stderr)
625 def test_composition(self) -> None:
626 source, expected = read_data("composition")
628 self.assertFormatEqual(expected, actual)
629 black.assert_equivalent(source, actual)
630 black.assert_stable(source, actual, DEFAULT_MODE)
632 @patch("black.dump_to_file", dump_to_stderr)
633 def test_composition_no_trailing_comma(self) -> None:
634 source, expected = read_data("composition_no_trailing_comma")
635 mode = replace(DEFAULT_MODE, target_versions={black.TargetVersion.PY38})
636 actual = fs(source, mode=mode)
637 self.assertFormatEqual(expected, actual)
638 black.assert_equivalent(source, actual)
639 black.assert_stable(source, actual, DEFAULT_MODE)
641 @patch("black.dump_to_file", dump_to_stderr)
642 def test_empty_lines(self) -> None:
643 source, expected = read_data("empty_lines")
645 self.assertFormatEqual(expected, actual)
646 black.assert_equivalent(source, actual)
647 black.assert_stable(source, actual, DEFAULT_MODE)
649 @patch("black.dump_to_file", dump_to_stderr)
650 def test_remove_parens(self) -> None:
651 source, expected = read_data("remove_parens")
653 self.assertFormatEqual(expected, actual)
654 black.assert_equivalent(source, actual)
655 black.assert_stable(source, actual, DEFAULT_MODE)
657 @patch("black.dump_to_file", dump_to_stderr)
658 def test_string_prefixes(self) -> None:
659 source, expected = read_data("string_prefixes")
661 self.assertFormatEqual(expected, actual)
662 black.assert_equivalent(source, actual)
663 black.assert_stable(source, actual, DEFAULT_MODE)
665 @patch("black.dump_to_file", dump_to_stderr)
666 def test_numeric_literals(self) -> None:
667 source, expected = read_data("numeric_literals")
668 mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
669 actual = fs(source, mode=mode)
670 self.assertFormatEqual(expected, actual)
671 black.assert_equivalent(source, actual)
672 black.assert_stable(source, actual, mode)
674 @patch("black.dump_to_file", dump_to_stderr)
675 def test_numeric_literals_ignoring_underscores(self) -> None:
676 source, expected = read_data("numeric_literals_skip_underscores")
677 mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
678 actual = fs(source, mode=mode)
679 self.assertFormatEqual(expected, actual)
680 black.assert_equivalent(source, actual)
681 black.assert_stable(source, actual, mode)
683 @patch("black.dump_to_file", dump_to_stderr)
684 def test_numeric_literals_py2(self) -> None:
685 source, expected = read_data("numeric_literals_py2")
687 self.assertFormatEqual(expected, actual)
688 black.assert_stable(source, actual, DEFAULT_MODE)
690 @patch("black.dump_to_file", dump_to_stderr)
691 def test_python2(self) -> None:
692 source, expected = read_data("python2")
694 self.assertFormatEqual(expected, actual)
695 black.assert_equivalent(source, actual)
696 black.assert_stable(source, actual, DEFAULT_MODE)
698 @patch("black.dump_to_file", dump_to_stderr)
699 def test_python2_print_function(self) -> None:
700 source, expected = read_data("python2_print_function")
701 mode = replace(DEFAULT_MODE, target_versions={TargetVersion.PY27})
702 actual = fs(source, mode=mode)
703 self.assertFormatEqual(expected, actual)
704 black.assert_equivalent(source, actual)
705 black.assert_stable(source, actual, mode)
707 @patch("black.dump_to_file", dump_to_stderr)
708 def test_python2_unicode_literals(self) -> None:
709 source, expected = read_data("python2_unicode_literals")
711 self.assertFormatEqual(expected, actual)
712 black.assert_equivalent(source, actual)
713 black.assert_stable(source, actual, DEFAULT_MODE)
715 @patch("black.dump_to_file", dump_to_stderr)
716 def test_stub(self) -> None:
717 mode = replace(DEFAULT_MODE, is_pyi=True)
718 source, expected = read_data("stub.pyi")
719 actual = fs(source, mode=mode)
720 self.assertFormatEqual(expected, actual)
721 black.assert_stable(source, actual, mode)
723 @patch("black.dump_to_file", dump_to_stderr)
724 def test_async_as_identifier(self) -> None:
725 source_path = (THIS_DIR / "data" / "async_as_identifier.py").resolve()
726 source, expected = read_data("async_as_identifier")
728 self.assertFormatEqual(expected, actual)
729 major, minor = sys.version_info[:2]
730 if major < 3 or (major <= 3 and minor < 7):
731 black.assert_equivalent(source, actual)
732 black.assert_stable(source, actual, DEFAULT_MODE)
733 # ensure black can parse this when the target is 3.6
734 self.invokeBlack([str(source_path), "--target-version", "py36"])
735 # but not on 3.7, because async/await is no longer an identifier
736 self.invokeBlack([str(source_path), "--target-version", "py37"], exit_code=123)
738 @patch("black.dump_to_file", dump_to_stderr)
739 def test_python37(self) -> None:
740 source_path = (THIS_DIR / "data" / "python37.py").resolve()
741 source, expected = read_data("python37")
743 self.assertFormatEqual(expected, actual)
744 major, minor = sys.version_info[:2]
745 if major > 3 or (major == 3 and minor >= 7):
746 black.assert_equivalent(source, actual)
747 black.assert_stable(source, actual, DEFAULT_MODE)
748 # ensure black can parse this when the target is 3.7
749 self.invokeBlack([str(source_path), "--target-version", "py37"])
750 # but not on 3.6, because we use async as a reserved keyword
751 self.invokeBlack([str(source_path), "--target-version", "py36"], exit_code=123)
753 @patch("black.dump_to_file", dump_to_stderr)
754 def test_python38(self) -> None:
755 source, expected = read_data("python38")
757 self.assertFormatEqual(expected, actual)
758 major, minor = sys.version_info[:2]
759 if major > 3 or (major == 3 and minor >= 8):
760 black.assert_equivalent(source, actual)
761 black.assert_stable(source, actual, DEFAULT_MODE)
763 @patch("black.dump_to_file", dump_to_stderr)
764 def test_python39(self) -> None:
765 source, expected = read_data("python39")
767 self.assertFormatEqual(expected, actual)
768 major, minor = sys.version_info[:2]
769 if major > 3 or (major == 3 and minor >= 9):
770 black.assert_equivalent(source, actual)
771 black.assert_stable(source, actual, DEFAULT_MODE)
773 @patch("black.dump_to_file", dump_to_stderr)
774 def test_fmtonoff(self) -> None:
775 source, expected = read_data("fmtonoff")
777 self.assertFormatEqual(expected, actual)
778 black.assert_equivalent(source, actual)
779 black.assert_stable(source, actual, DEFAULT_MODE)
781 @patch("black.dump_to_file", dump_to_stderr)
782 def test_fmtonoff2(self) -> None:
783 source, expected = read_data("fmtonoff2")
785 self.assertFormatEqual(expected, actual)
786 black.assert_equivalent(source, actual)
787 black.assert_stable(source, actual, DEFAULT_MODE)
789 @patch("black.dump_to_file", dump_to_stderr)
790 def test_fmtonoff3(self) -> None:
791 source, expected = read_data("fmtonoff3")
793 self.assertFormatEqual(expected, actual)
794 black.assert_equivalent(source, actual)
795 black.assert_stable(source, actual, DEFAULT_MODE)
797 @patch("black.dump_to_file", dump_to_stderr)
798 def test_fmtonoff4(self) -> None:
799 source, expected = read_data("fmtonoff4")
801 self.assertFormatEqual(expected, actual)
802 black.assert_equivalent(source, actual)
803 black.assert_stable(source, actual, DEFAULT_MODE)
805 @patch("black.dump_to_file", dump_to_stderr)
806 def test_remove_empty_parentheses_after_class(self) -> None:
807 source, expected = read_data("class_blank_parentheses")
809 self.assertFormatEqual(expected, actual)
810 black.assert_equivalent(source, actual)
811 black.assert_stable(source, actual, DEFAULT_MODE)
813 @patch("black.dump_to_file", dump_to_stderr)
814 def test_new_line_between_class_and_code(self) -> None:
815 source, expected = read_data("class_methods_new_line")
817 self.assertFormatEqual(expected, actual)
818 black.assert_equivalent(source, actual)
819 black.assert_stable(source, actual, DEFAULT_MODE)
821 @patch("black.dump_to_file", dump_to_stderr)
822 def test_bracket_match(self) -> None:
823 source, expected = read_data("bracketmatch")
825 self.assertFormatEqual(expected, actual)
826 black.assert_equivalent(source, actual)
827 black.assert_stable(source, actual, DEFAULT_MODE)
829 @patch("black.dump_to_file", dump_to_stderr)
830 def test_tuple_assign(self) -> None:
831 source, expected = read_data("tupleassign")
833 self.assertFormatEqual(expected, actual)
834 black.assert_equivalent(source, actual)
835 black.assert_stable(source, actual, DEFAULT_MODE)
837 @patch("black.dump_to_file", dump_to_stderr)
838 def test_beginning_backslash(self) -> None:
839 source, expected = read_data("beginning_backslash")
841 self.assertFormatEqual(expected, actual)
842 black.assert_equivalent(source, actual)
843 black.assert_stable(source, actual, DEFAULT_MODE)
845 def test_tab_comment_indentation(self) -> None:
846 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
847 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
848 self.assertFormatEqual(contents_spc, fs(contents_spc))
849 self.assertFormatEqual(contents_spc, fs(contents_tab))
851 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
852 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
853 self.assertFormatEqual(contents_spc, fs(contents_spc))
854 self.assertFormatEqual(contents_spc, fs(contents_tab))
856 # mixed tabs and spaces (valid Python 2 code)
857 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t# comment\n pass\n"
858 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
859 self.assertFormatEqual(contents_spc, fs(contents_spc))
860 self.assertFormatEqual(contents_spc, fs(contents_tab))
862 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t\t# comment\n pass\n"
863 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
864 self.assertFormatEqual(contents_spc, fs(contents_spc))
865 self.assertFormatEqual(contents_spc, fs(contents_tab))
867 def test_report_verbose(self) -> None:
868 report = black.Report(verbose=True)
872 def out(msg: str, **kwargs: Any) -> None:
873 out_lines.append(msg)
875 def err(msg: str, **kwargs: Any) -> None:
876 err_lines.append(msg)
878 with patch("black.out", out), patch("black.err", err):
879 report.done(Path("f1"), black.Changed.NO)
880 self.assertEqual(len(out_lines), 1)
881 self.assertEqual(len(err_lines), 0)
882 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
883 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
884 self.assertEqual(report.return_code, 0)
885 report.done(Path("f2"), black.Changed.YES)
886 self.assertEqual(len(out_lines), 2)
887 self.assertEqual(len(err_lines), 0)
888 self.assertEqual(out_lines[-1], "reformatted f2")
890 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
892 report.done(Path("f3"), black.Changed.CACHED)
893 self.assertEqual(len(out_lines), 3)
894 self.assertEqual(len(err_lines), 0)
896 out_lines[-1], "f3 wasn't modified on disk since last run."
899 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
901 self.assertEqual(report.return_code, 0)
903 self.assertEqual(report.return_code, 1)
905 report.failed(Path("e1"), "boom")
906 self.assertEqual(len(out_lines), 3)
907 self.assertEqual(len(err_lines), 1)
908 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
910 unstyle(str(report)),
911 "1 file reformatted, 2 files left unchanged, 1 file failed to"
914 self.assertEqual(report.return_code, 123)
915 report.done(Path("f3"), black.Changed.YES)
916 self.assertEqual(len(out_lines), 4)
917 self.assertEqual(len(err_lines), 1)
918 self.assertEqual(out_lines[-1], "reformatted f3")
920 unstyle(str(report)),
921 "2 files reformatted, 2 files left unchanged, 1 file failed to"
924 self.assertEqual(report.return_code, 123)
925 report.failed(Path("e2"), "boom")
926 self.assertEqual(len(out_lines), 4)
927 self.assertEqual(len(err_lines), 2)
928 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
930 unstyle(str(report)),
931 "2 files reformatted, 2 files left unchanged, 2 files failed to"
934 self.assertEqual(report.return_code, 123)
935 report.path_ignored(Path("wat"), "no match")
936 self.assertEqual(len(out_lines), 5)
937 self.assertEqual(len(err_lines), 2)
938 self.assertEqual(out_lines[-1], "wat ignored: no match")
940 unstyle(str(report)),
941 "2 files reformatted, 2 files left unchanged, 2 files failed to"
944 self.assertEqual(report.return_code, 123)
945 report.done(Path("f4"), black.Changed.NO)
946 self.assertEqual(len(out_lines), 6)
947 self.assertEqual(len(err_lines), 2)
948 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
950 unstyle(str(report)),
951 "2 files reformatted, 3 files left unchanged, 2 files failed to"
954 self.assertEqual(report.return_code, 123)
957 unstyle(str(report)),
958 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
959 " would fail to reformat.",
964 unstyle(str(report)),
965 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
966 " would fail to reformat.",
969 def test_report_quiet(self) -> None:
970 report = black.Report(quiet=True)
974 def out(msg: str, **kwargs: Any) -> None:
975 out_lines.append(msg)
977 def err(msg: str, **kwargs: Any) -> None:
978 err_lines.append(msg)
980 with patch("black.out", out), patch("black.err", err):
981 report.done(Path("f1"), black.Changed.NO)
982 self.assertEqual(len(out_lines), 0)
983 self.assertEqual(len(err_lines), 0)
984 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
985 self.assertEqual(report.return_code, 0)
986 report.done(Path("f2"), black.Changed.YES)
987 self.assertEqual(len(out_lines), 0)
988 self.assertEqual(len(err_lines), 0)
990 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
992 report.done(Path("f3"), black.Changed.CACHED)
993 self.assertEqual(len(out_lines), 0)
994 self.assertEqual(len(err_lines), 0)
996 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
998 self.assertEqual(report.return_code, 0)
1000 self.assertEqual(report.return_code, 1)
1001 report.check = False
1002 report.failed(Path("e1"), "boom")
1003 self.assertEqual(len(out_lines), 0)
1004 self.assertEqual(len(err_lines), 1)
1005 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
1007 unstyle(str(report)),
1008 "1 file reformatted, 2 files left unchanged, 1 file failed to"
1011 self.assertEqual(report.return_code, 123)
1012 report.done(Path("f3"), black.Changed.YES)
1013 self.assertEqual(len(out_lines), 0)
1014 self.assertEqual(len(err_lines), 1)
1016 unstyle(str(report)),
1017 "2 files reformatted, 2 files left unchanged, 1 file failed to"
1020 self.assertEqual(report.return_code, 123)
1021 report.failed(Path("e2"), "boom")
1022 self.assertEqual(len(out_lines), 0)
1023 self.assertEqual(len(err_lines), 2)
1024 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
1026 unstyle(str(report)),
1027 "2 files reformatted, 2 files left unchanged, 2 files failed to"
1030 self.assertEqual(report.return_code, 123)
1031 report.path_ignored(Path("wat"), "no match")
1032 self.assertEqual(len(out_lines), 0)
1033 self.assertEqual(len(err_lines), 2)
1035 unstyle(str(report)),
1036 "2 files reformatted, 2 files left unchanged, 2 files failed to"
1039 self.assertEqual(report.return_code, 123)
1040 report.done(Path("f4"), black.Changed.NO)
1041 self.assertEqual(len(out_lines), 0)
1042 self.assertEqual(len(err_lines), 2)
1044 unstyle(str(report)),
1045 "2 files reformatted, 3 files left unchanged, 2 files failed to"
1048 self.assertEqual(report.return_code, 123)
1051 unstyle(str(report)),
1052 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1053 " would fail to reformat.",
1055 report.check = False
1058 unstyle(str(report)),
1059 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1060 " would fail to reformat.",
1063 def test_report_normal(self) -> None:
1064 report = black.Report()
1068 def out(msg: str, **kwargs: Any) -> None:
1069 out_lines.append(msg)
1071 def err(msg: str, **kwargs: Any) -> None:
1072 err_lines.append(msg)
1074 with patch("black.out", out), patch("black.err", err):
1075 report.done(Path("f1"), black.Changed.NO)
1076 self.assertEqual(len(out_lines), 0)
1077 self.assertEqual(len(err_lines), 0)
1078 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
1079 self.assertEqual(report.return_code, 0)
1080 report.done(Path("f2"), black.Changed.YES)
1081 self.assertEqual(len(out_lines), 1)
1082 self.assertEqual(len(err_lines), 0)
1083 self.assertEqual(out_lines[-1], "reformatted f2")
1085 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
1087 report.done(Path("f3"), black.Changed.CACHED)
1088 self.assertEqual(len(out_lines), 1)
1089 self.assertEqual(len(err_lines), 0)
1090 self.assertEqual(out_lines[-1], "reformatted f2")
1092 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
1094 self.assertEqual(report.return_code, 0)
1096 self.assertEqual(report.return_code, 1)
1097 report.check = False
1098 report.failed(Path("e1"), "boom")
1099 self.assertEqual(len(out_lines), 1)
1100 self.assertEqual(len(err_lines), 1)
1101 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
1103 unstyle(str(report)),
1104 "1 file reformatted, 2 files left unchanged, 1 file failed to"
1107 self.assertEqual(report.return_code, 123)
1108 report.done(Path("f3"), black.Changed.YES)
1109 self.assertEqual(len(out_lines), 2)
1110 self.assertEqual(len(err_lines), 1)
1111 self.assertEqual(out_lines[-1], "reformatted f3")
1113 unstyle(str(report)),
1114 "2 files reformatted, 2 files left unchanged, 1 file failed to"
1117 self.assertEqual(report.return_code, 123)
1118 report.failed(Path("e2"), "boom")
1119 self.assertEqual(len(out_lines), 2)
1120 self.assertEqual(len(err_lines), 2)
1121 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
1123 unstyle(str(report)),
1124 "2 files reformatted, 2 files left unchanged, 2 files failed to"
1127 self.assertEqual(report.return_code, 123)
1128 report.path_ignored(Path("wat"), "no match")
1129 self.assertEqual(len(out_lines), 2)
1130 self.assertEqual(len(err_lines), 2)
1132 unstyle(str(report)),
1133 "2 files reformatted, 2 files left unchanged, 2 files failed to"
1136 self.assertEqual(report.return_code, 123)
1137 report.done(Path("f4"), black.Changed.NO)
1138 self.assertEqual(len(out_lines), 2)
1139 self.assertEqual(len(err_lines), 2)
1141 unstyle(str(report)),
1142 "2 files reformatted, 3 files left unchanged, 2 files failed to"
1145 self.assertEqual(report.return_code, 123)
1148 unstyle(str(report)),
1149 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1150 " would fail to reformat.",
1152 report.check = False
1155 unstyle(str(report)),
1156 "2 files would be reformatted, 3 files would be left unchanged, 2 files"
1157 " would fail to reformat.",
1160 def test_lib2to3_parse(self) -> None:
1161 with self.assertRaises(black.InvalidInput):
1162 black.lib2to3_parse("invalid syntax")
1164 straddling = "x + y"
1165 black.lib2to3_parse(straddling)
1166 black.lib2to3_parse(straddling, {TargetVersion.PY27})
1167 black.lib2to3_parse(straddling, {TargetVersion.PY36})
1168 black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
1170 py2_only = "print x"
1171 black.lib2to3_parse(py2_only)
1172 black.lib2to3_parse(py2_only, {TargetVersion.PY27})
1173 with self.assertRaises(black.InvalidInput):
1174 black.lib2to3_parse(py2_only, {TargetVersion.PY36})
1175 with self.assertRaises(black.InvalidInput):
1176 black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
1178 py3_only = "exec(x, end=y)"
1179 black.lib2to3_parse(py3_only)
1180 with self.assertRaises(black.InvalidInput):
1181 black.lib2to3_parse(py3_only, {TargetVersion.PY27})
1182 black.lib2to3_parse(py3_only, {TargetVersion.PY36})
1183 black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
1185 def test_get_features_used_decorator(self) -> None:
1186 # Test the feature detection of new decorator syntax
1187 # since this makes some test cases of test_get_features_used()
1188 # fails if it fails, this is tested first so that a useful case
1190 simples, relaxed = read_data("decorators")
1191 # skip explanation comments at the top of the file
1192 for simple_test in simples.split("##")[1:]:
1193 node = black.lib2to3_parse(simple_test)
1194 decorator = str(node.children[0].children[0]).strip()
1196 Feature.RELAXED_DECORATORS,
1197 black.get_features_used(node),
1199 f"decorator '{decorator}' follows python<=3.8 syntax"
1200 "but is detected as 3.9+"
1201 # f"The full node is\n{node!r}"
1204 # skip the '# output' comment at the top of the output part
1205 for relaxed_test in relaxed.split("##")[1:]:
1206 node = black.lib2to3_parse(relaxed_test)
1207 decorator = str(node.children[0].children[0]).strip()
1209 Feature.RELAXED_DECORATORS,
1210 black.get_features_used(node),
1212 f"decorator '{decorator}' uses python3.9+ syntax"
1213 "but is detected as python<=3.8"
1214 # f"The full node is\n{node!r}"
1218 def test_get_features_used(self) -> None:
1219 node = black.lib2to3_parse("def f(*, arg): ...\n")
1220 self.assertEqual(black.get_features_used(node), set())
1221 node = black.lib2to3_parse("def f(*, arg,): ...\n")
1222 self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
1223 node = black.lib2to3_parse("f(*arg,)\n")
1225 black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
1227 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
1228 self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
1229 node = black.lib2to3_parse("123_456\n")
1230 self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
1231 node = black.lib2to3_parse("123456\n")
1232 self.assertEqual(black.get_features_used(node), set())
1233 source, expected = read_data("function")
1234 node = black.lib2to3_parse(source)
1235 expected_features = {
1236 Feature.TRAILING_COMMA_IN_CALL,
1237 Feature.TRAILING_COMMA_IN_DEF,
1240 self.assertEqual(black.get_features_used(node), expected_features)
1241 node = black.lib2to3_parse(expected)
1242 self.assertEqual(black.get_features_used(node), expected_features)
1243 source, expected = read_data("expression")
1244 node = black.lib2to3_parse(source)
1245 self.assertEqual(black.get_features_used(node), set())
1246 node = black.lib2to3_parse(expected)
1247 self.assertEqual(black.get_features_used(node), set())
1249 def test_get_future_imports(self) -> None:
1250 node = black.lib2to3_parse("\n")
1251 self.assertEqual(set(), black.get_future_imports(node))
1252 node = black.lib2to3_parse("from __future__ import black\n")
1253 self.assertEqual({"black"}, black.get_future_imports(node))
1254 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
1255 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1256 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
1257 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
1258 node = black.lib2to3_parse(
1259 "from __future__ import multiple\nfrom __future__ import imports\n"
1261 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1262 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
1263 self.assertEqual({"black"}, black.get_future_imports(node))
1264 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
1265 self.assertEqual({"black"}, black.get_future_imports(node))
1266 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
1267 self.assertEqual(set(), black.get_future_imports(node))
1268 node = black.lib2to3_parse("from some.module import black\n")
1269 self.assertEqual(set(), black.get_future_imports(node))
1270 node = black.lib2to3_parse(
1271 "from __future__ import unicode_literals as _unicode_literals"
1273 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
1274 node = black.lib2to3_parse(
1275 "from __future__ import unicode_literals as _lol, print"
1277 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
1279 def test_debug_visitor(self) -> None:
1280 source, _ = read_data("debug_visitor.py")
1281 expected, _ = read_data("debug_visitor.out")
1285 def out(msg: str, **kwargs: Any) -> None:
1286 out_lines.append(msg)
1288 def err(msg: str, **kwargs: Any) -> None:
1289 err_lines.append(msg)
1291 with patch("black.out", out), patch("black.err", err):
1292 black.DebugVisitor.show(source)
1293 actual = "\n".join(out_lines) + "\n"
1295 if expected != actual:
1296 log_name = black.dump_to_file(*out_lines)
1300 f"AST print out is different. Actual version dumped to {log_name}",
1303 def test_format_file_contents(self) -> None:
1306 with self.assertRaises(black.NothingChanged):
1307 black.format_file_contents(empty, mode=mode, fast=False)
1309 with self.assertRaises(black.NothingChanged):
1310 black.format_file_contents(just_nl, mode=mode, fast=False)
1311 same = "j = [1, 2, 3]\n"
1312 with self.assertRaises(black.NothingChanged):
1313 black.format_file_contents(same, mode=mode, fast=False)
1314 different = "j = [1,2,3]"
1316 actual = black.format_file_contents(different, mode=mode, fast=False)
1317 self.assertEqual(expected, actual)
1318 invalid = "return if you can"
1319 with self.assertRaises(black.InvalidInput) as e:
1320 black.format_file_contents(invalid, mode=mode, fast=False)
1321 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
1323 def test_endmarker(self) -> None:
1324 n = black.lib2to3_parse("\n")
1325 self.assertEqual(n.type, black.syms.file_input)
1326 self.assertEqual(len(n.children), 1)
1327 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
1329 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
1330 def test_assertFormatEqual(self) -> None:
1334 def out(msg: str, **kwargs: Any) -> None:
1335 out_lines.append(msg)
1337 def err(msg: str, **kwargs: Any) -> None:
1338 err_lines.append(msg)
1340 with patch("black.out", out), patch("black.err", err):
1341 with self.assertRaises(AssertionError):
1342 self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")
1344 out_str = "".join(out_lines)
1345 self.assertTrue("Expected tree:" in out_str)
1346 self.assertTrue("Actual tree:" in out_str)
1347 self.assertEqual("".join(err_lines), "")
1349 def test_cache_broken_file(self) -> None:
1351 with cache_dir() as workspace:
1352 cache_file = black.get_cache_file(mode)
1353 with cache_file.open("w") as fobj:
1354 fobj.write("this is not a pickle")
1355 self.assertEqual(black.read_cache(mode), {})
1356 src = (workspace / "test.py").resolve()
1357 with src.open("w") as fobj:
1358 fobj.write("print('hello')")
1359 self.invokeBlack([str(src)])
1360 cache = black.read_cache(mode)
1361 self.assertIn(src, cache)
1363 def test_cache_single_file_already_cached(self) -> None:
1365 with cache_dir() as workspace:
1366 src = (workspace / "test.py").resolve()
1367 with src.open("w") as fobj:
1368 fobj.write("print('hello')")
1369 black.write_cache({}, [src], mode)
1370 self.invokeBlack([str(src)])
1371 with src.open("r") as fobj:
1372 self.assertEqual(fobj.read(), "print('hello')")
1375 def test_cache_multiple_files(self) -> None:
1377 with cache_dir() as workspace, patch(
1378 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1380 one = (workspace / "one.py").resolve()
1381 with one.open("w") as fobj:
1382 fobj.write("print('hello')")
1383 two = (workspace / "two.py").resolve()
1384 with two.open("w") as fobj:
1385 fobj.write("print('hello')")
1386 black.write_cache({}, [one], mode)
1387 self.invokeBlack([str(workspace)])
1388 with one.open("r") as fobj:
1389 self.assertEqual(fobj.read(), "print('hello')")
1390 with two.open("r") as fobj:
1391 self.assertEqual(fobj.read(), 'print("hello")\n')
1392 cache = black.read_cache(mode)
1393 self.assertIn(one, cache)
1394 self.assertIn(two, cache)
1396 def test_no_cache_when_writeback_diff(self) -> None:
1398 with cache_dir() as workspace:
1399 src = (workspace / "test.py").resolve()
1400 with src.open("w") as fobj:
1401 fobj.write("print('hello')")
1402 with patch("black.read_cache") as read_cache, patch(
1405 self.invokeBlack([str(src), "--diff"])
1406 cache_file = black.get_cache_file(mode)
1407 self.assertFalse(cache_file.exists())
1408 write_cache.assert_not_called()
1409 read_cache.assert_not_called()
1411 def test_no_cache_when_writeback_color_diff(self) -> None:
1413 with cache_dir() as workspace:
1414 src = (workspace / "test.py").resolve()
1415 with src.open("w") as fobj:
1416 fobj.write("print('hello')")
1417 with patch("black.read_cache") as read_cache, patch(
1420 self.invokeBlack([str(src), "--diff", "--color"])
1421 cache_file = black.get_cache_file(mode)
1422 self.assertFalse(cache_file.exists())
1423 write_cache.assert_not_called()
1424 read_cache.assert_not_called()
1427 def test_output_locking_when_writeback_diff(self) -> None:
1428 with cache_dir() as workspace:
1429 for tag in range(0, 4):
1430 src = (workspace / f"test{tag}.py").resolve()
1431 with src.open("w") as fobj:
1432 fobj.write("print('hello')")
1433 with patch("black.Manager", wraps=multiprocessing.Manager) as mgr:
1434 self.invokeBlack(["--diff", str(workspace)], exit_code=0)
1435 # this isn't quite doing what we want, but if it _isn't_
1436 # called then we cannot be using the lock it provides
1440 def test_output_locking_when_writeback_color_diff(self) -> None:
1441 with cache_dir() as workspace:
1442 for tag in range(0, 4):
1443 src = (workspace / f"test{tag}.py").resolve()
1444 with src.open("w") as fobj:
1445 fobj.write("print('hello')")
1446 with patch("black.Manager", wraps=multiprocessing.Manager) as mgr:
1447 self.invokeBlack(["--diff", "--color", str(workspace)], exit_code=0)
1448 # this isn't quite doing what we want, but if it _isn't_
1449 # called then we cannot be using the lock it provides
1452 def test_no_cache_when_stdin(self) -> None:
1455 result = CliRunner().invoke(
1456 black.main, ["-"], input=BytesIO(b"print('hello')")
1458 self.assertEqual(result.exit_code, 0)
1459 cache_file = black.get_cache_file(mode)
1460 self.assertFalse(cache_file.exists())
1462 def test_read_cache_no_cachefile(self) -> None:
1465 self.assertEqual(black.read_cache(mode), {})
1467 def test_write_cache_read_cache(self) -> None:
1469 with cache_dir() as workspace:
1470 src = (workspace / "test.py").resolve()
1472 black.write_cache({}, [src], mode)
1473 cache = black.read_cache(mode)
1474 self.assertIn(src, cache)
1475 self.assertEqual(cache[src], black.get_cache_info(src))
1477 def test_filter_cached(self) -> None:
1478 with TemporaryDirectory() as workspace:
1479 path = Path(workspace)
1480 uncached = (path / "uncached").resolve()
1481 cached = (path / "cached").resolve()
1482 cached_but_changed = (path / "changed").resolve()
1485 cached_but_changed.touch()
1486 cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
1487 todo, done = black.filter_cached(
1488 cache, {uncached, cached, cached_but_changed}
1490 self.assertEqual(todo, {uncached, cached_but_changed})
1491 self.assertEqual(done, {cached})
1493 def test_write_cache_creates_directory_if_needed(self) -> None:
1495 with cache_dir(exists=False) as workspace:
1496 self.assertFalse(workspace.exists())
1497 black.write_cache({}, [], mode)
1498 self.assertTrue(workspace.exists())
1501 def test_failed_formatting_does_not_get_cached(self) -> None:
1503 with cache_dir() as workspace, patch(
1504 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1506 failing = (workspace / "failing.py").resolve()
1507 with failing.open("w") as fobj:
1508 fobj.write("not actually python")
1509 clean = (workspace / "clean.py").resolve()
1510 with clean.open("w") as fobj:
1511 fobj.write('print("hello")\n')
1512 self.invokeBlack([str(workspace)], exit_code=123)
1513 cache = black.read_cache(mode)
1514 self.assertNotIn(failing, cache)
1515 self.assertIn(clean, cache)
1517 def test_write_cache_write_fail(self) -> None:
1519 with cache_dir(), patch.object(Path, "open") as mock:
1520 mock.side_effect = OSError
1521 black.write_cache({}, [], mode)
1524 @patch("black.ProcessPoolExecutor", MagicMock(side_effect=OSError))
1525 def test_works_in_mono_process_only_environment(self) -> None:
1526 with cache_dir() as workspace:
1528 (workspace / "one.py").resolve(),
1529 (workspace / "two.py").resolve(),
1531 f.write_text('print("hello")\n')
1532 self.invokeBlack([str(workspace)])
1535 def test_check_diff_use_together(self) -> None:
1537 # Files which will be reformatted.
1538 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1539 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1540 # Files which will not be reformatted.
1541 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1542 self.invokeBlack([str(src2), "--diff", "--check"])
1543 # Multi file command.
1544 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1546 def test_no_files(self) -> None:
1548 # Without an argument, black exits with error code 0.
1549 self.invokeBlack([])
1551 def test_broken_symlink(self) -> None:
1552 with cache_dir() as workspace:
1553 symlink = workspace / "broken_link.py"
1555 symlink.symlink_to("nonexistent.py")
1556 except OSError as e:
1557 self.skipTest(f"Can't create symlinks: {e}")
1558 self.invokeBlack([str(workspace.resolve())])
1560 def test_read_cache_line_lengths(self) -> None:
1562 short_mode = replace(DEFAULT_MODE, line_length=1)
1563 with cache_dir() as workspace:
1564 path = (workspace / "file.py").resolve()
1566 black.write_cache({}, [path], mode)
1567 one = black.read_cache(mode)
1568 self.assertIn(path, one)
1569 two = black.read_cache(short_mode)
1570 self.assertNotIn(path, two)
1572 def test_tricky_unicode_symbols(self) -> None:
1573 source, expected = read_data("tricky_unicode_symbols")
1575 self.assertFormatEqual(expected, actual)
1576 black.assert_equivalent(source, actual)
1577 black.assert_stable(source, actual, DEFAULT_MODE)
1579 def test_single_file_force_pyi(self) -> None:
1580 pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
1581 contents, expected = read_data("force_pyi")
1582 with cache_dir() as workspace:
1583 path = (workspace / "file.py").resolve()
1584 with open(path, "w") as fh:
1586 self.invokeBlack([str(path), "--pyi"])
1587 with open(path, "r") as fh:
1589 # verify cache with --pyi is separate
1590 pyi_cache = black.read_cache(pyi_mode)
1591 self.assertIn(path, pyi_cache)
1592 normal_cache = black.read_cache(DEFAULT_MODE)
1593 self.assertNotIn(path, normal_cache)
1594 self.assertFormatEqual(expected, actual)
1595 black.assert_equivalent(contents, actual)
1596 black.assert_stable(contents, actual, pyi_mode)
1599 def test_multi_file_force_pyi(self) -> None:
1600 reg_mode = DEFAULT_MODE
1601 pyi_mode = replace(DEFAULT_MODE, is_pyi=True)
1602 contents, expected = read_data("force_pyi")
1603 with cache_dir() as workspace:
1605 (workspace / "file1.py").resolve(),
1606 (workspace / "file2.py").resolve(),
1609 with open(path, "w") as fh:
1611 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1613 with open(path, "r") as fh:
1615 self.assertEqual(actual, expected)
1616 # verify cache with --pyi is separate
1617 pyi_cache = black.read_cache(pyi_mode)
1618 normal_cache = black.read_cache(reg_mode)
1620 self.assertIn(path, pyi_cache)
1621 self.assertNotIn(path, normal_cache)
1623 def test_pipe_force_pyi(self) -> None:
1624 source, expected = read_data("force_pyi")
1625 result = CliRunner().invoke(
1626 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1628 self.assertEqual(result.exit_code, 0)
1629 actual = result.output
1630 self.assertFormatEqual(actual, expected)
1632 def test_single_file_force_py36(self) -> None:
1633 reg_mode = DEFAULT_MODE
1634 py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
1635 source, expected = read_data("force_py36")
1636 with cache_dir() as workspace:
1637 path = (workspace / "file.py").resolve()
1638 with open(path, "w") as fh:
1640 self.invokeBlack([str(path), *PY36_ARGS])
1641 with open(path, "r") as fh:
1643 # verify cache with --target-version is separate
1644 py36_cache = black.read_cache(py36_mode)
1645 self.assertIn(path, py36_cache)
1646 normal_cache = black.read_cache(reg_mode)
1647 self.assertNotIn(path, normal_cache)
1648 self.assertEqual(actual, expected)
1651 def test_multi_file_force_py36(self) -> None:
1652 reg_mode = DEFAULT_MODE
1653 py36_mode = replace(DEFAULT_MODE, target_versions=PY36_VERSIONS)
1654 source, expected = read_data("force_py36")
1655 with cache_dir() as workspace:
1657 (workspace / "file1.py").resolve(),
1658 (workspace / "file2.py").resolve(),
1661 with open(path, "w") as fh:
1663 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1665 with open(path, "r") as fh:
1667 self.assertEqual(actual, expected)
1668 # verify cache with --target-version is separate
1669 pyi_cache = black.read_cache(py36_mode)
1670 normal_cache = black.read_cache(reg_mode)
1672 self.assertIn(path, pyi_cache)
1673 self.assertNotIn(path, normal_cache)
1675 def test_collections(self) -> None:
1676 source, expected = read_data("collections")
1678 self.assertFormatEqual(expected, actual)
1679 black.assert_equivalent(source, actual)
1680 black.assert_stable(source, actual, DEFAULT_MODE)
1682 def test_pipe_force_py36(self) -> None:
1683 source, expected = read_data("force_py36")
1684 result = CliRunner().invoke(
1686 ["-", "-q", "--target-version=py36"],
1687 input=BytesIO(source.encode("utf8")),
1689 self.assertEqual(result.exit_code, 0)
1690 actual = result.output
1691 self.assertFormatEqual(actual, expected)
1693 def test_include_exclude(self) -> None:
1694 path = THIS_DIR / "data" / "include_exclude_tests"
1695 include = re.compile(r"\.pyi?$")
1696 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1697 report = black.Report()
1698 gitignore = PathSpec.from_lines("gitwildmatch", [])
1699 sources: List[Path] = []
1701 Path(path / "b/dont_exclude/a.py"),
1702 Path(path / "b/dont_exclude/a.pyi"),
1704 this_abs = THIS_DIR.resolve()
1706 black.gen_python_files(
1707 path.iterdir(), this_abs, include, exclude, None, report, gitignore
1710 self.assertEqual(sorted(expected), sorted(sources))
1712 @patch("black.find_project_root", lambda *args: THIS_DIR.resolve())
1713 def test_exclude_for_issue_1572(self) -> None:
1714 # Exclude shouldn't touch files that were explicitly given to Black through the
1715 # CLI. Exclude is supposed to only apply to the recursive discovery of files.
1716 # https://github.com/psf/black/issues/1572
1717 path = THIS_DIR / "data" / "include_exclude_tests"
1719 exclude = r"/exclude/|a\.py"
1720 src = str(path / "b/exclude/a.py")
1721 report = black.Report()
1722 expected = [Path(path / "b/exclude/a.py")]
1735 self.assertEqual(sorted(expected), sorted(sources))
1737 def test_gitignore_exclude(self) -> None:
1738 path = THIS_DIR / "data" / "include_exclude_tests"
1739 include = re.compile(r"\.pyi?$")
1740 exclude = re.compile(r"")
1741 report = black.Report()
1742 gitignore = PathSpec.from_lines(
1743 "gitwildmatch", ["exclude/", ".definitely_exclude"]
1745 sources: List[Path] = []
1747 Path(path / "b/dont_exclude/a.py"),
1748 Path(path / "b/dont_exclude/a.pyi"),
1750 this_abs = THIS_DIR.resolve()
1752 black.gen_python_files(
1753 path.iterdir(), this_abs, include, exclude, None, report, gitignore
1756 self.assertEqual(sorted(expected), sorted(sources))
1758 def test_empty_include(self) -> None:
1759 path = THIS_DIR / "data" / "include_exclude_tests"
1760 report = black.Report()
1761 gitignore = PathSpec.from_lines("gitwildmatch", [])
1762 empty = re.compile(r"")
1763 sources: List[Path] = []
1765 Path(path / "b/exclude/a.pie"),
1766 Path(path / "b/exclude/a.py"),
1767 Path(path / "b/exclude/a.pyi"),
1768 Path(path / "b/dont_exclude/a.pie"),
1769 Path(path / "b/dont_exclude/a.py"),
1770 Path(path / "b/dont_exclude/a.pyi"),
1771 Path(path / "b/.definitely_exclude/a.pie"),
1772 Path(path / "b/.definitely_exclude/a.py"),
1773 Path(path / "b/.definitely_exclude/a.pyi"),
1775 this_abs = THIS_DIR.resolve()
1777 black.gen_python_files(
1781 re.compile(black.DEFAULT_EXCLUDES),
1787 self.assertEqual(sorted(expected), sorted(sources))
1789 def test_empty_exclude(self) -> None:
1790 path = THIS_DIR / "data" / "include_exclude_tests"
1791 report = black.Report()
1792 gitignore = PathSpec.from_lines("gitwildmatch", [])
1793 empty = re.compile(r"")
1794 sources: List[Path] = []
1796 Path(path / "b/dont_exclude/a.py"),
1797 Path(path / "b/dont_exclude/a.pyi"),
1798 Path(path / "b/exclude/a.py"),
1799 Path(path / "b/exclude/a.pyi"),
1800 Path(path / "b/.definitely_exclude/a.py"),
1801 Path(path / "b/.definitely_exclude/a.pyi"),
1803 this_abs = THIS_DIR.resolve()
1805 black.gen_python_files(
1808 re.compile(black.DEFAULT_INCLUDES),
1815 self.assertEqual(sorted(expected), sorted(sources))
1817 def test_invalid_include_exclude(self) -> None:
1818 for option in ["--include", "--exclude"]:
1819 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1821 def test_preserves_line_endings(self) -> None:
1822 with TemporaryDirectory() as workspace:
1823 test_file = Path(workspace) / "test.py"
1824 for nl in ["\n", "\r\n"]:
1825 contents = nl.join(["def f( ):", " pass"])
1826 test_file.write_bytes(contents.encode())
1827 ff(test_file, write_back=black.WriteBack.YES)
1828 updated_contents: bytes = test_file.read_bytes()
1829 self.assertIn(nl.encode(), updated_contents)
1831 self.assertNotIn(b"\r\n", updated_contents)
1833 def test_preserves_line_endings_via_stdin(self) -> None:
1834 for nl in ["\n", "\r\n"]:
1835 contents = nl.join(["def f( ):", " pass"])
1836 runner = BlackRunner()
1837 result = runner.invoke(
1838 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1840 self.assertEqual(result.exit_code, 0)
1841 output = runner.stdout_bytes
1842 self.assertIn(nl.encode("utf8"), output)
1844 self.assertNotIn(b"\r\n", output)
1846 def test_assert_equivalent_different_asts(self) -> None:
1847 with self.assertRaises(AssertionError):
1848 black.assert_equivalent("{}", "None")
1850 def test_symlink_out_of_root_directory(self) -> None:
1852 root = THIS_DIR.resolve()
1854 include = re.compile(black.DEFAULT_INCLUDES)
1855 exclude = re.compile(black.DEFAULT_EXCLUDES)
1856 report = black.Report()
1857 gitignore = PathSpec.from_lines("gitwildmatch", [])
1858 # `child` should behave like a symlink which resolved path is clearly
1859 # outside of the `root` directory.
1860 path.iterdir.return_value = [child]
1861 child.resolve.return_value = Path("/a/b/c")
1862 child.as_posix.return_value = "/a/b/c"
1863 child.is_symlink.return_value = True
1866 black.gen_python_files(
1867 path.iterdir(), root, include, exclude, None, report, gitignore
1870 except ValueError as ve:
1871 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1872 path.iterdir.assert_called_once()
1873 child.resolve.assert_called_once()
1874 child.is_symlink.assert_called_once()
1875 # `child` should behave like a strange file which resolved path is clearly
1876 # outside of the `root` directory.
1877 child.is_symlink.return_value = False
1878 with self.assertRaises(ValueError):
1880 black.gen_python_files(
1881 path.iterdir(), root, include, exclude, None, report, gitignore
1884 path.iterdir.assert_called()
1885 self.assertEqual(path.iterdir.call_count, 2)
1886 child.resolve.assert_called()
1887 self.assertEqual(child.resolve.call_count, 2)
1888 child.is_symlink.assert_called()
1889 self.assertEqual(child.is_symlink.call_count, 2)
1891 def test_shhh_click(self) -> None:
1893 from click import _unicodefun # type: ignore
1894 except ModuleNotFoundError:
1895 self.skipTest("Incompatible Click version")
1896 if not hasattr(_unicodefun, "_verify_python3_env"):
1897 self.skipTest("Incompatible Click version")
1898 # First, let's see if Click is crashing with a preferred ASCII charset.
1899 with patch("locale.getpreferredencoding") as gpe:
1900 gpe.return_value = "ASCII"
1901 with self.assertRaises(RuntimeError):
1902 _unicodefun._verify_python3_env()
1903 # Now, let's silence Click...
1905 # ...and confirm it's silent.
1906 with patch("locale.getpreferredencoding") as gpe:
1907 gpe.return_value = "ASCII"
1909 _unicodefun._verify_python3_env()
1910 except RuntimeError as re:
1911 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1913 def test_root_logger_not_used_directly(self) -> None:
1914 def fail(*args: Any, **kwargs: Any) -> None:
1915 self.fail("Record created with root logger")
1917 with patch.multiple(
1928 def test_invalid_config_return_code(self) -> None:
1929 tmp_file = Path(black.dump_to_file())
1931 tmp_config = Path(black.dump_to_file())
1933 args = ["--config", str(tmp_config), str(tmp_file)]
1934 self.invokeBlack(args, exit_code=2, ignore_config=False)
1938 def test_parse_pyproject_toml(self) -> None:
1939 test_toml_file = THIS_DIR / "test.toml"
1940 config = black.parse_pyproject_toml(str(test_toml_file))
1941 self.assertEqual(config["verbose"], 1)
1942 self.assertEqual(config["check"], "no")
1943 self.assertEqual(config["diff"], "y")
1944 self.assertEqual(config["color"], True)
1945 self.assertEqual(config["line_length"], 79)
1946 self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
1947 self.assertEqual(config["exclude"], r"\.pyi?$")
1948 self.assertEqual(config["include"], r"\.py?$")
1950 def test_read_pyproject_toml(self) -> None:
1951 test_toml_file = THIS_DIR / "test.toml"
1952 fake_ctx = FakeContext()
1953 black.read_pyproject_toml(fake_ctx, FakeParameter(), str(test_toml_file))
1954 config = fake_ctx.default_map
1955 self.assertEqual(config["verbose"], "1")
1956 self.assertEqual(config["check"], "no")
1957 self.assertEqual(config["diff"], "y")
1958 self.assertEqual(config["color"], "True")
1959 self.assertEqual(config["line_length"], "79")
1960 self.assertEqual(config["target_version"], ["py36", "py37", "py38"])
1961 self.assertEqual(config["exclude"], r"\.pyi?$")
1962 self.assertEqual(config["include"], r"\.py?$")
1964 def test_find_project_root(self) -> None:
1965 with TemporaryDirectory() as workspace:
1966 root = Path(workspace)
1967 test_dir = root / "test"
1970 src_dir = root / "src"
1973 root_pyproject = root / "pyproject.toml"
1974 root_pyproject.touch()
1975 src_pyproject = src_dir / "pyproject.toml"
1976 src_pyproject.touch()
1977 src_python = src_dir / "foo.py"
1981 black.find_project_root((src_dir, test_dir)), root.resolve()
1983 self.assertEqual(black.find_project_root((src_dir,)), src_dir.resolve())
1984 self.assertEqual(black.find_project_root((src_python,)), src_dir.resolve())
1986 def test_bpo_33660_workaround(self) -> None:
1987 if system() == "Windows":
1990 # https://bugs.python.org/issue33660
1992 old_cwd = Path.cwd()
1996 path = Path("workspace") / "project"
1997 report = black.Report(verbose=True)
1998 normalized_path = black.normalize_path_maybe_ignore(path, root, report)
1999 self.assertEqual(normalized_path, "workspace/project")
2001 os.chdir(str(old_cwd))
2004 with open(black.__file__, "r", encoding="utf-8") as _bf:
2005 black_source_lines = _bf.readlines()
2008 def tracefunc(frame: types.FrameType, event: str, arg: Any) -> Callable:
2009 """Show function calls `from black/__init__.py` as they happen.
2011 Register this with `sys.settrace()` in a test you're debugging.
2016 stack = len(inspect.stack()) - 19
2018 filename = frame.f_code.co_filename
2019 lineno = frame.f_lineno
2020 func_sig_lineno = lineno - 1
2021 funcname = black_source_lines[func_sig_lineno].strip()
2022 while funcname.startswith("@"):
2023 func_sig_lineno += 1
2024 funcname = black_source_lines[func_sig_lineno].strip()
2025 if "black/__init__.py" in filename:
2026 print(f"{' ' * stack}{lineno}:{funcname}")
2030 if __name__ == "__main__":
2031 unittest.main(module="test_black")