diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index ea4c9de47d73de..1d30adaee9218f 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -2,7 +2,6 @@ # these are all functions _testcapi exports whose name begins with 'test_'. from collections import OrderedDict -from contextlib import contextmanager, ExitStack import _thread import importlib.machinery import importlib.util @@ -20,7 +19,6 @@ import weakref from test import support from test.support import MISSING_C_DOCSTRINGS -from test.support import catch_unraisable_exception from test.support import import_helper from test.support import threading_helper from test.support import warnings_helper @@ -1705,333 +1703,5 @@ def func2(x=None): self.do_test(func2) -class TestDictWatchers(unittest.TestCase): - # types of watchers testcapimodule can add: - EVENTS = 0 # appends dict events as strings to global event list - ERROR = 1 # unconditionally sets and signals a RuntimeException - SECOND = 2 # always appends "second" to global event list - - def add_watcher(self, kind=EVENTS): - return _testcapi.add_dict_watcher(kind) - - def clear_watcher(self, watcher_id): - _testcapi.clear_dict_watcher(watcher_id) - - @contextmanager - def watcher(self, kind=EVENTS): - wid = self.add_watcher(kind) - try: - yield wid - finally: - self.clear_watcher(wid) - - def assert_events(self, expected): - actual = _testcapi.get_dict_watcher_events() - self.assertEqual(actual, expected) - - def watch(self, wid, d): - _testcapi.watch_dict(wid, d) - - def unwatch(self, wid, d): - _testcapi.unwatch_dict(wid, d) - - def test_set_new_item(self): - d = {} - with self.watcher() as wid: - self.watch(wid, d) - d["foo"] = "bar" - self.assert_events(["new:foo:bar"]) - - def test_set_existing_item(self): - d = {"foo": "bar"} - with self.watcher() as wid: - self.watch(wid, d) - d["foo"] = "baz" - self.assert_events(["mod:foo:baz"]) - - def test_clone(self): - d = {} - d2 = {"foo": "bar"} - with self.watcher() as wid: - self.watch(wid, d) - d.update(d2) - self.assert_events(["clone"]) - - def test_no_event_if_not_watched(self): - d = {} - with self.watcher() as wid: - d["foo"] = "bar" - self.assert_events([]) - - def test_del(self): - d = {"foo": "bar"} - with self.watcher() as wid: - self.watch(wid, d) - del d["foo"] - self.assert_events(["del:foo"]) - - def test_pop(self): - d = {"foo": "bar"} - with self.watcher() as wid: - self.watch(wid, d) - d.pop("foo") - self.assert_events(["del:foo"]) - - def test_clear(self): - d = {"foo": "bar"} - with self.watcher() as wid: - self.watch(wid, d) - d.clear() - self.assert_events(["clear"]) - - def test_dealloc(self): - d = {"foo": "bar"} - with self.watcher() as wid: - self.watch(wid, d) - del d - self.assert_events(["dealloc"]) - - def test_unwatch(self): - d = {} - with self.watcher() as wid: - self.watch(wid, d) - d["foo"] = "bar" - self.unwatch(wid, d) - d["hmm"] = "baz" - self.assert_events(["new:foo:bar"]) - - def test_error(self): - d = {} - with self.watcher(kind=self.ERROR) as wid: - self.watch(wid, d) - with catch_unraisable_exception() as cm: - d["foo"] = "bar" - self.assertIs(cm.unraisable.object, d) - self.assertEqual(str(cm.unraisable.exc_value), "boom!") - self.assert_events([]) - - def test_two_watchers(self): - d1 = {} - d2 = {} - with self.watcher() as wid1: - with self.watcher(kind=self.SECOND) as wid2: - self.watch(wid1, d1) - self.watch(wid2, d2) - d1["foo"] = "bar" - d2["hmm"] = "baz" - self.assert_events(["new:foo:bar", "second"]) - - def test_watch_non_dict(self): - with self.watcher() as wid: - with self.assertRaisesRegex(ValueError, r"Cannot watch non-dictionary"): - self.watch(wid, 1) - - def test_watch_out_of_range_watcher_id(self): - d = {} - with self.assertRaisesRegex(ValueError, r"Invalid dict watcher ID -1"): - self.watch(-1, d) - with self.assertRaisesRegex(ValueError, r"Invalid dict watcher ID 8"): - self.watch(8, d) # DICT_MAX_WATCHERS = 8 - - def test_watch_unassigned_watcher_id(self): - d = {} - with self.assertRaisesRegex(ValueError, r"No dict watcher set for ID 1"): - self.watch(1, d) - - def test_unwatch_non_dict(self): - with self.watcher() as wid: - with self.assertRaisesRegex(ValueError, r"Cannot watch non-dictionary"): - self.unwatch(wid, 1) - - def test_unwatch_out_of_range_watcher_id(self): - d = {} - with self.assertRaisesRegex(ValueError, r"Invalid dict watcher ID -1"): - self.unwatch(-1, d) - with self.assertRaisesRegex(ValueError, r"Invalid dict watcher ID 8"): - self.unwatch(8, d) # DICT_MAX_WATCHERS = 8 - - def test_unwatch_unassigned_watcher_id(self): - d = {} - with self.assertRaisesRegex(ValueError, r"No dict watcher set for ID 1"): - self.unwatch(1, d) - - def test_clear_out_of_range_watcher_id(self): - with self.assertRaisesRegex(ValueError, r"Invalid dict watcher ID -1"): - self.clear_watcher(-1) - with self.assertRaisesRegex(ValueError, r"Invalid dict watcher ID 8"): - self.clear_watcher(8) # DICT_MAX_WATCHERS = 8 - - def test_clear_unassigned_watcher_id(self): - with self.assertRaisesRegex(ValueError, r"No dict watcher set for ID 1"): - self.clear_watcher(1) - - -class TestTypeWatchers(unittest.TestCase): - # types of watchers testcapimodule can add: - TYPES = 0 # appends modified types to global event list - ERROR = 1 # unconditionally sets and signals a RuntimeException - WRAP = 2 # appends modified type wrapped in list to global event list - - # duplicating the C constant - TYPE_MAX_WATCHERS = 8 - - def add_watcher(self, kind=TYPES): - return _testcapi.add_type_watcher(kind) - - def clear_watcher(self, watcher_id): - _testcapi.clear_type_watcher(watcher_id) - - @contextmanager - def watcher(self, kind=TYPES): - wid = self.add_watcher(kind) - try: - yield wid - finally: - self.clear_watcher(wid) - - def assert_events(self, expected): - actual = _testcapi.get_type_modified_events() - self.assertEqual(actual, expected) - - def watch(self, wid, t): - _testcapi.watch_type(wid, t) - - def unwatch(self, wid, t): - _testcapi.unwatch_type(wid, t) - - def test_watch_type(self): - class C: pass - with self.watcher() as wid: - self.watch(wid, C) - C.foo = "bar" - self.assert_events([C]) - - def test_event_aggregation(self): - class C: pass - with self.watcher() as wid: - self.watch(wid, C) - C.foo = "bar" - C.bar = "baz" - # only one event registered for both modifications - self.assert_events([C]) - - def test_lookup_resets_aggregation(self): - class C: pass - with self.watcher() as wid: - self.watch(wid, C) - C.foo = "bar" - # lookup resets type version tag - self.assertEqual(C.foo, "bar") - C.bar = "baz" - # both events registered - self.assert_events([C, C]) - - def test_unwatch_type(self): - class C: pass - with self.watcher() as wid: - self.watch(wid, C) - C.foo = "bar" - self.assertEqual(C.foo, "bar") - self.assert_events([C]) - self.unwatch(wid, C) - C.bar = "baz" - self.assert_events([C]) - - def test_clear_watcher(self): - class C: pass - # outer watcher is unused, it's just to keep events list alive - with self.watcher() as _: - with self.watcher() as wid: - self.watch(wid, C) - C.foo = "bar" - self.assertEqual(C.foo, "bar") - self.assert_events([C]) - C.bar = "baz" - # Watcher on C has been cleared, no new event - self.assert_events([C]) - - def test_watch_type_subclass(self): - class C: pass - class D(C): pass - with self.watcher() as wid: - self.watch(wid, D) - C.foo = "bar" - self.assert_events([D]) - - def test_error(self): - class C: pass - with self.watcher(kind=self.ERROR) as wid: - self.watch(wid, C) - with catch_unraisable_exception() as cm: - C.foo = "bar" - self.assertIs(cm.unraisable.object, C) - self.assertEqual(str(cm.unraisable.exc_value), "boom!") - self.assert_events([]) - - def test_two_watchers(self): - class C1: pass - class C2: pass - with self.watcher() as wid1: - with self.watcher(kind=self.WRAP) as wid2: - self.assertNotEqual(wid1, wid2) - self.watch(wid1, C1) - self.watch(wid2, C2) - C1.foo = "bar" - C2.hmm = "baz" - self.assert_events([C1, [C2]]) - - def test_watch_non_type(self): - with self.watcher() as wid: - with self.assertRaisesRegex(ValueError, r"Cannot watch non-type"): - self.watch(wid, 1) - - def test_watch_out_of_range_watcher_id(self): - class C: pass - with self.assertRaisesRegex(ValueError, r"Invalid type watcher ID -1"): - self.watch(-1, C) - with self.assertRaisesRegex(ValueError, r"Invalid type watcher ID 8"): - self.watch(self.TYPE_MAX_WATCHERS, C) - - def test_watch_unassigned_watcher_id(self): - class C: pass - with self.assertRaisesRegex(ValueError, r"No type watcher set for ID 1"): - self.watch(1, C) - - def test_unwatch_non_type(self): - with self.watcher() as wid: - with self.assertRaisesRegex(ValueError, r"Cannot watch non-type"): - self.unwatch(wid, 1) - - def test_unwatch_out_of_range_watcher_id(self): - class C: pass - with self.assertRaisesRegex(ValueError, r"Invalid type watcher ID -1"): - self.unwatch(-1, C) - with self.assertRaisesRegex(ValueError, r"Invalid type watcher ID 8"): - self.unwatch(self.TYPE_MAX_WATCHERS, C) - - def test_unwatch_unassigned_watcher_id(self): - class C: pass - with self.assertRaisesRegex(ValueError, r"No type watcher set for ID 1"): - self.unwatch(1, C) - - def test_clear_out_of_range_watcher_id(self): - with self.assertRaisesRegex(ValueError, r"Invalid type watcher ID -1"): - self.clear_watcher(-1) - with self.assertRaisesRegex(ValueError, r"Invalid type watcher ID 8"): - self.clear_watcher(self.TYPE_MAX_WATCHERS) - - def test_clear_unassigned_watcher_id(self): - with self.assertRaisesRegex(ValueError, r"No type watcher set for ID 1"): - self.clear_watcher(1) - - def test_no_more_ids_available(self): - contexts = [self.watcher() for i in range(self.TYPE_MAX_WATCHERS)] - with ExitStack() as stack: - for ctx in contexts: - stack.enter_context(ctx) - with self.assertRaisesRegex(RuntimeError, r"no more type watcher IDs"): - self.add_watcher() - - if __name__ == "__main__": unittest.main() diff --git a/Lib/test/test_capi/test_watchers.py b/Lib/test/test_capi/test_watchers.py new file mode 100644 index 00000000000000..f635c746b4962d --- /dev/null +++ b/Lib/test/test_capi/test_watchers.py @@ -0,0 +1,340 @@ +import unittest + +from contextlib import contextmanager, ExitStack +from test.support import catch_unraisable_exception, import_helper + + +# Skip this test if the _testcapi module isn't available. +_testcapi = import_helper.import_module('_testcapi') + + +class TestDictWatchers(unittest.TestCase): + # types of watchers testcapimodule can add: + EVENTS = 0 # appends dict events as strings to global event list + ERROR = 1 # unconditionally sets and signals a RuntimeException + SECOND = 2 # always appends "second" to global event list + + def add_watcher(self, kind=EVENTS): + return _testcapi.add_dict_watcher(kind) + + def clear_watcher(self, watcher_id): + _testcapi.clear_dict_watcher(watcher_id) + + @contextmanager + def watcher(self, kind=EVENTS): + wid = self.add_watcher(kind) + try: + yield wid + finally: + self.clear_watcher(wid) + + def assert_events(self, expected): + actual = _testcapi.get_dict_watcher_events() + self.assertEqual(actual, expected) + + def watch(self, wid, d): + _testcapi.watch_dict(wid, d) + + def unwatch(self, wid, d): + _testcapi.unwatch_dict(wid, d) + + def test_set_new_item(self): + d = {} + with self.watcher() as wid: + self.watch(wid, d) + d["foo"] = "bar" + self.assert_events(["new:foo:bar"]) + + def test_set_existing_item(self): + d = {"foo": "bar"} + with self.watcher() as wid: + self.watch(wid, d) + d["foo"] = "baz" + self.assert_events(["mod:foo:baz"]) + + def test_clone(self): + d = {} + d2 = {"foo": "bar"} + with self.watcher() as wid: + self.watch(wid, d) + d.update(d2) + self.assert_events(["clone"]) + + def test_no_event_if_not_watched(self): + d = {} + with self.watcher() as wid: + d["foo"] = "bar" + self.assert_events([]) + + def test_del(self): + d = {"foo": "bar"} + with self.watcher() as wid: + self.watch(wid, d) + del d["foo"] + self.assert_events(["del:foo"]) + + def test_pop(self): + d = {"foo": "bar"} + with self.watcher() as wid: + self.watch(wid, d) + d.pop("foo") + self.assert_events(["del:foo"]) + + def test_clear(self): + d = {"foo": "bar"} + with self.watcher() as wid: + self.watch(wid, d) + d.clear() + self.assert_events(["clear"]) + + def test_dealloc(self): + d = {"foo": "bar"} + with self.watcher() as wid: + self.watch(wid, d) + del d + self.assert_events(["dealloc"]) + + def test_unwatch(self): + d = {} + with self.watcher() as wid: + self.watch(wid, d) + d["foo"] = "bar" + self.unwatch(wid, d) + d["hmm"] = "baz" + self.assert_events(["new:foo:bar"]) + + def test_error(self): + d = {} + with self.watcher(kind=self.ERROR) as wid: + self.watch(wid, d) + with catch_unraisable_exception() as cm: + d["foo"] = "bar" + self.assertIs(cm.unraisable.object, d) + self.assertEqual(str(cm.unraisable.exc_value), "boom!") + self.assert_events([]) + + def test_two_watchers(self): + d1 = {} + d2 = {} + with self.watcher() as wid1: + with self.watcher(kind=self.SECOND) as wid2: + self.watch(wid1, d1) + self.watch(wid2, d2) + d1["foo"] = "bar" + d2["hmm"] = "baz" + self.assert_events(["new:foo:bar", "second"]) + + def test_watch_non_dict(self): + with self.watcher() as wid: + with self.assertRaisesRegex(ValueError, r"Cannot watch non-dictionary"): + self.watch(wid, 1) + + def test_watch_out_of_range_watcher_id(self): + d = {} + with self.assertRaisesRegex(ValueError, r"Invalid dict watcher ID -1"): + self.watch(-1, d) + with self.assertRaisesRegex(ValueError, r"Invalid dict watcher ID 8"): + self.watch(8, d) # DICT_MAX_WATCHERS = 8 + + def test_watch_unassigned_watcher_id(self): + d = {} + with self.assertRaisesRegex(ValueError, r"No dict watcher set for ID 1"): + self.watch(1, d) + + def test_unwatch_non_dict(self): + with self.watcher() as wid: + with self.assertRaisesRegex(ValueError, r"Cannot watch non-dictionary"): + self.unwatch(wid, 1) + + def test_unwatch_out_of_range_watcher_id(self): + d = {} + with self.assertRaisesRegex(ValueError, r"Invalid dict watcher ID -1"): + self.unwatch(-1, d) + with self.assertRaisesRegex(ValueError, r"Invalid dict watcher ID 8"): + self.unwatch(8, d) # DICT_MAX_WATCHERS = 8 + + def test_unwatch_unassigned_watcher_id(self): + d = {} + with self.assertRaisesRegex(ValueError, r"No dict watcher set for ID 1"): + self.unwatch(1, d) + + def test_clear_out_of_range_watcher_id(self): + with self.assertRaisesRegex(ValueError, r"Invalid dict watcher ID -1"): + self.clear_watcher(-1) + with self.assertRaisesRegex(ValueError, r"Invalid dict watcher ID 8"): + self.clear_watcher(8) # DICT_MAX_WATCHERS = 8 + + def test_clear_unassigned_watcher_id(self): + with self.assertRaisesRegex(ValueError, r"No dict watcher set for ID 1"): + self.clear_watcher(1) + + +class TestTypeWatchers(unittest.TestCase): + # types of watchers testcapimodule can add: + TYPES = 0 # appends modified types to global event list + ERROR = 1 # unconditionally sets and signals a RuntimeException + WRAP = 2 # appends modified type wrapped in list to global event list + + # duplicating the C constant + TYPE_MAX_WATCHERS = 8 + + def add_watcher(self, kind=TYPES): + return _testcapi.add_type_watcher(kind) + + def clear_watcher(self, watcher_id): + _testcapi.clear_type_watcher(watcher_id) + + @contextmanager + def watcher(self, kind=TYPES): + wid = self.add_watcher(kind) + try: + yield wid + finally: + self.clear_watcher(wid) + + def assert_events(self, expected): + actual = _testcapi.get_type_modified_events() + self.assertEqual(actual, expected) + + def watch(self, wid, t): + _testcapi.watch_type(wid, t) + + def unwatch(self, wid, t): + _testcapi.unwatch_type(wid, t) + + def test_watch_type(self): + class C: pass + with self.watcher() as wid: + self.watch(wid, C) + C.foo = "bar" + self.assert_events([C]) + + def test_event_aggregation(self): + class C: pass + with self.watcher() as wid: + self.watch(wid, C) + C.foo = "bar" + C.bar = "baz" + # only one event registered for both modifications + self.assert_events([C]) + + def test_lookup_resets_aggregation(self): + class C: pass + with self.watcher() as wid: + self.watch(wid, C) + C.foo = "bar" + # lookup resets type version tag + self.assertEqual(C.foo, "bar") + C.bar = "baz" + # both events registered + self.assert_events([C, C]) + + def test_unwatch_type(self): + class C: pass + with self.watcher() as wid: + self.watch(wid, C) + C.foo = "bar" + self.assertEqual(C.foo, "bar") + self.assert_events([C]) + self.unwatch(wid, C) + C.bar = "baz" + self.assert_events([C]) + + def test_clear_watcher(self): + class C: pass + # outer watcher is unused, it's just to keep events list alive + with self.watcher() as _: + with self.watcher() as wid: + self.watch(wid, C) + C.foo = "bar" + self.assertEqual(C.foo, "bar") + self.assert_events([C]) + C.bar = "baz" + # Watcher on C has been cleared, no new event + self.assert_events([C]) + + def test_watch_type_subclass(self): + class C: pass + class D(C): pass + with self.watcher() as wid: + self.watch(wid, D) + C.foo = "bar" + self.assert_events([D]) + + def test_error(self): + class C: pass + with self.watcher(kind=self.ERROR) as wid: + self.watch(wid, C) + with catch_unraisable_exception() as cm: + C.foo = "bar" + self.assertIs(cm.unraisable.object, C) + self.assertEqual(str(cm.unraisable.exc_value), "boom!") + self.assert_events([]) + + def test_two_watchers(self): + class C1: pass + class C2: pass + with self.watcher() as wid1: + with self.watcher(kind=self.WRAP) as wid2: + self.assertNotEqual(wid1, wid2) + self.watch(wid1, C1) + self.watch(wid2, C2) + C1.foo = "bar" + C2.hmm = "baz" + self.assert_events([C1, [C2]]) + + def test_watch_non_type(self): + with self.watcher() as wid: + with self.assertRaisesRegex(ValueError, r"Cannot watch non-type"): + self.watch(wid, 1) + + def test_watch_out_of_range_watcher_id(self): + class C: pass + with self.assertRaisesRegex(ValueError, r"Invalid type watcher ID -1"): + self.watch(-1, C) + with self.assertRaisesRegex(ValueError, r"Invalid type watcher ID 8"): + self.watch(self.TYPE_MAX_WATCHERS, C) + + def test_watch_unassigned_watcher_id(self): + class C: pass + with self.assertRaisesRegex(ValueError, r"No type watcher set for ID 1"): + self.watch(1, C) + + def test_unwatch_non_type(self): + with self.watcher() as wid: + with self.assertRaisesRegex(ValueError, r"Cannot watch non-type"): + self.unwatch(wid, 1) + + def test_unwatch_out_of_range_watcher_id(self): + class C: pass + with self.assertRaisesRegex(ValueError, r"Invalid type watcher ID -1"): + self.unwatch(-1, C) + with self.assertRaisesRegex(ValueError, r"Invalid type watcher ID 8"): + self.unwatch(self.TYPE_MAX_WATCHERS, C) + + def test_unwatch_unassigned_watcher_id(self): + class C: pass + with self.assertRaisesRegex(ValueError, r"No type watcher set for ID 1"): + self.unwatch(1, C) + + def test_clear_out_of_range_watcher_id(self): + with self.assertRaisesRegex(ValueError, r"Invalid type watcher ID -1"): + self.clear_watcher(-1) + with self.assertRaisesRegex(ValueError, r"Invalid type watcher ID 8"): + self.clear_watcher(self.TYPE_MAX_WATCHERS) + + def test_clear_unassigned_watcher_id(self): + with self.assertRaisesRegex(ValueError, r"No type watcher set for ID 1"): + self.clear_watcher(1) + + def test_no_more_ids_available(self): + contexts = [self.watcher() for i in range(self.TYPE_MAX_WATCHERS)] + with ExitStack() as stack: + for ctx in contexts: + stack.enter_context(ctx) + with self.assertRaisesRegex(RuntimeError, r"no more type watcher IDs"): + self.add_watcher() + + +if __name__ == "__main__": + unittest.main() diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in index 26e7ffcdc85a18..c033dbc4960ca0 100644 --- a/Modules/Setup.stdlib.in +++ b/Modules/Setup.stdlib.in @@ -169,7 +169,7 @@ @MODULE__XXTESTFUZZ_TRUE@_xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c @MODULE__TESTBUFFER_TRUE@_testbuffer _testbuffer.c @MODULE__TESTINTERNALCAPI_TRUE@_testinternalcapi _testinternalcapi.c -@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/unicode.c _testcapi/getargs.c _testcapi/pytime.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c +@MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/unicode.c _testcapi/getargs.c _testcapi/pytime.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c # Some testing modules MUST be built as shared libraries. *shared* diff --git a/Modules/_testcapi/parts.h b/Modules/_testcapi/parts.h index a39007bad6dd3b..2bc2a0e6125732 100644 --- a/Modules/_testcapi/parts.h +++ b/Modules/_testcapi/parts.h @@ -32,6 +32,7 @@ int _PyTestCapi_Init_PyTime(PyObject *module); int _PyTestCapi_Init_DateTime(PyObject *module); int _PyTestCapi_Init_Docstring(PyObject *module); int _PyTestCapi_Init_Mem(PyObject *module); +int _PyTestCapi_Init_Watchers(PyObject *module); #ifdef LIMITED_API_AVAILABLE int _PyTestCapi_Init_VectorcallLimited(PyObject *module); diff --git a/Modules/_testcapi/watchers.c b/Modules/_testcapi/watchers.c new file mode 100644 index 00000000000000..e0d489a60e5d48 --- /dev/null +++ b/Modules/_testcapi/watchers.c @@ -0,0 +1,302 @@ +#include "parts.h" + + +// Test dict watching +static PyObject *g_dict_watch_events; +static int g_dict_watchers_installed; + +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; +} + +static PyObject * +watch_dict(PyObject *self, PyObject *args) +{ + PyObject *dict; + int watcher_id; + if (!PyArg_ParseTuple(args, "iO", &watcher_id, &dict)) { + return NULL; + } + if (PyDict_Watch(watcher_id, dict)) { + return NULL; + } + Py_RETURN_NONE; +} + +static PyObject * +unwatch_dict(PyObject *self, PyObject *args) +{ + PyObject *dict; + int watcher_id; + if (!PyArg_ParseTuple(args, "iO", &watcher_id, &dict)) { + return NULL; + } + 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); +} + +static PyObject * +watch_type(PyObject *self, PyObject *args) +{ + PyObject *type; + int watcher_id; + if (!PyArg_ParseTuple(args, "iO", &watcher_id, &type)) { + return NULL; + } + if (PyType_Watch(watcher_id, type)) { + return NULL; + } + Py_RETURN_NONE; +} + +static PyObject * +unwatch_type(PyObject *self, PyObject *args) +{ + PyObject *type; + int watcher_id; + if (!PyArg_ParseTuple(args, "iO", &watcher_id, &type)) { + return NULL; + } + if (PyType_Unwatch(watcher_id, type)) { + 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}, + {"watch_dict", watch_dict, METH_VARARGS, NULL}, + {"unwatch_dict", unwatch_dict, METH_VARARGS, NULL}, + {"get_dict_watcher_events", 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}, + {"watch_type", watch_type, METH_VARARGS, NULL}, + {"unwatch_type", unwatch_type, METH_VARARGS, NULL}, + {"get_type_modified_events", get_type_modified_events, METH_NOARGS, NULL}, + {NULL}, +}; + +int +_PyTestCapi_Init_Watchers(PyObject *mod) +{ + if (PyModule_AddFunctions(mod, test_methods) < 0) { + return -1; + } + return 0; +} diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 2c21782dde16c8..01b39233c537e7 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -3390,159 +3390,6 @@ test_tstate_capi(PyObject *self, PyObject *Py_UNUSED(args)) Py_RETURN_NONE; } - -// Test dict watching -static PyObject *g_dict_watch_events; -static int g_dict_watchers_installed; - -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) { - 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) { - return -1; - } - if (PyList_Append(g_dict_watch_events, msg) < 0) { - Py_DECREF(msg); - return -1; - } - Py_DECREF(msg); - 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; -} - -static PyObject * -watch_dict(PyObject *self, PyObject *args) -{ - PyObject *dict; - int watcher_id; - if (!PyArg_ParseTuple(args, "iO", &watcher_id, &dict)) { - return NULL; - } - if (PyDict_Watch(watcher_id, dict)) { - return NULL; - } - Py_RETURN_NONE; -} - -static PyObject * -unwatch_dict(PyObject *self, PyObject *args) -{ - PyObject *dict; - int watcher_id; - if (!PyArg_ParseTuple(args, "iO", &watcher_id, &dict)) { - return NULL; - } - 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 PyFloat_Pack2(), PyFloat_Pack4() and PyFloat_Pack8() static PyObject * test_float_pack(PyObject *self, PyObject *args) @@ -3988,128 +3835,6 @@ function_set_kw_defaults(PyObject *self, PyObject *args) Py_RETURN_NONE; } - -// 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) { - 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); -} - -static PyObject * -watch_type(PyObject *self, PyObject *args) -{ - PyObject *type; - int watcher_id; - if (!PyArg_ParseTuple(args, "iO", &watcher_id, &type)) { - return NULL; - } - if (PyType_Watch(watcher_id, type)) { - return NULL; - } - Py_RETURN_NONE; -} - -static PyObject * -unwatch_type(PyObject *self, PyObject *args) -{ - PyObject *type; - int watcher_id; - if (!PyArg_ParseTuple(args, "iO", &watcher_id, &type)) { - return NULL; - } - if (PyType_Unwatch(watcher_id, type)) { - return NULL; - } - Py_RETURN_NONE; -} - - static PyObject *test_buildvalue_issue38913(PyObject *, PyObject *); static PyMethodDef TestMethods[] = { @@ -4259,11 +3984,6 @@ static PyMethodDef TestMethods[] = { {"settrace_to_record", settrace_to_record, METH_O, NULL}, {"test_macros", test_macros, METH_NOARGS, NULL}, {"clear_managed_dict", clear_managed_dict, METH_O, NULL}, - {"add_dict_watcher", add_dict_watcher, METH_O, NULL}, - {"clear_dict_watcher", clear_dict_watcher, METH_O, NULL}, - {"watch_dict", watch_dict, METH_VARARGS, NULL}, - {"unwatch_dict", unwatch_dict, METH_VARARGS, NULL}, - {"get_dict_watcher_events", get_dict_watcher_events, METH_NOARGS, NULL}, {"function_get_code", function_get_code, METH_O, NULL}, {"function_get_globals", function_get_globals, METH_O, NULL}, {"function_get_module", function_get_module, METH_O, NULL}, @@ -4271,11 +3991,6 @@ static PyMethodDef TestMethods[] = { {"function_set_defaults", function_set_defaults, METH_VARARGS, NULL}, {"function_get_kw_defaults", function_get_kw_defaults, METH_O, NULL}, {"function_set_kw_defaults", function_set_kw_defaults, METH_VARARGS, NULL}, - {"add_type_watcher", add_type_watcher, METH_O, NULL}, - {"clear_type_watcher", clear_type_watcher, METH_O, NULL}, - {"watch_type", watch_type, METH_VARARGS, NULL}, - {"unwatch_type", unwatch_type, METH_VARARGS, NULL}, - {"get_type_modified_events", get_type_modified_events, METH_NOARGS, NULL}, {NULL, NULL} /* sentinel */ }; @@ -5096,6 +4811,9 @@ PyInit__testcapi(void) if (_PyTestCapi_Init_Mem(m) < 0) { return NULL; } + if (_PyTestCapi_Init_Watchers(m) < 0) { + return NULL; + } #ifndef LIMITED_API_AVAILABLE PyModule_AddObjectRef(m, "LIMITED_API_AVAILABLE", Py_False); diff --git a/PCbuild/_testcapi.vcxproj b/PCbuild/_testcapi.vcxproj index 0151d85a27fe8d..42e7d30ac8816c 100644 --- a/PCbuild/_testcapi.vcxproj +++ b/PCbuild/_testcapi.vcxproj @@ -103,6 +103,7 @@ + diff --git a/PCbuild/_testcapi.vcxproj.filters b/PCbuild/_testcapi.vcxproj.filters index c30c41bf5ee48a..75652c383da341 100644 --- a/PCbuild/_testcapi.vcxproj.filters +++ b/PCbuild/_testcapi.vcxproj.filters @@ -39,6 +39,9 @@ Source Files + + Source Files + pFad - Phonifier reborn Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved. Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages. Alternative Proxies:Alternative ProxypFad ProxypFad v3 ProxypFad v4 Proxy
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy