The Wayback Machine - https://web.archive.org/web/20221223064342/https://github.com/python/cpython/pull/96178
Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-96002: Add functional test for Argument Clinic #96178

Merged
merged 35 commits into from Nov 21, 2022

Conversation

colorfulappl
Copy link
Contributor

@colorfulappl colorfulappl commented Aug 22, 2022

A draft implementation of Argument Clinic functional test.

The test do:

  • Add a module with AC-declared functions, each function corresponds to one type of function signatures
  • Setup a venv, build the module and install it
  • Call the functions in the module to check if all the arguments are processed correctly (gathered to a tuple and returned)

Currently it's a draft and only two PoC of #32092 is added, so the test crashes.

cc. @erlend-aasland @kumaraditya303

@bedevere-bot
Copy link

bedevere-bot commented Aug 22, 2022

Most changes to Python require a NEWS entry.

Please add it using the blurb_it web app or the blurb command-line tool.

@bedevere-bot
Copy link

bedevere-bot commented Aug 22, 2022

Most changes to Python require a NEWS entry.

Please add it using the blurb_it web app or the blurb command-line tool.

@colorfulappl colorfulappl force-pushed the test_clinic_functionality branch from b703757 to e67dc12 Compare Aug 22, 2022
@erlend-aasland erlend-aasland marked this pull request as draft Aug 22, 2022
@erlend-aasland
Copy link
Contributor

erlend-aasland commented Aug 22, 2022

Thanks for taking this on!

I suggest you take a look at how the test_capi module is laid out; the C code lives in the Modules/ dir, and the test code lives in Lib/test/test_capi.py. The tests there can be split in two patterns:

  1. Tests that are fully implemented in C, for example test_from_spec_metatype_inheritance:

test_from_spec_metatype_inheritance(PyObject *self, PyObject *Py_UNUSED(ignored))
{
PyObject *metaclass = NULL;
PyObject *class = NULL;
PyObject *new = NULL;
PyObject *subclasses = NULL;
PyObject *result = NULL;
int r;
metaclass = PyType_FromSpecWithBases(&MinimalMetaclass_spec, (PyObject*)&PyType_Type);
if (metaclass == NULL) {
goto finally;
}
class = PyObject_CallFunction(metaclass, "s(){}", "TestClass");
if (class == NULL) {
goto finally;
}
MinimalType_spec.basicsize = (int)(((PyTypeObject*)class)->tp_basicsize);
new = PyType_FromSpecWithBases(&MinimalType_spec, class);
if (new == NULL) {
goto finally;
}
if (Py_TYPE(new) != (PyTypeObject*)metaclass) {
PyErr_SetString(PyExc_AssertionError,
"Metaclass not set properly!");
goto finally;
}
/* Assert that __subclasses__ is updated */
subclasses = PyObject_CallMethod(class, "__subclasses__", "");
if (!subclasses) {
goto finally;
}
r = PySequence_Contains(subclasses, new);
if (r < 0) {
goto finally;
}
if (r == 0) {
PyErr_SetString(PyExc_AssertionError,
"subclasses not set properly!");
goto finally;
}
result = Py_NewRef(Py_None);
finally:
Py_XDECREF(metaclass);
Py_XDECREF(class);
Py_XDECREF(new);
Py_XDECREF(subclasses);
return result;
}

  1. Tests that are set up in C and tested in Python, for example test_instancemethod:

class InstanceMethod:
id = _testcapi.instancemethod(id)
testfunction = _testcapi.instancemethod(testfunction)
class CAPITest(unittest.TestCase):
def test_instancemethod(self):
inst = InstanceMethod()
self.assertEqual(id(inst), inst.id())
self.assertTrue(inst.testfunction() is inst)
self.assertEqual(inst.testfunction.__doc__, testfunction.__doc__)
self.assertEqual(InstanceMethod.testfunction.__doc__, testfunction.__doc__)
InstanceMethod.testfunction.attribute = "test"
self.assertEqual(testfunction.attribute, "test")
self.assertRaises(AttributeError, setattr, inst.testfunction, "attribute", "test")

Py_INCREF(&PyInstanceMethod_Type);
PyModule_AddObject(m, "instancemethod", (PyObject *)&PyInstanceMethod_Type);

You'll also need to modify configure.ac, Makefile.pre.in, and Modules/Setup.stdlib for the new module; you can grep -i for _testcapi in those files to see what needs to be done.

@colorfulappl colorfulappl force-pushed the test_clinic_functionality branch from e67dc12 to 962434c Compare Aug 23, 2022
@colorfulappl
Copy link
Contributor Author

colorfulappl commented Aug 23, 2022

You'll also need to modify configure.ac, Makefile.pre.in, and Modules/Setup.stdlib for the new module; you can grep -i for _testcapi in those files to see what needs to be done.

