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 ff = partial(black.format_file_in_place, mode=black.FileMode(), fast=True)
33 fs = partial(black.format_str, mode=black.FileMode())
34 THIS_FILE = Path(__file__)
35 THIS_DIR = THIS_FILE.parent
36 EMPTY_LINE = "# EMPTY LINE WITH WHITESPACE" + " (this comment will be removed)"
38 f"--target-version={version.name.lower()}" for version in black.PY36_VERSIONS
44 def dump_to_stderr(*output: str) -> str:
45 return "\n" + "\n".join(output) + "\n"
48 def read_data(name: str, data: bool = True) -> Tuple[str, str]:
49 """read_data('test_name') -> 'input', 'output'"""
50 if not name.endswith((".py", ".pyi", ".out", ".diff")):
52 _input: List[str] = []
53 _output: List[str] = []
54 base_dir = THIS_DIR / "data" if data else THIS_DIR
55 with open(base_dir / name, "r", encoding="utf8") as test:
56 lines = test.readlines()
59 line = line.replace(EMPTY_LINE, "")
60 if line.rstrip() == "# output":
65 if _input and not _output:
66 # If there's no output marker, treat the entire file as already pre-formatted.
68 return "".join(_input).strip() + "\n", "".join(_output).strip() + "\n"
72 def cache_dir(exists: bool = True) -> Iterator[Path]:
73 with TemporaryDirectory() as workspace:
74 cache_dir = Path(workspace)
76 cache_dir = cache_dir / "new"
77 with patch("black.CACHE_DIR", cache_dir):
82 def event_loop(close: bool) -> Iterator[None]:
83 policy = asyncio.get_event_loop_policy()
84 loop = policy.new_event_loop()
85 asyncio.set_event_loop(loop)
95 def skip_if_exception(e: str) -> Iterator[None]:
98 except Exception as exc:
99 if exc.__class__.__name__ == e:
100 unittest.skip(f"Encountered expected exception {exc}, skipping")
105 class BlackRunner(CliRunner):
106 """Modify CliRunner so that stderr is not merged with stdout.
108 This is a hack that can be removed once we depend on Click 7.x"""
110 def __init__(self) -> None:
111 self.stderrbuf = BytesIO()
112 self.stdoutbuf = BytesIO()
113 self.stdout_bytes = b""
114 self.stderr_bytes = b""
118 def isolation(self, *args: Any, **kwargs: Any) -> Generator[BinaryIO, None, None]:
119 with super().isolation(*args, **kwargs) as output:
121 hold_stderr = sys.stderr
122 sys.stderr = TextIOWrapper(self.stderrbuf, encoding=self.charset)
125 self.stdout_bytes = sys.stdout.buffer.getvalue() # type: ignore
126 self.stderr_bytes = sys.stderr.buffer.getvalue() # type: ignore
127 sys.stderr = hold_stderr
130 class BlackTestCase(unittest.TestCase):
133 def assertFormatEqual(self, expected: str, actual: str) -> None:
134 if actual != expected and not os.environ.get("SKIP_AST_PRINT"):
135 bdv: black.DebugVisitor[Any]
136 black.out("Expected tree:", fg="green")
138 exp_node = black.lib2to3_parse(expected)
139 bdv = black.DebugVisitor()
140 list(bdv.visit(exp_node))
141 except Exception as ve:
143 black.out("Actual tree:", fg="red")
145 exp_node = black.lib2to3_parse(actual)
146 bdv = black.DebugVisitor()
147 list(bdv.visit(exp_node))
148 except Exception as ve:
150 self.assertEqual(expected, actual)
153 self, args: List[str], exit_code: int = 0, ignore_config: bool = True
155 runner = BlackRunner()
157 args = ["--config", str(THIS_DIR / "empty.toml"), *args]
158 result = runner.invoke(black.main, args)
159 self.assertEqual(result.exit_code, exit_code, msg=runner.stderr_bytes.decode())
161 @patch("black.dump_to_file", dump_to_stderr)
162 def test_empty(self) -> None:
163 source = expected = ""
165 self.assertFormatEqual(expected, actual)
166 black.assert_equivalent(source, actual)
167 black.assert_stable(source, actual, black.FileMode())
169 def test_empty_ff(self) -> None:
171 tmp_file = Path(black.dump_to_file())
173 self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES))
174 with open(tmp_file, encoding="utf8") as f:
178 self.assertFormatEqual(expected, actual)
180 @patch("black.dump_to_file", dump_to_stderr)
181 def test_self(self) -> None:
182 source, expected = read_data("test_black", data=False)
184 self.assertFormatEqual(expected, actual)
185 black.assert_equivalent(source, actual)
186 black.assert_stable(source, actual, black.FileMode())
187 self.assertFalse(ff(THIS_FILE))
189 @patch("black.dump_to_file", dump_to_stderr)
190 def test_black(self) -> None:
191 source, expected = read_data("../black", data=False)
193 self.assertFormatEqual(expected, actual)
194 black.assert_equivalent(source, actual)
195 black.assert_stable(source, actual, black.FileMode())
196 self.assertFalse(ff(THIS_DIR / ".." / "black.py"))
198 def test_piping(self) -> None:
199 source, expected = read_data("../black", data=False)
200 result = BlackRunner().invoke(
202 ["-", "--fast", f"--line-length={black.DEFAULT_LINE_LENGTH}"],
203 input=BytesIO(source.encode("utf8")),
205 self.assertEqual(result.exit_code, 0)
206 self.assertFormatEqual(expected, result.output)
207 black.assert_equivalent(source, result.output)
208 black.assert_stable(source, result.output, black.FileMode())
210 def test_piping_diff(self) -> None:
211 diff_header = re.compile(
212 rf"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d "
213 rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
215 source, _ = read_data("expression.py")
216 expected, _ = read_data("expression.diff")
217 config = THIS_DIR / "data" / "empty_pyproject.toml"
221 f"--line-length={black.DEFAULT_LINE_LENGTH}",
223 f"--config={config}",
225 result = BlackRunner().invoke(
226 black.main, args, input=BytesIO(source.encode("utf8"))
228 self.assertEqual(result.exit_code, 0)
229 actual = diff_header.sub("[Deterministic header]", result.output)
230 actual = actual.rstrip() + "\n" # the diff output has a trailing space
231 self.assertEqual(expected, actual)
233 @patch("black.dump_to_file", dump_to_stderr)
234 def test_setup(self) -> None:
235 source, expected = read_data("../setup", data=False)
237 self.assertFormatEqual(expected, actual)
238 black.assert_equivalent(source, actual)
239 black.assert_stable(source, actual, black.FileMode())
240 self.assertFalse(ff(THIS_DIR / ".." / "setup.py"))
242 @patch("black.dump_to_file", dump_to_stderr)
243 def test_function(self) -> None:
244 source, expected = read_data("function")
246 self.assertFormatEqual(expected, actual)
247 black.assert_equivalent(source, actual)
248 black.assert_stable(source, actual, black.FileMode())
250 @patch("black.dump_to_file", dump_to_stderr)
251 def test_function2(self) -> None:
252 source, expected = read_data("function2")
254 self.assertFormatEqual(expected, actual)
255 black.assert_equivalent(source, actual)
256 black.assert_stable(source, actual, black.FileMode())
258 @patch("black.dump_to_file", dump_to_stderr)
259 def test_function_trailing_comma(self) -> None:
260 source, expected = read_data("function_trailing_comma")
262 self.assertFormatEqual(expected, actual)
263 black.assert_equivalent(source, actual)
264 black.assert_stable(source, actual, black.FileMode())
266 @patch("black.dump_to_file", dump_to_stderr)
267 def test_expression(self) -> None:
268 source, expected = read_data("expression")
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_pep_572(self) -> None:
276 source, expected = read_data("pep_572")
278 self.assertFormatEqual(expected, actual)
279 black.assert_stable(source, actual, black.FileMode())
280 if sys.version_info >= (3, 8):
281 black.assert_equivalent(source, actual)
283 def test_pep_572_version_detection(self) -> None:
284 source, _ = read_data("pep_572")
285 root = black.lib2to3_parse(source)
286 features = black.get_features_used(root)
287 self.assertIn(black.Feature.ASSIGNMENT_EXPRESSIONS, features)
288 versions = black.detect_target_versions(root)
289 self.assertIn(black.TargetVersion.PY38, versions)
291 def test_expression_ff(self) -> None:
292 source, expected = read_data("expression")
293 tmp_file = Path(black.dump_to_file(source))
295 self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
296 with open(tmp_file, encoding="utf8") as f:
300 self.assertFormatEqual(expected, actual)
301 with patch("black.dump_to_file", dump_to_stderr):
302 black.assert_equivalent(source, actual)
303 black.assert_stable(source, actual, black.FileMode())
305 def test_expression_diff(self) -> None:
306 source, _ = read_data("expression.py")
307 expected, _ = read_data("expression.diff")
308 tmp_file = Path(black.dump_to_file(source))
309 diff_header = re.compile(
310 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
311 rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
314 result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
315 self.assertEqual(result.exit_code, 0)
318 actual = result.output
319 actual = diff_header.sub("[Deterministic header]", actual)
320 actual = actual.rstrip() + "\n" # the diff output has a trailing space
321 if expected != actual:
322 dump = black.dump_to_file(actual)
324 f"Expected diff isn't equal to the actual. If you made changes "
325 f"to expression.py and this is an anticipated difference, "
326 f"overwrite tests/data/expression.diff with {dump}"
328 self.assertEqual(expected, actual, msg)
330 @patch("black.dump_to_file", dump_to_stderr)
331 def test_fstring(self) -> None:
332 source, expected = read_data("fstring")
334 self.assertFormatEqual(expected, actual)
335 black.assert_equivalent(source, actual)
336 black.assert_stable(source, actual, black.FileMode())
338 @patch("black.dump_to_file", dump_to_stderr)
339 def test_pep_570(self) -> None:
340 source, expected = read_data("pep_570")
342 self.assertFormatEqual(expected, actual)
343 black.assert_stable(source, actual, black.FileMode())
344 if sys.version_info >= (3, 8):
345 black.assert_equivalent(source, actual)
347 def test_detect_pos_only_arguments(self) -> None:
348 source, _ = read_data("pep_570")
349 root = black.lib2to3_parse(source)
350 features = black.get_features_used(root)
351 self.assertIn(black.Feature.POS_ONLY_ARGUMENTS, features)
352 versions = black.detect_target_versions(root)
353 self.assertIn(black.TargetVersion.PY38, versions)
355 @patch("black.dump_to_file", dump_to_stderr)
356 def test_string_quotes(self) -> None:
357 source, expected = read_data("string_quotes")
359 self.assertFormatEqual(expected, actual)
360 black.assert_equivalent(source, actual)
361 black.assert_stable(source, actual, black.FileMode())
362 mode = black.FileMode(string_normalization=False)
363 not_normalized = fs(source, mode=mode)
364 self.assertFormatEqual(source, not_normalized)
365 black.assert_equivalent(source, not_normalized)
366 black.assert_stable(source, not_normalized, mode=mode)
368 @patch("black.dump_to_file", dump_to_stderr)
369 def test_slices(self) -> None:
370 source, expected = read_data("slices")
372 self.assertFormatEqual(expected, actual)
373 black.assert_equivalent(source, actual)
374 black.assert_stable(source, actual, black.FileMode())
376 @patch("black.dump_to_file", dump_to_stderr)
377 def test_comments(self) -> None:
378 source, expected = read_data("comments")
380 self.assertFormatEqual(expected, actual)
381 black.assert_equivalent(source, actual)
382 black.assert_stable(source, actual, black.FileMode())
384 @patch("black.dump_to_file", dump_to_stderr)
385 def test_comments2(self) -> None:
386 source, expected = read_data("comments2")
388 self.assertFormatEqual(expected, actual)
389 black.assert_equivalent(source, actual)
390 black.assert_stable(source, actual, black.FileMode())
392 @patch("black.dump_to_file", dump_to_stderr)
393 def test_comments3(self) -> None:
394 source, expected = read_data("comments3")
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_comments4(self) -> None:
402 source, expected = read_data("comments4")
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_comments5(self) -> None:
410 source, expected = read_data("comments5")
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_comments6(self) -> None:
418 source, expected = read_data("comments6")
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_comments7(self) -> None:
426 source, expected = read_data("comments7")
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_comment_after_escaped_newline(self) -> None:
434 source, expected = read_data("comment_after_escaped_newline")
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_cantfit(self) -> None:
442 source, expected = read_data("cantfit")
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_import_spacing(self) -> None:
450 source, expected = read_data("import_spacing")
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_composition(self) -> None:
458 source, expected = read_data("composition")
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_empty_lines(self) -> None:
466 source, expected = read_data("empty_lines")
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_remove_parens(self) -> None:
474 source, expected = read_data("remove_parens")
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_string_prefixes(self) -> None:
482 source, expected = read_data("string_prefixes")
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_numeric_literals(self) -> None:
490 source, expected = read_data("numeric_literals")
491 mode = black.FileMode(target_versions=black.PY36_VERSIONS)
492 actual = fs(source, mode=mode)
493 self.assertFormatEqual(expected, actual)
494 black.assert_equivalent(source, actual)
495 black.assert_stable(source, actual, mode)
497 @patch("black.dump_to_file", dump_to_stderr)
498 def test_numeric_literals_ignoring_underscores(self) -> None:
499 source, expected = read_data("numeric_literals_skip_underscores")
500 mode = black.FileMode(target_versions=black.PY36_VERSIONS)
501 actual = fs(source, mode=mode)
502 self.assertFormatEqual(expected, actual)
503 black.assert_equivalent(source, actual)
504 black.assert_stable(source, actual, mode)
506 @patch("black.dump_to_file", dump_to_stderr)
507 def test_numeric_literals_py2(self) -> None:
508 source, expected = read_data("numeric_literals_py2")
510 self.assertFormatEqual(expected, actual)
511 black.assert_stable(source, actual, black.FileMode())
513 @patch("black.dump_to_file", dump_to_stderr)
514 def test_python2(self) -> None:
515 source, expected = read_data("python2")
517 self.assertFormatEqual(expected, actual)
518 black.assert_equivalent(source, actual)
519 black.assert_stable(source, actual, black.FileMode())
521 @patch("black.dump_to_file", dump_to_stderr)
522 def test_python2_print_function(self) -> None:
523 source, expected = read_data("python2_print_function")
524 mode = black.FileMode(target_versions={TargetVersion.PY27})
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_python2_unicode_literals(self) -> None:
532 source, expected = read_data("python2_unicode_literals")
534 self.assertFormatEqual(expected, actual)
535 black.assert_equivalent(source, actual)
536 black.assert_stable(source, actual, black.FileMode())
538 @patch("black.dump_to_file", dump_to_stderr)
539 def test_stub(self) -> None:
540 mode = black.FileMode(is_pyi=True)
541 source, expected = read_data("stub.pyi")
542 actual = fs(source, mode=mode)
543 self.assertFormatEqual(expected, actual)
544 black.assert_stable(source, actual, mode)
546 @patch("black.dump_to_file", dump_to_stderr)
547 def test_async_as_identifier(self) -> None:
548 source_path = (THIS_DIR / "data" / "async_as_identifier.py").resolve()
549 source, expected = read_data("async_as_identifier")
551 self.assertFormatEqual(expected, actual)
552 major, minor = sys.version_info[:2]
553 if major < 3 or (major <= 3 and minor < 7):
554 black.assert_equivalent(source, actual)
555 black.assert_stable(source, actual, black.FileMode())
556 # ensure black can parse this when the target is 3.6
557 self.invokeBlack([str(source_path), "--target-version", "py36"])
558 # but not on 3.7, because async/await is no longer an identifier
559 self.invokeBlack([str(source_path), "--target-version", "py37"], exit_code=123)
561 @patch("black.dump_to_file", dump_to_stderr)
562 def test_python37(self) -> None:
563 source_path = (THIS_DIR / "data" / "python37.py").resolve()
564 source, expected = read_data("python37")
566 self.assertFormatEqual(expected, actual)
567 major, minor = sys.version_info[:2]
568 if major > 3 or (major == 3 and minor >= 7):
569 black.assert_equivalent(source, actual)
570 black.assert_stable(source, actual, black.FileMode())
571 # ensure black can parse this when the target is 3.7
572 self.invokeBlack([str(source_path), "--target-version", "py37"])
573 # but not on 3.6, because we use async as a reserved keyword
574 self.invokeBlack([str(source_path), "--target-version", "py36"], exit_code=123)
576 @patch("black.dump_to_file", dump_to_stderr)
577 def test_fmtonoff(self) -> None:
578 source, expected = read_data("fmtonoff")
580 self.assertFormatEqual(expected, actual)
581 black.assert_equivalent(source, actual)
582 black.assert_stable(source, actual, black.FileMode())
584 @patch("black.dump_to_file", dump_to_stderr)
585 def test_fmtonoff2(self) -> None:
586 source, expected = read_data("fmtonoff2")
588 self.assertFormatEqual(expected, actual)
589 black.assert_equivalent(source, actual)
590 black.assert_stable(source, actual, black.FileMode())
592 @patch("black.dump_to_file", dump_to_stderr)
593 def test_remove_empty_parentheses_after_class(self) -> None:
594 source, expected = read_data("class_blank_parentheses")
596 self.assertFormatEqual(expected, actual)
597 black.assert_equivalent(source, actual)
598 black.assert_stable(source, actual, black.FileMode())
600 @patch("black.dump_to_file", dump_to_stderr)
601 def test_new_line_between_class_and_code(self) -> None:
602 source, expected = read_data("class_methods_new_line")
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_bracket_match(self) -> None:
610 source, expected = read_data("bracketmatch")
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_tuple_assign(self) -> None:
618 source, expected = read_data("tupleassign")
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_beginning_backslash(self) -> None:
626 source, expected = read_data("beginning_backslash")
628 self.assertFormatEqual(expected, actual)
629 black.assert_equivalent(source, actual)
630 black.assert_stable(source, actual, black.FileMode())
632 def test_tab_comment_indentation(self) -> None:
633 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
634 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
635 self.assertFormatEqual(contents_spc, fs(contents_spc))
636 self.assertFormatEqual(contents_spc, fs(contents_tab))
638 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
639 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
640 self.assertFormatEqual(contents_spc, fs(contents_spc))
641 self.assertFormatEqual(contents_spc, fs(contents_tab))
643 # mixed tabs and spaces (valid Python 2 code)
644 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t# comment\n pass\n"
645 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
646 self.assertFormatEqual(contents_spc, fs(contents_spc))
647 self.assertFormatEqual(contents_spc, fs(contents_tab))
649 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t\t# comment\n pass\n"
650 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
651 self.assertFormatEqual(contents_spc, fs(contents_spc))
652 self.assertFormatEqual(contents_spc, fs(contents_tab))
654 def test_report_verbose(self) -> None:
655 report = black.Report(verbose=True)
659 def out(msg: str, **kwargs: Any) -> None:
660 out_lines.append(msg)
662 def err(msg: str, **kwargs: Any) -> None:
663 err_lines.append(msg)
665 with patch("black.out", out), patch("black.err", err):
666 report.done(Path("f1"), black.Changed.NO)
667 self.assertEqual(len(out_lines), 1)
668 self.assertEqual(len(err_lines), 0)
669 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
670 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
671 self.assertEqual(report.return_code, 0)
672 report.done(Path("f2"), black.Changed.YES)
673 self.assertEqual(len(out_lines), 2)
674 self.assertEqual(len(err_lines), 0)
675 self.assertEqual(out_lines[-1], "reformatted f2")
677 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
679 report.done(Path("f3"), black.Changed.CACHED)
680 self.assertEqual(len(out_lines), 3)
681 self.assertEqual(len(err_lines), 0)
683 out_lines[-1], "f3 wasn't modified on disk since last run."
686 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
688 self.assertEqual(report.return_code, 0)
690 self.assertEqual(report.return_code, 1)
692 report.failed(Path("e1"), "boom")
693 self.assertEqual(len(out_lines), 3)
694 self.assertEqual(len(err_lines), 1)
695 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
697 unstyle(str(report)),
698 "1 file reformatted, 2 files left unchanged, "
699 "1 file failed to reformat.",
701 self.assertEqual(report.return_code, 123)
702 report.done(Path("f3"), black.Changed.YES)
703 self.assertEqual(len(out_lines), 4)
704 self.assertEqual(len(err_lines), 1)
705 self.assertEqual(out_lines[-1], "reformatted f3")
707 unstyle(str(report)),
708 "2 files reformatted, 2 files left unchanged, "
709 "1 file failed to reformat.",
711 self.assertEqual(report.return_code, 123)
712 report.failed(Path("e2"), "boom")
713 self.assertEqual(len(out_lines), 4)
714 self.assertEqual(len(err_lines), 2)
715 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
717 unstyle(str(report)),
718 "2 files reformatted, 2 files left unchanged, "
719 "2 files failed to reformat.",
721 self.assertEqual(report.return_code, 123)
722 report.path_ignored(Path("wat"), "no match")
723 self.assertEqual(len(out_lines), 5)
724 self.assertEqual(len(err_lines), 2)
725 self.assertEqual(out_lines[-1], "wat ignored: no match")
727 unstyle(str(report)),
728 "2 files reformatted, 2 files left unchanged, "
729 "2 files failed to reformat.",
731 self.assertEqual(report.return_code, 123)
732 report.done(Path("f4"), black.Changed.NO)
733 self.assertEqual(len(out_lines), 6)
734 self.assertEqual(len(err_lines), 2)
735 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
737 unstyle(str(report)),
738 "2 files reformatted, 3 files left unchanged, "
739 "2 files failed to reformat.",
741 self.assertEqual(report.return_code, 123)
744 unstyle(str(report)),
745 "2 files would be reformatted, 3 files would be left unchanged, "
746 "2 files would fail to reformat.",
749 def test_report_quiet(self) -> None:
750 report = black.Report(quiet=True)
754 def out(msg: str, **kwargs: Any) -> None:
755 out_lines.append(msg)
757 def err(msg: str, **kwargs: Any) -> None:
758 err_lines.append(msg)
760 with patch("black.out", out), patch("black.err", err):
761 report.done(Path("f1"), black.Changed.NO)
762 self.assertEqual(len(out_lines), 0)
763 self.assertEqual(len(err_lines), 0)
764 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
765 self.assertEqual(report.return_code, 0)
766 report.done(Path("f2"), black.Changed.YES)
767 self.assertEqual(len(out_lines), 0)
768 self.assertEqual(len(err_lines), 0)
770 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
772 report.done(Path("f3"), black.Changed.CACHED)
773 self.assertEqual(len(out_lines), 0)
774 self.assertEqual(len(err_lines), 0)
776 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
778 self.assertEqual(report.return_code, 0)
780 self.assertEqual(report.return_code, 1)
782 report.failed(Path("e1"), "boom")
783 self.assertEqual(len(out_lines), 0)
784 self.assertEqual(len(err_lines), 1)
785 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
787 unstyle(str(report)),
788 "1 file reformatted, 2 files left unchanged, "
789 "1 file failed to reformat.",
791 self.assertEqual(report.return_code, 123)
792 report.done(Path("f3"), black.Changed.YES)
793 self.assertEqual(len(out_lines), 0)
794 self.assertEqual(len(err_lines), 1)
796 unstyle(str(report)),
797 "2 files reformatted, 2 files left unchanged, "
798 "1 file failed to reformat.",
800 self.assertEqual(report.return_code, 123)
801 report.failed(Path("e2"), "boom")
802 self.assertEqual(len(out_lines), 0)
803 self.assertEqual(len(err_lines), 2)
804 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
806 unstyle(str(report)),
807 "2 files reformatted, 2 files left unchanged, "
808 "2 files failed to reformat.",
810 self.assertEqual(report.return_code, 123)
811 report.path_ignored(Path("wat"), "no match")
812 self.assertEqual(len(out_lines), 0)
813 self.assertEqual(len(err_lines), 2)
815 unstyle(str(report)),
816 "2 files reformatted, 2 files left unchanged, "
817 "2 files failed to reformat.",
819 self.assertEqual(report.return_code, 123)
820 report.done(Path("f4"), black.Changed.NO)
821 self.assertEqual(len(out_lines), 0)
822 self.assertEqual(len(err_lines), 2)
824 unstyle(str(report)),
825 "2 files reformatted, 3 files left unchanged, "
826 "2 files failed to reformat.",
828 self.assertEqual(report.return_code, 123)
831 unstyle(str(report)),
832 "2 files would be reformatted, 3 files would be left unchanged, "
833 "2 files would fail to reformat.",
836 def test_report_normal(self) -> None:
837 report = black.Report()
841 def out(msg: str, **kwargs: Any) -> None:
842 out_lines.append(msg)
844 def err(msg: str, **kwargs: Any) -> None:
845 err_lines.append(msg)
847 with patch("black.out", out), patch("black.err", err):
848 report.done(Path("f1"), black.Changed.NO)
849 self.assertEqual(len(out_lines), 0)
850 self.assertEqual(len(err_lines), 0)
851 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
852 self.assertEqual(report.return_code, 0)
853 report.done(Path("f2"), black.Changed.YES)
854 self.assertEqual(len(out_lines), 1)
855 self.assertEqual(len(err_lines), 0)
856 self.assertEqual(out_lines[-1], "reformatted f2")
858 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
860 report.done(Path("f3"), black.Changed.CACHED)
861 self.assertEqual(len(out_lines), 1)
862 self.assertEqual(len(err_lines), 0)
863 self.assertEqual(out_lines[-1], "reformatted f2")
865 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
867 self.assertEqual(report.return_code, 0)
869 self.assertEqual(report.return_code, 1)
871 report.failed(Path("e1"), "boom")
872 self.assertEqual(len(out_lines), 1)
873 self.assertEqual(len(err_lines), 1)
874 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
876 unstyle(str(report)),
877 "1 file reformatted, 2 files left unchanged, "
878 "1 file failed to reformat.",
880 self.assertEqual(report.return_code, 123)
881 report.done(Path("f3"), black.Changed.YES)
882 self.assertEqual(len(out_lines), 2)
883 self.assertEqual(len(err_lines), 1)
884 self.assertEqual(out_lines[-1], "reformatted f3")
886 unstyle(str(report)),
887 "2 files reformatted, 2 files left unchanged, "
888 "1 file failed to reformat.",
890 self.assertEqual(report.return_code, 123)
891 report.failed(Path("e2"), "boom")
892 self.assertEqual(len(out_lines), 2)
893 self.assertEqual(len(err_lines), 2)
894 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
896 unstyle(str(report)),
897 "2 files reformatted, 2 files left unchanged, "
898 "2 files failed to reformat.",
900 self.assertEqual(report.return_code, 123)
901 report.path_ignored(Path("wat"), "no match")
902 self.assertEqual(len(out_lines), 2)
903 self.assertEqual(len(err_lines), 2)
905 unstyle(str(report)),
906 "2 files reformatted, 2 files left unchanged, "
907 "2 files failed to reformat.",
909 self.assertEqual(report.return_code, 123)
910 report.done(Path("f4"), black.Changed.NO)
911 self.assertEqual(len(out_lines), 2)
912 self.assertEqual(len(err_lines), 2)
914 unstyle(str(report)),
915 "2 files reformatted, 3 files left unchanged, "
916 "2 files failed to reformat.",
918 self.assertEqual(report.return_code, 123)
921 unstyle(str(report)),
922 "2 files would be reformatted, 3 files would be left unchanged, "
923 "2 files would fail to reformat.",
926 def test_lib2to3_parse(self) -> None:
927 with self.assertRaises(black.InvalidInput):
928 black.lib2to3_parse("invalid syntax")
931 black.lib2to3_parse(straddling)
932 black.lib2to3_parse(straddling, {TargetVersion.PY27})
933 black.lib2to3_parse(straddling, {TargetVersion.PY36})
934 black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
937 black.lib2to3_parse(py2_only)
938 black.lib2to3_parse(py2_only, {TargetVersion.PY27})
939 with self.assertRaises(black.InvalidInput):
940 black.lib2to3_parse(py2_only, {TargetVersion.PY36})
941 with self.assertRaises(black.InvalidInput):
942 black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
944 py3_only = "exec(x, end=y)"
945 black.lib2to3_parse(py3_only)
946 with self.assertRaises(black.InvalidInput):
947 black.lib2to3_parse(py3_only, {TargetVersion.PY27})
948 black.lib2to3_parse(py3_only, {TargetVersion.PY36})
949 black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
951 def test_get_features_used(self) -> None:
952 node = black.lib2to3_parse("def f(*, arg): ...\n")
953 self.assertEqual(black.get_features_used(node), set())
954 node = black.lib2to3_parse("def f(*, arg,): ...\n")
955 self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
956 node = black.lib2to3_parse("f(*arg,)\n")
958 black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
960 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
961 self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
962 node = black.lib2to3_parse("123_456\n")
963 self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
964 node = black.lib2to3_parse("123456\n")
965 self.assertEqual(black.get_features_used(node), set())
966 source, expected = read_data("function")
967 node = black.lib2to3_parse(source)
968 expected_features = {
969 Feature.TRAILING_COMMA_IN_CALL,
970 Feature.TRAILING_COMMA_IN_DEF,
973 self.assertEqual(black.get_features_used(node), expected_features)
974 node = black.lib2to3_parse(expected)
975 self.assertEqual(black.get_features_used(node), expected_features)
976 source, expected = read_data("expression")
977 node = black.lib2to3_parse(source)
978 self.assertEqual(black.get_features_used(node), set())
979 node = black.lib2to3_parse(expected)
980 self.assertEqual(black.get_features_used(node), set())
982 def test_get_future_imports(self) -> None:
983 node = black.lib2to3_parse("\n")
984 self.assertEqual(set(), black.get_future_imports(node))
985 node = black.lib2to3_parse("from __future__ import black\n")
986 self.assertEqual({"black"}, black.get_future_imports(node))
987 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
988 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
989 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
990 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
991 node = black.lib2to3_parse(
992 "from __future__ import multiple\nfrom __future__ import imports\n"
994 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
995 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
996 self.assertEqual({"black"}, black.get_future_imports(node))
997 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
998 self.assertEqual({"black"}, black.get_future_imports(node))
999 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
1000 self.assertEqual(set(), black.get_future_imports(node))
1001 node = black.lib2to3_parse("from some.module import black\n")
1002 self.assertEqual(set(), black.get_future_imports(node))
1003 node = black.lib2to3_parse(
1004 "from __future__ import unicode_literals as _unicode_literals"
1006 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
1007 node = black.lib2to3_parse(
1008 "from __future__ import unicode_literals as _lol, print"
1010 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
1012 def test_debug_visitor(self) -> None:
1013 source, _ = read_data("debug_visitor.py")
1014 expected, _ = read_data("debug_visitor.out")
1018 def out(msg: str, **kwargs: Any) -> None:
1019 out_lines.append(msg)
1021 def err(msg: str, **kwargs: Any) -> None:
1022 err_lines.append(msg)
1024 with patch("black.out", out), patch("black.err", err):
1025 black.DebugVisitor.show(source)
1026 actual = "\n".join(out_lines) + "\n"
1028 if expected != actual:
1029 log_name = black.dump_to_file(*out_lines)
1033 f"AST print out is different. Actual version dumped to {log_name}",
1036 def test_format_file_contents(self) -> None:
1038 mode = black.FileMode()
1039 with self.assertRaises(black.NothingChanged):
1040 black.format_file_contents(empty, mode=mode, fast=False)
1042 with self.assertRaises(black.NothingChanged):
1043 black.format_file_contents(just_nl, mode=mode, fast=False)
1044 same = "j = [1, 2, 3]\n"
1045 with self.assertRaises(black.NothingChanged):
1046 black.format_file_contents(same, mode=mode, fast=False)
1047 different = "j = [1,2,3]"
1049 actual = black.format_file_contents(different, mode=mode, fast=False)
1050 self.assertEqual(expected, actual)
1051 invalid = "return if you can"
1052 with self.assertRaises(black.InvalidInput) as e:
1053 black.format_file_contents(invalid, mode=mode, fast=False)
1054 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
1056 def test_endmarker(self) -> None:
1057 n = black.lib2to3_parse("\n")
1058 self.assertEqual(n.type, black.syms.file_input)
1059 self.assertEqual(len(n.children), 1)
1060 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
1062 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
1063 def test_assertFormatEqual(self) -> None:
1067 def out(msg: str, **kwargs: Any) -> None:
1068 out_lines.append(msg)
1070 def err(msg: str, **kwargs: Any) -> None:
1071 err_lines.append(msg)
1073 with patch("black.out", out), patch("black.err", err):
1074 with self.assertRaises(AssertionError):
1075 self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")
1077 out_str = "".join(out_lines)
1078 self.assertTrue("Expected tree:" in out_str)
1079 self.assertTrue("Actual tree:" in out_str)
1080 self.assertEqual("".join(err_lines), "")
1082 def test_cache_broken_file(self) -> None:
1083 mode = black.FileMode()
1084 with cache_dir() as workspace:
1085 cache_file = black.get_cache_file(mode)
1086 with cache_file.open("w") as fobj:
1087 fobj.write("this is not a pickle")
1088 self.assertEqual(black.read_cache(mode), {})
1089 src = (workspace / "test.py").resolve()
1090 with src.open("w") as fobj:
1091 fobj.write("print('hello')")
1092 self.invokeBlack([str(src)])
1093 cache = black.read_cache(mode)
1094 self.assertIn(src, cache)
1096 def test_cache_single_file_already_cached(self) -> None:
1097 mode = black.FileMode()
1098 with cache_dir() as workspace:
1099 src = (workspace / "test.py").resolve()
1100 with src.open("w") as fobj:
1101 fobj.write("print('hello')")
1102 black.write_cache({}, [src], mode)
1103 self.invokeBlack([str(src)])
1104 with src.open("r") as fobj:
1105 self.assertEqual(fobj.read(), "print('hello')")
1107 @event_loop(close=False)
1108 def test_cache_multiple_files(self) -> None:
1109 mode = black.FileMode()
1110 with cache_dir() as workspace, patch(
1111 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1113 one = (workspace / "one.py").resolve()
1114 with one.open("w") as fobj:
1115 fobj.write("print('hello')")
1116 two = (workspace / "two.py").resolve()
1117 with two.open("w") as fobj:
1118 fobj.write("print('hello')")
1119 black.write_cache({}, [one], mode)
1120 self.invokeBlack([str(workspace)])
1121 with one.open("r") as fobj:
1122 self.assertEqual(fobj.read(), "print('hello')")
1123 with two.open("r") as fobj:
1124 self.assertEqual(fobj.read(), 'print("hello")\n')
1125 cache = black.read_cache(mode)
1126 self.assertIn(one, cache)
1127 self.assertIn(two, cache)
1129 def test_no_cache_when_writeback_diff(self) -> None:
1130 mode = black.FileMode()
1131 with cache_dir() as workspace:
1132 src = (workspace / "test.py").resolve()
1133 with src.open("w") as fobj:
1134 fobj.write("print('hello')")
1135 self.invokeBlack([str(src), "--diff"])
1136 cache_file = black.get_cache_file(mode)
1137 self.assertFalse(cache_file.exists())
1139 def test_no_cache_when_stdin(self) -> None:
1140 mode = black.FileMode()
1142 result = CliRunner().invoke(
1143 black.main, ["-"], input=BytesIO(b"print('hello')")
1145 self.assertEqual(result.exit_code, 0)
1146 cache_file = black.get_cache_file(mode)
1147 self.assertFalse(cache_file.exists())
1149 def test_read_cache_no_cachefile(self) -> None:
1150 mode = black.FileMode()
1152 self.assertEqual(black.read_cache(mode), {})
1154 def test_write_cache_read_cache(self) -> None:
1155 mode = black.FileMode()
1156 with cache_dir() as workspace:
1157 src = (workspace / "test.py").resolve()
1159 black.write_cache({}, [src], mode)
1160 cache = black.read_cache(mode)
1161 self.assertIn(src, cache)
1162 self.assertEqual(cache[src], black.get_cache_info(src))
1164 def test_filter_cached(self) -> None:
1165 with TemporaryDirectory() as workspace:
1166 path = Path(workspace)
1167 uncached = (path / "uncached").resolve()
1168 cached = (path / "cached").resolve()
1169 cached_but_changed = (path / "changed").resolve()
1172 cached_but_changed.touch()
1173 cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
1174 todo, done = black.filter_cached(
1175 cache, {uncached, cached, cached_but_changed}
1177 self.assertEqual(todo, {uncached, cached_but_changed})
1178 self.assertEqual(done, {cached})
1180 def test_write_cache_creates_directory_if_needed(self) -> None:
1181 mode = black.FileMode()
1182 with cache_dir(exists=False) as workspace:
1183 self.assertFalse(workspace.exists())
1184 black.write_cache({}, [], mode)
1185 self.assertTrue(workspace.exists())
1187 @event_loop(close=False)
1188 def test_failed_formatting_does_not_get_cached(self) -> None:
1189 mode = black.FileMode()
1190 with cache_dir() as workspace, patch(
1191 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1193 failing = (workspace / "failing.py").resolve()
1194 with failing.open("w") as fobj:
1195 fobj.write("not actually python")
1196 clean = (workspace / "clean.py").resolve()
1197 with clean.open("w") as fobj:
1198 fobj.write('print("hello")\n')
1199 self.invokeBlack([str(workspace)], exit_code=123)
1200 cache = black.read_cache(mode)
1201 self.assertNotIn(failing, cache)
1202 self.assertIn(clean, cache)
1204 def test_write_cache_write_fail(self) -> None:
1205 mode = black.FileMode()
1206 with cache_dir(), patch.object(Path, "open") as mock:
1207 mock.side_effect = OSError
1208 black.write_cache({}, [], mode)
1210 @event_loop(close=False)
1211 def test_check_diff_use_together(self) -> None:
1213 # Files which will be reformatted.
1214 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1215 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1216 # Files which will not be reformatted.
1217 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1218 self.invokeBlack([str(src2), "--diff", "--check"])
1219 # Multi file command.
1220 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1222 def test_no_files(self) -> None:
1224 # Without an argument, black exits with error code 0.
1225 self.invokeBlack([])
1227 def test_broken_symlink(self) -> None:
1228 with cache_dir() as workspace:
1229 symlink = workspace / "broken_link.py"
1231 symlink.symlink_to("nonexistent.py")
1232 except OSError as e:
1233 self.skipTest(f"Can't create symlinks: {e}")
1234 self.invokeBlack([str(workspace.resolve())])
1236 def test_read_cache_line_lengths(self) -> None:
1237 mode = black.FileMode()
1238 short_mode = black.FileMode(line_length=1)
1239 with cache_dir() as workspace:
1240 path = (workspace / "file.py").resolve()
1242 black.write_cache({}, [path], mode)
1243 one = black.read_cache(mode)
1244 self.assertIn(path, one)
1245 two = black.read_cache(short_mode)
1246 self.assertNotIn(path, two)
1248 def test_tricky_unicode_symbols(self) -> None:
1249 source, expected = read_data("tricky_unicode_symbols")
1251 self.assertFormatEqual(expected, actual)
1252 black.assert_equivalent(source, actual)
1253 black.assert_stable(source, actual, black.FileMode())
1255 def test_single_file_force_pyi(self) -> None:
1256 reg_mode = black.FileMode()
1257 pyi_mode = black.FileMode(is_pyi=True)
1258 contents, expected = read_data("force_pyi")
1259 with cache_dir() as workspace:
1260 path = (workspace / "file.py").resolve()
1261 with open(path, "w") as fh:
1263 self.invokeBlack([str(path), "--pyi"])
1264 with open(path, "r") as fh:
1266 # verify cache with --pyi is separate
1267 pyi_cache = black.read_cache(pyi_mode)
1268 self.assertIn(path, pyi_cache)
1269 normal_cache = black.read_cache(reg_mode)
1270 self.assertNotIn(path, normal_cache)
1271 self.assertEqual(actual, expected)
1273 @event_loop(close=False)
1274 def test_multi_file_force_pyi(self) -> None:
1275 reg_mode = black.FileMode()
1276 pyi_mode = black.FileMode(is_pyi=True)
1277 contents, expected = read_data("force_pyi")
1278 with cache_dir() as workspace:
1280 (workspace / "file1.py").resolve(),
1281 (workspace / "file2.py").resolve(),
1284 with open(path, "w") as fh:
1286 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1288 with open(path, "r") as fh:
1290 self.assertEqual(actual, expected)
1291 # verify cache with --pyi is separate
1292 pyi_cache = black.read_cache(pyi_mode)
1293 normal_cache = black.read_cache(reg_mode)
1295 self.assertIn(path, pyi_cache)
1296 self.assertNotIn(path, normal_cache)
1298 def test_pipe_force_pyi(self) -> None:
1299 source, expected = read_data("force_pyi")
1300 result = CliRunner().invoke(
1301 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1303 self.assertEqual(result.exit_code, 0)
1304 actual = result.output
1305 self.assertFormatEqual(actual, expected)
1307 def test_single_file_force_py36(self) -> None:
1308 reg_mode = black.FileMode()
1309 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1310 source, expected = read_data("force_py36")
1311 with cache_dir() as workspace:
1312 path = (workspace / "file.py").resolve()
1313 with open(path, "w") as fh:
1315 self.invokeBlack([str(path), *PY36_ARGS])
1316 with open(path, "r") as fh:
1318 # verify cache with --target-version is separate
1319 py36_cache = black.read_cache(py36_mode)
1320 self.assertIn(path, py36_cache)
1321 normal_cache = black.read_cache(reg_mode)
1322 self.assertNotIn(path, normal_cache)
1323 self.assertEqual(actual, expected)
1325 @event_loop(close=False)
1326 def test_multi_file_force_py36(self) -> None:
1327 reg_mode = black.FileMode()
1328 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1329 source, expected = read_data("force_py36")
1330 with cache_dir() as workspace:
1332 (workspace / "file1.py").resolve(),
1333 (workspace / "file2.py").resolve(),
1336 with open(path, "w") as fh:
1338 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1340 with open(path, "r") as fh:
1342 self.assertEqual(actual, expected)
1343 # verify cache with --target-version is separate
1344 pyi_cache = black.read_cache(py36_mode)
1345 normal_cache = black.read_cache(reg_mode)
1347 self.assertIn(path, pyi_cache)
1348 self.assertNotIn(path, normal_cache)
1350 def test_pipe_force_py36(self) -> None:
1351 source, expected = read_data("force_py36")
1352 result = CliRunner().invoke(
1354 ["-", "-q", "--target-version=py36"],
1355 input=BytesIO(source.encode("utf8")),
1357 self.assertEqual(result.exit_code, 0)
1358 actual = result.output
1359 self.assertFormatEqual(actual, expected)
1361 def test_include_exclude(self) -> None:
1362 path = THIS_DIR / "data" / "include_exclude_tests"
1363 include = re.compile(r"\.pyi?$")
1364 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1365 report = black.Report()
1366 sources: List[Path] = []
1368 Path(path / "b/dont_exclude/a.py"),
1369 Path(path / "b/dont_exclude/a.pyi"),
1371 this_abs = THIS_DIR.resolve()
1373 black.gen_python_files_in_dir(path, this_abs, include, exclude, report)
1375 self.assertEqual(sorted(expected), sorted(sources))
1377 def test_empty_include(self) -> None:
1378 path = THIS_DIR / "data" / "include_exclude_tests"
1379 report = black.Report()
1380 empty = re.compile(r"")
1381 sources: List[Path] = []
1383 Path(path / "b/exclude/a.pie"),
1384 Path(path / "b/exclude/a.py"),
1385 Path(path / "b/exclude/a.pyi"),
1386 Path(path / "b/dont_exclude/a.pie"),
1387 Path(path / "b/dont_exclude/a.py"),
1388 Path(path / "b/dont_exclude/a.pyi"),
1389 Path(path / "b/.definitely_exclude/a.pie"),
1390 Path(path / "b/.definitely_exclude/a.py"),
1391 Path(path / "b/.definitely_exclude/a.pyi"),
1393 this_abs = THIS_DIR.resolve()
1395 black.gen_python_files_in_dir(
1396 path, this_abs, empty, re.compile(black.DEFAULT_EXCLUDES), report
1399 self.assertEqual(sorted(expected), sorted(sources))
1401 def test_empty_exclude(self) -> None:
1402 path = THIS_DIR / "data" / "include_exclude_tests"
1403 report = black.Report()
1404 empty = re.compile(r"")
1405 sources: List[Path] = []
1407 Path(path / "b/dont_exclude/a.py"),
1408 Path(path / "b/dont_exclude/a.pyi"),
1409 Path(path / "b/exclude/a.py"),
1410 Path(path / "b/exclude/a.pyi"),
1411 Path(path / "b/.definitely_exclude/a.py"),
1412 Path(path / "b/.definitely_exclude/a.pyi"),
1414 this_abs = THIS_DIR.resolve()
1416 black.gen_python_files_in_dir(
1417 path, this_abs, re.compile(black.DEFAULT_INCLUDES), empty, report
1420 self.assertEqual(sorted(expected), sorted(sources))
1422 def test_invalid_include_exclude(self) -> None:
1423 for option in ["--include", "--exclude"]:
1424 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1426 def test_preserves_line_endings(self) -> None:
1427 with TemporaryDirectory() as workspace:
1428 test_file = Path(workspace) / "test.py"
1429 for nl in ["\n", "\r\n"]:
1430 contents = nl.join(["def f( ):", " pass"])
1431 test_file.write_bytes(contents.encode())
1432 ff(test_file, write_back=black.WriteBack.YES)
1433 updated_contents: bytes = test_file.read_bytes()
1434 self.assertIn(nl.encode(), updated_contents)
1436 self.assertNotIn(b"\r\n", updated_contents)
1438 def test_preserves_line_endings_via_stdin(self) -> None:
1439 for nl in ["\n", "\r\n"]:
1440 contents = nl.join(["def f( ):", " pass"])
1441 runner = BlackRunner()
1442 result = runner.invoke(
1443 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1445 self.assertEqual(result.exit_code, 0)
1446 output = runner.stdout_bytes
1447 self.assertIn(nl.encode("utf8"), output)
1449 self.assertNotIn(b"\r\n", output)
1451 def test_assert_equivalent_different_asts(self) -> None:
1452 with self.assertRaises(AssertionError):
1453 black.assert_equivalent("{}", "None")
1455 def test_symlink_out_of_root_directory(self) -> None:
1459 include = re.compile(black.DEFAULT_INCLUDES)
1460 exclude = re.compile(black.DEFAULT_EXCLUDES)
1461 report = black.Report()
1462 # `child` should behave like a symlink which resolved path is clearly
1463 # outside of the `root` directory.
1464 path.iterdir.return_value = [child]
1465 child.resolve.return_value = Path("/a/b/c")
1466 child.is_symlink.return_value = True
1468 list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1469 except ValueError as ve:
1470 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1471 path.iterdir.assert_called_once()
1472 child.resolve.assert_called_once()
1473 child.is_symlink.assert_called_once()
1474 # `child` should behave like a strange file which resolved path is clearly
1475 # outside of the `root` directory.
1476 child.is_symlink.return_value = False
1477 with self.assertRaises(ValueError):
1478 list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1479 path.iterdir.assert_called()
1480 self.assertEqual(path.iterdir.call_count, 2)
1481 child.resolve.assert_called()
1482 self.assertEqual(child.resolve.call_count, 2)
1483 child.is_symlink.assert_called()
1484 self.assertEqual(child.is_symlink.call_count, 2)
1486 def test_shhh_click(self) -> None:
1488 from click import _unicodefun # type: ignore
1489 except ModuleNotFoundError:
1490 self.skipTest("Incompatible Click version")
1491 if not hasattr(_unicodefun, "_verify_python3_env"):
1492 self.skipTest("Incompatible Click version")
1493 # First, let's see if Click is crashing with a preferred ASCII charset.
1494 with patch("locale.getpreferredencoding") as gpe:
1495 gpe.return_value = "ASCII"
1496 with self.assertRaises(RuntimeError):
1497 _unicodefun._verify_python3_env()
1498 # Now, let's silence Click...
1500 # ...and confirm it's silent.
1501 with patch("locale.getpreferredencoding") as gpe:
1502 gpe.return_value = "ASCII"
1504 _unicodefun._verify_python3_env()
1505 except RuntimeError as re:
1506 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1508 def test_root_logger_not_used_directly(self) -> None:
1509 def fail(*args: Any, **kwargs: Any) -> None:
1510 self.fail("Record created with root logger")
1512 with patch.multiple(
1523 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1524 def test_blackd_main(self) -> None:
1525 with patch("blackd.web.run_app"):
1526 result = CliRunner().invoke(blackd.main, [])
1527 if result.exception is not None:
1528 raise result.exception
1529 self.assertEqual(result.exit_code, 0)
1532 class BlackDTestCase(AioHTTPTestCase):
1533 async def get_application(self) -> web.Application:
1534 return blackd.make_app()
1536 # TODO: remove these decorators once the below is released
1537 # https://github.com/aio-libs/aiohttp/pull/3727
1538 @skip_if_exception("ClientOSError")
1539 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1541 async def test_blackd_request_needs_formatting(self) -> None:
1542 response = await self.client.post("/", data=b"print('hello world')")
1543 self.assertEqual(response.status, 200)
1544 self.assertEqual(response.charset, "utf8")
1545 self.assertEqual(await response.read(), b'print("hello world")\n')
1547 @skip_if_exception("ClientOSError")
1548 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1550 async def test_blackd_request_no_change(self) -> None:
1551 response = await self.client.post("/", data=b'print("hello world")\n')
1552 self.assertEqual(response.status, 204)
1553 self.assertEqual(await response.read(), b"")
1555 @skip_if_exception("ClientOSError")
1556 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1558 async def test_blackd_request_syntax_error(self) -> None:
1559 response = await self.client.post("/", data=b"what even ( is")
1560 self.assertEqual(response.status, 400)
1561 content = await response.text()
1563 content.startswith("Cannot parse"),
1564 msg=f"Expected error to start with 'Cannot parse', got {repr(content)}",
1567 @skip_if_exception("ClientOSError")
1568 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1570 async def test_blackd_unsupported_version(self) -> None:
1571 response = await self.client.post(
1572 "/", data=b"what", headers={blackd.VERSION_HEADER: "2"}
1574 self.assertEqual(response.status, 501)
1576 @skip_if_exception("ClientOSError")
1577 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1579 async def test_blackd_supported_version(self) -> None:
1580 response = await self.client.post(
1581 "/", data=b"what", headers={blackd.VERSION_HEADER: "1"}
1583 self.assertEqual(response.status, 200)
1585 @skip_if_exception("ClientOSError")
1586 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1588 async def test_blackd_invalid_python_variant(self) -> None:
1589 async def check(header_value: str, expected_status: int = 400) -> None:
1590 response = await self.client.post(
1591 "/", data=b"what", headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1593 self.assertEqual(response.status, expected_status)
1596 await check("ruby3.5")
1597 await check("pyi3.6")
1598 await check("py1.5")
1600 await check("py2.8")
1602 await check("pypy3.0")
1603 await check("jython3.4")
1605 @skip_if_exception("ClientOSError")
1606 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1608 async def test_blackd_pyi(self) -> None:
1609 source, expected = read_data("stub.pyi")
1610 response = await self.client.post(
1611 "/", data=source, headers={blackd.PYTHON_VARIANT_HEADER: "pyi"}
1613 self.assertEqual(response.status, 200)
1614 self.assertEqual(await response.text(), expected)
1616 @skip_if_exception("ClientOSError")
1617 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1619 async def test_blackd_python_variant(self) -> None:
1622 " and_has_a_bunch_of,\n"
1623 " very_long_arguments_too,\n"
1624 " and_lots_of_them_as_well_lol,\n"
1625 " **and_very_long_keyword_arguments\n"
1630 async def check(header_value: str, expected_status: int) -> None:
1631 response = await self.client.post(
1632 "/", data=code, headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1635 response.status, expected_status, msg=await response.text()
1638 await check("3.6", 200)
1639 await check("py3.6", 200)
1640 await check("3.6,3.7", 200)
1641 await check("3.6,py3.7", 200)
1642 await check("py36,py37", 200)
1643 await check("36", 200)
1644 await check("3.6.4", 200)
1646 await check("2", 204)
1647 await check("2.7", 204)
1648 await check("py2.7", 204)
1649 await check("3.4", 204)
1650 await check("py3.4", 204)
1651 await check("py34,py36", 204)
1652 await check("34", 204)
1654 @skip_if_exception("ClientOSError")
1655 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1657 async def test_blackd_line_length(self) -> None:
1658 response = await self.client.post(
1659 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "7"}
1661 self.assertEqual(response.status, 200)
1663 @skip_if_exception("ClientOSError")
1664 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1666 async def test_blackd_invalid_line_length(self) -> None:
1667 response = await self.client.post(
1668 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "NaN"}
1670 self.assertEqual(response.status, 400)
1673 if __name__ == "__main__":
1674 unittest.main(module="test_black")