|
8 | 8 | from test.support.os_helper import TESTFN, unlink, rmtree |
9 | 9 | from textwrap import dedent |
10 | 10 | from unittest import TestCase |
| 11 | +import difflib |
11 | 12 | import inspect |
12 | 13 | import os.path |
13 | 14 | import re |
@@ -2893,6 +2894,148 @@ def test_cli_force(self): |
2893 | 2894 | generated = f.read() |
2894 | 2895 | self.assertEndsWith(generated, checksum) |
2895 | 2896 |
|
| 2897 | + DRY_RUN_CODE = dedent(""" |
| 2898 | + /*[clinic input] |
| 2899 | + func |
| 2900 | + a: int |
| 2901 | + / |
| 2902 | +
|
| 2903 | + Docstring. |
| 2904 | + [clinic start generated code]*/ |
| 2905 | + """) |
| 2906 | + |
| 2907 | + def make_dry_run_file(self, tmp_dir): |
| 2908 | + fn = os.path.join(tmp_dir, "test.c") |
| 2909 | + with open(fn, "w", encoding="utf-8") as f: |
| 2910 | + f.write(self.DRY_RUN_CODE) |
| 2911 | + return fn |
| 2912 | + |
| 2913 | + @staticmethod |
| 2914 | + def dest_file(fn): |
| 2915 | + # The default destination for the generated code. Its path is |
| 2916 | + # built from the "{dirname}/clinic/{basename}.h" template, so it |
| 2917 | + # always uses forward slashes, even on Windows. |
| 2918 | + dirname, basename = os.path.split(fn) |
| 2919 | + return f"{dirname}/clinic/{basename}.h" |
| 2920 | + |
| 2921 | + def check_unchanged(self, tmp_dir, fn, pre_mtime): |
| 2922 | + # Neither the source file nor the destination file |
| 2923 | + # nor its directory is created or modified. |
| 2924 | + with open(fn, encoding="utf-8") as f: |
| 2925 | + self.assertEqual(f.read(), self.DRY_RUN_CODE) |
| 2926 | + self.assertEqual(os.stat(fn).st_mtime_ns, pre_mtime) |
| 2927 | + self.assertEqual(os.listdir(tmp_dir), ["test.c"]) |
| 2928 | + |
| 2929 | + def test_cli_dry_run(self): |
| 2930 | + with os_helper.temp_dir() as tmp_dir: |
| 2931 | + fn = self.make_dry_run_file(tmp_dir) |
| 2932 | + pre_mtime = os.stat(fn).st_mtime_ns |
| 2933 | + out = self.expect_success("--dry-run", fn) |
| 2934 | + self.assertEqual(out.splitlines(), [ |
| 2935 | + f"would create {self.dest_file(fn)}", |
| 2936 | + f"would update {fn}", |
| 2937 | + ]) |
| 2938 | + self.check_unchanged(tmp_dir, fn, pre_mtime) |
| 2939 | + |
| 2940 | + def test_cli_dry_run_no_change(self): |
| 2941 | + with os_helper.temp_dir() as tmp_dir: |
| 2942 | + fn = self.make_dry_run_file(tmp_dir) |
| 2943 | + self.expect_success(fn) |
| 2944 | + self.assertEqual(self.expect_success("--dry-run", fn), "") |
| 2945 | + self.assertEqual(self.expect_success("--diff", fn), "") |
| 2946 | + |
| 2947 | + def test_cli_dry_run_no_clinic_block(self): |
| 2948 | + with os_helper.temp_dir() as tmp_dir: |
| 2949 | + fn = os.path.join(tmp_dir, "test.c") |
| 2950 | + with open(fn, "w", encoding="utf-8") as f: |
| 2951 | + f.write("int x;\n") |
| 2952 | + self.assertEqual(self.expect_success("--dry-run", fn), "") |
| 2953 | + |
| 2954 | + def test_cli_dry_run_output(self): |
| 2955 | + with os_helper.temp_dir() as tmp_dir: |
| 2956 | + fn = self.make_dry_run_file(tmp_dir) |
| 2957 | + out_fn = os.path.join(tmp_dir, "output.c") |
| 2958 | + out = self.expect_success("--dry-run", "-o", out_fn, fn) |
| 2959 | + self.assertIn(f"would create {out_fn}", out) |
| 2960 | + self.assertNotIn(f"would update {fn}", out) |
| 2961 | + self.assertFalse(os.path.exists(out_fn)) |
| 2962 | + |
| 2963 | + def test_cli_dry_run_make(self): |
| 2964 | + with os_helper.temp_dir() as tmp_dir: |
| 2965 | + fn = self.make_dry_run_file(tmp_dir) |
| 2966 | + pre_mtime = os.stat(fn).st_mtime_ns |
| 2967 | + out = self.expect_success("--dry-run", "--make", "--srcdir", tmp_dir) |
| 2968 | + self.assertIn(f"would update {fn}", out) |
| 2969 | + self.check_unchanged(tmp_dir, fn, pre_mtime) |
| 2970 | + |
| 2971 | + def test_cli_dry_run_verbose(self): |
| 2972 | + with os_helper.temp_dir() as tmp_dir: |
| 2973 | + fn = self.make_dry_run_file(tmp_dir) |
| 2974 | + out, err, code = self.run_clinic("-v", "--dry-run", fn) |
| 2975 | + self.assertEqual(code, 0) |
| 2976 | + # The progress goes to stderr, so that the standard output |
| 2977 | + # contains only the report. |
| 2978 | + self.assertEqual(err.splitlines(), [fn]) |
| 2979 | + self.assertEqual(out.splitlines(), [ |
| 2980 | + f"would create {self.dest_file(fn)}", |
| 2981 | + f"would update {fn}", |
| 2982 | + ]) |
| 2983 | + |
| 2984 | + def test_cli_dry_run_checksum_mismatch(self): |
| 2985 | + invalid_input = dedent(""" |
| 2986 | + /*[clinic input] |
| 2987 | + output preset block |
| 2988 | + module test |
| 2989 | + test.fn |
| 2990 | + a: int |
| 2991 | + [clinic start generated code]*/ |
| 2992 | + /*[clinic end generated code: output=bogus input=bogus]*/ |
| 2993 | + """) |
| 2994 | + with os_helper.temp_dir() as tmp_dir: |
| 2995 | + fn = os.path.join(tmp_dir, "test.c") |
| 2996 | + with open(fn, "w", encoding="utf-8") as f: |
| 2997 | + f.write(invalid_input) |
| 2998 | + pre_mtime = os.stat(fn).st_mtime_ns |
| 2999 | + # The dry run does not disable the checksum verification. |
| 3000 | + _, err = self.expect_failure("--dry-run", fn) |
| 3001 | + self.assertIn("Checksum mismatch!", err) |
| 3002 | + # With -f the change is reported, but still not written. |
| 3003 | + out = self.expect_success("--dry-run", "-f", fn) |
| 3004 | + self.assertIn(f"would update {fn}", out) |
| 3005 | + with open(fn, encoding="utf-8") as f: |
| 3006 | + self.assertEqual(f.read(), invalid_input) |
| 3007 | + self.assertEqual(os.stat(fn).st_mtime_ns, pre_mtime) |
| 3008 | + |
| 3009 | + def test_cli_diff(self): |
| 3010 | + with os_helper.temp_dir() as tmp_dir: |
| 3011 | + fn = self.make_dry_run_file(tmp_dir) |
| 3012 | + pre_mtime = os.stat(fn).st_mtime_ns |
| 3013 | + out = self.expect_success("--diff", fn) |
| 3014 | + self.check_unchanged(tmp_dir, fn, pre_mtime) |
| 3015 | + |
| 3016 | + # A new file is created by the patch. |
| 3017 | + dest_fn = self.dest_file(fn) |
| 3018 | + self.assertStartsWith(out, f"--- /dev/null\n+++ {dest_fn}\n@@ -0,0 +1,") |
| 3019 | + self.assertIn(f"--- {fn}\n+++ {fn}\n", out) |
| 3020 | + self.assertIn("+/*[clinic end generated code:", out) |
| 3021 | + |
| 3022 | + # The patch is what clinic would have written. |
| 3023 | + self.expect_success(fn) |
| 3024 | + with open(fn, encoding="utf-8") as f: |
| 3025 | + new_contents = f.read() |
| 3026 | + expected = "".join(difflib.unified_diff( |
| 3027 | + self.DRY_RUN_CODE.splitlines(keepends=True), |
| 3028 | + new_contents.splitlines(keepends=True), |
| 3029 | + fromfile=fn, tofile=fn)) |
| 3030 | + self.assertEndsWith(out, expected) |
| 3031 | + |
| 3032 | + def test_cli_fail_converters_and_dry_run(self): |
| 3033 | + for opt in "--dry-run", "--diff": |
| 3034 | + with self.subTest(opt=opt): |
| 3035 | + _, err = self.expect_failure("--converters", opt) |
| 3036 | + msg = "can't use --dry-run or --diff with --converters" |
| 3037 | + self.assertIn(msg, err) |
| 3038 | + |
2896 | 3039 | def test_cli_make(self): |
2897 | 3040 | c_code = dedent(""" |
2898 | 3041 | /*[clinic input] |
|
0 commit comments