Thanks for your advise.
I have rewritten this commit and now it is able to run testcases written both in C or Python.
Could you take a look again?

Once the framework is ready, I will add more case into it.

Copy link
Contributor

@erlend-aasland erlend-aasland left a comment

This is nice. I don't think there's need for a new Lib/test/test_clinic_functionality.py file; we could probably just expand Lib/test_clinic.py. Alternatively, create a file-system namespace Lib/test_clinic/, but I don't know if that's worth it (and we could always do such a refactor afterwards).

(I'm not sure I like the _testclinicfunctionality.c name, but I have no good alternative either; perhaps just _testclinic.c)

Python/stdlib_module_names.h Outdated Show resolved Hide resolved
Modules/_testclinicfunctionality.c Outdated Show resolved Hide resolved
Modules/_testclinicfunctionality.c Outdated Show resolved Hide resolved
Modules/_testclinicfunctionality.c Outdated Show resolved Hide resolved
Lib/test/test_clinic_functionality.py Outdated Show resolved Hide resolved
@bedevere-bot
Copy link

bedevere-bot commented Sep 4, 2022

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

@erlend-aasland
Copy link
Contributor

erlend-aasland commented Oct 25, 2022

@colorfulappl, are you planning to pursue this PR? If not, would you mind me taking over and driving it forward. I'd really like to have this merged; it is good work.

@colorfulappl
Copy link
Contributor Author

colorfulappl commented Oct 26, 2022

Sorry, I have been busy with other stuff these days so this PR is delayed. :(

I am planning to finish this in this week. Here I made some changes at your suggestions and am going to add more test cases.

@kumaraditya303 kumaraditya303 self-requested a review Oct 26, 2022
@kumaraditya303
Copy link
Contributor

kumaraditya303 commented Nov 14, 2022

The following patch fixes the refleaks:

diff --git a/Modules/_testclinic.c b/Modules/_testclinic.c
index 0d58bee7bb..77b9d2671d 100644
--- a/Modules/_testclinic.c
+++ b/Modules/_testclinic.c
@@ -50,9 +50,8 @@ pack_arguments_newref(int argc, ...)
  * Do not accept NULL arguments unless error occurs. */
 #define RETURN_PACKED_ARGS(argc, ...) do { \
         assert(!PyErr_Occurred()); \
-        PyObject *out[argc] = {NULL,}; \
+        PyObject *out[argc] = {__VA_ARGS__}; \
         for (int _i = 0; _i < argc; _i++) { \
-            out[_i] = ((PyObject *[]){__VA_ARGS__})[_i]; \
             assert(out[_i] || PyErr_Occurred()); \
             if (!out[_i]) { \
                 for (int _j = 0; _j < _i; _j++) { \
@kumaraditya303 ➜ /workspaces/cpython (test_clinic_functionality ✗) $ ./python -m test -R 3:3 test_clinic
0:00:00 load avg: 2.72 Run tests sequentially
0:00:00 load avg: 2.72 [1/1] test_clinic
beginning 6 repetitions
123456
......

== Tests result: SUCCESS ==

1 test OK.

Total duration: 986 ms
Tests result: SUCCESS

@colorfulappl
Copy link
Contributor Author

colorfulappl commented Nov 14, 2022

@kumaraditya303 Thank you very much for this revision.
This patch does resolve the reference leak problem.

After this patch,

RETURN_PACKED_ARGS(3,
                   PyLong_FromLong(a),
                   PyLong_FromLong(b),
                   PyLong_FromLong(c));

is expanded to

do {
    assert(!PyErr_Occurred());
    PyObject *out[3] = {PyLong_FromUnsignedLong(a),
                        PyLong_FromUnsignedLong(b),
                        PyLong_FromUnsignedLong(c)};
    for (int _i = 0; _i < 3; _i++) {
        assert(out[_i] || PyErr_Occurred());
        if (!out[_i]) {
            for (int _j = 0; _j < _i; _j++) {
                Py_DECREF(out[_j]);
            }
            return NULL;
        }
    }
    PyObject *tuple = PyTuple_New(3);
    if (!tuple) {
        for (int _i = 0; _i < 3; _i++) {
            Py_DECREF(out[_i]);
        }
        return NULL;
    }
    for (int _i = 0; _i < 3; _i++) {
        PyTuple_SET_ITEM(tuple, _i, out[_i]);
    }
    return tuple;
} while (0);

.

But since all the PyLong_FromUnsignedLong functions are invoked at the initialization of out, there is still the object leak problem which erlend-aasland mentioned:

  • For Python C APIs that return PyObject *, we must check their return value immediately and bail if needed. For example, as in char_converter_impl, calling multiple PyLong_FromUnsignedLong without checking their return value may result in exception objects leaking.

There should be some way can we delay the evaluation of the arguments to where we actually use them.
Any good ideas?

@colorfulappl
Copy link
Contributor Author

colorfulappl commented Nov 14, 2022

Now RETURN_PACKED_ARGS macro accepts a wrapper function and invoke it inside a for loop.
This indeed handles the arguments one-by-one, though not in an elegant manner.

Copy link
Contributor

@erlend-aasland erlend-aasland left a comment

Now RETURN_PACKED_ARGS macro accepts a wrapper function and invoke it inside a for loop.
This indeed handles the arguments one-by-one, though not in an elegant manner.

IMO, it matters more that it is readable and that it is correct, rather than how elegant it is.

I added a few more comments.

(Just a heads-up regarding the macro: I'll reformat it before merging1, but let's not spend time doing that while finishing the review trying to land this; it'll be too much distraction.)

Footnotes

  1. fix indentation and align line breakers neatly

Modules/_testclinic.c Outdated Show resolved Hide resolved
Modules/_testclinic.c Outdated Show resolved Hide resolved
@erlend-aasland
Copy link
Contributor

erlend-aasland commented Nov 14, 2022

Also, I'll wait for @kumaraditya303's thumbs up before landing this.

@isidentical, do you want to have a look?

@erlend-aasland
Copy link
Contributor

erlend-aasland commented Nov 15, 2022

BTW, @colorfulappl, please resolve the conflicts in Modules/Setup.stdlib.in (it's because of my recent PRs for the _testcapi module).

# Conflicts:
#	Modules/Setup.stdlib.in
@colorfulappl
Copy link
Contributor Author

colorfulappl commented Nov 15, 2022

Sure. It's done. :)

@erlend-aasland
Copy link
Contributor

erlend-aasland commented Nov 15, 2022

Note to self: Like with _testcapi, we should consider adding a short README (or C comment) regarding how to add new tests. We can do this in a follow-up PR.

Copy link
Contributor

@kumaraditya303 kumaraditya303 left a comment

LGTM

@erlend-aasland
Copy link
Contributor

erlend-aasland commented Nov 15, 2022

I'm going to give Batuhan a chance to chime in before merging. If I haven't heard anything until Friday, I'll land this. Thanks for doing this, and thanks for your patience with our nitpicking, @colorfulappl :)

@colorfulappl
Copy link
Contributor Author

colorfulappl commented Nov 15, 2022

@erlend-aasland @kumaraditya303 Thank you very much for your patient guidance! 🎉

# Conflicts:
#	Modules/Setup.stdlib.in
@erlend-aasland erlend-aasland merged commit c450c8c into python:main Nov 21, 2022
15 checks passed
@erlend-aasland
Copy link
Contributor

erlend-aasland commented Nov 21, 2022

@colorfulappl: please go ahead with the various Argument Clinic bugfixes and their accompanying tests :)

@erlend-aasland
Copy link
Contributor

erlend-aasland commented Nov 30, 2022

@kumaraditya303 I think we should backport this to 3.11 and 3.10. CC. @pablogsal as RM.

There are multiple clinic bugfixes coming up; IMO we should backport those bugfixes. Backporting this will benefit such bugfix backports.

@kumaraditya303
Copy link
Contributor

kumaraditya303 commented Nov 30, 2022

I think we should backport this to 3.11 and 3.10.

SGTM

colorfulappl added a commit to colorfulappl/cpython that referenced this pull request Dec 14, 2022
(cherry picked from commit c450c8c)

Co-authored-by: Kumar Aditya <[email protected]>
Co-authored-by: Erlend E. Aasland <[email protected]>
@bedevere-bot
Copy link

bedevere-bot commented Dec 14, 2022

GH-100230 is a backport of this pull request to the 3.11 branch.

colorfulappl added a commit to colorfulappl/cpython that referenced this pull request Dec 14, 2022
(cherry picked from commit c450c8c)

Co-authored-by: Kumar Aditya <[email protected]>
Co-authored-by: Erlend E. Aasland <[email protected]>
@bedevere-bot
Copy link

bedevere-bot commented Dec 14, 2022

GH-100232 is a backport of this pull request to the 3.10 branch.

kumaraditya303 added a commit that referenced this pull request Dec 17, 2022
…100230)

(cherry picked from commit c450c8c)

Co-authored-by: Kumar Aditya <[email protected]>
Co-authored-by: Erlend E. Aasland <[email protected]>
kumaraditya303 added a commit that referenced this pull request Dec 17, 2022
…100232)

(cherry picked from commit c450c8c)

Co-authored-by: Kumar Aditya <[email protected]>
Co-authored-by: Erlend E. Aasland <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
tests Tests in the Lib/test dir
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants