+ with patch("black.out", out), patch("black.err", err):
+ report.done(Path("f1"), black.Changed.NO)
+ self.assertEqual(len(out_lines), 0)
+ self.assertEqual(len(err_lines), 0)
+ self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
+ self.assertEqual(report.return_code, 0)
+ report.done(Path("f2"), black.Changed.YES)
+ self.assertEqual(len(out_lines), 0)
+ self.assertEqual(len(err_lines), 0)
+ self.assertEqual(
+ unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
+ )
+ report.done(Path("f3"), black.Changed.CACHED)
+ self.assertEqual(len(out_lines), 0)
+ self.assertEqual(len(err_lines), 0)
+ self.assertEqual(
+ unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
+ )
+ self.assertEqual(report.return_code, 0)
+ report.check = True
+ self.assertEqual(report.return_code, 1)
+ report.check = False
+ report.failed(Path("e1"), "boom")
+ self.assertEqual(len(out_lines), 0)
+ self.assertEqual(len(err_lines), 1)
+ self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
+ self.assertEqual(
+ unstyle(str(report)),
+ "1 file reformatted, 2 files left unchanged, 1 file failed to"
+ " reformat.",
+ )
+ self.assertEqual(report.return_code, 123)
+ report.done(Path("f3"), black.Changed.YES)
+ self.assertEqual(len(out_lines), 0)
+ self.assertEqual(len(err_lines), 1)
+ self.assertEqual(
+ unstyle(str(report)),
+ "2 files reformatted, 2 files left unchanged, 1 file failed to"
+ " reformat.",
+ )
+ self.assertEqual(report.return_code, 123)
+ report.failed(Path("e2"), "boom")
+ self.assertEqual(len(out_lines), 0)
+ self.assertEqual(len(err_lines), 2)
+ self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
+ self.assertEqual(
+ unstyle(str(report)),
+ "2 files reformatted, 2 files left unchanged, 2 files failed to"
+ " reformat.",
+ )
+ self.assertEqual(report.return_code, 123)
+ report.path_ignored(Path("wat"), "no match")
+ self.assertEqual(len(out_lines), 0)
+ self.assertEqual(len(err_lines), 2)
+ self.assertEqual(
+ unstyle(str(report)),
+ "2 files reformatted, 2 files left unchanged, 2 files failed to"
+ " reformat.",
+ )
+ self.assertEqual(report.return_code, 123)
+ report.done(Path("f4"), black.Changed.NO)
+ self.assertEqual(len(out_lines), 0)
+ self.assertEqual(len(err_lines), 2)
+ self.assertEqual(
+ unstyle(str(report)),
+ "2 files reformatted, 3 files left unchanged, 2 files failed to"
+ " reformat.",
+ )
+ self.assertEqual(report.return_code, 123)
+ report.check = True
+ self.assertEqual(
+ unstyle(str(report)),
+ "2 files would be reformatted, 3 files would be left unchanged, 2 files"
+ " would fail to reformat.",
+ )
+ report.check = False
+ report.diff = True
+ self.assertEqual(
+ unstyle(str(report)),
+ "2 files would be reformatted, 3 files would be left unchanged, 2 files"
+ " would fail to reformat.",
+ )
+
+ def test_report_normal(self) -> None:
+ report = black.Report()
+ out_lines = []
+ err_lines = []
+
+ def out(msg: str, **kwargs: Any) -> None:
+ out_lines.append(msg)
+
+ def err(msg: str, **kwargs: Any) -> None:
+ err_lines.append(msg)
+
+ with patch("black.out", out), patch("black.err", err):
+ report.done(Path("f1"), black.Changed.NO)
+ self.assertEqual(len(out_lines), 0)
+ self.assertEqual(len(err_lines), 0)
+ self.assertEqual(unstyle(str(report)), "1 file left unchanged.")
+ self.assertEqual(report.return_code, 0)
+ report.done(Path("f2"), black.Changed.YES)
+ self.assertEqual(len(out_lines), 1)
+ self.assertEqual(len(err_lines), 0)
+ self.assertEqual(out_lines[-1], "reformatted f2")
+ self.assertEqual(
+ unstyle(str(report)), "1 file reformatted, 1 file left unchanged."
+ )
+ report.done(Path("f3"), black.Changed.CACHED)
+ self.assertEqual(len(out_lines), 1)
+ self.assertEqual(len(err_lines), 0)
+ self.assertEqual(out_lines[-1], "reformatted f2")
+ self.assertEqual(
+ unstyle(str(report)), "1 file reformatted, 2 files left unchanged."
+ )
+ self.assertEqual(report.return_code, 0)
+ report.check = True
+ self.assertEqual(report.return_code, 1)
+ report.check = False
+ report.failed(Path("e1"), "boom")
+ self.assertEqual(len(out_lines), 1)
+ self.assertEqual(len(err_lines), 1)
+ self.assertEqual(err_lines[-1], "error: cannot format e1: boom")
+ self.assertEqual(
+ unstyle(str(report)),
+ "1 file reformatted, 2 files left unchanged, 1 file failed to"
+ " reformat.",
+ )
+ self.assertEqual(report.return_code, 123)
+ report.done(Path("f3"), black.Changed.YES)
+ self.assertEqual(len(out_lines), 2)
+ self.assertEqual(len(err_lines), 1)
+ self.assertEqual(out_lines[-1], "reformatted f3")
+ self.assertEqual(
+ unstyle(str(report)),
+ "2 files reformatted, 2 files left unchanged, 1 file failed to"
+ " reformat.",
+ )
+ self.assertEqual(report.return_code, 123)
+ report.failed(Path("e2"), "boom")
+ self.assertEqual(len(out_lines), 2)
+ self.assertEqual(len(err_lines), 2)
+ self.assertEqual(err_lines[-1], "error: cannot format e2: boom")
+ self.assertEqual(
+ unstyle(str(report)),
+ "2 files reformatted, 2 files left unchanged, 2 files failed to"
+ " reformat.",
+ )
+ self.assertEqual(report.return_code, 123)
+ report.path_ignored(Path("wat"), "no match")
+ self.assertEqual(len(out_lines), 2)
+ self.assertEqual(len(err_lines), 2)
+ self.assertEqual(
+ unstyle(str(report)),
+ "2 files reformatted, 2 files left unchanged, 2 files failed to"
+ " reformat.",
+ )
+ self.assertEqual(report.return_code, 123)
+ report.done(Path("f4"), black.Changed.NO)
+ self.assertEqual(len(out_lines), 2)
+ self.assertEqual(len(err_lines), 2)
+ self.assertEqual(
+ unstyle(str(report)),
+ "2 files reformatted, 3 files left unchanged, 2 files failed to"
+ " reformat.",
+ )
+ self.assertEqual(report.return_code, 123)
+ report.check = True
+ self.assertEqual(
+ unstyle(str(report)),
+ "2 files would be reformatted, 3 files would be left unchanged, 2 files"
+ " would fail to reformat.",
+ )
+ report.check = False
+ report.diff = True
+ self.assertEqual(
+ unstyle(str(report)),
+ "2 files would be reformatted, 3 files would be left unchanged, 2 files"
+ " would fail to reformat.",
+ )
+
+ def test_lib2to3_parse(self) -> None:
+ with self.assertRaises(black.InvalidInput):
+ black.lib2to3_parse("invalid syntax")
+
+ straddling = "x + y"
+ black.lib2to3_parse(straddling)
+ black.lib2to3_parse(straddling, {TargetVersion.PY27})
+ black.lib2to3_parse(straddling, {TargetVersion.PY36})
+ black.lib2to3_parse(straddling, {TargetVersion.PY27, TargetVersion.PY36})
+
+ py2_only = "print x"
+ black.lib2to3_parse(py2_only)
+ black.lib2to3_parse(py2_only, {TargetVersion.PY27})
+ with self.assertRaises(black.InvalidInput):
+ black.lib2to3_parse(py2_only, {TargetVersion.PY36})
+ with self.assertRaises(black.InvalidInput):
+ black.lib2to3_parse(py2_only, {TargetVersion.PY27, TargetVersion.PY36})
+
+ py3_only = "exec(x, end=y)"
+ black.lib2to3_parse(py3_only)
+ with self.assertRaises(black.InvalidInput):
+ black.lib2to3_parse(py3_only, {TargetVersion.PY27})
+ black.lib2to3_parse(py3_only, {TargetVersion.PY36})
+ black.lib2to3_parse(py3_only, {TargetVersion.PY27, TargetVersion.PY36})
+
+ def test_get_features_used_decorator(self) -> None:
+ # Test the feature detection of new decorator syntax
+ # since this makes some test cases of test_get_features_used()
+ # fails if it fails, this is tested first so that a useful case
+ # is identified
+ simples, relaxed = read_data("decorators")
+ # skip explanation comments at the top of the file
+ for simple_test in simples.split("##")[1:]:
+ node = black.lib2to3_parse(simple_test)
+ decorator = str(node.children[0].children[0]).strip()
+ self.assertNotIn(
+ Feature.RELAXED_DECORATORS,
+ black.get_features_used(node),
+ msg=(
+ f"decorator '{decorator}' follows python<=3.8 syntax"
+ "but is detected as 3.9+"
+ # f"The full node is\n{node!r}"
+ ),
+ )
+ # skip the '# output' comment at the top of the output part
+ for relaxed_test in relaxed.split("##")[1:]:
+ node = black.lib2to3_parse(relaxed_test)
+ decorator = str(node.children[0].children[0]).strip()
+ self.assertIn(
+ Feature.RELAXED_DECORATORS,
+ black.get_features_used(node),
+ msg=(
+ f"decorator '{decorator}' uses python3.9+ syntax"
+ "but is detected as python<=3.8"
+ # f"The full node is\n{node!r}"
+ ),
+ )
+
+ def test_get_features_used(self) -> None: