Skip to content

Commit 0bd784b

Browse files
gh-106368: Increase Argument Clinic test coverage (#107611)
Add tests for directives and destinations
1 parent a443c31 commit 0bd784b

File tree

1 file changed

+296
-0
lines changed

1 file changed

+296
-0
lines changed

Lib/test/test_clinic.py

+296
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ def __repr__(self):
117117

118118

119119
class ClinicWholeFileTest(TestCase):
120+
maxDiff = None
120121

121122
def expect_failure(self, raw, errmsg, *, filename=None, lineno=None):
122123
_expect_failure(self, self.clinic.parse, raw, errmsg,
@@ -426,6 +427,230 @@ def test_module_already_got_one(self):
426427
"""
427428
self.expect_failure(block, err, lineno=3)
428429

430+
def test_destination_already_got_one(self):
431+
err = "Destination already exists: 'test'"
432+
block = """
433+
/*[clinic input]
434+
destination test new buffer
435+
destination test new buffer
436+
[clinic start generated code]*/
437+
"""
438+
self.expect_failure(block, err, lineno=3)
439+
440+
def test_destination_does_not_exist(self):
441+
err = "Destination does not exist: '/dev/null'"
442+
block = """
443+
/*[clinic input]
444+
output everything /dev/null
445+
[clinic start generated code]*/
446+
"""
447+
self.expect_failure(block, err, lineno=2)
448+
449+
def test_class_already_got_one(self):
450+
err = "Already defined class 'C'!"
451+
block = """
452+
/*[clinic input]
453+
class C "" ""
454+
class C "" ""
455+
[clinic start generated code]*/
456+
"""
457+
self.expect_failure(block, err, lineno=3)
458+
459+
def test_cant_nest_module_inside_class(self):
460+
err = "Can't nest a module inside a class!"
461+
block = """
462+
/*[clinic input]
463+
class C "" ""
464+
module C.m
465+
[clinic start generated code]*/
466+
"""
467+
self.expect_failure(block, err, lineno=3)
468+
469+
def test_dest_buffer_not_empty_at_eof(self):
470+
expected_warning = ("Destination buffer 'buffer' not empty at "
471+
"end of file, emptying.")
472+
expected_generated = dedent("""
473+
/*[clinic input]
474+
output everything buffer
475+
fn
476+
a: object
477+
/
478+
[clinic start generated code]*/
479+
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=1c4668687f5fd002]*/
480+
481+
/*[clinic input]
482+
dump buffer
483+
[clinic start generated code]*/
484+
485+
PyDoc_VAR(fn__doc__);
486+
487+
PyDoc_STRVAR(fn__doc__,
488+
"fn($module, a, /)\\n"
489+
"--\\n"
490+
"\\n");
491+
492+
#define FN_METHODDEF \\
493+
{"fn", (PyCFunction)fn, METH_O, fn__doc__},
494+
495+
static PyObject *
496+
fn(PyObject *module, PyObject *a)
497+
/*[clinic end generated code: output=be6798b148ab4e53 input=524ce2e021e4eba6]*/
498+
""")
499+
block = dedent("""
500+
/*[clinic input]
501+
output everything buffer
502+
fn
503+
a: object
504+
/
505+
[clinic start generated code]*/
506+
""")
507+
with support.captured_stdout() as stdout:
508+
generated = self.clinic.parse(block)
509+
self.assertIn(expected_warning, stdout.getvalue())
510+
self.assertEqual(generated, expected_generated)
511+
512+
def test_directive_set_misuse(self):
513+
err = "unknown variable 'ets'"
514+
block = """
515+
/*[clinic input]
516+
set ets tse
517+
[clinic start generated code]*/
518+
"""
519+
self.expect_failure(block, err, lineno=2)
520+
521+
def test_directive_set_prefix(self):
522+
block = dedent("""
523+
/*[clinic input]
524+
set line_prefix '// '
525+
output everything suppress
526+
output docstring_prototype buffer
527+
fn
528+
a: object
529+
/
530+
[clinic start generated code]*/
531+
/* We need to dump the buffer.
532+
* If not, Argument Clinic will emit a warning */
533+
/*[clinic input]
534+
dump buffer
535+
[clinic start generated code]*/
536+
""")
537+
generated = self.clinic.parse(block)
538+
expected_docstring_prototype = "// PyDoc_VAR(fn__doc__);"
539+
self.assertIn(expected_docstring_prototype, generated)
540+
541+
def test_directive_set_suffix(self):
542+
block = dedent("""
543+
/*[clinic input]
544+
set line_suffix ' // test'
545+
output everything suppress
546+
output docstring_prototype buffer
547+
fn
548+
a: object
549+
/
550+
[clinic start generated code]*/
551+
/* We need to dump the buffer.
552+
* If not, Argument Clinic will emit a warning */
553+
/*[clinic input]
554+
dump buffer
555+
[clinic start generated code]*/
556+
""")
557+
generated = self.clinic.parse(block)
558+
expected_docstring_prototype = "PyDoc_VAR(fn__doc__); // test"
559+
self.assertIn(expected_docstring_prototype, generated)
560+
561+
def test_directive_set_prefix_and_suffix(self):
562+
block = dedent("""
563+
/*[clinic input]
564+
set line_prefix '{block comment start} '
565+
set line_suffix ' {block comment end}'
566+
output everything suppress
567+
output docstring_prototype buffer
568+
fn
569+
a: object
570+
/
571+
[clinic start generated code]*/
572+
/* We need to dump the buffer.
573+
* If not, Argument Clinic will emit a warning */
574+
/*[clinic input]
575+
dump buffer
576+
[clinic start generated code]*/
577+
""")
578+
generated = self.clinic.parse(block)
579+
expected_docstring_prototype = "/* PyDoc_VAR(fn__doc__); */"
580+
self.assertIn(expected_docstring_prototype, generated)
581+
582+
def test_directive_printout(self):
583+
block = dedent("""
584+
/*[clinic input]
585+
output everything buffer
586+
printout test
587+
[clinic start generated code]*/
588+
""")
589+
expected = dedent("""
590+
/*[clinic input]
591+
output everything buffer
592+
printout test
593+
[clinic start generated code]*/
594+
test
595+
/*[clinic end generated code: output=4e1243bd22c66e76 input=898f1a32965d44ca]*/
596+
""")
597+
generated = self.clinic.parse(block)
598+
self.assertEqual(generated, expected)
599+
600+
def test_directive_preserve_twice(self):
601+
err = "Can't have preserve twice in one block!"
602+
block = """
603+
/*[clinic input]
604+
preserve
605+
preserve
606+
[clinic start generated code]*/
607+
"""
608+
self.expect_failure(block, err, lineno=3)
609+
610+
def test_directive_preserve_input(self):
611+
err = "'preserve' only works for blocks that don't produce any output!"
612+
block = """
613+
/*[clinic input]
614+
preserve
615+
fn
616+
a: object
617+
/
618+
[clinic start generated code]*/
619+
"""
620+
self.expect_failure(block, err, lineno=6)
621+
622+
def test_directive_preserve_output(self):
623+
err = "'preserve' only works for blocks that don't produce any output!"
624+
block = dedent("""
625+
/*[clinic input]
626+
output everything buffer
627+
preserve
628+
[clinic start generated code]*/
629+
// Preserve this
630+
/*[clinic end generated code: output=eaa49677ae4c1f7d input=559b5db18fddae6a]*/
631+
/*[clinic input]
632+
dump buffer
633+
[clinic start generated code]*/
634+
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=524ce2e021e4eba6]*/
635+
""")
636+
generated = self.clinic.parse(block)
637+
self.assertEqual(generated, block)
638+
639+
def test_directive_output_invalid_command(self):
640+
err = (
641+
"Invalid command / destination name 'cmd', must be one of:\n"
642+
" preset push pop print everything cpp_if docstring_prototype "
643+
"docstring_definition methoddef_define impl_prototype "
644+
"parser_prototype parser_definition cpp_endif methoddef_ifndef "
645+
"impl_definition"
646+
)
647+
block = """
648+
/*[clinic input]
649+
output cmd buffer
650+
[clinic start generated code]*/
651+
"""
652+
self.expect_failure(block, err, lineno=2)
653+
429654

430655
class ClinicGroupPermuterTest(TestCase):
431656
def _test(self, l, m, r, output):
@@ -1496,6 +1721,16 @@ class Foo "" ""
14961721
"""
14971722
self.expect_failure(block, err, lineno=3)
14981723

