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.
4 from concurrent.futures import ThreadPoolExecutor
5 from contextlib import contextmanager
6 from functools import partial
7 from io import BytesIO, TextIOWrapper
9 from pathlib import Path
12 from tempfile import TemporaryDirectory
13 from typing import Any, BinaryIO, Generator, List, Tuple, Iterator, TypeVar
15 from unittest.mock import patch, MagicMock
17 from click import unstyle
18 from click.testing import CliRunner
21 from black import Feature, TargetVersion
25 from aiohttp.test_utils import AioHTTPTestCase, unittest_run_loop
26 from aiohttp import web
28 has_blackd_deps = False
30 has_blackd_deps = True
32 from pathspec import PathSpec
34 ff = partial(black.format_file_in_place, mode=black.FileMode(), fast=True)
35 fs = partial(black.format_str, mode=black.FileMode())
36 THIS_FILE = Path(__file__)
37 THIS_DIR = THIS_FILE.parent
38 EMPTY_LINE = "# EMPTY LINE WITH WHITESPACE" + " (this comment will be removed)"
40 f"--target-version={version.name.lower()}" for version in black.PY36_VERSIONS
46 def dump_to_stderr(*output: str) -> str:
47 return "\n" + "\n".join(output) + "\n"
50 def read_data(name: str, data: bool = True) -> Tuple[str, str]:
51 """read_data('test_name') -> 'input', 'output'"""
52 if not name.endswith((".py", ".pyi", ".out", ".diff")):
54 _input: List[str] = []
55 _output: List[str] = []
56 base_dir = THIS_DIR / "data" if data else THIS_DIR
57 with open(base_dir / name, "r", encoding="utf8") as test:
58 lines = test.readlines()
61 line = line.replace(EMPTY_LINE, "")
62 if line.rstrip() == "# output":
67 if _input and not _output:
68 # If there's no output marker, treat the entire file as already pre-formatted.
70 return "".join(_input).strip() + "\n", "".join(_output).strip() + "\n"
74 def cache_dir(exists: bool = True) -> Iterator[Path]:
75 with TemporaryDirectory() as workspace:
76 cache_dir = Path(workspace)
78 cache_dir = cache_dir / "new"
79 with patch("black.CACHE_DIR", cache_dir):
84 def event_loop(close: bool) -> Iterator[None]:
85 policy = asyncio.get_event_loop_policy()
86 loop = policy.new_event_loop()
87 asyncio.set_event_loop(loop)
97 def skip_if_exception(e: str) -> Iterator[None]:
100 except Exception as exc:
101 if exc.__class__.__name__ == e:
102 unittest.skip(f"Encountered expected exception {exc}, skipping")
107 class BlackRunner(CliRunner):
108 """Modify CliRunner so that stderr is not merged with stdout.
110 This is a hack that can be removed once we depend on Click 7.x"""
112 def __init__(self) -> None:
113 self.stderrbuf = BytesIO()
114 self.stdoutbuf = BytesIO()
115 self.stdout_bytes = b""
116 self.stderr_bytes = b""
120 def isolation(self, *args: Any, **kwargs: Any) -> Generator[BinaryIO, None, None]:
121 with super().isolation(*args, **kwargs) as output:
123 hold_stderr = sys.stderr
124 sys.stderr = TextIOWrapper(self.stderrbuf, encoding=self.charset)
127 self.stdout_bytes = sys.stdout.buffer.getvalue() # type: ignore
128 self.stderr_bytes = sys.stderr.buffer.getvalue() # type: ignore
129 sys.stderr = hold_stderr
132 class BlackTestCase(unittest.TestCase):
135 def assertFormatEqual(self, expected: str, actual: str) -> None:
136 if actual != expected and not os.environ.get("SKIP_AST_PRINT"):
137 bdv: black.DebugVisitor[Any]
138 black.out("Expected tree:", fg="green")
140 exp_node = black.lib2to3_parse(expected)
141 bdv = black.DebugVisitor()
142 list(bdv.visit(exp_node))
143 except Exception as ve:
145 black.out("Actual tree:", fg="red")
147 exp_node = black.lib2to3_parse(actual)
148 bdv = black.DebugVisitor()
149 list(bdv.visit(exp_node))
150 except Exception as ve:
152 self.assertEqual(expected, actual)
155 self, args: List[str], exit_code: int = 0, ignore_config: bool = True
157 runner = BlackRunner()
159 args = ["--config", str(THIS_DIR / "empty.toml"), *args]
160 result = runner.invoke(black.main, args)
161 self.assertEqual(result.exit_code, exit_code, msg=runner.stderr_bytes.decode())
163 @patch("black.dump_to_file", dump_to_stderr)
164 def checkSourceFile(self, name: str) -> None:
165 path = THIS_DIR.parent / name
166 source, expected = read_data(str(path), data=False)
168 self.assertFormatEqual(expected, actual)
169 black.assert_equivalent(source, actual)
170 black.assert_stable(source, actual, black.FileMode())
171 self.assertFalse(ff(path))
173 @patch("black.dump_to_file", dump_to_stderr)
174 def test_empty(self) -> None:
175 source = expected = ""
177 self.assertFormatEqual(expected, actual)
178 black.assert_equivalent(source, actual)
179 black.assert_stable(source, actual, black.FileMode())
181 def test_empty_ff(self) -> None:
183 tmp_file = Path(black.dump_to_file())
185 self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES))
186 with open(tmp_file, encoding="utf8") as f:
190 self.assertFormatEqual(expected, actual)
192 def test_self(self) -> None:
193 self.checkSourceFile("tests/test_black.py")
195 def test_black(self) -> None:
196 self.checkSourceFile("black.py")
198 def test_pygram(self) -> None:
199 self.checkSourceFile("blib2to3/pygram.py")
201 def test_pytree(self) -> None:
202 self.checkSourceFile("blib2to3/pytree.py")
204 def test_conv(self) -> None:
205 self.checkSourceFile("blib2to3/pgen2/conv.py")
207 def test_driver(self) -> None:
208 self.checkSourceFile("blib2to3/pgen2/driver.py")
210 def test_grammar(self) -> None:
211 self.checkSourceFile("blib2to3/pgen2/grammar.py")
213 def test_literals(self) -> None:
214 self.checkSourceFile("blib2to3/pgen2/literals.py")
216 def test_parse(self) -> None:
217 self.checkSourceFile("blib2to3/pgen2/parse.py")
219 def test_pgen(self) -> None:
220 self.checkSourceFile("blib2to3/pgen2/pgen.py")
222 def test_tokenize(self) -> None:
223 self.checkSourceFile("blib2to3/pgen2/tokenize.py")
225 def test_token(self) -> None:
226 self.checkSourceFile("blib2to3/pgen2/token.py")
228 def test_setup(self) -> None:
229 self.checkSourceFile("setup.py")
231 def test_piping(self) -> None:
232 source, expected = read_data("../black", data=False)
233 result = BlackRunner().invoke(
235 ["-", "--fast", f"--line-length={black.DEFAULT_LINE_LENGTH}"],
236 input=BytesIO(source.encode("utf8")),
238 self.assertEqual(result.exit_code, 0)
239 self.assertFormatEqual(expected, result.output)
240 black.assert_equivalent(source, result.output)
241 black.assert_stable(source, result.output, black.FileMode())
243 def test_piping_diff(self) -> None:
244 diff_header = re.compile(
245 rf"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d "
246 rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
248 source, _ = read_data("expression.py")
249 expected, _ = read_data("expression.diff")
250 config = THIS_DIR / "data" / "empty_pyproject.toml"
254 f"--line-length={black.DEFAULT_LINE_LENGTH}",
256 f"--config={config}",
258 result = BlackRunner().invoke(
259 black.main, args, input=BytesIO(source.encode("utf8"))
261 self.assertEqual(result.exit_code, 0)
262 actual = diff_header.sub("[Deterministic header]", result.output)
263 actual = actual.rstrip() + "\n" # the diff output has a trailing space
264 self.assertEqual(expected, actual)
266 @patch("black.dump_to_file", dump_to_stderr)
267 def test_function(self) -> None:
268 source, expected = read_data("function")
270 self.assertFormatEqual(expected, actual)
271 black.assert_equivalent(source, actual)
272 black.assert_stable(source, actual, black.FileMode())
274 @patch("black.dump_to_file", dump_to_stderr)
275 def test_function2(self) -> None:
276 source, expected = read_data("function2")
278 self.assertFormatEqual(expected, actual)
279 black.assert_equivalent(source, actual)
280 black.assert_stable(source, actual, black.FileMode())
282 @patch("black.dump_to_file", dump_to_stderr)
283 def test_function_trailing_comma(self) -> None:
284 source, expected = read_data("function_trailing_comma")
286 self.assertFormatEqual(expected, actual)
287 black.assert_equivalent(source, actual)
288 black.assert_stable(source, actual, black.FileMode())
290 @patch("black.dump_to_file", dump_to_stderr)
291 def test_expression(self) -> None:
292 source, expected = read_data("expression")
294 self.assertFormatEqual(expected, actual)
295 black.assert_equivalent(source, actual)
296 black.assert_stable(source, actual, black.FileMode())
298 @patch("black.dump_to_file", dump_to_stderr)
299 def test_pep_572(self) -> None:
300 source, expected = read_data("pep_572")
302 self.assertFormatEqual(expected, actual)
303 black.assert_stable(source, actual, black.FileMode())
304 if sys.version_info >= (3, 8):
305 black.assert_equivalent(source, actual)
307 def test_pep_572_version_detection(self) -> None:
308 source, _ = read_data("pep_572")
309 root = black.lib2to3_parse(source)
310 features = black.get_features_used(root)
311 self.assertIn(black.Feature.ASSIGNMENT_EXPRESSIONS, features)
312 versions = black.detect_target_versions(root)
313 self.assertIn(black.TargetVersion.PY38, versions)
315 def test_expression_ff(self) -> None:
316 source, expected = read_data("expression")
317 tmp_file = Path(black.dump_to_file(source))
319 self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
320 with open(tmp_file, encoding="utf8") as f:
324 self.assertFormatEqual(expected, actual)
325 with patch("black.dump_to_file", dump_to_stderr):
326 black.assert_equivalent(source, actual)
327 black.assert_stable(source, actual, black.FileMode())
329 def test_expression_diff(self) -> None:
330 source, _ = read_data("expression.py")
331 expected, _ = read_data("expression.diff")
332 tmp_file = Path(black.dump_to_file(source))
333 diff_header = re.compile(
334 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
335 rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
338 result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
339 self.assertEqual(result.exit_code, 0)
342 actual = result.output
343 actual = diff_header.sub("[Deterministic header]", actual)
344 actual = actual.rstrip() + "\n" # the diff output has a trailing space
345 if expected != actual:
346 dump = black.dump_to_file(actual)
348 f"Expected diff isn't equal to the actual. If you made changes "
349 f"to expression.py and this is an anticipated difference, "
350 f"overwrite tests/data/expression.diff with {dump}"
352 self.assertEqual(expected, actual, msg)
354 @patch("black.dump_to_file", dump_to_stderr)
355 def test_fstring(self) -> None:
356 source, expected = read_data("fstring")
358 self.assertFormatEqual(expected, actual)
359 black.assert_equivalent(source, actual)
360 black.assert_stable(source, actual, black.FileMode())
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, black.FileMode())
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")
383 self.assertFormatEqual(expected, actual)
384 black.assert_equivalent(source, actual)
385 black.assert_stable(source, actual, black.FileMode())
386 mode = black.FileMode(string_normalization=False)
387 not_normalized = fs(source, mode=mode)
388 self.assertFormatEqual(source, not_normalized)
389 black.assert_equivalent(source, not_normalized)
390 black.assert_stable(source, not_normalized, mode=mode)
392 @patch("black.dump_to_file", dump_to_stderr)
393 def test_slices(self) -> None:
394 source, expected = read_data("slices")
396 self.assertFormatEqual(expected, actual)
397 black.assert_equivalent(source, actual)
398 black.assert_stable(source, actual, black.FileMode())
400 @patch("black.dump_to_file", dump_to_stderr)
401 def test_comments(self) -> None:
402 source, expected = read_data("comments")
404 self.assertFormatEqual(expected, actual)
405 black.assert_equivalent(source, actual)
406 black.assert_stable(source, actual, black.FileMode())
408 @patch("black.dump_to_file", dump_to_stderr)
409 def test_comments2(self) -> None:
410 source, expected = read_data("comments2")
412 self.assertFormatEqual(expected, actual)
413 black.assert_equivalent(source, actual)
414 black.assert_stable(source, actual, black.FileMode())
416 @patch("black.dump_to_file", dump_to_stderr)
417 def test_comments3(self) -> None:
418 source, expected = read_data("comments3")
420 self.assertFormatEqual(expected, actual)
421 black.assert_equivalent(source, actual)
422 black.assert_stable(source, actual, black.FileMode())
424 @patch("black.dump_to_file", dump_to_stderr)
425 def test_comments4(self) -> None:
426 source, expected = read_data("comments4")
428 self.assertFormatEqual(expected, actual)
429 black.assert_equivalent(source, actual)
430 black.assert_stable(source, actual, black.FileMode())
432 @patch("black.dump_to_file", dump_to_stderr)
433 def test_comments5(self) -> None:
434 source, expected = read_data("comments5")
436 self.assertFormatEqual(expected, actual)
437 black.assert_equivalent(source, actual)
438 black.assert_stable(source, actual, black.FileMode())
440 @patch("black.dump_to_file", dump_to_stderr)
441 def test_comments6(self) -> None:
442 source, expected = read_data("comments6")
444 self.assertFormatEqual(expected, actual)
445 black.assert_equivalent(source, actual)
446 black.assert_stable(source, actual, black.FileMode())
448 @patch("black.dump_to_file", dump_to_stderr)
449 def test_comments7(self) -> None:
450 source, expected = read_data("comments7")
452 self.assertFormatEqual(expected, actual)
453 black.assert_equivalent(source, actual)
454 black.assert_stable(source, actual, black.FileMode())
456 @patch("black.dump_to_file", dump_to_stderr)
457 def test_comment_after_escaped_newline(self) -> None:
458 source, expected = read_data("comment_after_escaped_newline")
460 self.assertFormatEqual(expected, actual)
461 black.assert_equivalent(source, actual)
462 black.assert_stable(source, actual, black.FileMode())
464 @patch("black.dump_to_file", dump_to_stderr)
465 def test_cantfit(self) -> None:
466 source, expected = read_data("cantfit")
468 self.assertFormatEqual(expected, actual)
469 black.assert_equivalent(source, actual)
470 black.assert_stable(source, actual, black.FileMode())
472 @patch("black.dump_to_file", dump_to_stderr)
473 def test_import_spacing(self) -> None:
474 source, expected = read_data("import_spacing")
476 self.assertFormatEqual(expected, actual)
477 black.assert_equivalent(source, actual)
478 black.assert_stable(source, actual, black.FileMode())
480 @patch("black.dump_to_file", dump_to_stderr)
481 def test_composition(self) -> None:
482 source, expected = read_data("composition")
484 self.assertFormatEqual(expected, actual)
485 black.assert_equivalent(source, actual)
486 black.assert_stable(source, actual, black.FileMode())
488 @patch("black.dump_to_file", dump_to_stderr)
489 def test_empty_lines(self) -> None:
490 source, expected = read_data("empty_lines")
492 self.assertFormatEqual(expected, actual)
493 black.assert_equivalent(source, actual)
494 black.assert_stable(source, actual, black.FileMode())
496 @patch("black.dump_to_file", dump_to_stderr)
497 def test_remove_parens(self) -> None:
498 source, expected = read_data("remove_parens")
500 self.assertFormatEqual(expected, actual)
501 black.assert_equivalent(source, actual)
502 black.assert_stable(source, actual, black.FileMode())
504 @patch("black.dump_to_file", dump_to_stderr)
505 def test_string_prefixes(self) -> None:
506 source, expected = read_data("string_prefixes")
508 self.assertFormatEqual(expected, actual)
509 black.assert_equivalent(source, actual)
510 black.assert_stable(source, actual, black.FileMode())
512 @patch("black.dump_to_file", dump_to_stderr)
513 def test_numeric_literals(self) -> None:
514 source, expected = read_data("numeric_literals")
515 mode = black.FileMode(target_versions=black.PY36_VERSIONS)
516 actual = fs(source, mode=mode)
517 self.assertFormatEqual(expected, actual)
518 black.assert_equivalent(source, actual)
519 black.assert_stable(source, actual, mode)
521 @patch("black.dump_to_file", dump_to_stderr)
522 def test_numeric_literals_ignoring_underscores(self) -> None:
523 source, expected = read_data("numeric_literals_skip_underscores")
524 mode = black.FileMode(target_versions=black.PY36_VERSIONS)
525 actual = fs(source, mode=mode)
526 self.assertFormatEqual(expected, actual)
527 black.assert_equivalent(source, actual)
528 black.assert_stable(source, actual, mode)
530 @patch("black.dump_to_file", dump_to_stderr)
531 def test_numeric_literals_py2(self) -> None:
532 source, expected = read_data("numeric_literals_py2")
534 self.assertFormatEqual(expected, actual)
535 black.assert_stable(source, actual, black.FileMode())
537 @patch("black.dump_to_file", dump_to_stderr)
538 def test_python2(self) -> None:
539 source, expected = read_data("python2")
541 self.assertFormatEqual(expected, actual)
542 black.assert_equivalent(source, actual)
543 black.assert_stable(source, actual, black.FileMode())
545 @patch("black.dump_to_file", dump_to_stderr)
546 def test_python2_print_function(self) -> None:
547 source, expected = read_data("python2_print_function")
548 mode = black.FileMode(target_versions={TargetVersion.PY27})
549 actual = fs(source, mode=mode)
550 self.assertFormatEqual(expected, actual)
551 black.assert_equivalent(source, actual)
552 black.assert_stable(source, actual, mode)
554 @patch("black.dump_to_file", dump_to_stderr)
555 def test_python2_unicode_literals(self) -> None:
556 source, expected = read_data("python2_unicode_literals")
558 self.assertFormatEqual(expected, actual)
559 black.assert_equivalent(source, actual)
560 black.assert_stable(source, actual, black.FileMode())
562 @patch("black.dump_to_file", dump_to_stderr)
563 def test_stub(self) -> None:
564 mode = black.FileMode(is_pyi=True)
565 source, expected = read_data("stub.pyi")
566 actual = fs(source, mode=mode)
567 self.assertFormatEqual(expected, actual)
568 black.assert_stable(source, actual, mode)
570 @patch("black.dump_to_file", dump_to_stderr)
571 def test_async_as_identifier(self) -> None:
572 source_path = (THIS_DIR / "data" / "async_as_identifier.py").resolve()
573 source, expected = read_data("async_as_identifier")
575 self.assertFormatEqual(expected, actual)
576 major, minor = sys.version_info[:2]
577 if major < 3 or (major <= 3 and minor < 7):
578 black.assert_equivalent(source, actual)
579 black.assert_stable(source, actual, black.FileMode())
580 # ensure black can parse this when the target is 3.6
581 self.invokeBlack([str(source_path), "--target-version", "py36"])
582 # but not on 3.7, because async/await is no longer an identifier
583 self.invokeBlack([str(source_path), "--target-version", "py37"], exit_code=123)
585 @patch("black.dump_to_file", dump_to_stderr)
586 def test_python37(self) -> None:
587 source_path = (THIS_DIR / "data" / "python37.py").resolve()
588 source, expected = read_data("python37")
590 self.assertFormatEqual(expected, actual)
591 major, minor = sys.version_info[:2]
592 if major > 3 or (major == 3 and minor >= 7):
593 black.assert_equivalent(source, actual)
594 black.assert_stable(source, actual, black.FileMode())
595 # ensure black can parse this when the target is 3.7
596 self.invokeBlack([str(source_path), "--target-version", "py37"])
597 # but not on 3.6, because we use async as a reserved keyword
598 self.invokeBlack([str(source_path), "--target-version", "py36"], exit_code=123)
600 @patch("black.dump_to_file", dump_to_stderr)
601 def test_fmtonoff(self) -> None:
602 source, expected = read_data("fmtonoff")
604 self.assertFormatEqual(expected, actual)
605 black.assert_equivalent(source, actual)
606 black.assert_stable(source, actual, black.FileMode())
608 @patch("black.dump_to_file", dump_to_stderr)
609 def test_fmtonoff2(self) -> None:
610 source, expected = read_data("fmtonoff2")
612 self.assertFormatEqual(expected, actual)
613 black.assert_equivalent(source, actual)
614 black.assert_stable(source, actual, black.FileMode())
616 @patch("black.dump_to_file", dump_to_stderr)
617 def test_remove_empty_parentheses_after_class(self) -> None:
618 source, expected = read_data("class_blank_parentheses")
620 self.assertFormatEqual(expected, actual)
621 black.assert_equivalent(source, actual)
622 black.assert_stable(source, actual, black.FileMode())
624 @patch("black.dump_to_file", dump_to_stderr)
625 def test_new_line_between_class_and_code(self) -> None:
626 source, expected = read_data("class_methods_new_line")
628 self.assertFormatEqual(expected, actual)
629 black.assert_equivalent(source, actual)
630 black.assert_stable(source, actual, black.FileMode())
632 @patch("black.dump_to_file", dump_to_stderr)
633 def test_bracket_match(self) -> None:
634 source, expected = read_data("bracketmatch")
636 self.assertFormatEqual(expected, actual)
637 black.assert_equivalent(source, actual)
638 black.assert_stable(source, actual, black.FileMode())
640 @patch("black.dump_to_file", dump_to_stderr)
641 def test_tuple_assign(self) -> None:
642 source, expected = read_data("tupleassign")
644 self.assertFormatEqual(expected, actual)
645 black.assert_equivalent(source, actual)
646 black.assert_stable(source, actual, black.FileMode())
648 @patch("black.dump_to_file", dump_to_stderr)
649 def test_beginning_backslash(self) -> None:
650 source, expected = read_data("beginning_backslash")
652 self.assertFormatEqual(expected, actual)
653 black.assert_equivalent(source, actual)
654 black.assert_stable(source, actual, black.FileMode())
656 def test_tab_comment_indentation(self) -> None:
657 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
658 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
659 self.assertFormatEqual(contents_spc, fs(contents_spc))
660 self.assertFormatEqual(contents_spc, fs(contents_tab))
662 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
663 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
664 self.assertFormatEqual(contents_spc, fs(contents_spc))
665 self.assertFormatEqual(contents_spc, fs(contents_tab))
667 # mixed tabs and spaces (valid Python 2 code)
668 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t# comment\n pass\n"
669 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
670 self.assertFormatEqual(contents_spc, fs(contents_spc))
671 self.assertFormatEqual(contents_spc, fs(contents_tab))
673 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t\t# comment\n pass\n"
674 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
675 self.assertFormatEqual(contents_spc, fs(contents_spc))
676 self.assertFormatEqual(contents_spc, fs(contents_tab))
678 def test_report_verbose(self) -> None:
679 report = black.Report(verbose=True)
683 def out(msg: str, **kwargs: Any) -> None:
684 out_lines.append(msg)
686 def err(msg: str, **kwargs: Any) -> None:
687 err_lines.append(msg)
689 with patch("black.out", out), patch("black.err", err):
690 report.done(Path("f1"), black.Changed.NO)
691 self.assertEqual(len(out_lines), 1)
692 self.assertEqual(len(err_lines), 0)
693 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
694 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
695 self.assertEqual(report.return_code, 0)
696 report.done(Path("f2"), black.Changed.YES)
697 self.assertEqual(len(out_lines), 2)
698 self.assertEqual(len(err_lines), 0)
699 self.assertEqual(out_lines[-1], "reformatted f2")
701 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
703 report.done(Path("f3"), black.Changed.CACHED)
704 self.assertEqual(len(out_lines), 3)
705 self.assertEqual(len(err_lines), 0)
707 out_lines[-1], "f3 wasn't modified on disk since last run."
710 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
712 self.assertEqual(report.return_code, 0)
714 self.assertEqual(report.return_code, 1)
716 report.failed(Path("e1"), "boom")
717 self.assertEqual(len(out_lines), 3)
718 self.assertEqual(len(err_lines), 1)
719 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
721 unstyle(str(report)),
722 "1 file reformatted, 2 files left unchanged, "
723 "1 file failed to reformat.",
725 self.assertEqual(report.return_code, 123)
726 report.done(Path("f3"), black.Changed.YES)
727 self.assertEqual(len(out_lines), 4)
728 self.assertEqual(len(err_lines), 1)
729 self.assertEqual(out_lines[-1], "reformatted f3")
731 unstyle(str(report)),
732 "2 files reformatted, 2 files left unchanged, "
733 "1 file failed to reformat.",
735 self.assertEqual(report.return_code, 123)
736 report.failed(Path("e2"), "boom")
737 self.assertEqual(len(out_lines), 4)
738 self.assertEqual(len(err_lines), 2)
739 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
741 unstyle(str(report)),
742 "2 files reformatted, 2 files left unchanged, "
743 "2 files failed to reformat.",
745 self.assertEqual(report.return_code, 123)
746 report.path_ignored(Path("wat"), "no match")
747 self.assertEqual(len(out_lines), 5)
748 self.assertEqual(len(err_lines), 2)
749 self.assertEqual(out_lines[-1], "wat ignored: no match")
751 unstyle(str(report)),
752 "2 files reformatted, 2 files left unchanged, "
753 "2 files failed to reformat.",
755 self.assertEqual(report.return_code, 123)
756 report.done(Path("f4"), black.Changed.NO)
757 self.assertEqual(len(out_lines), 6)
758 self.assertEqual(len(err_lines), 2)
759 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
761 unstyle(str(report)),
762 "2 files reformatted, 3 files left unchanged, "
763 "2 files failed to reformat.",
765 self.assertEqual(report.return_code, 123)
768 unstyle(str(report)),
769 "2 files would be reformatted, 3 files would be left unchanged, "
770 "2 files would fail to reformat.",
773 def test_report_quiet(self) -> None:
774 report = black.Report(quiet=True)
778 def out(msg: str, **kwargs: Any) -> None:
779 out_lines.append(msg)
781 def err(msg: str, **kwargs: Any) -> None:
782 err_lines.append(msg)
784 with patch("black.out", out), patch("black.err", err):
785 report.done(Path("f1"), black.Changed.NO)
786 self.assertEqual(len(out_lines), 0)
787 self.assertEqual(len(err_lines), 0)
788 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
789 self.assertEqual(report.return_code, 0)
790 report.done(Path("f2"), black.Changed.YES)
791 self.assertEqual(len(out_lines), 0)
792 self.assertEqual(len(err_lines), 0)
794 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
796 report.done(Path("f3"), black.Changed.CACHED)
797 self.assertEqual(len(out_lines), 0)
798 self.assertEqual(len(err_lines), 0)
800 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
802 self.assertEqual(report.return_code, 0)
804 self.assertEqual(report.return_code, 1)
806 report.failed(Path("e1"), "boom")
807 self.assertEqual(len(out_lines), 0)
808 self.assertEqual(len(err_lines), 1)
809 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
811 unstyle(str(report)),
812 "1 file reformatted, 2 files left unchanged, "
813 "1 file failed to reformat.",
815 self.assertEqual(report.return_code, 123)
816 report.done(Path("f3"), black.Changed.YES)
817 self.assertEqual(len(out_lines), 0)
818 self.assertEqual(len(err_lines), 1)
820 unstyle(str(report)),
821 "2 files reformatted, 2 files left unchanged, "
822 "1 file failed to reformat.",
824 self.assertEqual(report.return_code, 123)
825 report.failed(Path("e2"), "boom")
826 self.assertEqual(len(out_lines), 0)
827 self.assertEqual(len(err_lines), 2)
828 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
830 unstyle(str(report)),
831 "2 files reformatted, 2 files left unchanged, "
832 "2 files failed to reformat.",
834 self.assertEqual(report.return_code, 123)
835 report.path_ignored(Path("wat"), "no match")
836 self.assertEqual(len(out_lines), 0)
837 self.assertEqual(len(err_lines), 2)
839 unstyle(str(report)),
840 "2 files reformatted, 2 files left unchanged, "
841 "2 files failed to reformat.",
843 self.assertEqual(report.return_code, 123)
844 report.done(Path("f4"), black.Changed.NO)
845 self.assertEqual(len(out_lines), 0)
846 self.assertEqual(len(err_lines), 2)
848 unstyle(str(report)),
849 "2 files reformatted, 3 files left unchanged, "
850 "2 files failed to reformat.",
852 self.assertEqual(report.return_code, 123)
855 unstyle(str(report)),
856 "2 files would be reformatted, 3 files would be left unchanged, "
857 "2 files would fail to reformat.",
860 def test_report_normal(self) -> None:
861 report = black.Report()
865 def out(msg: str, **kwargs: Any) -> None:
866 out_lines.append(msg)
868 def err(msg: str, **kwargs: Any) -> None:
869 err_lines.append(msg)
871 with patch("black.out", out), patch("black.err", err):
872 report.done(Path("f1"), black.Changed.NO)
873 self.assertEqual(len(out_lines), 0)
874 self.assertEqual(len(err_lines), 0)
875 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
876 self.assertEqual(report.return_code, 0)
877 report.done(Path("f2"), black.Changed.YES)
878 self.assertEqual(len(out_lines), 1)
879 self.assertEqual(len(err_lines), 0)
880 self.assertEqual(out_lines[-1], "reformatted f2")
882 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
884 report.done(Path("f3"), black.Changed.CACHED)
885 self.assertEqual(len(out_lines), 1)
886 self.assertEqual(len(err_lines), 0)
887 self.assertEqual(out_lines[-1], "reformatted f2")
889 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
891 self.assertEqual(report.return_code, 0)
893 self.assertEqual(report.return_code, 1)
895 report.failed(Path("e1"), "boom")
896 self.assertEqual(len(out_lines), 1)
897 self.assertEqual(len(err_lines), 1)
898 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
900 unstyle(str(report)),
901 "1 file reformatted, 2 files left unchanged, "
902 "1 file failed to reformat.",
904 self.assertEqual(report.return_code, 123)
905 report.done(Path("f3"), black.Changed.YES)
906 self.assertEqual(len(out_lines), 2)
907 self.assertEqual(len(err_lines), 1)
908 self.assertEqual(out_lines[-1], "reformatted f3")
910 unstyle(str(report)),
911 "2 files reformatted, 2 files left unchanged, "
912 "1 file failed to reformat.",
914 self.assertEqual(report.return_code, 123)
915 report.failed(Path("e2"), "boom")
916 self.assertEqual(len(out_lines), 2)
917 self.assertEqual(len(err_lines), 2)
918 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
920 unstyle(str(report)),
921 "2 files reformatted, 2 files left unchanged, "
922 "2 files failed to reformat.",
924 self.assertEqual(report.return_code, 123)
925 report.path_ignored(Path("wat"), "no match")
926 self.assertEqual(len(out_lines), 2)
927 self.assertEqual(len(err_lines), 2)
929 unstyle(str(report)),
930 "2 files reformatted, 2 files left unchanged, "
931 "2 files failed to reformat.",
933 self.assertEqual(report.return_code, 123)
934 report.done(Path("f4"), black.Changed.NO)
935 self.assertEqual(len(out_lines), 2)
936 self.assertEqual(len(err_lines), 2)
938 unstyle(str(report)),
939 "2 files reformatted, 3 files left unchanged, "
940 "2 files failed to reformat.",
942 self.assertEqual(report.return_code, 123)
945 unstyle(str(report)),
946 "2 files would be reformatted, 3 files would be left unchanged, "
947 "2 files would fail to reformat.",
950 def test_lib2to3_parse(self) -> None:
951 with self.assertRaises(black.InvalidInput):
952 black.lib2to3_parse("invalid syntax")
955 black.lib2to3_parse(straddling)
956 black.lib2to3_parse(straddling, {TargetVersion.PY27})
957 black.lib2to3_parse(straddling, {TargetVersion.PY36})
958 black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
961 black.lib2to3_parse(py2_only)
962 black.lib2to3_parse(py2_only, {TargetVersion.PY27})
963 with self.assertRaises(black.InvalidInput):
964 black.lib2to3_parse(py2_only, {TargetVersion.PY36})
965 with self.assertRaises(black.InvalidInput):
966 black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
968 py3_only = "exec(x, end=y)"
969 black.lib2to3_parse(py3_only)
970 with self.assertRaises(black.InvalidInput):
971 black.lib2to3_parse(py3_only, {TargetVersion.PY27})
972 black.lib2to3_parse(py3_only, {TargetVersion.PY36})
973 black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
975 def test_get_features_used(self) -> None:
976 node = black.lib2to3_parse("def f(*, arg): ...\n")
977 self.assertEqual(black.get_features_used(node), set())
978 node = black.lib2to3_parse("def f(*, arg,): ...\n")
979 self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
980 node = black.lib2to3_parse("f(*arg,)\n")
982 black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
984 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
985 self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
986 node = black.lib2to3_parse("123_456\n")
987 self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
988 node = black.lib2to3_parse("123456\n")
989 self.assertEqual(black.get_features_used(node), set())
990 source, expected = read_data("function")
991 node = black.lib2to3_parse(source)
992 expected_features = {
993 Feature.TRAILING_COMMA_IN_CALL,
994 Feature.TRAILING_COMMA_IN_DEF,
997 self.assertEqual(black.get_features_used(node), expected_features)
998 node = black.lib2to3_parse(expected)
999 self.assertEqual(black.get_features_used(node), expected_features)
1000 source, expected = read_data("expression")
1001 node = black.lib2to3_parse(source)
1002 self.assertEqual(black.get_features_used(node), set())
1003 node = black.lib2to3_parse(expected)
1004 self.assertEqual(black.get_features_used(node), set())
1006 def test_get_future_imports(self) -> None:
1007 node = black.lib2to3_parse("\n")
1008 self.assertEqual(set(), black.get_future_imports(node))
1009 node = black.lib2to3_parse("from __future__ import black\n")
1010 self.assertEqual({"black"}, black.get_future_imports(node))
1011 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
1012 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1013 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
1014 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
1015 node = black.lib2to3_parse(
1016 "from __future__ import multiple\nfrom __future__ import imports\n"
1018 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1019 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
1020 self.assertEqual({"black"}, black.get_future_imports(node))
1021 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
1022 self.assertEqual({"black"}, black.get_future_imports(node))
1023 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
1024 self.assertEqual(set(), black.get_future_imports(node))
1025 node = black.lib2to3_parse("from some.module import black\n")
1026 self.assertEqual(set(), black.get_future_imports(node))
1027 node = black.lib2to3_parse(
1028 "from __future__ import unicode_literals as _unicode_literals"
1030 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
1031 node = black.lib2to3_parse(
1032 "from __future__ import unicode_literals as _lol, print"
1034 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
1036 def test_debug_visitor(self) -> None:
1037 source, _ = read_data("debug_visitor.py")
1038 expected, _ = read_data("debug_visitor.out")
1042 def out(msg: str, **kwargs: Any) -> None:
1043 out_lines.append(msg)
1045 def err(msg: str, **kwargs: Any) -> None:
1046 err_lines.append(msg)
1048 with patch("black.out", out), patch("black.err", err):
1049 black.DebugVisitor.show(source)
1050 actual = "\n".join(out_lines) + "\n"
1052 if expected != actual:
1053 log_name = black.dump_to_file(*out_lines)
1057 f"AST print out is different. Actual version dumped to {log_name}",
1060 def test_format_file_contents(self) -> None:
1062 mode = black.FileMode()
1063 with self.assertRaises(black.NothingChanged):
1064 black.format_file_contents(empty, mode=mode, fast=False)
1066 with self.assertRaises(black.NothingChanged):
1067 black.format_file_contents(just_nl, mode=mode, fast=False)
1068 same = "j = [1, 2, 3]\n"
1069 with self.assertRaises(black.NothingChanged):
1070 black.format_file_contents(same, mode=mode, fast=False)
1071 different = "j = [1,2,3]"
1073 actual = black.format_file_contents(different, mode=mode, fast=False)
1074 self.assertEqual(expected, actual)
1075 invalid = "return if you can"
1076 with self.assertRaises(black.InvalidInput) as e:
1077 black.format_file_contents(invalid, mode=mode, fast=False)
1078 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
1080 def test_endmarker(self) -> None:
1081 n = black.lib2to3_parse("\n")
1082 self.assertEqual(n.type, black.syms.file_input)
1083 self.assertEqual(len(n.children), 1)
1084 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
1086 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
1087 def test_assertFormatEqual(self) -> None:
1091 def out(msg: str, **kwargs: Any) -> None:
1092 out_lines.append(msg)
1094 def err(msg: str, **kwargs: Any) -> None:
1095 err_lines.append(msg)
1097 with patch("black.out", out), patch("black.err", err):
1098 with self.assertRaises(AssertionError):
1099 self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")
1101 out_str = "".join(out_lines)
1102 self.assertTrue("Expected tree:" in out_str)
1103 self.assertTrue("Actual tree:" in out_str)
1104 self.assertEqual("".join(err_lines), "")
1106 def test_cache_broken_file(self) -> None:
1107 mode = black.FileMode()
1108 with cache_dir() as workspace:
1109 cache_file = black.get_cache_file(mode)
1110 with cache_file.open("w") as fobj:
1111 fobj.write("this is not a pickle")
1112 self.assertEqual(black.read_cache(mode), {})
1113 src = (workspace / "test.py").resolve()
1114 with src.open("w") as fobj:
1115 fobj.write("print('hello')")
1116 self.invokeBlack([str(src)])
1117 cache = black.read_cache(mode)
1118 self.assertIn(src, cache)
1120 def test_cache_single_file_already_cached(self) -> None:
1121 mode = black.FileMode()
1122 with cache_dir() as workspace:
1123 src = (workspace / "test.py").resolve()
1124 with src.open("w") as fobj:
1125 fobj.write("print('hello')")
1126 black.write_cache({}, [src], mode)
1127 self.invokeBlack([str(src)])
1128 with src.open("r") as fobj:
1129 self.assertEqual(fobj.read(), "print('hello')")
1131 @event_loop(close=False)
1132 def test_cache_multiple_files(self) -> None:
1133 mode = black.FileMode()
1134 with cache_dir() as workspace, patch(
1135 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1137 one = (workspace / "one.py").resolve()
1138 with one.open("w") as fobj:
1139 fobj.write("print('hello')")
1140 two = (workspace / "two.py").resolve()
1141 with two.open("w") as fobj:
1142 fobj.write("print('hello')")
1143 black.write_cache({}, [one], mode)
1144 self.invokeBlack([str(workspace)])
1145 with one.open("r") as fobj:
1146 self.assertEqual(fobj.read(), "print('hello')")
1147 with two.open("r") as fobj:
1148 self.assertEqual(fobj.read(), 'print("hello")\n')
1149 cache = black.read_cache(mode)
1150 self.assertIn(one, cache)
1151 self.assertIn(two, cache)
1153 def test_no_cache_when_writeback_diff(self) -> None:
1154 mode = black.FileMode()
1155 with cache_dir() as workspace:
1156 src = (workspace / "test.py").resolve()
1157 with src.open("w") as fobj:
1158 fobj.write("print('hello')")
1159 self.invokeBlack([str(src), "--diff"])
1160 cache_file = black.get_cache_file(mode)
1161 self.assertFalse(cache_file.exists())
1163 def test_no_cache_when_stdin(self) -> None:
1164 mode = black.FileMode()
1166 result = CliRunner().invoke(
1167 black.main, ["-"], input=BytesIO(b"print('hello')")
1169 self.assertEqual(result.exit_code, 0)
1170 cache_file = black.get_cache_file(mode)
1171 self.assertFalse(cache_file.exists())
1173 def test_read_cache_no_cachefile(self) -> None:
1174 mode = black.FileMode()
1176 self.assertEqual(black.read_cache(mode), {})
1178 def test_write_cache_read_cache(self) -> None:
1179 mode = black.FileMode()
1180 with cache_dir() as workspace:
1181 src = (workspace / "test.py").resolve()
1183 black.write_cache({}, [src], mode)
1184 cache = black.read_cache(mode)
1185 self.assertIn(src, cache)
1186 self.assertEqual(cache[src], black.get_cache_info(src))
1188 def test_filter_cached(self) -> None:
1189 with TemporaryDirectory() as workspace:
1190 path = Path(workspace)
1191 uncached = (path / "uncached").resolve()
1192 cached = (path / "cached").resolve()
1193 cached_but_changed = (path / "changed").resolve()
1196 cached_but_changed.touch()
1197 cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
1198 todo, done = black.filter_cached(
1199 cache, {uncached, cached, cached_but_changed}
1201 self.assertEqual(todo, {uncached, cached_but_changed})
1202 self.assertEqual(done, {cached})
1204 def test_write_cache_creates_directory_if_needed(self) -> None:
1205 mode = black.FileMode()
1206 with cache_dir(exists=False) as workspace:
1207 self.assertFalse(workspace.exists())
1208 black.write_cache({}, [], mode)
1209 self.assertTrue(workspace.exists())
1211 @event_loop(close=False)
1212 def test_failed_formatting_does_not_get_cached(self) -> None:
1213 mode = black.FileMode()
1214 with cache_dir() as workspace, patch(
1215 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1217 failing = (workspace / "failing.py").resolve()
1218 with failing.open("w") as fobj:
1219 fobj.write("not actually python")
1220 clean = (workspace / "clean.py").resolve()
1221 with clean.open("w") as fobj:
1222 fobj.write('print("hello")\n')
1223 self.invokeBlack([str(workspace)], exit_code=123)
1224 cache = black.read_cache(mode)
1225 self.assertNotIn(failing, cache)
1226 self.assertIn(clean, cache)
1228 def test_write_cache_write_fail(self) -> None:
1229 mode = black.FileMode()
1230 with cache_dir(), patch.object(Path, "open") as mock:
1231 mock.side_effect = OSError
1232 black.write_cache({}, [], mode)
1234 @event_loop(close=False)
1235 def test_check_diff_use_together(self) -> None:
1237 # Files which will be reformatted.
1238 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1239 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1240 # Files which will not be reformatted.
1241 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1242 self.invokeBlack([str(src2), "--diff", "--check"])
1243 # Multi file command.
1244 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1246 def test_no_files(self) -> None:
1248 # Without an argument, black exits with error code 0.
1249 self.invokeBlack([])
1251 def test_broken_symlink(self) -> None:
1252 with cache_dir() as workspace:
1253 symlink = workspace / "broken_link.py"
1255 symlink.symlink_to("nonexistent.py")
1256 except OSError as e:
1257 self.skipTest(f"Can't create symlinks: {e}")
1258 self.invokeBlack([str(workspace.resolve())])
1260 def test_read_cache_line_lengths(self) -> None:
1261 mode = black.FileMode()
1262 short_mode = black.FileMode(line_length=1)
1263 with cache_dir() as workspace:
1264 path = (workspace / "file.py").resolve()
1266 black.write_cache({}, [path], mode)
1267 one = black.read_cache(mode)
1268 self.assertIn(path, one)
1269 two = black.read_cache(short_mode)
1270 self.assertNotIn(path, two)
1272 def test_tricky_unicode_symbols(self) -> None:
1273 source, expected = read_data("tricky_unicode_symbols")
1275 self.assertFormatEqual(expected, actual)
1276 black.assert_equivalent(source, actual)
1277 black.assert_stable(source, actual, black.FileMode())
1279 def test_single_file_force_pyi(self) -> None:
1280 reg_mode = black.FileMode()
1281 pyi_mode = black.FileMode(is_pyi=True)
1282 contents, expected = read_data("force_pyi")
1283 with cache_dir() as workspace:
1284 path = (workspace / "file.py").resolve()
1285 with open(path, "w") as fh:
1287 self.invokeBlack([str(path), "--pyi"])
1288 with open(path, "r") as fh:
1290 # verify cache with --pyi is separate
1291 pyi_cache = black.read_cache(pyi_mode)
1292 self.assertIn(path, pyi_cache)
1293 normal_cache = black.read_cache(reg_mode)
1294 self.assertNotIn(path, normal_cache)
1295 self.assertEqual(actual, expected)
1297 @event_loop(close=False)
1298 def test_multi_file_force_pyi(self) -> None:
1299 reg_mode = black.FileMode()
1300 pyi_mode = black.FileMode(is_pyi=True)
1301 contents, expected = read_data("force_pyi")
1302 with cache_dir() as workspace:
1304 (workspace / "file1.py").resolve(),
1305 (workspace / "file2.py").resolve(),
1308 with open(path, "w") as fh:
1310 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1312 with open(path, "r") as fh:
1314 self.assertEqual(actual, expected)
1315 # verify cache with --pyi is separate
1316 pyi_cache = black.read_cache(pyi_mode)
1317 normal_cache = black.read_cache(reg_mode)
1319 self.assertIn(path, pyi_cache)
1320 self.assertNotIn(path, normal_cache)
1322 def test_pipe_force_pyi(self) -> None:
1323 source, expected = read_data("force_pyi")
1324 result = CliRunner().invoke(
1325 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1327 self.assertEqual(result.exit_code, 0)
1328 actual = result.output
1329 self.assertFormatEqual(actual, expected)
1331 def test_single_file_force_py36(self) -> None:
1332 reg_mode = black.FileMode()
1333 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1334 source, expected = read_data("force_py36")
1335 with cache_dir() as workspace:
1336 path = (workspace / "file.py").resolve()
1337 with open(path, "w") as fh:
1339 self.invokeBlack([str(path), *PY36_ARGS])
1340 with open(path, "r") as fh:
1342 # verify cache with --target-version is separate
1343 py36_cache = black.read_cache(py36_mode)
1344 self.assertIn(path, py36_cache)
1345 normal_cache = black.read_cache(reg_mode)
1346 self.assertNotIn(path, normal_cache)
1347 self.assertEqual(actual, expected)
1349 @event_loop(close=False)
1350 def test_multi_file_force_py36(self) -> None:
1351 reg_mode = black.FileMode()
1352 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1353 source, expected = read_data("force_py36")
1354 with cache_dir() as workspace:
1356 (workspace / "file1.py").resolve(),
1357 (workspace / "file2.py").resolve(),
1360 with open(path, "w") as fh:
1362 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1364 with open(path, "r") as fh:
1366 self.assertEqual(actual, expected)
1367 # verify cache with --target-version is separate
1368 pyi_cache = black.read_cache(py36_mode)
1369 normal_cache = black.read_cache(reg_mode)
1371 self.assertIn(path, pyi_cache)
1372 self.assertNotIn(path, normal_cache)
1374 def test_collections(self) -> None:
1375 source, expected = read_data("collections")
1377 self.assertFormatEqual(expected, actual)
1378 black.assert_equivalent(source, actual)
1379 black.assert_stable(source, actual, black.FileMode())
1381 def test_pipe_force_py36(self) -> None:
1382 source, expected = read_data("force_py36")
1383 result = CliRunner().invoke(
1385 ["-", "-q", "--target-version=py36"],
1386 input=BytesIO(source.encode("utf8")),
1388 self.assertEqual(result.exit_code, 0)
1389 actual = result.output
1390 self.assertFormatEqual(actual, expected)
1392 def test_include_exclude(self) -> None:
1393 path = THIS_DIR / "data" / "include_exclude_tests"
1394 include = re.compile(r"\.pyi?$")
1395 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1396 report = black.Report()
1397 gitignore = PathSpec.from_lines("gitwildmatch", [])
1398 sources: List[Path] = []
1400 Path(path / "b/dont_exclude/a.py"),
1401 Path(path / "b/dont_exclude/a.pyi"),
1403 this_abs = THIS_DIR.resolve()
1405 black.gen_python_files_in_dir(
1406 path, this_abs, include, exclude, report, gitignore
1409 self.assertEqual(sorted(expected), sorted(sources))
1411 def test_gitignore_exclude(self) -> None:
1412 path = THIS_DIR / "data" / "include_exclude_tests"
1413 include = re.compile(r"\.pyi?$")
1414 exclude = re.compile(r"")
1415 report = black.Report()
1416 gitignore = PathSpec.from_lines(
1417 "gitwildmatch", ["exclude/", ".definitely_exclude"]
1419 sources: List[Path] = []
1421 Path(path / "b/dont_exclude/a.py"),
1422 Path(path / "b/dont_exclude/a.pyi"),
1424 this_abs = THIS_DIR.resolve()
1426 black.gen_python_files_in_dir(
1427 path, this_abs, include, exclude, report, gitignore
1430 self.assertEqual(sorted(expected), sorted(sources))
1432 def test_empty_include(self) -> None:
1433 path = THIS_DIR / "data" / "include_exclude_tests"
1434 report = black.Report()
1435 gitignore = PathSpec.from_lines("gitwildmatch", [])
1436 empty = re.compile(r"")
1437 sources: List[Path] = []
1439 Path(path / "b/exclude/a.pie"),
1440 Path(path / "b/exclude/a.py"),
1441 Path(path / "b/exclude/a.pyi"),
1442 Path(path / "b/dont_exclude/a.pie"),
1443 Path(path / "b/dont_exclude/a.py"),
1444 Path(path / "b/dont_exclude/a.pyi"),
1445 Path(path / "b/.definitely_exclude/a.pie"),
1446 Path(path / "b/.definitely_exclude/a.py"),
1447 Path(path / "b/.definitely_exclude/a.pyi"),
1449 this_abs = THIS_DIR.resolve()
1451 black.gen_python_files_in_dir(
1455 re.compile(black.DEFAULT_EXCLUDES),
1460 self.assertEqual(sorted(expected), sorted(sources))
1462 def test_empty_exclude(self) -> None:
1463 path = THIS_DIR / "data" / "include_exclude_tests"
1464 report = black.Report()
1465 gitignore = PathSpec.from_lines("gitwildmatch", [])
1466 empty = re.compile(r"")
1467 sources: List[Path] = []
1469 Path(path / "b/dont_exclude/a.py"),
1470 Path(path / "b/dont_exclude/a.pyi"),
1471 Path(path / "b/exclude/a.py"),
1472 Path(path / "b/exclude/a.pyi"),
1473 Path(path / "b/.definitely_exclude/a.py"),
1474 Path(path / "b/.definitely_exclude/a.pyi"),
1476 this_abs = THIS_DIR.resolve()
1478 black.gen_python_files_in_dir(
1481 re.compile(black.DEFAULT_INCLUDES),
1487 self.assertEqual(sorted(expected), sorted(sources))
1489 def test_invalid_include_exclude(self) -> None:
1490 for option in ["--include", "--exclude"]:
1491 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1493 def test_preserves_line_endings(self) -> None:
1494 with TemporaryDirectory() as workspace:
1495 test_file = Path(workspace) / "test.py"
1496 for nl in ["\n", "\r\n"]:
1497 contents = nl.join(["def f( ):", " pass"])
1498 test_file.write_bytes(contents.encode())
1499 ff(test_file, write_back=black.WriteBack.YES)
1500 updated_contents: bytes = test_file.read_bytes()
1501 self.assertIn(nl.encode(), updated_contents)
1503 self.assertNotIn(b"\r\n", updated_contents)
1505 def test_preserves_line_endings_via_stdin(self) -> None:
1506 for nl in ["\n", "\r\n"]:
1507 contents = nl.join(["def f( ):", " pass"])
1508 runner = BlackRunner()
1509 result = runner.invoke(
1510 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1512 self.assertEqual(result.exit_code, 0)
1513 output = runner.stdout_bytes
1514 self.assertIn(nl.encode("utf8"), output)
1516 self.assertNotIn(b"\r\n", output)
1518 def test_assert_equivalent_different_asts(self) -> None:
1519 with self.assertRaises(AssertionError):
1520 black.assert_equivalent("{}", "None")
1522 def test_symlink_out_of_root_directory(self) -> None:
1526 include = re.compile(black.DEFAULT_INCLUDES)
1527 exclude = re.compile(black.DEFAULT_EXCLUDES)
1528 report = black.Report()
1529 gitignore = PathSpec.from_lines("gitwildmatch", [])
1530 # `child` should behave like a symlink which resolved path is clearly
1531 # outside of the `root` directory.
1532 path.iterdir.return_value = [child]
1533 child.resolve.return_value = Path("/a/b/c")
1534 child.is_symlink.return_value = True
1537 black.gen_python_files_in_dir(
1538 path, root, include, exclude, report, gitignore
1541 except ValueError as ve:
1542 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1543 path.iterdir.assert_called_once()
1544 child.resolve.assert_called_once()
1545 child.is_symlink.assert_called_once()
1546 # `child` should behave like a strange file which resolved path is clearly
1547 # outside of the `root` directory.
1548 child.is_symlink.return_value = False
1549 with self.assertRaises(ValueError):
1551 black.gen_python_files_in_dir(
1552 path, root, include, exclude, report, gitignore
1555 path.iterdir.assert_called()
1556 self.assertEqual(path.iterdir.call_count, 2)
1557 child.resolve.assert_called()
1558 self.assertEqual(child.resolve.call_count, 2)
1559 child.is_symlink.assert_called()
1560 self.assertEqual(child.is_symlink.call_count, 2)
1562 def test_shhh_click(self) -> None:
1564 from click import _unicodefun # type: ignore
1565 except ModuleNotFoundError:
1566 self.skipTest("Incompatible Click version")
1567 if not hasattr(_unicodefun, "_verify_python3_env"):
1568 self.skipTest("Incompatible Click version")
1569 # First, let's see if Click is crashing with a preferred ASCII charset.
1570 with patch("locale.getpreferredencoding") as gpe:
1571 gpe.return_value = "ASCII"
1572 with self.assertRaises(RuntimeError):
1573 _unicodefun._verify_python3_env()
1574 # Now, let's silence Click...
1576 # ...and confirm it's silent.
1577 with patch("locale.getpreferredencoding") as gpe:
1578 gpe.return_value = "ASCII"
1580 _unicodefun._verify_python3_env()
1581 except RuntimeError as re:
1582 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1584 def test_root_logger_not_used_directly(self) -> None:
1585 def fail(*args: Any, **kwargs: Any) -> None:
1586 self.fail("Record created with root logger")
1588 with patch.multiple(
1599 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1600 def test_blackd_main(self) -> None:
1601 with patch("blackd.web.run_app"):
1602 result = CliRunner().invoke(blackd.main, [])
1603 if result.exception is not None:
1604 raise result.exception
1605 self.assertEqual(result.exit_code, 0)
1608 class BlackDTestCase(AioHTTPTestCase):
1609 async def get_application(self) -> web.Application:
1610 return blackd.make_app()
1612 # TODO: remove these decorators once the below is released
1613 # https://github.com/aio-libs/aiohttp/pull/3727
1614 @skip_if_exception("ClientOSError")
1615 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1617 async def test_blackd_request_needs_formatting(self) -> None:
1618 response = await self.client.post("/", data=b"print('hello world')")
1619 self.assertEqual(response.status, 200)
1620 self.assertEqual(response.charset, "utf8")
1621 self.assertEqual(await response.read(), b'print("hello world")\n')
1623 @skip_if_exception("ClientOSError")
1624 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1626 async def test_blackd_request_no_change(self) -> None:
1627 response = await self.client.post("/", data=b'print("hello world")\n')
1628 self.assertEqual(response.status, 204)
1629 self.assertEqual(await response.read(), b"")
1631 @skip_if_exception("ClientOSError")
1632 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1634 async def test_blackd_request_syntax_error(self) -> None:
1635 response = await self.client.post("/", data=b"what even ( is")
1636 self.assertEqual(response.status, 400)
1637 content = await response.text()
1639 content.startswith("Cannot parse"),
1640 msg=f"Expected error to start with 'Cannot parse', got {repr(content)}",
1643 @skip_if_exception("ClientOSError")
1644 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1646 async def test_blackd_unsupported_version(self) -> None:
1647 response = await self.client.post(
1648 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "2"}
1650 self.assertEqual(response.status, 501)
1652 @skip_if_exception("ClientOSError")
1653 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1655 async def test_blackd_supported_version(self) -> None:
1656 response = await self.client.post(
1657 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "1"}
1659 self.assertEqual(response.status, 200)
1661 @skip_if_exception("ClientOSError")
1662 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1664 async def test_blackd_invalid_python_variant(self) -> None:
1665 async def check(header_value: str, expected_status: int = 400) -> None:
1666 response = await self.client.post(
1667 "/", data=b"what", headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1669 self.assertEqual(response.status, expected_status)
1672 await check("ruby3.5")
1673 await check("pyi3.6")
1674 await check("py1.5")
1676 await check("py2.8")
1678 await check("pypy3.0")
1679 await check("jython3.4")
1681 @skip_if_exception("ClientOSError")
1682 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1684 async def test_blackd_pyi(self) -> None:
1685 source, expected = read_data("stub.pyi")
1686 response = await self.client.post(
1687 "/", data=source, headers={blackd.PYTHON_VARIANT_HEADER: "pyi"}
1689 self.assertEqual(response.status, 200)
1690 self.assertEqual(await response.text(), expected)
1692 @skip_if_exception("ClientOSError")
1693 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1695 async def test_blackd_python_variant(self) -> None:
1698 " and_has_a_bunch_of,\n"
1699 " very_long_arguments_too,\n"
1700 " and_lots_of_them_as_well_lol,\n"
1701 " **and_very_long_keyword_arguments\n"
1706 async def check(header_value: str, expected_status: int) -> None:
1707 response = await self.client.post(
1708 "/", data=code, headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1711 response.status, expected_status, msg=await response.text()
1714 await check("3.6", 200)
1715 await check("py3.6", 200)
1716 await check("3.6,3.7", 200)
1717 await check("3.6,py3.7", 200)
1718 await check("py36,py37", 200)
1719 await check("36", 200)
1720 await check("3.6.4", 200)
1722 await check("2", 204)
1723 await check("2.7", 204)
1724 await check("py2.7", 204)
1725 await check("3.4", 204)
1726 await check("py3.4", 204)
1727 await check("py34,py36", 204)
1728 await check("34", 204)
1730 @skip_if_exception("ClientOSError")
1731 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1733 async def test_blackd_line_length(self) -> None:
1734 response = await self.client.post(
1735 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "7"}
1737 self.assertEqual(response.status, 200)
1739 @skip_if_exception("ClientOSError")
1740 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1742 async def test_blackd_invalid_line_length(self) -> None:
1743 response = await self.client.post(
1744 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "NaN"}
1746 self.assertEqual(response.status, 400)
1748 @skip_if_exception("ClientOSError")
1749 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1751 async def test_blackd_response_black_version_header(self) -> None:
1752 response = await self.client.post("/")
1753 self.assertIsNotNone(response.headers.get(blackd.BLACK_VERSION_HEADER))
1756 if __name__ == "__main__":
1757 unittest.main(module="test_black")