summaryrefslogtreecommitdiffstats
path: root/Lib/test
diff options
context:
space:
mode:
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>2023-07-24 19:19:36 (GMT)
committerGitHub <noreply@github.com>2023-07-24 19:19:36 (GMT)
commit69d4e8c090efd4f3a958ba056580bc1ee9865d0e (patch)
treea634cc5750f5776af0c98e783ca49289eb6f0e39 /Lib/test
parentbeb5514826e5e1fb2ec2cd1ad137b84091399f7a (diff)
downloadcpython-69d4e8c090efd4f3a958ba056580bc1ee9865d0e.zip
cpython-69d4e8c090efd4f3a958ba056580bc1ee9865d0e.tar.gz
cpython-69d4e8c090efd4f3a958ba056580bc1ee9865d0e.tar.bz2
[3.12] gh-106368: Increase Argument Clinic CLI test coverage (GH-107156) (#107189)
Instead of hacking into the Clinic class, use the Argument Clinic tool to run the ClinicExternalTest test suite. (cherry picked from commit 83a2837b328c58b243f7d97bec12c64ec66681c5) Co-authored-by: Erlend E. Aasland <erlend@python.org> Co-authored-by: Nikita Sobolev <mail@sobolevn.me>
Diffstat (limited to 'Lib/test')
-rw-r--r--Lib/test/test_clinic.py190
1 files changed, 176 insertions, 14 deletions
diff --git a/Lib/test/test_clinic.py b/Lib/test/test_clinic.py
index c24b8cd..f527120 100644
--- a/Lib/test/test_clinic.py
+++ b/Lib/test/test_clinic.py
@@ -4,11 +4,14 @@
from test import support, test_tools
from test.support import os_helper
+from test.support import SHORT_TIMEOUT, requires_subprocess
+from test.support.os_helper import TESTFN, unlink
from textwrap import dedent
from unittest import TestCase
import collections
import inspect
import os.path
+import subprocess
import sys
import unittest
@@ -1346,31 +1349,190 @@ class ClinicParserTest(_ParserBase):
class ClinicExternalTest(TestCase):
maxDiff = None
+ def _do_test(self, *args, expect_success=True):
+ clinic_py = os.path.join(test_tools.toolsdir, "clinic", "clinic.py")
+ with subprocess.Popen(
+ [sys.executable, "-Xutf8", clinic_py, *args],
+ encoding="utf-8",
+ bufsize=0,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ ) as proc:
+ proc.wait()
+ if expect_success == bool(proc.returncode):
+ self.fail("".join(proc.stderr))
+ stdout = proc.stdout.read()
+ stderr = proc.stderr.read()
+ # Clinic never writes to stderr.
+ self.assertEqual(stderr, "")
+ return stdout
+
+ def expect_success(self, *args):
+ return self._do_test(*args)
+
+ def expect_failure(self, *args):
+ return self._do_test(*args, expect_success=False)
+
def test_external(self):
CLINIC_TEST = 'clinic.test.c'
- # bpo-42398: Test that the destination file is left unchanged if the
- # content does not change. Moreover, check also that the file
- # modification time does not change in this case.
source = support.findfile(CLINIC_TEST)
with open(source, 'r', encoding='utf-8') as f:
orig_contents = f.read()
- with os_helper.temp_dir() as tmp_dir:
- testfile = os.path.join(tmp_dir, CLINIC_TEST)
- with open(testfile, 'w', encoding='utf-8') as f:
- f.write(orig_contents)
- old_mtime_ns = os.stat(testfile).st_mtime_ns
-
- clinic.parse_file(testfile)
+ # Run clinic CLI and verify that it does not complain.
+ self.addCleanup(unlink, TESTFN)
+ out = self.expect_success("-f", "-o", TESTFN, source)
+ self.assertEqual(out, "")
- with open(testfile, 'r', encoding='utf-8') as f:
- new_contents = f.read()
- new_mtime_ns = os.stat(testfile).st_mtime_ns
+ with open(TESTFN, 'r', encoding='utf-8') as f:
+ new_contents = f.read()
self.assertEqual(new_contents, orig_contents)
+
+ def test_no_change(self):
+ # bpo-42398: Test that the destination file is left unchanged if the
+ # content does not change. Moreover, check also that the file
+ # modification time does not change in this case.
+ code = dedent("""
+ /*[clinic input]
+ [clinic start generated code]*/
+ /*[clinic end generated code: output=da39a3ee5e6b4b0d input=da39a3ee5e6b4b0d]*/
+ """)
+ with os_helper.temp_dir() as tmp_dir:
+ fn = os.path.join(tmp_dir, "test.c")
+ with open(fn, "w", encoding="utf-8") as f:
+ f.write(code)
+ pre_mtime = os.stat(fn).st_mtime_ns
+ self.expect_success(fn)
+ post_mtime = os.stat(fn).st_mtime_ns
# Don't change the file modification time
# if the content does not change
- self.assertEqual(new_mtime_ns, old_mtime_ns)
+ self.assertEqual(pre_mtime, post_mtime)
+
+ def test_cli_force(self):
+ invalid_input = dedent("""
+ /*[clinic input]
+ output preset block
+ module test
+ test.fn
+ a: int
+ [clinic start generated code]*/
+
+ const char *hand_edited = "output block is overwritten";
+ /*[clinic end generated code: output=bogus input=bogus]*/
+ """)
+ fail_msg = dedent("""
+ Checksum mismatch!
+ Expected: bogus
+ Computed: 2ed19
+ Suggested fix: remove all generated code including the end marker,
+ or use the '-f' option.
+ """)
+ with os_helper.temp_dir() as tmp_dir:
+ fn = os.path.join(tmp_dir, "test.c")
+ with open(fn, "w", encoding="utf-8") as f:
+ f.write(invalid_input)
+ # First, run the CLI without -f and expect failure.
+ # Note, we cannot check the entire fail msg, because the path to
+ # the tmp file will change for every run.
+ out = self.expect_failure(fn)
+ self.assertTrue(out.endswith(fail_msg))
+ # Then, force regeneration; success expected.
+ out = self.expect_success("-f", fn)
+ self.assertEqual(out, "")
+ # Verify by checking the checksum.
+ checksum = (
+ "/*[clinic end generated code: "
+ "output=2124c291eb067d76 input=9543a8d2da235301]*/\n"
+ )
+ with open(fn, 'r', encoding='utf-8') as f:
+ generated = f.read()
+ self.assertTrue(generated.endswith(checksum))
+
+ def test_cli_verbose(self):
+ with os_helper.temp_dir() as tmp_dir:
+ fn = os.path.join(tmp_dir, "test.c")
+ with open(fn, "w", encoding="utf-8") as f:
+ f.write("")
+ out = self.expect_success("-v", fn)
+ self.assertEqual(out.strip(), fn)
+
+ def test_cli_help(self):
+ out = self.expect_success("-h")
+ self.assertIn("usage: clinic.py", out)
+
+ def test_cli_converters(self):
+ prelude = dedent("""
+ Legacy converters:
+ B C D L O S U Y Z Z#
+ b c d f h i l p s s# s* u u# w* y y# y* z z# z*
+
+ Converters:
+ """)
+ expected_converters = (
+ "bool",
+ "byte",
+ "char",
+ "defining_class",
+ "double",
+ "fildes",
+ "float",
+ "int",
+ "long",
+ "long_long",
+ "object",
+ "Py_buffer",
+ "Py_complex",
+ "Py_ssize_t",
+ "Py_UNICODE",
+ "PyByteArrayObject",
+ "PyBytesObject",
+ "self",
+ "short",
+ "size_t",
+ "slice_index",
+ "str",
+ "unicode",
+ "unsigned_char",
+ "unsigned_int",
+ "unsigned_long",
+ "unsigned_long_long",
+ "unsigned_short",
+ )
+ finale = dedent("""
+ Return converters:
+ bool()
+ double()
+ float()
+ init()
+ int()
+ long()
+ Py_ssize_t()
+ size_t()
+ unsigned_int()
+ unsigned_long()
+
+ All converters also accept (c_default=None, py_default=None, annotation=None).
+ All return converters also accept (py_default=None).
+ """)
+ out = self.expect_success("--converters")
+ # We cannot simply compare the output, because the repr of the *accept*
+ # param may change (it's a set, thus unordered). So, let's compare the
+ # start and end of the expected output, and then assert that the
+ # converters appear lined up in alphabetical order.
+ self.assertTrue(out.startswith(prelude), out)
+ self.assertTrue(out.endswith(finale), out)
+
+ out = out.removeprefix(prelude)
+ out = out.removesuffix(finale)
+ lines = out.split("\n")
+ for converter, line in zip(expected_converters, lines):
+ line = line.lstrip()
+ with self.subTest(converter=converter):
+ self.assertTrue(
+ line.startswith(converter),
+ f"expected converter {converter!r}, got {line!r}"
+ )
try: