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 = "l = [1, 2, 3]\n"
1045 with self.assertRaises(black.NothingChanged):
1046 black.format_file_contents(same, mode=mode, fast=False)
1047 different = "l = [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("l = [1, 2, 3]", "l = [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_single_file_force_pyi(self) -> None:
1249 reg_mode = black.FileMode()
1250 pyi_mode = black.FileMode(is_pyi=True)
1251 contents, expected = read_data("force_pyi")
1252 with cache_dir() as workspace:
1253 path = (workspace / "file.py").resolve()
1254 with open(path, "w") as fh:
1256 self.invokeBlack([str(path), "--pyi"])
1257 with open(path, "r") as fh:
1259 # verify cache with --pyi is separate
1260 pyi_cache = black.read_cache(pyi_mode)
1261 self.assertIn(path, pyi_cache)
1262 normal_cache = black.read_cache(reg_mode)
1263 self.assertNotIn(path, normal_cache)
1264 self.assertEqual(actual, expected)
1266 @event_loop(close=False)
1267 def test_multi_file_force_pyi(self) -> None:
1268 reg_mode = black.FileMode()
1269 pyi_mode = black.FileMode(is_pyi=True)
1270 contents, expected = read_data("force_pyi")
1271 with cache_dir() as workspace:
1273 (workspace / "file1.py").resolve(),
1274 (workspace / "file2.py").resolve(),
1277 with open(path, "w") as fh:
1279 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1281 with open(path, "r") as fh:
1283 self.assertEqual(actual, expected)
1284 # verify cache with --pyi is separate
1285 pyi_cache = black.read_cache(pyi_mode)
1286 normal_cache = black.read_cache(reg_mode)
1288 self.assertIn(path, pyi_cache)
1289 self.assertNotIn(path, normal_cache)
1291 def test_pipe_force_pyi(self) -> None:
1292 source, expected = read_data("force_pyi")
1293 result = CliRunner().invoke(
1294 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1296 self.assertEqual(result.exit_code, 0)
1297 actual = result.output
1298 self.assertFormatEqual(actual, expected)
1300 def test_single_file_force_py36(self) -> None:
1301 reg_mode = black.FileMode()
1302 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1303 source, expected = read_data("force_py36")
1304 with cache_dir() as workspace:
1305 path = (workspace / "file.py").resolve()
1306 with open(path, "w") as fh:
1308 self.invokeBlack([str(path), *PY36_ARGS])
1309 with open(path, "r") as fh:
1311 # verify cache with --target-version is separate
1312 py36_cache = black.read_cache(py36_mode)
1313 self.assertIn(path, py36_cache)
1314 normal_cache = black.read_cache(reg_mode)
1315 self.assertNotIn(path, normal_cache)
1316 self.assertEqual(actual, expected)
1318 @event_loop(close=False)
1319 def test_multi_file_force_py36(self) -> None:
1320 reg_mode = black.FileMode()
1321 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1322 source, expected = read_data("force_py36")
1323 with cache_dir() as workspace:
1325 (workspace / "file1.py").resolve(),
1326 (workspace / "file2.py").resolve(),
1329 with open(path, "w") as fh:
1331 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1333 with open(path, "r") as fh:
1335 self.assertEqual(actual, expected)
1336 # verify cache with --target-version is separate
1337 pyi_cache = black.read_cache(py36_mode)
1338 normal_cache = black.read_cache(reg_mode)
1340 self.assertIn(path, pyi_cache)
1341 self.assertNotIn(path, normal_cache)
1343 def test_pipe_force_py36(self) -> None:
1344 source, expected = read_data("force_py36")
1345 result = CliRunner().invoke(
1347 ["-", "-q", "--target-version=py36"],
1348 input=BytesIO(source.encode("utf8")),
1350 self.assertEqual(result.exit_code, 0)
1351 actual = result.output
1352 self.assertFormatEqual(actual, expected)
1354 def test_include_exclude(self) -> None:
1355 path = THIS_DIR / "data" / "include_exclude_tests"
1356 include = re.compile(r"\.pyi?$")
1357 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1358 report = black.Report()
1359 sources: List[Path] = []
1361 Path(path / "b/dont_exclude/a.py"),
1362 Path(path / "b/dont_exclude/a.pyi"),
1364 this_abs = THIS_DIR.resolve()
1366 black.gen_python_files_in_dir(path, this_abs, include, exclude, report)
1368 self.assertEqual(sorted(expected), sorted(sources))
1370 def test_empty_include(self) -> None:
1371 path = THIS_DIR / "data" / "include_exclude_tests"
1372 report = black.Report()
1373 empty = re.compile(r"")
1374 sources: List[Path] = []
1376 Path(path / "b/exclude/a.pie"),
1377 Path(path / "b/exclude/a.py"),
1378 Path(path / "b/exclude/a.pyi"),
1379 Path(path / "b/dont_exclude/a.pie"),
1380 Path(path / "b/dont_exclude/a.py"),
1381 Path(path / "b/dont_exclude/a.pyi"),
1382 Path(path / "b/.definitely_exclude/a.pie"),
1383 Path(path / "b/.definitely_exclude/a.py"),
1384 Path(path / "b/.definitely_exclude/a.pyi"),
1386 this_abs = THIS_DIR.resolve()
1388 black.gen_python_files_in_dir(
1389 path, this_abs, empty, re.compile(black.DEFAULT_EXCLUDES), report
1392 self.assertEqual(sorted(expected), sorted(sources))
1394 def test_empty_exclude(self) -> None:
1395 path = THIS_DIR / "data" / "include_exclude_tests"
1396 report = black.Report()
1397 empty = re.compile(r"")
1398 sources: List[Path] = []
1400 Path(path / "b/dont_exclude/a.py"),
1401 Path(path / "b/dont_exclude/a.pyi"),
1402 Path(path / "b/exclude/a.py"),
1403 Path(path / "b/exclude/a.pyi"),
1404 Path(path / "b/.definitely_exclude/a.py"),
1405 Path(path / "b/.definitely_exclude/a.pyi"),
1407 this_abs = THIS_DIR.resolve()
1409 black.gen_python_files_in_dir(
1410 path, this_abs, re.compile(black.DEFAULT_INCLUDES), empty, report
1413 self.assertEqual(sorted(expected), sorted(sources))
1415 def test_invalid_include_exclude(self) -> None:
1416 for option in ["--include", "--exclude"]:
1417 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1419 def test_preserves_line_endings(self) -> None:
1420 with TemporaryDirectory() as workspace:
1421 test_file = Path(workspace) / "test.py"
1422 for nl in ["\n", "\r\n"]:
1423 contents = nl.join(["def f( ):", " pass"])
1424 test_file.write_bytes(contents.encode())
1425 ff(test_file, write_back=black.WriteBack.YES)
1426 updated_contents: bytes = test_file.read_bytes()
1427 self.assertIn(nl.encode(), updated_contents)
1429 self.assertNotIn(b"\r\n", updated_contents)
1431 def test_preserves_line_endings_via_stdin(self) -> None:
1432 for nl in ["\n", "\r\n"]:
1433 contents = nl.join(["def f( ):", " pass"])
1434 runner = BlackRunner()
1435 result = runner.invoke(
1436 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1438 self.assertEqual(result.exit_code, 0)
1439 output = runner.stdout_bytes
1440 self.assertIn(nl.encode("utf8"), output)
1442 self.assertNotIn(b"\r\n", output)
1444 def test_assert_equivalent_different_asts(self) -> None:
1445 with self.assertRaises(AssertionError):
1446 black.assert_equivalent("{}", "None")
1448 def test_symlink_out_of_root_directory(self) -> None:
1452 include = re.compile(black.DEFAULT_INCLUDES)
1453 exclude = re.compile(black.DEFAULT_EXCLUDES)
1454 report = black.Report()
1455 # `child` should behave like a symlink which resolved path is clearly
1456 # outside of the `root` directory.
1457 path.iterdir.return_value = [child]
1458 child.resolve.return_value = Path("/a/b/c")
1459 child.is_symlink.return_value = True
1461 list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1462 except ValueError as ve:
1463 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1464 path.iterdir.assert_called_once()
1465 child.resolve.assert_called_once()
1466 child.is_symlink.assert_called_once()
1467 # `child` should behave like a strange file which resolved path is clearly
1468 # outside of the `root` directory.
1469 child.is_symlink.return_value = False
1470 with self.assertRaises(ValueError):
1471 list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1472 path.iterdir.assert_called()
1473 self.assertEqual(path.iterdir.call_count, 2)
1474 child.resolve.assert_called()
1475 self.assertEqual(child.resolve.call_count, 2)
1476 child.is_symlink.assert_called()
1477 self.assertEqual(child.is_symlink.call_count, 2)
1479 def test_shhh_click(self) -> None:
1481 from click import _unicodefun # type: ignore
1482 except ModuleNotFoundError:
1483 self.skipTest("Incompatible Click version")
1484 if not hasattr(_unicodefun, "_verify_python3_env"):
1485 self.skipTest("Incompatible Click version")
1486 # First, let's see if Click is crashing with a preferred ASCII charset.
1487 with patch("locale.getpreferredencoding") as gpe:
1488 gpe.return_value = "ASCII"
1489 with self.assertRaises(RuntimeError):
1490 _unicodefun._verify_python3_env()
1491 # Now, let's silence Click...
1493 # ...and confirm it's silent.
1494 with patch("locale.getpreferredencoding") as gpe:
1495 gpe.return_value = "ASCII"
1497 _unicodefun._verify_python3_env()
1498 except RuntimeError as re:
1499 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1501 def test_root_logger_not_used_directly(self) -> None:
1502 def fail(*args: Any, **kwargs: Any) -> None:
1503 self.fail("Record created with root logger")
1505 with patch.multiple(
1516 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1517 def test_blackd_main(self) -> None:
1518 with patch("blackd.web.run_app"):
1519 result = CliRunner().invoke(blackd.main, [])
1520 if result.exception is not None:
1521 raise result.exception
1522 self.assertEqual(result.exit_code, 0)
1525 class BlackDTestCase(AioHTTPTestCase):
1526 async def get_application(self) -> web.Application:
1527 return blackd.make_app()
1529 # TODO: remove these decorators once the below is released
1530 # https://github.com/aio-libs/aiohttp/pull/3727
1531 @skip_if_exception("ClientOSError")
1532 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1534 async def test_blackd_request_needs_formatting(self) -> None:
1535 response = await self.client.post("/", data=b"print('hello world')")
1536 self.assertEqual(response.status, 200)
1537 self.assertEqual(response.charset, "utf8")
1538 self.assertEqual(await response.read(), b'print("hello world")\n')
1540 @skip_if_exception("ClientOSError")
1541 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1543 async def test_blackd_request_no_change(self) -> None:
1544 response = await self.client.post("/", data=b'print("hello world")\n')
1545 self.assertEqual(response.status, 204)
1546 self.assertEqual(await response.read(), b"")
1548 @skip_if_exception("ClientOSError")
1549 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1551 async def test_blackd_request_syntax_error(self) -> None:
1552 response = await self.client.post("/", data=b"what even ( is")
1553 self.assertEqual(response.status, 400)
1554 content = await response.text()
1556 content.startswith("Cannot parse"),
1557 msg=f"Expected error to start with 'Cannot parse', got {repr(content)}",
1560 @skip_if_exception("ClientOSError")
1561 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1563 async def test_blackd_unsupported_version(self) -> None:
1564 response = await self.client.post(
1565 "/", data=b"what", headers={blackd.VERSION_HEADER: "2"}
1567 self.assertEqual(response.status, 501)
1569 @skip_if_exception("ClientOSError")
1570 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1572 async def test_blackd_supported_version(self) -> None:
1573 response = await self.client.post(
1574 "/", data=b"what", headers={blackd.VERSION_HEADER: "1"}
1576 self.assertEqual(response.status, 200)
1578 @skip_if_exception("ClientOSError")
1579 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1581 async def test_blackd_invalid_python_variant(self) -> None:
1582 async def check(header_value: str, expected_status: int = 400) -> None:
1583 response = await self.client.post(
1584 "/", data=b"what", headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1586 self.assertEqual(response.status, expected_status)
1589 await check("ruby3.5")
1590 await check("pyi3.6")
1591 await check("py1.5")
1593 await check("py2.8")
1595 await check("pypy3.0")
1596 await check("jython3.4")
1598 @skip_if_exception("ClientOSError")
1599 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1601 async def test_blackd_pyi(self) -> None:
1602 source, expected = read_data("stub.pyi")
1603 response = await self.client.post(
1604 "/", data=source, headers={blackd.PYTHON_VARIANT_HEADER: "pyi"}
1606 self.assertEqual(response.status, 200)
1607 self.assertEqual(await response.text(), expected)
1609 @skip_if_exception("ClientOSError")
1610 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1612 async def test_blackd_python_variant(self) -> None:
1615 " and_has_a_bunch_of,\n"
1616 " very_long_arguments_too,\n"
1617 " and_lots_of_them_as_well_lol,\n"
1618 " **and_very_long_keyword_arguments\n"
1623 async def check(header_value: str, expected_status: int) -> None:
1624 response = await self.client.post(
1625 "/", data=code, headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1627 self.assertEqual(response.status, expected_status)
1629 await check("3.6", 200)
1630 await check("py3.6", 200)
1631 await check("3.6,3.7", 200)
1632 await check("3.6,py3.7", 200)
1634 await check("2", 204)
1635 await check("2.7", 204)
1636 await check("py2.7", 204)
1637 await check("3.4", 204)
1638 await check("py3.4", 204)
1640 @skip_if_exception("ClientOSError")
1641 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1643 async def test_blackd_line_length(self) -> None:
1644 response = await self.client.post(
1645 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "7"}
1647 self.assertEqual(response.status, 200)
1649 @skip_if_exception("ClientOSError")
1650 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1652 async def test_blackd_invalid_line_length(self) -> None:
1653 response = await self.client.post(
1654 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "NaN"}
1656 self.assertEqual(response.status, 400)
1659 if __name__ == "__main__":
1660 unittest.main(module="test_black")