|
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 |
@@ -3198,6 +3199,148 @@ def test_cli_force(self): |
3198 | 3199 | generated = f.read() |
3199 | 3200 | self.assertEndsWith(generated, checksum) |
3200 | 3201 |
|
| 3202 | + DRY_RUN_CODE = dedent(""" |
| 3203 | + /*[clinic input] |
| 3204 | + func |
| 3205 | + a: int |
| 3206 | + / |
| 3207 | +
|
| 3208 | + Docstring. |
| 3209 | + [clinic start generated code]*/ |
| 3210 | + """) |
| 3211 | + |
| 3212 | + def make_dry_run_file(self, tmp_dir): |
| 3213 | + fn = os.path.join(tmp_dir, "test.c") |
| 3214 | + with open(fn, "w", encoding="utf-8") as f: |
| 3215 | + f.write(self.DRY_RUN_CODE) |
| 3216 | + return fn |
| 3217 | + |
| 3218 | + @staticmethod |
| 3219 | + def dest_file(fn): |
| 3220 | + # The default destination for the generated code. Its path is |
| 3221 | + # built from the "{dirname}/clinic/{basename}.h" template, so it |
| 3222 | + # always uses forward slashes, even on Windows. |
| 3223 | + dirname, basename = os.path.split(fn) |
| 3224 | + return f"{dirname}/clinic/{basename}.h" |
| 3225 | + |
| 3226 | + def check_unchanged(self, tmp_dir, fn, pre_mtime): |
| 3227 | + # Neither the source file nor the destination file |
| 3228 | + # nor its directory is created or modified. |
| 3229 | + with open(fn, encoding="utf-8") as f: |
| 3230 | + self.assertEqual(f.read(), self.DRY_RUN_CODE) |
| 3231 | + self.assertEqual(os.stat(fn).st_mtime_ns, pre_mtime) |
| 3232 | + self.assertEqual(os.listdir(tmp_dir), ["test.c"]) |
| 3233 | + |
| 3234 | + def test_cli_dry_run(self): |
| 3235 | + with os_helper.temp_dir() as tmp_dir: |
| 3236 | + fn = self.make_dry_run_file(tmp_dir) |
| 3237 | + pre_mtime = os.stat(fn).st_mtime_ns |
| 3238 | + out = self.expect_success("--dry-run", fn) |
| 3239 | + self.assertEqual(out.splitlines(), [ |
| 3240 | + f"would create {self.dest_file(fn)}", |
| 3241 | + f"would update {fn}", |
| 3242 | + ]) |
| 3243 | + self.check_unchanged(tmp_dir, fn, pre_mtime) |
| 3244 | + |
| 3245 | + def test_cli_dry_run_no_change(self): |
| 3246 | + with os_helper.temp_dir() as tmp_dir: |
| 3247 | + fn = self.make_dry_run_file(tmp_dir) |
| 3248 | + self.expect_success(fn) |
| 3249 | + self.assertEqual(self.expect_success("--dry-run", fn), "") |
| 3250 | + self.assertEqual(self.expect_success("--diff", fn), "") |
| 3251 | + |
| 3252 | + def test_cli_dry_run_no_clinic_block(self): |
| 3253 | + with os_helper.temp_dir() as tmp_dir: |
| 3254 | + fn = os.path.join(tmp_dir, "test.c") |
| 3255 | + with open(fn, "w", encoding="utf-8") as f: |
| 3256 | + f.write("int x;\n") |
| 3257 | + self.assertEqual(self.expect_success("--dry-run", fn), "") |
| 3258 | + |
| 3259 | + def test_cli_dry_run_output(self): |
| 3260 | + with os_helper.temp_dir() as tmp_dir: |
| 3261 | + fn = self.make_dry_run_file(tmp_dir) |
| 3262 | + out_fn = os.path.join(tmp_dir, "output.c") |
| 3263 | + out = self.expect_success("--dry-run", "-o", out_fn, fn) |
| 3264 | + self.assertIn(f"would create {out_fn}", out) |
| 3265 | + self.assertNotIn(f"would update {fn}", out) |
| 3266 | + self.assertFalse(os.path.exists(out_fn)) |
| 3267 | + |
| 3268 | + def test_cli_dry_run_make(self): |
| 3269 | + with os_helper.temp_dir() as tmp_dir: |
| 3270 | + fn = self.make_dry_run_file(tmp_dir) |
| 3271 | + pre_mtime = os.stat(fn).st_mtime_ns |
| 3272 | + out = self.expect_success("--dry-run", "--make", "--srcdir", tmp_dir) |
| 3273 | + self.assertIn(f"would update {fn}", out) |
| 3274 | + self.check_unchanged(tmp_dir, fn, pre_mtime) |
| 3275 | + |
| 3276 | + def test_cli_dry_run_verbose(self): |
| 3277 | + with os_helper.temp_dir() as tmp_dir: |
| 3278 | + fn = self.make_dry_run_file(tmp_dir) |
| 3279 | + out, err, code = self.run_clinic("-v", "--dry-run", fn) |
| 3280 | + self.assertEqual(code, 0) |
| 3281 | + # The progress goes to stderr, so that the standard output |
| 3282 | + # contains only the report. |
| 3283 | + self.assertEqual(err.splitlines(), [fn]) |
| 3284 | + self.assertEqual(out.splitlines(), [ |
| 3285 | + f"would create {self.dest_file(fn)}", |
| 3286 | + f"would update {fn}", |
| 3287 | + ]) |
| 3288 | + |
| 3289 | + def test_cli_dry_run_checksum_mismatch(self): |
| 3290 | + invalid_input = dedent(""" |
| 3291 | + /*[clinic input] |
| 3292 | + output preset block |
| 3293 | + module test |
| 3294 | + test.fn |
| 3295 | + a: int |
| 3296 | + [clinic start generated code]*/ |
| 3297 | + /*[clinic end generated code: output=bogus input=bogus]*/ |
| 3298 | + """) |
| 3299 | + with os_helper.temp_dir() as tmp_dir: |
| 3300 | + fn = os.path.join(tmp_dir, "test.c") |
| 3301 | + with open(fn, "w", encoding="utf-8") as f: |
| 3302 | + f.write(invalid_input) |
| 3303 | + pre_mtime = os.stat(fn).st_mtime_ns |
| 3304 | + # The dry run does not disable the checksum verification. |
| 3305 | + _, err = self.expect_failure("--dry-run", fn) |
| 3306 | + self.assertIn("Checksum mismatch!", err) |
| 3307 | + # With -f the change is reported, but still not written. |
| 3308 | + out = self.expect_success("--dry-run", "-f", fn) |
| 3309 | + self.assertIn(f"would update {fn}", out) |
| 3310 | + with open(fn, encoding="utf-8") as f: |
| 3311 | + self.assertEqual(f.read(), invalid_input) |
| 3312 | + self.assertEqual(os.stat(fn).st_mtime_ns, pre_mtime) |
| 3313 | + |
| 3314 | + def test_cli_diff(self): |
| 3315 | + with os_helper.temp_dir() as tmp_dir: |
| 3316 | + fn = self.make_dry_run_file(tmp_dir) |
| 3317 | + pre_mtime = os.stat(fn).st_mtime_ns |
| 3318 | + out = self.expect_success("--diff", fn) |
| 3319 | + self.check_unchanged(tmp_dir, fn, pre_mtime) |
| 3320 | + |
| 3321 | + # A new file is created by the patch. |
| 3322 | + dest_fn = self.dest_file(fn) |
| 3323 | + self.assertStartsWith(out, f"--- /dev/null\n+++ {dest_fn}\n@@ -0,0 +1,") |
| 3324 | + self.assertIn(f"--- {fn}\n+++ {fn}\n", out) |
| 3325 | + self.assertIn("+/*[clinic end generated code:", out) |
| 3326 | + |
| 3327 | + # The patch is what clinic would have written. |
| 3328 | + self.expect_success(fn) |
| 3329 | + with open(fn, encoding="utf-8") as f: |
| 3330 | + new_contents = f.read() |
| 3331 | + expected = "".join(difflib.unified_diff( |
| 3332 | + self.DRY_RUN_CODE.splitlines(keepends=True), |
| 3333 | + new_contents.splitlines(keepends=True), |
| 3334 | + fromfile=fn, tofile=fn)) |
| 3335 | + self.assertEndsWith(out, expected) |
| 3336 | + |
| 3337 | + def test_cli_fail_converters_and_dry_run(self): |
| 3338 | + for opt in "--dry-run", "--diff": |
| 3339 | + with self.subTest(opt=opt): |
| 3340 | + _, err = self.expect_failure("--converters", opt) |
| 3341 | + msg = "can't use --dry-run or --diff with --converters" |
| 3342 | + self.assertIn(msg, err) |
| 3343 | + |
3201 | 3344 | def test_cli_make(self): |
3202 | 3345 | c_code = dedent(""" |
3203 | 3346 | /*[clinic input] |
|
0 commit comments