|
9 | 9 | from test.support.os_helper import TESTFN, unlink, rmtree |
10 | 10 | from textwrap import dedent |
11 | 11 | from unittest import TestCase |
| 12 | +import difflib |
12 | 13 | import inspect |
13 | 14 | import os.path |
14 | 15 | import re |
@@ -3113,6 +3114,148 @@ def test_cli_force(self): |
3113 | 3114 | generated = f.read() |
3114 | 3115 | self.assertEndsWith(generated, checksum) |
3115 | 3116 |
|
| 3117 | + DRY_RUN_CODE = dedent(""" |
| 3118 | + /*[clinic input] |
| 3119 | + func |
| 3120 | + a: int |
| 3121 | + / |
| 3122 | +
|
| 3123 | + Docstring. |
| 3124 | + [clinic start generated code]*/ |
| 3125 | + """) |
| 3126 | + |
| 3127 | + def make_dry_run_file(self, tmp_dir): |
| 3128 | + fn = os.path.join(tmp_dir, "test.c") |
| 3129 | + with open(fn, "w", encoding="utf-8") as f: |
| 3130 | + f.write(self.DRY_RUN_CODE) |
| 3131 | + return fn |
| 3132 | + |
| 3133 | + @staticmethod |
| 3134 | + def dest_file(fn): |
| 3135 | + # The default destination for the generated code. Its path is |
| 3136 | + # built from the "{dirname}/clinic/{basename}.h" template, so it |
| 3137 | + # always uses forward slashes, even on Windows. |
| 3138 | + dirname, basename = os.path.split(fn) |
| 3139 | + return f"{dirname}/clinic/{basename}.h" |
| 3140 | + |
| 3141 | + def check_unchanged(self, tmp_dir, fn, pre_mtime): |
| 3142 | + # Neither the source file nor the destination file |
| 3143 | + # nor its directory is created or modified. |
| 3144 | + with open(fn, encoding="utf-8") as f: |
| 3145 | + self.assertEqual(f.read(), self.DRY_RUN_CODE) |
| 3146 | + self.assertEqual(os.stat(fn).st_mtime_ns, pre_mtime) |
| 3147 | + self.assertEqual(os.listdir(tmp_dir), ["test.c"]) |
| 3148 | + |
| 3149 | + def test_cli_dry_run(self): |
| 3150 | + with os_helper.temp_dir() as tmp_dir: |
| 3151 | + fn = self.make_dry_run_file(tmp_dir) |
| 3152 | + pre_mtime = os.stat(fn).st_mtime_ns |
| 3153 | + out = self.expect_success("--dry-run", fn) |
| 3154 | + self.assertEqual(out.splitlines(), [ |
| 3155 | + f"would create {self.dest_file(fn)}", |
| 3156 | + f"would update {fn}", |
| 3157 | + ]) |
| 3158 | + self.check_unchanged(tmp_dir, fn, pre_mtime) |
| 3159 | + |
| 3160 | + def test_cli_dry_run_no_change(self): |
| 3161 | + with os_helper.temp_dir() as tmp_dir: |
| 3162 | + fn = self.make_dry_run_file(tmp_dir) |
| 3163 | + self.expect_success(fn) |
| 3164 | + self.assertEqual(self.expect_success("--dry-run", fn), "") |
| 3165 | + self.assertEqual(self.expect_success("--diff", fn), "") |
| 3166 | + |
| 3167 | + def test_cli_dry_run_no_clinic_block(self): |
| 3168 | + with os_helper.temp_dir() as tmp_dir: |
| 3169 | + fn = os.path.join(tmp_dir, "test.c") |
| 3170 | + with open(fn, "w", encoding="utf-8") as f: |
| 3171 | + f.write("int x;\n") |
| 3172 | + self.assertEqual(self.expect_success("--dry-run", fn), "") |
| 3173 | + |
| 3174 | + def test_cli_dry_run_output(self): |
| 3175 | + with os_helper.temp_dir() as tmp_dir: |
| 3176 | + fn = self.make_dry_run_file(tmp_dir) |
| 3177 | + out_fn = os.path.join(tmp_dir, "output.c") |
| 3178 | + out = self.expect_success("--dry-run", "-o", out_fn, fn) |
| 3179 | + self.assertIn(f"would create {out_fn}", out) |
| 3180 | + self.assertNotIn(f"would update {fn}", out) |
| 3181 | + self.assertFalse(os.path.exists(out_fn)) |
| 3182 | + |
| 3183 | + def test_cli_dry_run_make(self): |
| 3184 | + with os_helper.temp_dir() as tmp_dir: |
| 3185 | + fn = self.make_dry_run_file(tmp_dir) |
| 3186 | + pre_mtime = os.stat(fn).st_mtime_ns |
| 3187 | + out = self.expect_success("--dry-run", "--make", "--srcdir", tmp_dir) |
| 3188 | + self.assertIn(f"would update {fn}", out) |
| 3189 | + self.check_unchanged(tmp_dir, fn, pre_mtime) |
| 3190 | + |
| 3191 | + def test_cli_dry_run_verbose(self): |
| 3192 | + with os_helper.temp_dir() as tmp_dir: |
| 3193 | + fn = self.make_dry_run_file(tmp_dir) |
| 3194 | + out, err, code = self.run_clinic("-v", "--dry-run", fn) |
| 3195 | + self.assertEqual(code, 0) |
| 3196 | + # The progress goes to stderr, so that the standard output |
| 3197 | + # contains only the report. |
| 3198 | + self.assertEqual(err.splitlines(), [fn]) |
| 3199 | + self.assertEqual(out.splitlines(), [ |
| 3200 | + f"would create {self.dest_file(fn)}", |
| 3201 | + f"would update {fn}", |
| 3202 | + ]) |
| 3203 | + |
| 3204 | + def test_cli_dry_run_checksum_mismatch(self): |
| 3205 | + invalid_input = dedent(""" |
| 3206 | + /*[clinic input] |
| 3207 | + output preset block |
| 3208 | + module test |
| 3209 | + test.fn |
| 3210 | + a: int |
| 3211 | + [clinic start generated code]*/ |
| 3212 | + /*[clinic end generated code: output=bogus input=bogus]*/ |
| 3213 | + """) |
| 3214 | + with os_helper.temp_dir() as tmp_dir: |
| 3215 | + fn = os.path.join(tmp_dir, "test.c") |
| 3216 | + with open(fn, "w", encoding="utf-8") as f: |
| 3217 | + f.write(invalid_input) |
| 3218 | + pre_mtime = os.stat(fn).st_mtime_ns |
| 3219 | + # The dry run does not disable the checksum verification. |
| 3220 | + _, err = self.expect_failure("--dry-run", fn) |
| 3221 | + self.assertIn("Checksum mismatch!", err) |
| 3222 | + # With -f the change is reported, but still not written. |
| 3223 | + out = self.expect_success("--dry-run", "-f", fn) |
| 3224 | + self.assertIn(f"would update {fn}", out) |
| 3225 | + with open(fn, encoding="utf-8") as f: |
| 3226 | + self.assertEqual(f.read(), invalid_input) |
| 3227 | + self.assertEqual(os.stat(fn).st_mtime_ns, pre_mtime) |
| 3228 | + |
| 3229 | + def test_cli_diff(self): |
| 3230 | + with os_helper.temp_dir() as tmp_dir: |
| 3231 | + fn = self.make_dry_run_file(tmp_dir) |
| 3232 | + pre_mtime = os.stat(fn).st_mtime_ns |
| 3233 | + out = self.expect_success("--diff", fn) |
| 3234 | + self.check_unchanged(tmp_dir, fn, pre_mtime) |
| 3235 | + |
| 3236 | + # A new file is created by the patch. |
| 3237 | + dest_fn = self.dest_file(fn) |
| 3238 | + self.assertStartsWith(out, f"--- /dev/null\n+++ {dest_fn}\n@@ -0,0 +1,") |
| 3239 | + self.assertIn(f"--- {fn}\n+++ {fn}\n", out) |
| 3240 | + self.assertIn("+/*[clinic end generated code:", out) |
| 3241 | + |
| 3242 | + # The patch is what clinic would have written. |
| 3243 | + self.expect_success(fn) |
| 3244 | + with open(fn, encoding="utf-8") as f: |
| 3245 | + new_contents = f.read() |
| 3246 | + expected = "".join(difflib.unified_diff( |
| 3247 | + self.DRY_RUN_CODE.splitlines(keepends=True), |
| 3248 | + new_contents.splitlines(keepends=True), |
| 3249 | + fromfile=fn, tofile=fn)) |
| 3250 | + self.assertEndsWith(out, expected) |
| 3251 | + |
| 3252 | + def test_cli_fail_converters_and_dry_run(self): |
| 3253 | + for opt in "--dry-run", "--diff": |
| 3254 | + with self.subTest(opt=opt): |
| 3255 | + _, err = self.expect_failure("--converters", opt) |
| 3256 | + msg = "can't use --dry-run or --diff with --converters" |
| 3257 | + self.assertIn(msg, err) |
| 3258 | + |
3116 | 3259 | def test_cli_make(self): |
3117 | 3260 | c_code = dedent(""" |
3118 | 3261 | /*[clinic input] |
|
0 commit comments