1724+
def test_duplicate_coexist(self):
1725+
err = "Called @coexist twice"
1726+
block = """
1727+
module m
1728+
@coexist
1729+
@coexist
1730+
m.fn
1731+
"""
1732+
self.expect_failure(block, err, lineno=2)
1733+
14991734
def test_unused_param(self):
15001735
block = self.parse("""
15011736
module foo
@@ -1931,6 +2166,67 @@ def test_cli_fail_make_without_srcdir(self):
19312166
msg = "error: --srcdir must not be empty with --make"
19322167
self.assertIn(msg, err)
19332168

2169+
def test_file_dest(self):
2170+
block = dedent("""
2171+
/*[clinic input]
2172+
destination test new file {path}.h
2173+
output everything test
2174+
func
2175+
a: object
2176+
/
2177+
[clinic start generated code]*/
2178+
""")
2179+
expected_checksum_line = (
2180+
"/*[clinic end generated code: "
2181+
"output=da39a3ee5e6b4b0d input=b602ab8e173ac3bd]*/\n"
2182+
)
2183+
expected_output = dedent("""\
2184+
/*[clinic input]
2185+
preserve
2186+
[clinic start generated code]*/
2187+
2188+
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
2189+
# include "pycore_gc.h" // PyGC_Head
2190+
# include "pycore_runtime.h" // _Py_ID()
2191+
#endif
2192+
2193+
2194+
PyDoc_VAR(func__doc__);
2195+
2196+
PyDoc_STRVAR(func__doc__,
2197+
"func($module, a, /)\\n"
2198+
"--\\n"
2199+
"\\n");
2200+
2201+
#define FUNC_METHODDEF \\
2202+
{"func", (PyCFunction)func, METH_O, func__doc__},
2203+
2204+
static PyObject *
2205+
func(PyObject *module, PyObject *a)
2206+
/*[clinic end generated code: output=56c09670e89a0d9a input=a9049054013a1b77]*/
2207+
""")
2208+
with os_helper.temp_dir() as tmp_dir:
2209+
in_fn = os.path.join(tmp_dir, "test.c")
2210+
out_fn = os.path.join(tmp_dir, "test.c.h")
2211+
with open(in_fn, "w", encoding="utf-8") as f:
2212+
f.write(block)
2213+
with open(out_fn, "w", encoding="utf-8") as f:
2214+
f.write("") # Write an empty output file!
2215+
# Clinic should complain about the empty output file.
2216+
_, err = self.expect_failure(in_fn)
2217+
expected_err = (f"Modified destination file {out_fn!r}, "
2218+
"not overwriting!")
2219+
self.assertIn(expected_err, err)
2220+
# Run clinic again, this time with the -f option.
2221+
out = self.expect_success("-f", in_fn)
2222+
# Read back the generated output.
2223+
with open(in_fn, encoding="utf-8") as f:
2224+
data = f.read()
2225+
expected_block = f"{block}{expected_checksum_line}"
2226+
self.assertEqual(data, expected_block)
2227+
with open(out_fn, encoding="utf-8") as f:
2228+
data = f.read()
2229+
self.assertEqual(data, expected_output)
19342230

19352231
try:
19362232
import _testclinic as ac_tester

0 commit comments

Comments
 (0)