|
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 |
@@ -2711,6 +2712,148 @@ def test_cli_force(self): |
2711 | 2712 | self.assertTrue(generated.endswith(checksum), |
2712 | 2713 | (generated, checksum)) |
2713 | 2714 |
|
| 2715 | + DRY_RUN_CODE = dedent(""" |
| 2716 | + /*[clinic input] |
| 2717 | + func |
| 2718 | + a: int |
| 2719 | + / |
| 2720 | +
|
| 2721 | + Docstring. |
| 2722 | + [clinic start generated code]*/ |
| 2723 | + """) |
| 2724 | + |
| 2725 | + def make_dry_run_file(self, tmp_dir): |
| 2726 | + fn = os.path.join(tmp_dir, "test.c") |
| 2727 | + with open(fn, "w", encoding="utf-8") as f: |
| 2728 | + f.write(self.DRY_RUN_CODE) |
| 2729 | + return fn |
| 2730 | + |
| 2731 | + @staticmethod |
| 2732 | + def dest_file(fn): |
| 2733 | + # The default destination for the generated code. Its path is |
| 2734 | + # built from the "{dirname}/clinic/{basename}.h" template, so it |
| 2735 | + # always uses forward slashes, even on Windows. |
| 2736 | + dirname, basename = os.path.split(fn) |
| 2737 | + return f"{dirname}/clinic/{basename}.h" |
| 2738 | + |
| 2739 | + def check_unchanged(self, tmp_dir, fn, pre_mtime): |
| 2740 | + # Neither the source file nor the destination file |
| 2741 | + # nor its directory is created or modified. |
| 2742 | + with open(fn, encoding="utf-8") as f: |
| 2743 | + self.assertEqual(f.read(), self.DRY_RUN_CODE) |
| 2744 | + self.assertEqual(os.stat(fn).st_mtime_ns, pre_mtime) |
| 2745 | + self.assertEqual(os.listdir(tmp_dir), ["test.c"]) |
| 2746 | + |
| 2747 | + def test_cli_dry_run(self): |
| 2748 | + with os_helper.temp_dir() as tmp_dir: |
| 2749 | + fn = self.make_dry_run_file(tmp_dir) |
| 2750 | + pre_mtime = os.stat(fn).st_mtime_ns |
| 2751 | + out = self.expect_success("--dry-run", fn) |
| 2752 | + self.assertEqual(out.splitlines(), [ |
| 2753 | + f"would create {self.dest_file(fn)}", |
| 2754 | + f"would update {fn}", |
| 2755 | + ]) |
| 2756 | + self.check_unchanged(tmp_dir, fn, pre_mtime) |
| 2757 | + |
| 2758 | + def test_cli_dry_run_no_change(self): |
| 2759 | + with os_helper.temp_dir() as tmp_dir: |
| 2760 | + fn = self.make_dry_run_file(tmp_dir) |
| 2761 | + self.expect_success(fn) |
| 2762 | + self.assertEqual(self.expect_success("--dry-run", fn), "") |
| 2763 | + self.assertEqual(self.expect_success("--diff", fn), "") |
| 2764 | + |
| 2765 | + def test_cli_dry_run_no_clinic_block(self): |
| 2766 | + with os_helper.temp_dir() as tmp_dir: |
| 2767 | + fn = os.path.join(tmp_dir, "test.c") |
| 2768 | + with open(fn, "w", encoding="utf-8") as f: |
| 2769 | + f.write("int x;\n") |
| 2770 | + self.assertEqual(self.expect_success("--dry-run", fn), "") |
| 2771 | + |
| 2772 | + def test_cli_dry_run_output(self): |
| 2773 | + with os_helper.temp_dir() as tmp_dir: |
| 2774 | + fn = self.make_dry_run_file(tmp_dir) |
| 2775 | + out_fn = os.path.join(tmp_dir, "output.c") |
| 2776 | + out = self.expect_success("--dry-run", "-o", out_fn, fn) |
| 2777 | + self.assertIn(f"would create {out_fn}", out) |
| 2778 | + self.assertNotIn(f"would update {fn}", out) |
| 2779 | + self.assertFalse(os.path.exists(out_fn)) |
| 2780 | + |
| 2781 | + def test_cli_dry_run_make(self): |
| 2782 | + with os_helper.temp_dir() as tmp_dir: |
| 2783 | + fn = self.make_dry_run_file(tmp_dir) |
| 2784 | + pre_mtime = os.stat(fn).st_mtime_ns |
| 2785 | + out = self.expect_success("--dry-run", "--make", "--srcdir", tmp_dir) |
| 2786 | + self.assertIn(f"would update {fn}", out) |
| 2787 | + self.check_unchanged(tmp_dir, fn, pre_mtime) |
| 2788 | + |
| 2789 | + def test_cli_dry_run_verbose(self): |
| 2790 | + with os_helper.temp_dir() as tmp_dir: |
| 2791 | + fn = self.make_dry_run_file(tmp_dir) |
| 2792 | + out, err, code = self.run_clinic("-v", "--dry-run", fn) |
| 2793 | + self.assertEqual(code, 0) |
| 2794 | + # The progress goes to stderr, so that the standard output |
| 2795 | + # contains only the report. |
| 2796 | + self.assertEqual(err.splitlines(), [fn]) |
| 2797 | + self.assertEqual(out.splitlines(), [ |
| 2798 | + f"would create {self.dest_file(fn)}", |
| 2799 | + f"would update {fn}", |
| 2800 | + ]) |
| 2801 | + |
| 2802 | + def test_cli_dry_run_checksum_mismatch(self): |
| 2803 | + invalid_input = dedent(""" |
| 2804 | + /*[clinic input] |
| 2805 | + output preset block |
| 2806 | + module test |
| 2807 | + test.fn |
| 2808 | + a: int |
| 2809 | + [clinic start generated code]*/ |
| 2810 | + /*[clinic end generated code: output=bogus input=bogus]*/ |
| 2811 | + """) |
| 2812 | + with os_helper.temp_dir() as tmp_dir: |
| 2813 | + fn = os.path.join(tmp_dir, "test.c") |
| 2814 | + with open(fn, "w", encoding="utf-8") as f: |
| 2815 | + f.write(invalid_input) |
| 2816 | + pre_mtime = os.stat(fn).st_mtime_ns |
| 2817 | + # The dry run does not disable the checksum verification. |
| 2818 | + _, err = self.expect_failure("--dry-run", fn) |
| 2819 | + self.assertIn("Checksum mismatch!", err) |
| 2820 | + # With -f the change is reported, but still not written. |
| 2821 | + out = self.expect_success("--dry-run", "-f", fn) |
| 2822 | + self.assertIn(f"would update {fn}", out) |
| 2823 | + with open(fn, encoding="utf-8") as f: |
| 2824 | + self.assertEqual(f.read(), invalid_input) |
| 2825 | + self.assertEqual(os.stat(fn).st_mtime_ns, pre_mtime) |
| 2826 | + |
| 2827 | + def test_cli_diff(self): |
| 2828 | + with os_helper.temp_dir() as tmp_dir: |
| 2829 | + fn = self.make_dry_run_file(tmp_dir) |
| 2830 | + pre_mtime = os.stat(fn).st_mtime_ns |
| 2831 | + out = self.expect_success("--diff", fn) |
| 2832 | + self.check_unchanged(tmp_dir, fn, pre_mtime) |
| 2833 | + |
| 2834 | + # A new file is created by the patch. |
| 2835 | + dest_fn = self.dest_file(fn) |
| 2836 | + self.assertStartsWith(out, f"--- /dev/null\n+++ {dest_fn}\n@@ -0,0 +1,") |
| 2837 | + self.assertIn(f"--- {fn}\n+++ {fn}\n", out) |
| 2838 | + self.assertIn("+/*[clinic end generated code:", out) |
| 2839 | + |
| 2840 | + # The patch is what clinic would have written. |
| 2841 | + self.expect_success(fn) |
| 2842 | + with open(fn, encoding="utf-8") as f: |
| 2843 | + new_contents = f.read() |
| 2844 | + expected = "".join(difflib.unified_diff( |
| 2845 | + self.DRY_RUN_CODE.splitlines(keepends=True), |
| 2846 | + new_contents.splitlines(keepends=True), |
| 2847 | + fromfile=fn, tofile=fn)) |
| 2848 | + self.assertEndsWith(out, expected) |
| 2849 | + |
| 2850 | + def test_cli_fail_converters_and_dry_run(self): |
| 2851 | + for opt in "--dry-run", "--diff": |
| 2852 | + with self.subTest(opt=opt): |
| 2853 | + _, err = self.expect_failure("--converters", opt) |
| 2854 | + msg = "can't use --dry-run or --diff with --converters" |
| 2855 | + self.assertIn(msg, err) |
| 2856 | + |
2714 | 2857 | def test_cli_make(self): |
2715 | 2858 | c_code = dedent(""" |
2716 | 2859 | /*[clinic input] |
|
0 commit comments