cpython/Modules/_testcapi/watchers.c
Kirill Podoprigora bee112a94d
gh-124872: Replace enter/exit events with "switched" (#125532)
Users want to know when the current context switches to a different
context object.  Right now this happens when and only when a context
is entered or exited, so the enter and exit events are synonymous with
"switched".  However, if the changes proposed for gh-99633 are
implemented, the current context will also switch for reasons other
than context enter or exit.  Since users actually care about context
switches and not enter or exit, replace the enter and exit events with
a single switched event.

The former exit event was emitted just before exiting the context.
The new switched event is emitted after the context is exited to match
the semantics users expect of an event with a past-tense name.  If
users need the ability to clean up before the switch takes effect,
another event type can be added in the future.  It is not added here
because YAGNI.

I skipped 0 in the enum as a matter of practice.  Skipping 0 makes it
easier to troubleshoot when code forgets to set zeroed memory, and it
aligns with best practices for other tools (e.g.,
https://protobuf.dev/programming-guides/dos-donts/#unspecified-enum).

Co-authored-by: Richard Hansen <rhansen@rhansen.org>
Co-authored-by: Victor Stinner <vstinner@python.org>
2024-10-16 13:53:21 +02:00

868 lines
24 KiB
C

// clinic/watchers.c.h uses internal pycore_modsupport.h API
#define PYTESTCAPI_NEED_INTERNAL_API
#include "parts.h"
#include "clinic/watchers.c.h"
#define Py_BUILD_CORE
#include "pycore_function.h" // FUNC_MAX_WATCHERS
#include "pycore_code.h" // CODE_MAX_WATCHERS
#include "pycore_context.h" // CONTEXT_MAX_WATCHERS
/*[clinic input]
module _testcapi
[clinic start generated code]*/
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=6361033e795369fc]*/
// Test dict watching
static PyObject *g_dict_watch_events = NULL;
static int g_dict_watchers_installed = 0;
static int
dict_watch_callback(PyDict_WatchEvent event,
PyObject *dict,
PyObject *key,
PyObject *new_value)
{
PyObject *msg;
switch (event) {
case PyDict_EVENT_CLEARED:
msg = PyUnicode_FromString("clear");
break;
case PyDict_EVENT_DEALLOCATED:
msg = PyUnicode_FromString("dealloc");
break;
case PyDict_EVENT_CLONED:
msg = PyUnicode_FromString("clone");
break;
case PyDict_EVENT_ADDED:
msg = PyUnicode_FromFormat("new:%S:%S", key, new_value);
break;
case PyDict_EVENT_MODIFIED:
msg = PyUnicode_FromFormat("mod:%S:%S", key, new_value);
break;
case PyDict_EVENT_DELETED:
msg = PyUnicode_FromFormat("del:%S", key);
break;
default:
msg = PyUnicode_FromString("unknown");
}
if (msg == NULL) {
return -1;
}
assert(PyList_Check(g_dict_watch_events));
if (PyList_Append(g_dict_watch_events, msg) < 0) {
Py_DECREF(msg);
return -1;
}
Py_DECREF(msg);
return 0;
}
static int
dict_watch_callback_second(PyDict_WatchEvent event,
PyObject *dict,
PyObject *key,
PyObject *new_value)
{
PyObject *msg = PyUnicode_FromString("second");
if (msg == NULL) {
return -1;
}
int rc = PyList_Append(g_dict_watch_events, msg);
Py_DECREF(msg);
if (rc < 0) {
return -1;
}
return 0;
}
static int
dict_watch_callback_error(PyDict_WatchEvent event,
PyObject *dict,
PyObject *key,
PyObject *new_value)
{
PyErr_SetString(PyExc_RuntimeError, "boom!");
return -1;
}
static PyObject *
add_dict_watcher(PyObject *self, PyObject *kind)
{
int watcher_id;
assert(PyLong_Check(kind));
long kind_l = PyLong_AsLong(kind);
if (kind_l == 2) {
watcher_id = PyDict_AddWatcher(dict_watch_callback_second);
}
else if (kind_l == 1) {
watcher_id = PyDict_AddWatcher(dict_watch_callback_error);
}
else {
watcher_id = PyDict_AddWatcher(dict_watch_callback);
}
if (watcher_id < 0) {
return NULL;
}
if (!g_dict_watchers_installed) {
assert(!g_dict_watch_events);
if (!(g_dict_watch_events = PyList_New(0))) {
return NULL;
}
}
g_dict_watchers_installed++;
return PyLong_FromLong(watcher_id);
}
static PyObject *
clear_dict_watcher(PyObject *self, PyObject *watcher_id)
{
if (PyDict_ClearWatcher(PyLong_AsLong(watcher_id))) {
return NULL;
}
g_dict_watchers_installed--;
if (!g_dict_watchers_installed) {
assert(g_dict_watch_events);
Py_CLEAR(g_dict_watch_events);
}
Py_RETURN_NONE;
}
/*[clinic input]
_testcapi.watch_dict
watcher_id: int
dict: object
/
[clinic start generated code]*/
static PyObject *
_testcapi_watch_dict_impl(PyObject *module, int watcher_id, PyObject *dict)
/*[clinic end generated code: output=1426e0273cebe2d8 input=269b006d60c358bd]*/
{
if (PyDict_Watch(watcher_id, dict)) {
return NULL;
}
Py_RETURN_NONE;
}
/*[clinic input]
_testcapi.unwatch_dict = _testcapi.watch_dict
[clinic start generated code]*/
static PyObject *
_testcapi_unwatch_dict_impl(PyObject *module, int watcher_id, PyObject *dict)
/*[clinic end generated code: output=512b1a71ae33c351 input=cae7dc1b6f7713b8]*/
{
if (PyDict_Unwatch(watcher_id, dict)) {
return NULL;
}
Py_RETURN_NONE;
}
static PyObject *
get_dict_watcher_events(PyObject *self, PyObject *Py_UNUSED(args))
{
if (!g_dict_watch_events) {
PyErr_SetString(PyExc_RuntimeError, "no watchers active");
return NULL;
}
return Py_NewRef(g_dict_watch_events);
}
// Test type watchers
static PyObject *g_type_modified_events;
static int g_type_watchers_installed;
static int
type_modified_callback(PyTypeObject *type)
{
assert(PyList_Check(g_type_modified_events));
if(PyList_Append(g_type_modified_events, (PyObject *)type) < 0) {
return -1;
}
return 0;
}
static int
type_modified_callback_wrap(PyTypeObject *type)
{
assert(PyList_Check(g_type_modified_events));
PyObject *list = PyList_New(0);
if (list == NULL) {
return -1;
}
if (PyList_Append(list, (PyObject *)type) < 0) {
Py_DECREF(list);
return -1;
}
if (PyList_Append(g_type_modified_events, list) < 0) {
Py_DECREF(list);
return -1;
}
Py_DECREF(list);
return 0;
}
static int
type_modified_callback_error(PyTypeObject *type)
{
PyErr_SetString(PyExc_RuntimeError, "boom!");
return -1;
}
static PyObject *
add_type_watcher(PyObject *self, PyObject *kind)
{
int watcher_id;
assert(PyLong_Check(kind));
long kind_l = PyLong_AsLong(kind);
if (kind_l == 2) {
watcher_id = PyType_AddWatcher(type_modified_callback_wrap);
}
else if (kind_l == 1) {
watcher_id = PyType_AddWatcher(type_modified_callback_error);
}
else {
watcher_id = PyType_AddWatcher(type_modified_callback);
}
if (watcher_id < 0) {
return NULL;
}
if (!g_type_watchers_installed) {
assert(!g_type_modified_events);
if (!(g_type_modified_events = PyList_New(0))) {
return NULL;
}
}
g_type_watchers_installed++;
return PyLong_FromLong(watcher_id);
}
static PyObject *
clear_type_watcher(PyObject *self, PyObject *watcher_id)
{
if (PyType_ClearWatcher(PyLong_AsLong(watcher_id))) {
return NULL;
}
g_type_watchers_installed--;
if (!g_type_watchers_installed) {
assert(g_type_modified_events);
Py_CLEAR(g_type_modified_events);
}
Py_RETURN_NONE;
}
static PyObject *
get_type_modified_events(PyObject *self, PyObject *Py_UNUSED(args))
{
if (!g_type_modified_events) {
PyErr_SetString(PyExc_RuntimeError, "no watchers active");
return NULL;
}
return Py_NewRef(g_type_modified_events);
}
/*[clinic input]
_testcapi.watch_type
watcher_id: int
type: object
/
[clinic start generated code]*/
static PyObject *
_testcapi_watch_type_impl(PyObject *module, int watcher_id, PyObject *type)
/*[clinic end generated code: output=fdf4777126724fc4 input=5a808bf12be7e3ed]*/
{
if (PyType_Watch(watcher_id, type)) {
return NULL;
}
Py_RETURN_NONE;
}
/*[clinic input]
_testcapi.unwatch_type = _testcapi.watch_type
[clinic start generated code]*/
static PyObject *
_testcapi_unwatch_type_impl(PyObject *module, int watcher_id, PyObject *type)
/*[clinic end generated code: output=0389672d4ad5f68b input=6701911fb45edc9e]*/
{
if (PyType_Unwatch(watcher_id, type)) {
return NULL;
}
Py_RETURN_NONE;
}
// Test code object watching
#define NUM_CODE_WATCHERS 2
static int code_watcher_ids[NUM_CODE_WATCHERS] = {-1, -1};
static int num_code_object_created_events[NUM_CODE_WATCHERS] = {0, 0};
static int num_code_object_destroyed_events[NUM_CODE_WATCHERS] = {0, 0};
static int
handle_code_object_event(int which_watcher, PyCodeEvent event, PyCodeObject *co) {
if (event == PY_CODE_EVENT_CREATE) {
num_code_object_created_events[which_watcher]++;
}
else if (event == PY_CODE_EVENT_DESTROY) {
num_code_object_destroyed_events[which_watcher]++;
}
else {
return -1;
}
return 0;
}
static int
first_code_object_callback(PyCodeEvent event, PyCodeObject *co)
{
return handle_code_object_event(0, event, co);
}
static int
second_code_object_callback(PyCodeEvent event, PyCodeObject *co)
{
return handle_code_object_event(1, event, co);
}
static int
noop_code_event_handler(PyCodeEvent event, PyCodeObject *co)
{
return 0;
}
static int
error_code_event_handler(PyCodeEvent event, PyCodeObject *co)
{
PyErr_SetString(PyExc_RuntimeError, "boom!");
return -1;
}
static PyObject *
add_code_watcher(PyObject *self, PyObject *which_watcher)
{
int watcher_id;
assert(PyLong_Check(which_watcher));
long which_l = PyLong_AsLong(which_watcher);
if (which_l == 0) {
watcher_id = PyCode_AddWatcher(first_code_object_callback);
code_watcher_ids[0] = watcher_id;
num_code_object_created_events[0] = 0;
num_code_object_destroyed_events[0] = 0;
}
else if (which_l == 1) {
watcher_id = PyCode_AddWatcher(second_code_object_callback);
code_watcher_ids[1] = watcher_id;
num_code_object_created_events[1] = 0;
num_code_object_destroyed_events[1] = 0;
}
else if (which_l == 2) {
watcher_id = PyCode_AddWatcher(error_code_event_handler);
}
else {
PyErr_Format(PyExc_ValueError, "invalid watcher %d", which_l);
return NULL;
}
if (watcher_id < 0) {
return NULL;
}
return PyLong_FromLong(watcher_id);
}
static PyObject *
clear_code_watcher(PyObject *self, PyObject *watcher_id)
{
assert(PyLong_Check(watcher_id));
long watcher_id_l = PyLong_AsLong(watcher_id);
if (PyCode_ClearWatcher(watcher_id_l) < 0) {
return NULL;
}
// reset static events counters
if (watcher_id_l >= 0) {
for (int i = 0; i < NUM_CODE_WATCHERS; i++) {
if (watcher_id_l == code_watcher_ids[i]) {
code_watcher_ids[i] = -1;
num_code_object_created_events[i] = 0;
num_code_object_destroyed_events[i] = 0;
}
}
}
Py_RETURN_NONE;
}
static PyObject *
get_code_watcher_num_created_events(PyObject *self, PyObject *watcher_id)
{
assert(PyLong_Check(watcher_id));
long watcher_id_l = PyLong_AsLong(watcher_id);
assert(watcher_id_l >= 0 && watcher_id_l < NUM_CODE_WATCHERS);
return PyLong_FromLong(num_code_object_created_events[watcher_id_l]);
}
static PyObject *
get_code_watcher_num_destroyed_events(PyObject *self, PyObject *watcher_id)
{
assert(PyLong_Check(watcher_id));
long watcher_id_l = PyLong_AsLong(watcher_id);
assert(watcher_id_l >= 0 && watcher_id_l < NUM_CODE_WATCHERS);
return PyLong_FromLong(num_code_object_destroyed_events[watcher_id_l]);
}
static PyObject *
allocate_too_many_code_watchers(PyObject *self, PyObject *args)
{
int watcher_ids[CODE_MAX_WATCHERS + 1];
int num_watchers = 0;
for (unsigned long i = 0; i < sizeof(watcher_ids) / sizeof(int); i++) {
int watcher_id = PyCode_AddWatcher(noop_code_event_handler);
if (watcher_id == -1) {
break;
}
watcher_ids[i] = watcher_id;
num_watchers++;
}
PyObject *exc = PyErr_GetRaisedException();
for (int i = 0; i < num_watchers; i++) {
if (PyCode_ClearWatcher(watcher_ids[i]) < 0) {
PyErr_WriteUnraisable(Py_None);
break;
}
}
if (exc) {
PyErr_SetRaisedException(exc);
return NULL;
}
else if (PyErr_Occurred()) {
return NULL;
}
Py_RETURN_NONE;
}
// Test function watchers
#define NUM_TEST_FUNC_WATCHERS 2
static PyObject *pyfunc_watchers[NUM_TEST_FUNC_WATCHERS];
static int func_watcher_ids[NUM_TEST_FUNC_WATCHERS] = {-1, -1};
static PyObject *
get_id(PyObject *obj)
{
PyObject *builtins = PyEval_GetBuiltins(); // borrowed ref.
if (builtins == NULL) {
return NULL;
}
PyObject *id_str = PyUnicode_FromString("id");
if (id_str == NULL) {
return NULL;
}
PyObject *id_func = PyObject_GetItem(builtins, id_str);
Py_DECREF(id_str);
if (id_func == NULL) {
return NULL;
}
PyObject *stack[] = {obj};
PyObject *id = PyObject_Vectorcall(id_func, stack, 1, NULL);
Py_DECREF(id_func);
return id;
}
static int
call_pyfunc_watcher(PyObject *watcher, PyFunction_WatchEvent event,
PyFunctionObject *func, PyObject *new_value)
{
PyObject *event_obj = PyLong_FromLong(event);
if (event_obj == NULL) {
return -1;
}
if (new_value == NULL) {
new_value = Py_None;
}
Py_INCREF(new_value);
PyObject *func_or_id = NULL;
if (event == PyFunction_EVENT_DESTROY) {
/* Don't expose a function that's about to be destroyed to managed code */
func_or_id = get_id((PyObject *) func);
if (func_or_id == NULL) {
Py_DECREF(event_obj);
Py_DECREF(new_value);
return -1;
}
}
else {
Py_INCREF(func);
func_or_id = (PyObject *) func;
}
PyObject *stack[] = {event_obj, func_or_id, new_value};
PyObject *res = PyObject_Vectorcall(watcher, stack, 3, NULL);
int st = (res == NULL) ? -1 : 0;
Py_XDECREF(res);
Py_DECREF(new_value);
Py_DECREF(event_obj);
Py_DECREF(func_or_id);
return st;
}
static int
first_func_watcher_callback(PyFunction_WatchEvent event, PyFunctionObject *func,
PyObject *new_value)
{
return call_pyfunc_watcher(pyfunc_watchers[0], event, func, new_value);
}
static int
second_func_watcher_callback(PyFunction_WatchEvent event,
PyFunctionObject *func, PyObject *new_value)
{
return call_pyfunc_watcher(pyfunc_watchers[1], event, func, new_value);
}
static PyFunction_WatchCallback func_watcher_callbacks[NUM_TEST_FUNC_WATCHERS] = {
first_func_watcher_callback,
second_func_watcher_callback
};
static int
add_func_event(PyObject *module, const char *name, PyFunction_WatchEvent event)
{
return PyModule_Add(module, name, PyLong_FromLong(event));
}
static PyObject *
add_func_watcher(PyObject *self, PyObject *func)
{
if (!PyFunction_Check(func)) {
PyErr_SetString(PyExc_TypeError, "'func' must be a function");
return NULL;
}
int idx = -1;
for (int i = 0; i < NUM_TEST_FUNC_WATCHERS; i++) {
if (func_watcher_ids[i] == -1) {
idx = i;
break;
}
}
if (idx == -1) {
PyErr_SetString(PyExc_RuntimeError, "no free test watchers");
return NULL;
}
func_watcher_ids[idx] = PyFunction_AddWatcher(func_watcher_callbacks[idx]);
if (func_watcher_ids[idx] < 0) {
return NULL;
}
pyfunc_watchers[idx] = Py_NewRef(func);
PyObject *result = PyLong_FromLong(func_watcher_ids[idx]);
if (result == NULL) {
return NULL;
}
return result;
}
static PyObject *
clear_func_watcher(PyObject *self, PyObject *watcher_id_obj)
{
long watcher_id = PyLong_AsLong(watcher_id_obj);
if ((watcher_id < INT_MIN) || (watcher_id > INT_MAX)) {
PyErr_SetString(PyExc_ValueError, "invalid watcher ID");
return NULL;
}
int wid = (int) watcher_id;
if (PyFunction_ClearWatcher(wid) < 0) {
return NULL;
}
int idx = -1;
for (int i = 0; i < NUM_TEST_FUNC_WATCHERS; i++) {
if (func_watcher_ids[i] == wid) {
idx = i;
break;
}
}
assert(idx != -1);
Py_CLEAR(pyfunc_watchers[idx]);
func_watcher_ids[idx] = -1;
Py_RETURN_NONE;
}
static int
noop_func_event_handler(PyFunction_WatchEvent event, PyFunctionObject *func,
PyObject *new_value)
{
return 0;
}
static PyObject *
allocate_too_many_func_watchers(PyObject *self, PyObject *args)
{
int watcher_ids[FUNC_MAX_WATCHERS + 1];
int num_watchers = 0;
for (unsigned long i = 0; i < sizeof(watcher_ids) / sizeof(int); i++) {
int watcher_id = PyFunction_AddWatcher(noop_func_event_handler);
if (watcher_id == -1) {
break;
}
watcher_ids[i] = watcher_id;
num_watchers++;
}
PyObject *exc = PyErr_GetRaisedException();
for (int i = 0; i < num_watchers; i++) {
if (PyFunction_ClearWatcher(watcher_ids[i]) < 0) {
PyErr_WriteUnraisable(Py_None);
break;
}
}
if (exc) {
PyErr_SetRaisedException(exc);
return NULL;
}
else if (PyErr_Occurred()) {
return NULL;
}
Py_RETURN_NONE;
}
// Test contexct object watchers
#define NUM_CONTEXT_WATCHERS 2
static int context_watcher_ids[NUM_CONTEXT_WATCHERS] = {-1, -1};
static PyObject *context_switches[NUM_CONTEXT_WATCHERS];
static int
handle_context_watcher_event(int which_watcher, PyContextEvent event, PyObject *ctx) {
if (event == Py_CONTEXT_SWITCHED) {
PyList_Append(context_switches[which_watcher], ctx);
}
else {
return -1;
}
return 0;
}
static int
first_context_watcher_callback(PyContextEvent event, PyObject *ctx) {
return handle_context_watcher_event(0, event, ctx);
}
static int
second_context_watcher_callback(PyContextEvent event, PyObject *ctx) {
return handle_context_watcher_event(1, event, ctx);
}
static int
noop_context_event_handler(PyContextEvent event, PyObject *ctx) {
return 0;
}
static int
error_context_event_handler(PyContextEvent event, PyObject *ctx) {
PyErr_SetString(PyExc_RuntimeError, "boom!");
return -1;
}
static PyObject *
add_context_watcher(PyObject *self, PyObject *which_watcher)
{
static const PyContext_WatchCallback callbacks[] = {
&first_context_watcher_callback,
&second_context_watcher_callback,
&error_context_event_handler,
};
assert(PyLong_Check(which_watcher));
long which_l = PyLong_AsLong(which_watcher);
if (which_l < 0 || which_l >= (long)Py_ARRAY_LENGTH(callbacks)) {
PyErr_Format(PyExc_ValueError, "invalid watcher %d", which_l);
return NULL;
}
int watcher_id = PyContext_AddWatcher(callbacks[which_l]);
if (watcher_id < 0) {
return NULL;
}
if (which_l >= 0 && which_l < NUM_CONTEXT_WATCHERS) {
context_watcher_ids[which_l] = watcher_id;
Py_XSETREF(context_switches[which_l], PyList_New(0));
if (context_switches[which_l] == NULL) {
return NULL;
}
}
return PyLong_FromLong(watcher_id);
}
static PyObject *
clear_context_watcher(PyObject *self, PyObject *watcher_id)
{
assert(PyLong_Check(watcher_id));
long watcher_id_l = PyLong_AsLong(watcher_id);
if (PyContext_ClearWatcher(watcher_id_l) < 0) {
return NULL;
}
// reset static events counters
if (watcher_id_l >= 0) {
for (int i = 0; i < NUM_CONTEXT_WATCHERS; i++) {
if (watcher_id_l == context_watcher_ids[i]) {
context_watcher_ids[i] = -1;
Py_CLEAR(context_switches[i]);
}
}
}
Py_RETURN_NONE;
}
static PyObject *
clear_context_stack(PyObject *Py_UNUSED(self), PyObject *Py_UNUSED(args))
{
PyThreadState *tstate = PyThreadState_Get();
if (tstate->context == NULL) {
Py_RETURN_NONE;
}
if (((PyContext *)tstate->context)->ctx_prev != NULL) {
PyErr_SetString(PyExc_RuntimeError,
"must first exit all non-base contexts");
return NULL;
}
Py_CLEAR(tstate->context);
Py_RETURN_NONE;
}
static PyObject *
get_context_switches(PyObject *Py_UNUSED(self), PyObject *watcher_id)
{
assert(PyLong_Check(watcher_id));
long watcher_id_l = PyLong_AsLong(watcher_id);
if (watcher_id_l < 0 || watcher_id_l >= NUM_CONTEXT_WATCHERS) {
PyErr_Format(PyExc_ValueError, "invalid watcher %ld", watcher_id_l);
return NULL;
}
if (context_switches[watcher_id_l] == NULL) {
return PyList_New(0);
}
return Py_NewRef(context_switches[watcher_id_l]);
}
static PyObject *
allocate_too_many_context_watchers(PyObject *self, PyObject *args)
{
int watcher_ids[CONTEXT_MAX_WATCHERS + 1];
int num_watchers = 0;
for (unsigned long i = 0; i < sizeof(watcher_ids) / sizeof(int); i++) {
int watcher_id = PyContext_AddWatcher(noop_context_event_handler);
if (watcher_id == -1) {
break;
}
watcher_ids[i] = watcher_id;
num_watchers++;
}
PyObject *exc = PyErr_GetRaisedException();
for (int i = 0; i < num_watchers; i++) {
if (PyContext_ClearWatcher(watcher_ids[i]) < 0) {
PyErr_WriteUnraisable(Py_None);
break;
}
}
if (exc) {
PyErr_SetRaisedException(exc);
return NULL;
}
else if (PyErr_Occurred()) {
return NULL;
}
Py_RETURN_NONE;
}
/*[clinic input]
_testcapi.set_func_defaults_via_capi
func: object
defaults: object
/
[clinic start generated code]*/
static PyObject *
_testcapi_set_func_defaults_via_capi_impl(PyObject *module, PyObject *func,
PyObject *defaults)
/*[clinic end generated code: output=caf0cb39db31ac24 input=e04a8508ca9d42fc]*/
{
if (PyFunction_SetDefaults(func, defaults) < 0) {
return NULL;
}
Py_RETURN_NONE;
}
/*[clinic input]
_testcapi.set_func_kwdefaults_via_capi = _testcapi.set_func_defaults_via_capi
[clinic start generated code]*/
static PyObject *
_testcapi_set_func_kwdefaults_via_capi_impl(PyObject *module, PyObject *func,
PyObject *defaults)
/*[clinic end generated code: output=9ed3b08177025070 input=f3cd1ca3c18de8ce]*/
{
if (PyFunction_SetKwDefaults(func, defaults) < 0) {
return NULL;
}
Py_RETURN_NONE;
}
static PyMethodDef test_methods[] = {
// Dict watchers.
{"add_dict_watcher", add_dict_watcher, METH_O, NULL},
{"clear_dict_watcher", clear_dict_watcher, METH_O, NULL},
_TESTCAPI_WATCH_DICT_METHODDEF
_TESTCAPI_UNWATCH_DICT_METHODDEF
{"get_dict_watcher_events",
(PyCFunction) get_dict_watcher_events, METH_NOARGS, NULL},
// Type watchers.
{"add_type_watcher", add_type_watcher, METH_O, NULL},
{"clear_type_watcher", clear_type_watcher, METH_O, NULL},
_TESTCAPI_WATCH_TYPE_METHODDEF
_TESTCAPI_UNWATCH_TYPE_METHODDEF
{"get_type_modified_events",
(PyCFunction) get_type_modified_events, METH_NOARGS, NULL},
// Code object watchers.
{"add_code_watcher", add_code_watcher, METH_O, NULL},
{"clear_code_watcher", clear_code_watcher, METH_O, NULL},
{"get_code_watcher_num_created_events",
get_code_watcher_num_created_events, METH_O, NULL},
{"get_code_watcher_num_destroyed_events",
get_code_watcher_num_destroyed_events, METH_O, NULL},
{"allocate_too_many_code_watchers",
(PyCFunction) allocate_too_many_code_watchers, METH_NOARGS, NULL},
// Function watchers.
{"add_func_watcher", add_func_watcher, METH_O, NULL},
{"clear_func_watcher", clear_func_watcher, METH_O, NULL},
_TESTCAPI_SET_FUNC_DEFAULTS_VIA_CAPI_METHODDEF
_TESTCAPI_SET_FUNC_KWDEFAULTS_VIA_CAPI_METHODDEF
{"allocate_too_many_func_watchers", allocate_too_many_func_watchers,
METH_NOARGS, NULL},
// Code object watchers.
{"add_context_watcher", add_context_watcher, METH_O, NULL},
{"clear_context_watcher", clear_context_watcher, METH_O, NULL},
{"clear_context_stack", clear_context_stack, METH_NOARGS, NULL},
{"get_context_switches", get_context_switches, METH_O, NULL},
{"allocate_too_many_context_watchers",
(PyCFunction) allocate_too_many_context_watchers, METH_NOARGS, NULL},
{NULL},
};
int
_PyTestCapi_Init_Watchers(PyObject *mod)
{
if (PyModule_AddFunctions(mod, test_methods) < 0) {
return -1;
}
/* Expose each event as an attribute on the module */
#define ADD_EVENT(event) \
if (add_func_event(mod, "PYFUNC_EVENT_" #event, \
PyFunction_EVENT_##event)) { \
return -1; \
}
PY_FOREACH_FUNC_EVENT(ADD_EVENT);
#undef ADD_EVENT
return 0;
}