|
46 | 46 | from black.debug import DebugVisitor |
47 | 47 | from black.mode import Mode, Preview |
48 | 48 | from black.output import color_diff, diff |
| 49 | +from black.parsing import ASTSafetyError |
49 | 50 | from black.report import Report |
50 | 51 |
|
51 | 52 | # Import other test classes |
@@ -1473,10 +1474,6 @@ def test_normalize_line_endings(self) -> None: |
1473 | 1474 | ff(test_file, write_back=black.WriteBack.YES) |
1474 | 1475 | self.assertEqual(test_file.read_bytes(), expected) |
1475 | 1476 |
|
1476 | | - def test_assert_equivalent_different_asts(self) -> None: |
1477 | | - with self.assertRaises(AssertionError): |
1478 | | - black.assert_equivalent("{}", "None") |
1479 | | - |
1480 | 1477 | def test_root_logger_not_used_directly(self) -> None: |
1481 | 1478 | def fail(*args: Any, **kwargs: Any) -> None: |
1482 | 1479 | self.fail("Record created with root logger") |
@@ -1962,16 +1959,6 @@ def test_for_handled_unexpected_eof_error(self) -> None: |
1962 | 1959 |
|
1963 | 1960 | exc_info.match("Cannot parse: 2:0: EOF in multi-line statement") |
1964 | 1961 |
|
1965 | | - def test_equivalency_ast_parse_failure_includes_error(self) -> None: |
1966 | | - with pytest.raises(AssertionError) as err: |
1967 | | - black.assert_equivalent("a«»a = 1", "a«»a = 1") |
1968 | | - |
1969 | | - err.match("--safe") |
1970 | | - # Unfortunately the SyntaxError message has changed in newer versions so we |
1971 | | - # can't match it directly. |
1972 | | - err.match("invalid character") |
1973 | | - err.match(r"\(<unknown>, line 1\)") |
1974 | | - |
1975 | 1962 | def test_line_ranges_with_code_option(self) -> None: |
1976 | 1963 | code = textwrap.dedent("""\ |
1977 | 1964 | if a == b: |
@@ -2822,6 +2809,113 @@ def test_format_file_contents(self) -> None: |
2822 | 2809 | black.format_file_contents("x = 1\n", fast=True, mode=black.Mode()) |
2823 | 2810 |
|
2824 | 2811 |
|
| 2812 | +class TestASTSafety(BlackBaseTestCase): |
| 2813 | + def check_ast_equivalence( |
| 2814 | + self, source: str, dest: str, *, should_fail: bool = False |
| 2815 | + ) -> None: |
| 2816 | + # If we get a failure, make sure it's not because the code itself |
| 2817 | + # is invalid, since that will also cause assert_equivalent() to throw |
| 2818 | + # ASTSafetyError. |
| 2819 | + source = textwrap.dedent(source) |
| 2820 | + dest = textwrap.dedent(dest) |
| 2821 | + black.parse_ast(source) |
| 2822 | + black.parse_ast(dest) |
| 2823 | + if should_fail: |
| 2824 | + with self.assertRaises(ASTSafetyError): |
| 2825 | + black.assert_equivalent(source, dest) |
| 2826 | + else: |
| 2827 | + black.assert_equivalent(source, dest) |
| 2828 | + |
| 2829 | + def test_assert_equivalent_basic(self) -> None: |
| 2830 | + self.check_ast_equivalence("{}", "None", should_fail=True) |
| 2831 | + self.check_ast_equivalence("1+2", "1 + 2") |
| 2832 | + self.check_ast_equivalence("hi # comment", "hi") |
| 2833 | + |
| 2834 | + def test_assert_equivalent_del(self) -> None: |
| 2835 | + self.check_ast_equivalence("del (a, b)", "del a, b") |
| 2836 | + |
| 2837 | + def test_assert_equivalent_strings(self) -> None: |
| 2838 | + self.check_ast_equivalence('x = "x"', 'x = " x "', should_fail=True) |
| 2839 | + self.check_ast_equivalence( |
| 2840 | + ''' |
| 2841 | + """docstring """ |
| 2842 | + ''', |
| 2843 | + ''' |
| 2844 | + """docstring""" |
| 2845 | + ''', |
| 2846 | + ) |
| 2847 | + self.check_ast_equivalence( |
| 2848 | + ''' |
| 2849 | + """docstring """ |
| 2850 | + ''', |
| 2851 | + ''' |
| 2852 | + """ddocstring""" |
| 2853 | + ''', |
| 2854 | + should_fail=True, |
| 2855 | + ) |
| 2856 | + self.check_ast_equivalence( |
| 2857 | + ''' |
| 2858 | + class A: |
| 2859 | + """ |
| 2860 | +
|
| 2861 | + docstring |
| 2862 | +
|
| 2863 | +
|
| 2864 | + """ |
| 2865 | + ''', |
| 2866 | + ''' |
| 2867 | + class A: |
| 2868 | + """docstring""" |
| 2869 | + ''', |
| 2870 | + ) |
| 2871 | + self.check_ast_equivalence( |
| 2872 | + """ |
| 2873 | + def f(): |
| 2874 | + " docstring " |
| 2875 | + """, |
| 2876 | + ''' |
| 2877 | + def f(): |
| 2878 | + """docstring""" |
| 2879 | + ''', |
| 2880 | + ) |
| 2881 | + self.check_ast_equivalence( |
| 2882 | + """ |
| 2883 | + async def f(): |
| 2884 | + " docstring " |
| 2885 | + """, |
| 2886 | + ''' |
| 2887 | + async def f(): |
| 2888 | + """docstring""" |
| 2889 | + ''', |
| 2890 | + ) |
| 2891 | + |
| 2892 | + def test_assert_equivalent_fstring(self) -> None: |
| 2893 | + major, minor = sys.version_info[:2] |
| 2894 | + if major < 3 or (major == 3 and minor < 12): |
| 2895 | + pytest.skip("relies on 3.12+ syntax") |
| 2896 | + # https://github.com/psf/black/issues/4268 |
| 2897 | + self.check_ast_equivalence( |
| 2898 | + """print(f"{"|".join([a,b,c])}")""", |
| 2899 | + """print(f"{" | ".join([a,b,c])}")""", |
| 2900 | + should_fail=True, |
| 2901 | + ) |
| 2902 | + self.check_ast_equivalence( |
| 2903 | + """print(f"{"|".join(['a','b','c'])}")""", |
| 2904 | + """print(f"{" | ".join(['a','b','c'])}")""", |
| 2905 | + should_fail=True, |
| 2906 | + ) |
| 2907 | + |
| 2908 | + def test_equivalency_ast_parse_failure_includes_error(self) -> None: |
| 2909 | + with pytest.raises(ASTSafetyError) as err: |
| 2910 | + black.assert_equivalent("a«»a = 1", "a«»a = 1") |
| 2911 | + |
| 2912 | + err.match("--safe") |
| 2913 | + # Unfortunately the SyntaxError message has changed in newer versions so we |
| 2914 | + # can't match it directly. |
| 2915 | + err.match("invalid character") |
| 2916 | + err.match(r"\(<unknown>, line 1\)") |
| 2917 | + |
| 2918 | + |
2825 | 2919 | try: |
2826 | 2920 | with open(black.__file__, "r", encoding="utf-8") as _bf: |
2827 | 2921 | black_source_lines = _bf.readlines() |
|
0 commit comments