@@ -5169,6 +5169,206 @@ test_tstate_capi(PyObject *self, PyObject *Py_UNUSED(args))
51695169}
51705170
51715171
5172+ // Test dict watching
5173+ static PyObject * g_dict_watch_events ;
5174+
5175+ static void
5176+ dict_watch_callback (PyDict_WatchEvent event ,
5177+ PyObject * dict ,
5178+ PyObject * key ,
5179+ PyObject * new_value )
5180+ {
5181+ PyObject * msg ;
5182+ switch (event ) {
5183+ case PyDict_EVENT_CLEARED :
5184+ msg = PyUnicode_FromString ("clear" );
5185+ break ;
5186+ case PyDict_EVENT_DEALLOCED :
5187+ msg = PyUnicode_FromString ("dealloc" );
5188+ break ;
5189+ case PyDict_EVENT_CLONED :
5190+ msg = PyUnicode_FromString ("clone" );
5191+ break ;
5192+ case PyDict_EVENT_ADDED :
5193+ msg = PyUnicode_FromFormat ("new:%S:%S" , key , new_value );
5194+ break ;
5195+ case PyDict_EVENT_MODIFIED :
5196+ msg = PyUnicode_FromFormat ("mod:%S:%S" , key , new_value );
5197+ break ;
5198+ case PyDict_EVENT_DELETED :
5199+ msg = PyUnicode_FromFormat ("del:%S" , key );
5200+ break ;
5201+ default :
5202+ msg = PyUnicode_FromString ("unknown" );
5203+ }
5204+ assert (PyList_Check (g_dict_watch_events ));
5205+ PyList_Append (g_dict_watch_events , msg );
5206+ }
5207+
5208+ static int
5209+ dict_watch_assert (Py_ssize_t expected_num_events ,
5210+ const char * expected_last_msg )
5211+ {
5212+ char buf [512 ];
5213+ Py_ssize_t actual_num_events = PyList_Size (g_dict_watch_events );
5214+ if (expected_num_events != actual_num_events ) {
5215+ snprintf (buf ,
5216+ 512 ,
5217+ "got %d dict watch events, expected %d" ,
5218+ (int )actual_num_events ,
5219+ (int )expected_num_events );
5220+ raiseTestError ("test_watch_dict" , (const char * )& buf );
5221+ return -1 ;
5222+ }
5223+ PyObject * last_msg = PyList_GetItem (g_dict_watch_events ,
5224+ PyList_Size (g_dict_watch_events )- 1 );
5225+ if (PyUnicode_CompareWithASCIIString (last_msg , expected_last_msg )) {
5226+ snprintf (buf ,
5227+ 512 ,
5228+ "last event is '%s', expected '%s'" ,
5229+ PyUnicode_AsUTF8 (last_msg ),
5230+ expected_last_msg );
5231+ raiseTestError ("test_watch_dict" , (const char * )& buf );
5232+ return -1 ;
5233+ }
5234+ return 0 ;
5235+ }
5236+
5237+ static int
5238+ try_watch (int watcher_id , PyObject * obj ) {
5239+ if (PyDict_Watch (watcher_id , obj )) {
5240+ raiseTestError ("test_watch_dict" , "PyDict_Watch() failed on dict" );
5241+ return -1 ;
5242+ }
5243+ return 0 ;
5244+ }
5245+
5246+ static int
5247+ dict_watch_assert_error (int watcher_id , PyObject * obj , const char * fail_msg )
5248+ {
5249+ if (!PyDict_Watch (watcher_id , obj )) {
5250+ raiseTestError ("test_watch_dict" , fail_msg );
5251+ return -1 ;
5252+ } else if (!PyErr_Occurred ()) {
5253+ raiseTestError ("test_watch_dict" , "PyDict_Watch() returned error code without exception set" );
5254+ return -1 ;
5255+ } else {
5256+ PyErr_Clear ();
5257+ }
5258+ return 0 ;
5259+ }
5260+
5261+ static PyObject *
5262+ test_watch_dict (PyObject * self , PyObject * Py_UNUSED (args ))
5263+ {
5264+ PyObject * watched = PyDict_New ();
5265+ PyObject * unwatched = PyDict_New ();
5266+ PyObject * one = PyLong_FromLong (1 );
5267+ PyObject * two = PyLong_FromLong (2 );
5268+ PyObject * key1 = PyUnicode_FromString ("key1" );
5269+ PyObject * key2 = PyUnicode_FromString ("key2" );
5270+
5271+ g_dict_watch_events = PyList_New (0 );
5272+
5273+ int wid = PyDict_AddWatcher (dict_watch_callback );
5274+ if (try_watch (wid , watched )) {
5275+ return NULL ;
5276+ }
5277+
5278+ PyDict_SetItem (unwatched , key1 , two );
5279+ PyDict_Merge (watched , unwatched , 1 );
5280+
5281+ if (dict_watch_assert (1 , "clone" )) {
5282+ return NULL ;
5283+ }
5284+
5285+ PyDict_SetItem (watched , key1 , one );
5286+ PyDict_SetItem (unwatched , key1 , one );
5287+
5288+ if (dict_watch_assert (2 , "mod:key1:1" )) {
5289+ return NULL ;
5290+ }
5291+
5292+ PyDict_SetItemString (watched , "key1" , two );
5293+ PyDict_SetItemString (unwatched , "key1" , two );
5294+
5295+ if (dict_watch_assert (3 , "mod:key1:2" )) {
5296+ return NULL ;
5297+ }
5298+
5299+ PyDict_SetItem (watched , key2 , one );
5300+ PyDict_SetItem (unwatched , key2 , one );
5301+
5302+ if (dict_watch_assert (4 , "new:key2:1" )) {
5303+ return NULL ;
5304+ }
5305+
5306+ _PyDict_Pop (watched , key2 , Py_None );
5307+ _PyDict_Pop (unwatched , key2 , Py_None );
5308+
5309+ if (dict_watch_assert (5 , "del:key2" )) {
5310+ return NULL ;
5311+ }
5312+
5313+ PyDict_DelItemString (watched , "key1" );
5314+ PyDict_DelItemString (unwatched , "key1" );
5315+
5316+ if (dict_watch_assert (6 , "del:key1" )) {
5317+ return NULL ;
5318+ }
5319+
5320+ PyDict_SetDefault (watched , key1 , one );
5321+ PyDict_SetDefault (unwatched , key1 , one );
5322+
5323+ if (dict_watch_assert (7 , "new:key1:1" )) {
5324+ return NULL ;
5325+ }
5326+
5327+ PyDict_Clear (watched );
5328+ PyDict_Clear (unwatched );
5329+
5330+ if (dict_watch_assert (8 , "clear" )) {
5331+ return NULL ;
5332+ }
5333+
5334+ PyObject * copy = PyDict_Copy (watched );
5335+ // copied dict is not watched, so this does not add an event
5336+ Py_CLEAR (copy );
5337+
5338+ Py_CLEAR (watched );
5339+
5340+ if (dict_watch_assert (9 , "dealloc" )) {
5341+ return NULL ;
5342+ }
5343+
5344+ // it is an error to try to watch a non-dict
5345+ if (dict_watch_assert_error (wid , one , "PyDict_Watch() succeeded on non-dict" )) {
5346+ return NULL ;
5347+ }
5348+
5349+ // It is an error to pass an out-of-range watcher ID
5350+ if (dict_watch_assert_error (-1 , unwatched , "PyDict_Watch() succeeded on negative watcher ID" )) {
5351+ return NULL ;
5352+ }
5353+ if (dict_watch_assert_error (8 , unwatched , "PyDict_Watch() succeeded on too-large watcher ID" )) {
5354+ return NULL ;
5355+ }
5356+
5357+ // It is an error to pass a never-registered watcher ID
5358+ if (dict_watch_assert_error (7 , unwatched , "PyDict_Watch() succeeded on unused watcher ID" )) {
5359+ return NULL ;
5360+ }
5361+
5362+ Py_CLEAR (unwatched );
5363+ Py_CLEAR (g_dict_watch_events );
5364+ Py_DECREF (one );
5365+ Py_DECREF (two );
5366+ Py_DECREF (key1 );
5367+ Py_DECREF (key2 );
5368+ Py_RETURN_NONE ;
5369+ }
5370+
5371+
51725372// Test PyFloat_Pack2(), PyFloat_Pack4() and PyFloat_Pack8()
51735373static PyObject *
51745374test_float_pack (PyObject * self , PyObject * args )
@@ -5762,6 +5962,7 @@ static PyMethodDef TestMethods[] = {
57625962 {"settrace_to_record" , settrace_to_record , METH_O , NULL },
57635963 {"test_macros" , test_macros , METH_NOARGS , NULL },
57645964 {"clear_managed_dict" , clear_managed_dict , METH_O , NULL },
5965+ {"test_watch_dict" , test_watch_dict , METH_NOARGS , NULL },
57655966 {NULL , NULL } /* sentinel */
57665967};
57675968
0 commit comments