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 checkSourceFile(self, name: str) -> None:
163 path = THIS_DIR.parent / name
164 source, expected = read_data(str(path), data=False)
166 self.assertFormatEqual(expected, actual)
167 black.assert_equivalent(source, actual)
168 black.assert_stable(source, actual, black.FileMode())
169 self.assertFalse(ff(path))
171 @patch("black.dump_to_file", dump_to_stderr)
172 def test_empty(self) -> None:
173 source = expected = ""
175 self.assertFormatEqual(expected, actual)
176 black.assert_equivalent(source, actual)
177 black.assert_stable(source, actual, black.FileMode())
179 def test_empty_ff(self) -> None:
181 tmp_file = Path(black.dump_to_file())
183 self.assertFalse(ff(tmp_file, write_back=black.WriteBack.YES))
184 with open(tmp_file, encoding="utf8") as f:
188 self.assertFormatEqual(expected, actual)
190 def test_self(self) -> None:
191 self.checkSourceFile("tests/test_black.py")
193 def test_black(self) -> None:
194 self.checkSourceFile("black.py")
196 def test_pygram(self) -> None:
197 self.checkSourceFile("blib2to3/pygram.py")
199 def test_pytree(self) -> None:
200 self.checkSourceFile("blib2to3/pytree.py")
202 def test_conv(self) -> None:
203 self.checkSourceFile("blib2to3/pgen2/conv.py")
205 def test_driver(self) -> None:
206 self.checkSourceFile("blib2to3/pgen2/driver.py")
208 def test_grammar(self) -> None:
209 self.checkSourceFile("blib2to3/pgen2/grammar.py")
211 def test_literals(self) -> None:
212 self.checkSourceFile("blib2to3/pgen2/literals.py")
214 def test_parse(self) -> None:
215 self.checkSourceFile("blib2to3/pgen2/parse.py")
217 def test_pgen(self) -> None:
218 self.checkSourceFile("blib2to3/pgen2/pgen.py")
220 def test_tokenize(self) -> None:
221 self.checkSourceFile("blib2to3/pgen2/tokenize.py")
223 def test_token(self) -> None:
224 self.checkSourceFile("blib2to3/pgen2/token.py")
226 def test_setup(self) -> None:
227 self.checkSourceFile("setup.py")
229 def test_piping(self) -> None:
230 source, expected = read_data("../black", data=False)
231 result = BlackRunner().invoke(
233 ["-", "--fast", f"--line-length={black.DEFAULT_LINE_LENGTH}"],
234 input=BytesIO(source.encode("utf8")),
236 self.assertEqual(result.exit_code, 0)
237 self.assertFormatEqual(expected, result.output)
238 black.assert_equivalent(source, result.output)
239 black.assert_stable(source, result.output, black.FileMode())
241 def test_piping_diff(self) -> None:
242 diff_header = re.compile(
243 rf"(STDIN|STDOUT)\t\d\d\d\d-\d\d-\d\d "
244 rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
246 source, _ = read_data("expression.py")
247 expected, _ = read_data("expression.diff")
248 config = THIS_DIR / "data" / "empty_pyproject.toml"
252 f"--line-length={black.DEFAULT_LINE_LENGTH}",
254 f"--config={config}",
256 result = BlackRunner().invoke(
257 black.main, args, input=BytesIO(source.encode("utf8"))
259 self.assertEqual(result.exit_code, 0)
260 actual = diff_header.sub("[Deterministic header]", result.output)
261 actual = actual.rstrip() + "\n" # the diff output has a trailing space
262 self.assertEqual(expected, actual)
264 @patch("black.dump_to_file", dump_to_stderr)
265 def test_function(self) -> None:
266 source, expected = read_data("function")
268 self.assertFormatEqual(expected, actual)
269 black.assert_equivalent(source, actual)
270 black.assert_stable(source, actual, black.FileMode())
272 @patch("black.dump_to_file", dump_to_stderr)
273 def test_function2(self) -> None:
274 source, expected = read_data("function2")
276 self.assertFormatEqual(expected, actual)
277 black.assert_equivalent(source, actual)
278 black.assert_stable(source, actual, black.FileMode())
280 @patch("black.dump_to_file", dump_to_stderr)
281 def test_function_trailing_comma(self) -> None:
282 source, expected = read_data("function_trailing_comma")
284 self.assertFormatEqual(expected, actual)
285 black.assert_equivalent(source, actual)
286 black.assert_stable(source, actual, black.FileMode())
288 @patch("black.dump_to_file", dump_to_stderr)
289 def test_expression(self) -> None:
290 source, expected = read_data("expression")
292 self.assertFormatEqual(expected, actual)
293 black.assert_equivalent(source, actual)
294 black.assert_stable(source, actual, black.FileMode())
296 @patch("black.dump_to_file", dump_to_stderr)
297 def test_pep_572(self) -> None:
298 source, expected = read_data("pep_572")
300 self.assertFormatEqual(expected, actual)
301 black.assert_stable(source, actual, black.FileMode())
302 if sys.version_info >= (3, 8):
303 black.assert_equivalent(source, actual)
305 def test_pep_572_version_detection(self) -> None:
306 source, _ = read_data("pep_572")
307 root = black.lib2to3_parse(source)
308 features = black.get_features_used(root)
309 self.assertIn(black.Feature.ASSIGNMENT_EXPRESSIONS, features)
310 versions = black.detect_target_versions(root)
311 self.assertIn(black.TargetVersion.PY38, versions)
313 def test_expression_ff(self) -> None:
314 source, expected = read_data("expression")
315 tmp_file = Path(black.dump_to_file(source))
317 self.assertTrue(ff(tmp_file, write_back=black.WriteBack.YES))
318 with open(tmp_file, encoding="utf8") as f:
322 self.assertFormatEqual(expected, actual)
323 with patch("black.dump_to_file", dump_to_stderr):
324 black.assert_equivalent(source, actual)
325 black.assert_stable(source, actual, black.FileMode())
327 def test_expression_diff(self) -> None:
328 source, _ = read_data("expression.py")
329 expected, _ = read_data("expression.diff")
330 tmp_file = Path(black.dump_to_file(source))
331 diff_header = re.compile(
332 rf"{re.escape(str(tmp_file))}\t\d\d\d\d-\d\d-\d\d "
333 rf"\d\d:\d\d:\d\d\.\d\d\d\d\d\d \+\d\d\d\d"
336 result = BlackRunner().invoke(black.main, ["--diff", str(tmp_file)])
337 self.assertEqual(result.exit_code, 0)
340 actual = result.output
341 actual = diff_header.sub("[Deterministic header]", actual)
342 actual = actual.rstrip() + "\n" # the diff output has a trailing space
343 if expected != actual:
344 dump = black.dump_to_file(actual)
346 f"Expected diff isn't equal to the actual. If you made changes "
347 f"to expression.py and this is an anticipated difference, "
348 f"overwrite tests/data/expression.diff with {dump}"
350 self.assertEqual(expected, actual, msg)
352 @patch("black.dump_to_file", dump_to_stderr)
353 def test_fstring(self) -> None:
354 source, expected = read_data("fstring")
356 self.assertFormatEqual(expected, actual)
357 black.assert_equivalent(source, actual)
358 black.assert_stable(source, actual, black.FileMode())
360 @patch("black.dump_to_file", dump_to_stderr)
361 def test_pep_570(self) -> None:
362 source, expected = read_data("pep_570")
364 self.assertFormatEqual(expected, actual)
365 black.assert_stable(source, actual, black.FileMode())
366 if sys.version_info >= (3, 8):
367 black.assert_equivalent(source, actual)
369 def test_detect_pos_only_arguments(self) -> None:
370 source, _ = read_data("pep_570")
371 root = black.lib2to3_parse(source)
372 features = black.get_features_used(root)
373 self.assertIn(black.Feature.POS_ONLY_ARGUMENTS, features)
374 versions = black.detect_target_versions(root)
375 self.assertIn(black.TargetVersion.PY38, versions)
377 @patch("black.dump_to_file", dump_to_stderr)
378 def test_string_quotes(self) -> None:
379 source, expected = read_data("string_quotes")
381 self.assertFormatEqual(expected, actual)
382 black.assert_equivalent(source, actual)
383 black.assert_stable(source, actual, black.FileMode())
384 mode = black.FileMode(string_normalization=False)
385 not_normalized = fs(source, mode=mode)
386 self.assertFormatEqual(source, not_normalized)
387 black.assert_equivalent(source, not_normalized)
388 black.assert_stable(source, not_normalized, mode=mode)
390 @patch("black.dump_to_file", dump_to_stderr)
391 def test_slices(self) -> None:
392 source, expected = read_data("slices")
394 self.assertFormatEqual(expected, actual)
395 black.assert_equivalent(source, actual)
396 black.assert_stable(source, actual, black.FileMode())
398 @patch("black.dump_to_file", dump_to_stderr)
399 def test_comments(self) -> None:
400 source, expected = read_data("comments")
402 self.assertFormatEqual(expected, actual)
403 black.assert_equivalent(source, actual)
404 black.assert_stable(source, actual, black.FileMode())
406 @patch("black.dump_to_file", dump_to_stderr)
407 def test_comments2(self) -> None:
408 source, expected = read_data("comments2")
410 self.assertFormatEqual(expected, actual)
411 black.assert_equivalent(source, actual)
412 black.assert_stable(source, actual, black.FileMode())
414 @patch("black.dump_to_file", dump_to_stderr)
415 def test_comments3(self) -> None:
416 source, expected = read_data("comments3")
418 self.assertFormatEqual(expected, actual)
419 black.assert_equivalent(source, actual)
420 black.assert_stable(source, actual, black.FileMode())
422 @patch("black.dump_to_file", dump_to_stderr)
423 def test_comments4(self) -> None:
424 source, expected = read_data("comments4")
426 self.assertFormatEqual(expected, actual)
427 black.assert_equivalent(source, actual)
428 black.assert_stable(source, actual, black.FileMode())
430 @patch("black.dump_to_file", dump_to_stderr)
431 def test_comments5(self) -> None:
432 source, expected = read_data("comments5")
434 self.assertFormatEqual(expected, actual)
435 black.assert_equivalent(source, actual)
436 black.assert_stable(source, actual, black.FileMode())
438 @patch("black.dump_to_file", dump_to_stderr)
439 def test_comments6(self) -> None:
440 source, expected = read_data("comments6")
442 self.assertFormatEqual(expected, actual)
443 black.assert_equivalent(source, actual)
444 black.assert_stable(source, actual, black.FileMode())
446 @patch("black.dump_to_file", dump_to_stderr)
447 def test_comments7(self) -> None:
448 source, expected = read_data("comments7")
450 self.assertFormatEqual(expected, actual)
451 black.assert_equivalent(source, actual)
452 black.assert_stable(source, actual, black.FileMode())
454 @patch("black.dump_to_file", dump_to_stderr)
455 def test_comment_after_escaped_newline(self) -> None:
456 source, expected = read_data("comment_after_escaped_newline")
458 self.assertFormatEqual(expected, actual)
459 black.assert_equivalent(source, actual)
460 black.assert_stable(source, actual, black.FileMode())
462 @patch("black.dump_to_file", dump_to_stderr)
463 def test_cantfit(self) -> None:
464 source, expected = read_data("cantfit")
466 self.assertFormatEqual(expected, actual)
467 black.assert_equivalent(source, actual)
468 black.assert_stable(source, actual, black.FileMode())
470 @patch("black.dump_to_file", dump_to_stderr)
471 def test_import_spacing(self) -> None:
472 source, expected = read_data("import_spacing")
474 self.assertFormatEqual(expected, actual)
475 black.assert_equivalent(source, actual)
476 black.assert_stable(source, actual, black.FileMode())
478 @patch("black.dump_to_file", dump_to_stderr)
479 def test_composition(self) -> None:
480 source, expected = read_data("composition")
482 self.assertFormatEqual(expected, actual)
483 black.assert_equivalent(source, actual)
484 black.assert_stable(source, actual, black.FileMode())
486 @patch("black.dump_to_file", dump_to_stderr)
487 def test_empty_lines(self) -> None:
488 source, expected = read_data("empty_lines")
490 self.assertFormatEqual(expected, actual)
491 black.assert_equivalent(source, actual)
492 black.assert_stable(source, actual, black.FileMode())
494 @patch("black.dump_to_file", dump_to_stderr)
495 def test_remove_parens(self) -> None:
496 source, expected = read_data("remove_parens")
498 self.assertFormatEqual(expected, actual)
499 black.assert_equivalent(source, actual)
500 black.assert_stable(source, actual, black.FileMode())
502 @patch("black.dump_to_file", dump_to_stderr)
503 def test_string_prefixes(self) -> None:
504 source, expected = read_data("string_prefixes")
506 self.assertFormatEqual(expected, actual)
507 black.assert_equivalent(source, actual)
508 black.assert_stable(source, actual, black.FileMode())
510 @patch("black.dump_to_file", dump_to_stderr)
511 def test_numeric_literals(self) -> None:
512 source, expected = read_data("numeric_literals")
513 mode = black.FileMode(target_versions=black.PY36_VERSIONS)
514 actual = fs(source, mode=mode)
515 self.assertFormatEqual(expected, actual)
516 black.assert_equivalent(source, actual)
517 black.assert_stable(source, actual, mode)
519 @patch("black.dump_to_file", dump_to_stderr)
520 def test_numeric_literals_ignoring_underscores(self) -> None:
521 source, expected = read_data("numeric_literals_skip_underscores")
522 mode = black.FileMode(target_versions=black.PY36_VERSIONS)
523 actual = fs(source, mode=mode)
524 self.assertFormatEqual(expected, actual)
525 black.assert_equivalent(source, actual)
526 black.assert_stable(source, actual, mode)
528 @patch("black.dump_to_file", dump_to_stderr)
529 def test_numeric_literals_py2(self) -> None:
530 source, expected = read_data("numeric_literals_py2")
532 self.assertFormatEqual(expected, actual)
533 black.assert_stable(source, actual, black.FileMode())
535 @patch("black.dump_to_file", dump_to_stderr)
536 def test_python2(self) -> None:
537 source, expected = read_data("python2")
539 self.assertFormatEqual(expected, actual)
540 black.assert_equivalent(source, actual)
541 black.assert_stable(source, actual, black.FileMode())
543 @patch("black.dump_to_file", dump_to_stderr)
544 def test_python2_print_function(self) -> None:
545 source, expected = read_data("python2_print_function")
546 mode = black.FileMode(target_versions={TargetVersion.PY27})
547 actual = fs(source, mode=mode)
548 self.assertFormatEqual(expected, actual)
549 black.assert_equivalent(source, actual)
550 black.assert_stable(source, actual, mode)
552 @patch("black.dump_to_file", dump_to_stderr)
553 def test_python2_unicode_literals(self) -> None:
554 source, expected = read_data("python2_unicode_literals")
556 self.assertFormatEqual(expected, actual)
557 black.assert_equivalent(source, actual)
558 black.assert_stable(source, actual, black.FileMode())
560 @patch("black.dump_to_file", dump_to_stderr)
561 def test_stub(self) -> None:
562 mode = black.FileMode(is_pyi=True)
563 source, expected = read_data("stub.pyi")
564 actual = fs(source, mode=mode)
565 self.assertFormatEqual(expected, actual)
566 black.assert_stable(source, actual, mode)
568 @patch("black.dump_to_file", dump_to_stderr)
569 def test_async_as_identifier(self) -> None:
570 source_path = (THIS_DIR / "data" / "async_as_identifier.py").resolve()
571 source, expected = read_data("async_as_identifier")
573 self.assertFormatEqual(expected, actual)
574 major, minor = sys.version_info[:2]
575 if major < 3 or (major <= 3 and minor < 7):
576 black.assert_equivalent(source, actual)
577 black.assert_stable(source, actual, black.FileMode())
578 # ensure black can parse this when the target is 3.6
579 self.invokeBlack([str(source_path), "--target-version", "py36"])
580 # but not on 3.7, because async/await is no longer an identifier
581 self.invokeBlack([str(source_path), "--target-version", "py37"], exit_code=123)
583 @patch("black.dump_to_file", dump_to_stderr)
584 def test_python37(self) -> None:
585 source_path = (THIS_DIR / "data" / "python37.py").resolve()
586 source, expected = read_data("python37")
588 self.assertFormatEqual(expected, actual)
589 major, minor = sys.version_info[:2]
590 if major > 3 or (major == 3 and minor >= 7):
591 black.assert_equivalent(source, actual)
592 black.assert_stable(source, actual, black.FileMode())
593 # ensure black can parse this when the target is 3.7
594 self.invokeBlack([str(source_path), "--target-version", "py37"])
595 # but not on 3.6, because we use async as a reserved keyword
596 self.invokeBlack([str(source_path), "--target-version", "py36"], exit_code=123)
598 @patch("black.dump_to_file", dump_to_stderr)
599 def test_fmtonoff(self) -> None:
600 source, expected = read_data("fmtonoff")
602 self.assertFormatEqual(expected, actual)
603 black.assert_equivalent(source, actual)
604 black.assert_stable(source, actual, black.FileMode())
606 @patch("black.dump_to_file", dump_to_stderr)
607 def test_fmtonoff2(self) -> None:
608 source, expected = read_data("fmtonoff2")
610 self.assertFormatEqual(expected, actual)
611 black.assert_equivalent(source, actual)
612 black.assert_stable(source, actual, black.FileMode())
614 @patch("black.dump_to_file", dump_to_stderr)
615 def test_remove_empty_parentheses_after_class(self) -> None:
616 source, expected = read_data("class_blank_parentheses")
618 self.assertFormatEqual(expected, actual)
619 black.assert_equivalent(source, actual)
620 black.assert_stable(source, actual, black.FileMode())
622 @patch("black.dump_to_file", dump_to_stderr)
623 def test_new_line_between_class_and_code(self) -> None:
624 source, expected = read_data("class_methods_new_line")
626 self.assertFormatEqual(expected, actual)
627 black.assert_equivalent(source, actual)
628 black.assert_stable(source, actual, black.FileMode())
630 @patch("black.dump_to_file", dump_to_stderr)
631 def test_bracket_match(self) -> None:
632 source, expected = read_data("bracketmatch")
634 self.assertFormatEqual(expected, actual)
635 black.assert_equivalent(source, actual)
636 black.assert_stable(source, actual, black.FileMode())
638 @patch("black.dump_to_file", dump_to_stderr)
639 def test_tuple_assign(self) -> None:
640 source, expected = read_data("tupleassign")
642 self.assertFormatEqual(expected, actual)
643 black.assert_equivalent(source, actual)
644 black.assert_stable(source, actual, black.FileMode())
646 @patch("black.dump_to_file", dump_to_stderr)
647 def test_beginning_backslash(self) -> None:
648 source, expected = read_data("beginning_backslash")
650 self.assertFormatEqual(expected, actual)
651 black.assert_equivalent(source, actual)
652 black.assert_stable(source, actual, black.FileMode())
654 def test_tab_comment_indentation(self) -> None:
655 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t# comment\n\tpass\n"
656 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
657 self.assertFormatEqual(contents_spc, fs(contents_spc))
658 self.assertFormatEqual(contents_spc, fs(contents_tab))
660 contents_tab = "if 1:\n\tif 2:\n\t\tpass\n\t\t# comment\n\tpass\n"
661 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
662 self.assertFormatEqual(contents_spc, fs(contents_spc))
663 self.assertFormatEqual(contents_spc, fs(contents_tab))
665 # mixed tabs and spaces (valid Python 2 code)
666 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t# comment\n pass\n"
667 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
668 self.assertFormatEqual(contents_spc, fs(contents_spc))
669 self.assertFormatEqual(contents_spc, fs(contents_tab))
671 contents_tab = "if 1:\n if 2:\n\t\tpass\n\t\t# comment\n pass\n"
672 contents_spc = "if 1:\n if 2:\n pass\n # comment\n pass\n"
673 self.assertFormatEqual(contents_spc, fs(contents_spc))
674 self.assertFormatEqual(contents_spc, fs(contents_tab))
676 def test_report_verbose(self) -> None:
677 report = black.Report(verbose=True)
681 def out(msg: str, **kwargs: Any) -> None:
682 out_lines.append(msg)
684 def err(msg: str, **kwargs: Any) -> None:
685 err_lines.append(msg)
687 with patch("black.out", out), patch("black.err", err):
688 report.done(Path("f1"), black.Changed.NO)
689 self.assertEqual(len(out_lines), 1)
690 self.assertEqual(len(err_lines), 0)
691 self.assertEqual(out_lines[-1], "f1 already well formatted, good job.")
692 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
693 self.assertEqual(report.return_code, 0)
694 report.done(Path("f2"), black.Changed.YES)
695 self.assertEqual(len(out_lines), 2)
696 self.assertEqual(len(err_lines), 0)
697 self.assertEqual(out_lines[-1], "reformatted f2")
699 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
701 report.done(Path("f3"), black.Changed.CACHED)
702 self.assertEqual(len(out_lines), 3)
703 self.assertEqual(len(err_lines), 0)
705 out_lines[-1], "f3 wasn't modified on disk since last run."
708 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
710 self.assertEqual(report.return_code, 0)
712 self.assertEqual(report.return_code, 1)
714 report.failed(Path("e1"), "boom")
715 self.assertEqual(len(out_lines), 3)
716 self.assertEqual(len(err_lines), 1)
717 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
719 unstyle(str(report)),
720 "1 file reformatted, 2 files left unchanged, "
721 "1 file failed to reformat.",
723 self.assertEqual(report.return_code, 123)
724 report.done(Path("f3"), black.Changed.YES)
725 self.assertEqual(len(out_lines), 4)
726 self.assertEqual(len(err_lines), 1)
727 self.assertEqual(out_lines[-1], "reformatted f3")
729 unstyle(str(report)),
730 "2 files reformatted, 2 files left unchanged, "
731 "1 file failed to reformat.",
733 self.assertEqual(report.return_code, 123)
734 report.failed(Path("e2"), "boom")
735 self.assertEqual(len(out_lines), 4)
736 self.assertEqual(len(err_lines), 2)
737 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
739 unstyle(str(report)),
740 "2 files reformatted, 2 files left unchanged, "
741 "2 files failed to reformat.",
743 self.assertEqual(report.return_code, 123)
744 report.path_ignored(Path("wat"), "no match")
745 self.assertEqual(len(out_lines), 5)
746 self.assertEqual(len(err_lines), 2)
747 self.assertEqual(out_lines[-1], "wat ignored: no match")
749 unstyle(str(report)),
750 "2 files reformatted, 2 files left unchanged, "
751 "2 files failed to reformat.",
753 self.assertEqual(report.return_code, 123)
754 report.done(Path("f4"), black.Changed.NO)
755 self.assertEqual(len(out_lines), 6)
756 self.assertEqual(len(err_lines), 2)
757 self.assertEqual(out_lines[-1], "f4 already well formatted, good job.")
759 unstyle(str(report)),
760 "2 files reformatted, 3 files left unchanged, "
761 "2 files failed to reformat.",
763 self.assertEqual(report.return_code, 123)
766 unstyle(str(report)),
767 "2 files would be reformatted, 3 files would be left unchanged, "
768 "2 files would fail to reformat.",
771 def test_report_quiet(self) -> None:
772 report = black.Report(quiet=True)
776 def out(msg: str, **kwargs: Any) -> None:
777 out_lines.append(msg)
779 def err(msg: str, **kwargs: Any) -> None:
780 err_lines.append(msg)
782 with patch("black.out", out), patch("black.err", err):
783 report.done(Path("f1"), black.Changed.NO)
784 self.assertEqual(len(out_lines), 0)
785 self.assertEqual(len(err_lines), 0)
786 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
787 self.assertEqual(report.return_code, 0)
788 report.done(Path("f2"), black.Changed.YES)
789 self.assertEqual(len(out_lines), 0)
790 self.assertEqual(len(err_lines), 0)
792 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
794 report.done(Path("f3"), black.Changed.CACHED)
795 self.assertEqual(len(out_lines), 0)
796 self.assertEqual(len(err_lines), 0)
798 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
800 self.assertEqual(report.return_code, 0)
802 self.assertEqual(report.return_code, 1)
804 report.failed(Path("e1"), "boom")
805 self.assertEqual(len(out_lines), 0)
806 self.assertEqual(len(err_lines), 1)
807 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
809 unstyle(str(report)),
810 "1 file reformatted, 2 files left unchanged, "
811 "1 file failed to reformat.",
813 self.assertEqual(report.return_code, 123)
814 report.done(Path("f3"), black.Changed.YES)
815 self.assertEqual(len(out_lines), 0)
816 self.assertEqual(len(err_lines), 1)
818 unstyle(str(report)),
819 "2 files reformatted, 2 files left unchanged, "
820 "1 file failed to reformat.",
822 self.assertEqual(report.return_code, 123)
823 report.failed(Path("e2"), "boom")
824 self.assertEqual(len(out_lines), 0)
825 self.assertEqual(len(err_lines), 2)
826 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
828 unstyle(str(report)),
829 "2 files reformatted, 2 files left unchanged, "
830 "2 files failed to reformat.",
832 self.assertEqual(report.return_code, 123)
833 report.path_ignored(Path("wat"), "no match")
834 self.assertEqual(len(out_lines), 0)
835 self.assertEqual(len(err_lines), 2)
837 unstyle(str(report)),
838 "2 files reformatted, 2 files left unchanged, "
839 "2 files failed to reformat.",
841 self.assertEqual(report.return_code, 123)
842 report.done(Path("f4"), black.Changed.NO)
843 self.assertEqual(len(out_lines), 0)
844 self.assertEqual(len(err_lines), 2)
846 unstyle(str(report)),
847 "2 files reformatted, 3 files left unchanged, "
848 "2 files failed to reformat.",
850 self.assertEqual(report.return_code, 123)
853 unstyle(str(report)),
854 "2 files would be reformatted, 3 files would be left unchanged, "
855 "2 files would fail to reformat.",
858 def test_report_normal(self) -> None:
859 report = black.Report()
863 def out(msg: str, **kwargs: Any) -> None:
864 out_lines.append(msg)
866 def err(msg: str, **kwargs: Any) -> None:
867 err_lines.append(msg)
869 with patch("black.out", out), patch("black.err", err):
870 report.done(Path("f1"), black.Changed.NO)
871 self.assertEqual(len(out_lines), 0)
872 self.assertEqual(len(err_lines), 0)
873 self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
874 self.assertEqual(report.return_code, 0)
875 report.done(Path("f2"), black.Changed.YES)
876 self.assertEqual(len(out_lines), 1)
877 self.assertEqual(len(err_lines), 0)
878 self.assertEqual(out_lines[-1], "reformatted f2")
880 unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
882 report.done(Path("f3"), black.Changed.CACHED)
883 self.assertEqual(len(out_lines), 1)
884 self.assertEqual(len(err_lines), 0)
885 self.assertEqual(out_lines[-1], "reformatted f2")
887 unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
889 self.assertEqual(report.return_code, 0)
891 self.assertEqual(report.return_code, 1)
893 report.failed(Path("e1"), "boom")
894 self.assertEqual(len(out_lines), 1)
895 self.assertEqual(len(err_lines), 1)
896 self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
898 unstyle(str(report)),
899 "1 file reformatted, 2 files left unchanged, "
900 "1 file failed to reformat.",
902 self.assertEqual(report.return_code, 123)
903 report.done(Path("f3"), black.Changed.YES)
904 self.assertEqual(len(out_lines), 2)
905 self.assertEqual(len(err_lines), 1)
906 self.assertEqual(out_lines[-1], "reformatted f3")
908 unstyle(str(report)),
909 "2 files reformatted, 2 files left unchanged, "
910 "1 file failed to reformat.",
912 self.assertEqual(report.return_code, 123)
913 report.failed(Path("e2"), "boom")
914 self.assertEqual(len(out_lines), 2)
915 self.assertEqual(len(err_lines), 2)
916 self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
918 unstyle(str(report)),
919 "2 files reformatted, 2 files left unchanged, "
920 "2 files failed to reformat.",
922 self.assertEqual(report.return_code, 123)
923 report.path_ignored(Path("wat"), "no match")
924 self.assertEqual(len(out_lines), 2)
925 self.assertEqual(len(err_lines), 2)
927 unstyle(str(report)),
928 "2 files reformatted, 2 files left unchanged, "
929 "2 files failed to reformat.",
931 self.assertEqual(report.return_code, 123)
932 report.done(Path("f4"), black.Changed.NO)
933 self.assertEqual(len(out_lines), 2)
934 self.assertEqual(len(err_lines), 2)
936 unstyle(str(report)),
937 "2 files reformatted, 3 files left unchanged, "
938 "2 files failed to reformat.",
940 self.assertEqual(report.return_code, 123)
943 unstyle(str(report)),
944 "2 files would be reformatted, 3 files would be left unchanged, "
945 "2 files would fail to reformat.",
948 def test_lib2to3_parse(self) -> None:
949 with self.assertRaises(black.InvalidInput):
950 black.lib2to3_parse("invalid syntax")
953 black.lib2to3_parse(straddling)
954 black.lib2to3_parse(straddling, {TargetVersion.PY27})
955 black.lib2to3_parse(straddling, {TargetVersion.PY36})
956 black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
959 black.lib2to3_parse(py2_only)
960 black.lib2to3_parse(py2_only, {TargetVersion.PY27})
961 with self.assertRaises(black.InvalidInput):
962 black.lib2to3_parse(py2_only, {TargetVersion.PY36})
963 with self.assertRaises(black.InvalidInput):
964 black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
966 py3_only = "exec(x, end=y)"
967 black.lib2to3_parse(py3_only)
968 with self.assertRaises(black.InvalidInput):
969 black.lib2to3_parse(py3_only, {TargetVersion.PY27})
970 black.lib2to3_parse(py3_only, {TargetVersion.PY36})
971 black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
973 def test_get_features_used(self) -> None:
974 node = black.lib2to3_parse("def f(*, arg): ...\n")
975 self.assertEqual(black.get_features_used(node), set())
976 node = black.lib2to3_parse("def f(*, arg,): ...\n")
977 self.assertEqual(black.get_features_used(node), {Feature.TRAILING_COMMA_IN_DEF})
978 node = black.lib2to3_parse("f(*arg,)\n")
980 black.get_features_used(node), {Feature.TRAILING_COMMA_IN_CALL}
982 node = black.lib2to3_parse("def f(*, arg): f'string'\n")
983 self.assertEqual(black.get_features_used(node), {Feature.F_STRINGS})
984 node = black.lib2to3_parse("123_456\n")
985 self.assertEqual(black.get_features_used(node), {Feature.NUMERIC_UNDERSCORES})
986 node = black.lib2to3_parse("123456\n")
987 self.assertEqual(black.get_features_used(node), set())
988 source, expected = read_data("function")
989 node = black.lib2to3_parse(source)
990 expected_features = {
991 Feature.TRAILING_COMMA_IN_CALL,
992 Feature.TRAILING_COMMA_IN_DEF,
995 self.assertEqual(black.get_features_used(node), expected_features)
996 node = black.lib2to3_parse(expected)
997 self.assertEqual(black.get_features_used(node), expected_features)
998 source, expected = read_data("expression")
999 node = black.lib2to3_parse(source)
1000 self.assertEqual(black.get_features_used(node), set())
1001 node = black.lib2to3_parse(expected)
1002 self.assertEqual(black.get_features_used(node), set())
1004 def test_get_future_imports(self) -> None:
1005 node = black.lib2to3_parse("\n")
1006 self.assertEqual(set(), black.get_future_imports(node))
1007 node = black.lib2to3_parse("from __future__ import black\n")
1008 self.assertEqual({"black"}, black.get_future_imports(node))
1009 node = black.lib2to3_parse("from __future__ import multiple, imports\n")
1010 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1011 node = black.lib2to3_parse("from __future__ import (parenthesized, imports)\n")
1012 self.assertEqual({"parenthesized", "imports"}, black.get_future_imports(node))
1013 node = black.lib2to3_parse(
1014 "from __future__ import multiple\nfrom __future__ import imports\n"
1016 self.assertEqual({"multiple", "imports"}, black.get_future_imports(node))
1017 node = black.lib2to3_parse("# comment\nfrom __future__ import black\n")
1018 self.assertEqual({"black"}, black.get_future_imports(node))
1019 node = black.lib2to3_parse('"""docstring"""\nfrom __future__ import black\n')
1020 self.assertEqual({"black"}, black.get_future_imports(node))
1021 node = black.lib2to3_parse("some(other, code)\nfrom __future__ import black\n")
1022 self.assertEqual(set(), black.get_future_imports(node))
1023 node = black.lib2to3_parse("from some.module import black\n")
1024 self.assertEqual(set(), black.get_future_imports(node))
1025 node = black.lib2to3_parse(
1026 "from __future__ import unicode_literals as _unicode_literals"
1028 self.assertEqual({"unicode_literals"}, black.get_future_imports(node))
1029 node = black.lib2to3_parse(
1030 "from __future__ import unicode_literals as _lol, print"
1032 self.assertEqual({"unicode_literals", "print"}, black.get_future_imports(node))
1034 def test_debug_visitor(self) -> None:
1035 source, _ = read_data("debug_visitor.py")
1036 expected, _ = read_data("debug_visitor.out")
1040 def out(msg: str, **kwargs: Any) -> None:
1041 out_lines.append(msg)
1043 def err(msg: str, **kwargs: Any) -> None:
1044 err_lines.append(msg)
1046 with patch("black.out", out), patch("black.err", err):
1047 black.DebugVisitor.show(source)
1048 actual = "\n".join(out_lines) + "\n"
1050 if expected != actual:
1051 log_name = black.dump_to_file(*out_lines)
1055 f"AST print out is different. Actual version dumped to {log_name}",
1058 def test_format_file_contents(self) -> None:
1060 mode = black.FileMode()
1061 with self.assertRaises(black.NothingChanged):
1062 black.format_file_contents(empty, mode=mode, fast=False)
1064 with self.assertRaises(black.NothingChanged):
1065 black.format_file_contents(just_nl, mode=mode, fast=False)
1066 same = "j = [1, 2, 3]\n"
1067 with self.assertRaises(black.NothingChanged):
1068 black.format_file_contents(same, mode=mode, fast=False)
1069 different = "j = [1,2,3]"
1071 actual = black.format_file_contents(different, mode=mode, fast=False)
1072 self.assertEqual(expected, actual)
1073 invalid = "return if you can"
1074 with self.assertRaises(black.InvalidInput) as e:
1075 black.format_file_contents(invalid, mode=mode, fast=False)
1076 self.assertEqual(str(e.exception), "Cannot parse: 1:7: return if you can")
1078 def test_endmarker(self) -> None:
1079 n = black.lib2to3_parse("\n")
1080 self.assertEqual(n.type, black.syms.file_input)
1081 self.assertEqual(len(n.children), 1)
1082 self.assertEqual(n.children[0].type, black.token.ENDMARKER)
1084 @unittest.skipIf(os.environ.get("SKIP_AST_PRINT"), "user set SKIP_AST_PRINT")
1085 def test_assertFormatEqual(self) -> None:
1089 def out(msg: str, **kwargs: Any) -> None:
1090 out_lines.append(msg)
1092 def err(msg: str, **kwargs: Any) -> None:
1093 err_lines.append(msg)
1095 with patch("black.out", out), patch("black.err", err):
1096 with self.assertRaises(AssertionError):
1097 self.assertFormatEqual("j = [1, 2, 3]", "j = [1, 2, 3,]")
1099 out_str = "".join(out_lines)
1100 self.assertTrue("Expected tree:" in out_str)
1101 self.assertTrue("Actual tree:" in out_str)
1102 self.assertEqual("".join(err_lines), "")
1104 def test_cache_broken_file(self) -> None:
1105 mode = black.FileMode()
1106 with cache_dir() as workspace:
1107 cache_file = black.get_cache_file(mode)
1108 with cache_file.open("w") as fobj:
1109 fobj.write("this is not a pickle")
1110 self.assertEqual(black.read_cache(mode), {})
1111 src = (workspace / "test.py").resolve()
1112 with src.open("w") as fobj:
1113 fobj.write("print('hello')")
1114 self.invokeBlack([str(src)])
1115 cache = black.read_cache(mode)
1116 self.assertIn(src, cache)
1118 def test_cache_single_file_already_cached(self) -> None:
1119 mode = black.FileMode()
1120 with cache_dir() as workspace:
1121 src = (workspace / "test.py").resolve()
1122 with src.open("w") as fobj:
1123 fobj.write("print('hello')")
1124 black.write_cache({}, [src], mode)
1125 self.invokeBlack([str(src)])
1126 with src.open("r") as fobj:
1127 self.assertEqual(fobj.read(), "print('hello')")
1129 @event_loop(close=False)
1130 def test_cache_multiple_files(self) -> None:
1131 mode = black.FileMode()
1132 with cache_dir() as workspace, patch(
1133 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1135 one = (workspace / "one.py").resolve()
1136 with one.open("w") as fobj:
1137 fobj.write("print('hello')")
1138 two = (workspace / "two.py").resolve()
1139 with two.open("w") as fobj:
1140 fobj.write("print('hello')")
1141 black.write_cache({}, [one], mode)
1142 self.invokeBlack([str(workspace)])
1143 with one.open("r") as fobj:
1144 self.assertEqual(fobj.read(), "print('hello')")
1145 with two.open("r") as fobj:
1146 self.assertEqual(fobj.read(), 'print("hello")\n')
1147 cache = black.read_cache(mode)
1148 self.assertIn(one, cache)
1149 self.assertIn(two, cache)
1151 def test_no_cache_when_writeback_diff(self) -> None:
1152 mode = black.FileMode()
1153 with cache_dir() as workspace:
1154 src = (workspace / "test.py").resolve()
1155 with src.open("w") as fobj:
1156 fobj.write("print('hello')")
1157 self.invokeBlack([str(src), "--diff"])
1158 cache_file = black.get_cache_file(mode)
1159 self.assertFalse(cache_file.exists())
1161 def test_no_cache_when_stdin(self) -> None:
1162 mode = black.FileMode()
1164 result = CliRunner().invoke(
1165 black.main, ["-"], input=BytesIO(b"print('hello')")
1167 self.assertEqual(result.exit_code, 0)
1168 cache_file = black.get_cache_file(mode)
1169 self.assertFalse(cache_file.exists())
1171 def test_read_cache_no_cachefile(self) -> None:
1172 mode = black.FileMode()
1174 self.assertEqual(black.read_cache(mode), {})
1176 def test_write_cache_read_cache(self) -> None:
1177 mode = black.FileMode()
1178 with cache_dir() as workspace:
1179 src = (workspace / "test.py").resolve()
1181 black.write_cache({}, [src], mode)
1182 cache = black.read_cache(mode)
1183 self.assertIn(src, cache)
1184 self.assertEqual(cache[src], black.get_cache_info(src))
1186 def test_filter_cached(self) -> None:
1187 with TemporaryDirectory() as workspace:
1188 path = Path(workspace)
1189 uncached = (path / "uncached").resolve()
1190 cached = (path / "cached").resolve()
1191 cached_but_changed = (path / "changed").resolve()
1194 cached_but_changed.touch()
1195 cache = {cached: black.get_cache_info(cached), cached_but_changed: (0.0, 0)}
1196 todo, done = black.filter_cached(
1197 cache, {uncached, cached, cached_but_changed}
1199 self.assertEqual(todo, {uncached, cached_but_changed})
1200 self.assertEqual(done, {cached})
1202 def test_write_cache_creates_directory_if_needed(self) -> None:
1203 mode = black.FileMode()
1204 with cache_dir(exists=False) as workspace:
1205 self.assertFalse(workspace.exists())
1206 black.write_cache({}, [], mode)
1207 self.assertTrue(workspace.exists())
1209 @event_loop(close=False)
1210 def test_failed_formatting_does_not_get_cached(self) -> None:
1211 mode = black.FileMode()
1212 with cache_dir() as workspace, patch(
1213 "black.ProcessPoolExecutor", new=ThreadPoolExecutor
1215 failing = (workspace / "failing.py").resolve()
1216 with failing.open("w") as fobj:
1217 fobj.write("not actually python")
1218 clean = (workspace / "clean.py").resolve()
1219 with clean.open("w") as fobj:
1220 fobj.write('print("hello")\n')
1221 self.invokeBlack([str(workspace)], exit_code=123)
1222 cache = black.read_cache(mode)
1223 self.assertNotIn(failing, cache)
1224 self.assertIn(clean, cache)
1226 def test_write_cache_write_fail(self) -> None:
1227 mode = black.FileMode()
1228 with cache_dir(), patch.object(Path, "open") as mock:
1229 mock.side_effect = OSError
1230 black.write_cache({}, [], mode)
1232 @event_loop(close=False)
1233 def test_check_diff_use_together(self) -> None:
1235 # Files which will be reformatted.
1236 src1 = (THIS_DIR / "data" / "string_quotes.py").resolve()
1237 self.invokeBlack([str(src1), "--diff", "--check"], exit_code=1)
1238 # Files which will not be reformatted.
1239 src2 = (THIS_DIR / "data" / "composition.py").resolve()
1240 self.invokeBlack([str(src2), "--diff", "--check"])
1241 # Multi file command.
1242 self.invokeBlack([str(src1), str(src2), "--diff", "--check"], exit_code=1)
1244 def test_no_files(self) -> None:
1246 # Without an argument, black exits with error code 0.
1247 self.invokeBlack([])
1249 def test_broken_symlink(self) -> None:
1250 with cache_dir() as workspace:
1251 symlink = workspace / "broken_link.py"
1253 symlink.symlink_to("nonexistent.py")
1254 except OSError as e:
1255 self.skipTest(f"Can't create symlinks: {e}")
1256 self.invokeBlack([str(workspace.resolve())])
1258 def test_read_cache_line_lengths(self) -> None:
1259 mode = black.FileMode()
1260 short_mode = black.FileMode(line_length=1)
1261 with cache_dir() as workspace:
1262 path = (workspace / "file.py").resolve()
1264 black.write_cache({}, [path], mode)
1265 one = black.read_cache(mode)
1266 self.assertIn(path, one)
1267 two = black.read_cache(short_mode)
1268 self.assertNotIn(path, two)
1270 def test_tricky_unicode_symbols(self) -> None:
1271 source, expected = read_data("tricky_unicode_symbols")
1273 self.assertFormatEqual(expected, actual)
1274 black.assert_equivalent(source, actual)
1275 black.assert_stable(source, actual, black.FileMode())
1277 def test_single_file_force_pyi(self) -> None:
1278 reg_mode = black.FileMode()
1279 pyi_mode = black.FileMode(is_pyi=True)
1280 contents, expected = read_data("force_pyi")
1281 with cache_dir() as workspace:
1282 path = (workspace / "file.py").resolve()
1283 with open(path, "w") as fh:
1285 self.invokeBlack([str(path), "--pyi"])
1286 with open(path, "r") as fh:
1288 # verify cache with --pyi is separate
1289 pyi_cache = black.read_cache(pyi_mode)
1290 self.assertIn(path, pyi_cache)
1291 normal_cache = black.read_cache(reg_mode)
1292 self.assertNotIn(path, normal_cache)
1293 self.assertEqual(actual, expected)
1295 @event_loop(close=False)
1296 def test_multi_file_force_pyi(self) -> None:
1297 reg_mode = black.FileMode()
1298 pyi_mode = black.FileMode(is_pyi=True)
1299 contents, expected = read_data("force_pyi")
1300 with cache_dir() as workspace:
1302 (workspace / "file1.py").resolve(),
1303 (workspace / "file2.py").resolve(),
1306 with open(path, "w") as fh:
1308 self.invokeBlack([str(p) for p in paths] + ["--pyi"])
1310 with open(path, "r") as fh:
1312 self.assertEqual(actual, expected)
1313 # verify cache with --pyi is separate
1314 pyi_cache = black.read_cache(pyi_mode)
1315 normal_cache = black.read_cache(reg_mode)
1317 self.assertIn(path, pyi_cache)
1318 self.assertNotIn(path, normal_cache)
1320 def test_pipe_force_pyi(self) -> None:
1321 source, expected = read_data("force_pyi")
1322 result = CliRunner().invoke(
1323 black.main, ["-", "-q", "--pyi"], input=BytesIO(source.encode("utf8"))
1325 self.assertEqual(result.exit_code, 0)
1326 actual = result.output
1327 self.assertFormatEqual(actual, expected)
1329 def test_single_file_force_py36(self) -> None:
1330 reg_mode = black.FileMode()
1331 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1332 source, expected = read_data("force_py36")
1333 with cache_dir() as workspace:
1334 path = (workspace / "file.py").resolve()
1335 with open(path, "w") as fh:
1337 self.invokeBlack([str(path), *PY36_ARGS])
1338 with open(path, "r") as fh:
1340 # verify cache with --target-version is separate
1341 py36_cache = black.read_cache(py36_mode)
1342 self.assertIn(path, py36_cache)
1343 normal_cache = black.read_cache(reg_mode)
1344 self.assertNotIn(path, normal_cache)
1345 self.assertEqual(actual, expected)
1347 @event_loop(close=False)
1348 def test_multi_file_force_py36(self) -> None:
1349 reg_mode = black.FileMode()
1350 py36_mode = black.FileMode(target_versions=black.PY36_VERSIONS)
1351 source, expected = read_data("force_py36")
1352 with cache_dir() as workspace:
1354 (workspace / "file1.py").resolve(),
1355 (workspace / "file2.py").resolve(),
1358 with open(path, "w") as fh:
1360 self.invokeBlack([str(p) for p in paths] + PY36_ARGS)
1362 with open(path, "r") as fh:
1364 self.assertEqual(actual, expected)
1365 # verify cache with --target-version is separate
1366 pyi_cache = black.read_cache(py36_mode)
1367 normal_cache = black.read_cache(reg_mode)
1369 self.assertIn(path, pyi_cache)
1370 self.assertNotIn(path, normal_cache)
1372 def test_pipe_force_py36(self) -> None:
1373 source, expected = read_data("force_py36")
1374 result = CliRunner().invoke(
1376 ["-", "-q", "--target-version=py36"],
1377 input=BytesIO(source.encode("utf8")),
1379 self.assertEqual(result.exit_code, 0)
1380 actual = result.output
1381 self.assertFormatEqual(actual, expected)
1383 def test_include_exclude(self) -> None:
1384 path = THIS_DIR / "data" / "include_exclude_tests"
1385 include = re.compile(r"\.pyi?$")
1386 exclude = re.compile(r"/exclude/|/\.definitely_exclude/")
1387 report = black.Report()
1388 sources: List[Path] = []
1390 Path(path / "b/dont_exclude/a.py"),
1391 Path(path / "b/dont_exclude/a.pyi"),
1393 this_abs = THIS_DIR.resolve()
1395 black.gen_python_files_in_dir(path, this_abs, include, exclude, report)
1397 self.assertEqual(sorted(expected), sorted(sources))
1399 def test_empty_include(self) -> None:
1400 path = THIS_DIR / "data" / "include_exclude_tests"
1401 report = black.Report()
1402 empty = re.compile(r"")
1403 sources: List[Path] = []
1405 Path(path / "b/exclude/a.pie"),
1406 Path(path / "b/exclude/a.py"),
1407 Path(path / "b/exclude/a.pyi"),
1408 Path(path / "b/dont_exclude/a.pie"),
1409 Path(path / "b/dont_exclude/a.py"),
1410 Path(path / "b/dont_exclude/a.pyi"),
1411 Path(path / "b/.definitely_exclude/a.pie"),
1412 Path(path / "b/.definitely_exclude/a.py"),
1413 Path(path / "b/.definitely_exclude/a.pyi"),
1415 this_abs = THIS_DIR.resolve()
1417 black.gen_python_files_in_dir(
1418 path, this_abs, empty, re.compile(black.DEFAULT_EXCLUDES), report
1421 self.assertEqual(sorted(expected), sorted(sources))
1423 def test_empty_exclude(self) -> None:
1424 path = THIS_DIR / "data" / "include_exclude_tests"
1425 report = black.Report()
1426 empty = re.compile(r"")
1427 sources: List[Path] = []
1429 Path(path / "b/dont_exclude/a.py"),
1430 Path(path / "b/dont_exclude/a.pyi"),
1431 Path(path / "b/exclude/a.py"),
1432 Path(path / "b/exclude/a.pyi"),
1433 Path(path / "b/.definitely_exclude/a.py"),
1434 Path(path / "b/.definitely_exclude/a.pyi"),
1436 this_abs = THIS_DIR.resolve()
1438 black.gen_python_files_in_dir(
1439 path, this_abs, re.compile(black.DEFAULT_INCLUDES), empty, report
1442 self.assertEqual(sorted(expected), sorted(sources))
1444 def test_invalid_include_exclude(self) -> None:
1445 for option in ["--include", "--exclude"]:
1446 self.invokeBlack(["-", option, "**()(!!*)"], exit_code=2)
1448 def test_preserves_line_endings(self) -> None:
1449 with TemporaryDirectory() as workspace:
1450 test_file = Path(workspace) / "test.py"
1451 for nl in ["\n", "\r\n"]:
1452 contents = nl.join(["def f( ):", " pass"])
1453 test_file.write_bytes(contents.encode())
1454 ff(test_file, write_back=black.WriteBack.YES)
1455 updated_contents: bytes = test_file.read_bytes()
1456 self.assertIn(nl.encode(), updated_contents)
1458 self.assertNotIn(b"\r\n", updated_contents)
1460 def test_preserves_line_endings_via_stdin(self) -> None:
1461 for nl in ["\n", "\r\n"]:
1462 contents = nl.join(["def f( ):", " pass"])
1463 runner = BlackRunner()
1464 result = runner.invoke(
1465 black.main, ["-", "--fast"], input=BytesIO(contents.encode("utf8"))
1467 self.assertEqual(result.exit_code, 0)
1468 output = runner.stdout_bytes
1469 self.assertIn(nl.encode("utf8"), output)
1471 self.assertNotIn(b"\r\n", output)
1473 def test_assert_equivalent_different_asts(self) -> None:
1474 with self.assertRaises(AssertionError):
1475 black.assert_equivalent("{}", "None")
1477 def test_symlink_out_of_root_directory(self) -> None:
1481 include = re.compile(black.DEFAULT_INCLUDES)
1482 exclude = re.compile(black.DEFAULT_EXCLUDES)
1483 report = black.Report()
1484 # `child` should behave like a symlink which resolved path is clearly
1485 # outside of the `root` directory.
1486 path.iterdir.return_value = [child]
1487 child.resolve.return_value = Path("/a/b/c")
1488 child.is_symlink.return_value = True
1490 list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1491 except ValueError as ve:
1492 self.fail(f"`get_python_files_in_dir()` failed: {ve}")
1493 path.iterdir.assert_called_once()
1494 child.resolve.assert_called_once()
1495 child.is_symlink.assert_called_once()
1496 # `child` should behave like a strange file which resolved path is clearly
1497 # outside of the `root` directory.
1498 child.is_symlink.return_value = False
1499 with self.assertRaises(ValueError):
1500 list(black.gen_python_files_in_dir(path, root, include, exclude, report))
1501 path.iterdir.assert_called()
1502 self.assertEqual(path.iterdir.call_count, 2)
1503 child.resolve.assert_called()
1504 self.assertEqual(child.resolve.call_count, 2)
1505 child.is_symlink.assert_called()
1506 self.assertEqual(child.is_symlink.call_count, 2)
1508 def test_shhh_click(self) -> None:
1510 from click import _unicodefun # type: ignore
1511 except ModuleNotFoundError:
1512 self.skipTest("Incompatible Click version")
1513 if not hasattr(_unicodefun, "_verify_python3_env"):
1514 self.skipTest("Incompatible Click version")
1515 # First, let's see if Click is crashing with a preferred ASCII charset.
1516 with patch("locale.getpreferredencoding") as gpe:
1517 gpe.return_value = "ASCII"
1518 with self.assertRaises(RuntimeError):
1519 _unicodefun._verify_python3_env()
1520 # Now, let's silence Click...
1522 # ...and confirm it's silent.
1523 with patch("locale.getpreferredencoding") as gpe:
1524 gpe.return_value = "ASCII"
1526 _unicodefun._verify_python3_env()
1527 except RuntimeError as re:
1528 self.fail(f"`patch_click()` failed, exception still raised: {re}")
1530 def test_root_logger_not_used_directly(self) -> None:
1531 def fail(*args: Any, **kwargs: Any) -> None:
1532 self.fail("Record created with root logger")
1534 with patch.multiple(
1545 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1546 def test_blackd_main(self) -> None:
1547 with patch("blackd.web.run_app"):
1548 result = CliRunner().invoke(blackd.main, [])
1549 if result.exception is not None:
1550 raise result.exception
1551 self.assertEqual(result.exit_code, 0)
1554 class BlackDTestCase(AioHTTPTestCase):
1555 async def get_application(self) -> web.Application:
1556 return blackd.make_app()
1558 # TODO: remove these decorators once the below is released
1559 # https://github.com/aio-libs/aiohttp/pull/3727
1560 @skip_if_exception("ClientOSError")
1561 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1563 async def test_blackd_request_needs_formatting(self) -> None:
1564 response = await self.client.post("/", data=b"print('hello world')")
1565 self.assertEqual(response.status, 200)
1566 self.assertEqual(response.charset, "utf8")
1567 self.assertEqual(await response.read(), b'print("hello world")\n')
1569 @skip_if_exception("ClientOSError")
1570 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1572 async def test_blackd_request_no_change(self) -> None:
1573 response = await self.client.post("/", data=b'print("hello world")\n')
1574 self.assertEqual(response.status, 204)
1575 self.assertEqual(await response.read(), b"")
1577 @skip_if_exception("ClientOSError")
1578 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1580 async def test_blackd_request_syntax_error(self) -> None:
1581 response = await self.client.post("/", data=b"what even ( is")
1582 self.assertEqual(response.status, 400)
1583 content = await response.text()
1585 content.startswith("Cannot parse"),
1586 msg=f"Expected error to start with 'Cannot parse', got {repr(content)}",
1589 @skip_if_exception("ClientOSError")
1590 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1592 async def test_blackd_unsupported_version(self) -> None:
1593 response = await self.client.post(
1594 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "2"}
1596 self.assertEqual(response.status, 501)
1598 @skip_if_exception("ClientOSError")
1599 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1601 async def test_blackd_supported_version(self) -> None:
1602 response = await self.client.post(
1603 "/", data=b"what", headers={blackd.PROTOCOL_VERSION_HEADER: "1"}
1605 self.assertEqual(response.status, 200)
1607 @skip_if_exception("ClientOSError")
1608 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1610 async def test_blackd_invalid_python_variant(self) -> None:
1611 async def check(header_value: str, expected_status: int = 400) -> None:
1612 response = await self.client.post(
1613 "/", data=b"what", headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1615 self.assertEqual(response.status, expected_status)
1618 await check("ruby3.5")
1619 await check("pyi3.6")
1620 await check("py1.5")
1622 await check("py2.8")
1624 await check("pypy3.0")
1625 await check("jython3.4")
1627 @skip_if_exception("ClientOSError")
1628 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1630 async def test_blackd_pyi(self) -> None:
1631 source, expected = read_data("stub.pyi")
1632 response = await self.client.post(
1633 "/", data=source, headers={blackd.PYTHON_VARIANT_HEADER: "pyi"}
1635 self.assertEqual(response.status, 200)
1636 self.assertEqual(await response.text(), expected)
1638 @skip_if_exception("ClientOSError")
1639 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1641 async def test_blackd_python_variant(self) -> None:
1644 " and_has_a_bunch_of,\n"
1645 " very_long_arguments_too,\n"
1646 " and_lots_of_them_as_well_lol,\n"
1647 " **and_very_long_keyword_arguments\n"
1652 async def check(header_value: str, expected_status: int) -> None:
1653 response = await self.client.post(
1654 "/", data=code, headers={blackd.PYTHON_VARIANT_HEADER: header_value}
1657 response.status, expected_status, msg=await response.text()
1660 await check("3.6", 200)
1661 await check("py3.6", 200)
1662 await check("3.6,3.7", 200)
1663 await check("3.6,py3.7", 200)
1664 await check("py36,py37", 200)
1665 await check("36", 200)
1666 await check("3.6.4", 200)
1668 await check("2", 204)
1669 await check("2.7", 204)
1670 await check("py2.7", 204)
1671 await check("3.4", 204)
1672 await check("py3.4", 204)
1673 await check("py34,py36", 204)
1674 await check("34", 204)
1676 @skip_if_exception("ClientOSError")
1677 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1679 async def test_blackd_line_length(self) -> None:
1680 response = await self.client.post(
1681 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "7"}
1683 self.assertEqual(response.status, 200)
1685 @skip_if_exception("ClientOSError")
1686 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1688 async def test_blackd_invalid_line_length(self) -> None:
1689 response = await self.client.post(
1690 "/", data=b'print("hello")\n', headers={blackd.LINE_LENGTH_HEADER: "NaN"}
1692 self.assertEqual(response.status, 400)
1694 @skip_if_exception("ClientOSError")
1695 @unittest.skipUnless(has_blackd_deps, "blackd's dependencies are not installed")
1697 async def test_blackd_response_black_version_header(self) -> None:
1698 response = await self.client.post("/")
1699 self.assertIsNotNone(response.headers.get(blackd.BLACK_VERSION_HEADER))
1702 if __name__ == "__main__":
1703 unittest.main(module="test_black")