@@ -5775,6 +5775,218 @@ test_tstate_capi(PyObject *self, PyObject *Py_UNUSED(args))
5775
5775
}
5776
5776
5777
5777
5778
+ // Test dict watching
5779
+ static PyObject * g_dict_watch_events ;
5780
+ static PyDict_WatchCallback g_prev_callback ;
5781
+
5782
+ static void
5783
+ dict_watch_callback (PyDict_WatchEvent event ,
5784
+ PyObject * dict ,
5785
+ PyObject * key ,
5786
+ PyObject * new_value )
5787
+ {
5788
+ PyObject * msg ;
5789
+ switch (event ) {
5790
+ case PyDict_EVENT_CLEARED :
5791
+ msg = PyUnicode_FromString ("clear" );
5792
+ break ;
5793
+ case PyDict_EVENT_DEALLOCED :
5794
+ msg = PyUnicode_FromString ("dealloc" );
5795
+ break ;
5796
+ case PyDict_EVENT_CLONED :
5797
+ msg = PyUnicode_FromString ("clone" );
5798
+ break ;
5799
+ case PyDict_EVENT_ADDED :
5800
+ msg = PyUnicode_FromFormat ("new:%S:%S" , key , new_value );
5801
+ break ;
5802
+ case PyDict_EVENT_MODIFIED :
5803
+ msg = PyUnicode_FromFormat ("mod:%S:%S" , key , new_value );
5804
+ break ;
5805
+ case PyDict_EVENT_DELETED :
5806
+ msg = PyUnicode_FromFormat ("del:%S" , key );
5807
+ break ;
5808
+ default :
5809
+ msg = PyUnicode_FromString ("unknown" );
5810
+ }
5811
+ assert (PyList_Check (g_dict_watch_events ));
5812
+ PyList_Append (g_dict_watch_events , msg );
5813
+ if (g_prev_callback != NULL ) {
5814
+ g_prev_callback (event , dict , key , new_value );
5815
+ }
5816
+ }
5817
+
5818
+ static int
5819
+ dict_watch_assert (Py_ssize_t expected_num_events ,
5820
+ const char * expected_last_msg )
5821
+ {
5822
+ char buf [512 ];
5823
+ Py_ssize_t actual_num_events = PyList_Size (g_dict_watch_events );
5824
+ if (expected_num_events != actual_num_events ) {
5825
+ snprintf (buf ,
5826
+ 512 ,
5827
+ "got %d dict watch events, expected %d" ,
5828
+ (int )actual_num_events ,
5829
+ (int )expected_num_events );
5830
+ raiseTestError ("test_watch_dict" , (const char * )& buf );
5831
+ return -1 ;
5832
+ }
5833
+ PyObject * last_msg = PyList_GetItem (g_dict_watch_events ,
5834
+ PyList_Size (g_dict_watch_events )- 1 );
5835
+ if (PyUnicode_CompareWithASCIIString (last_msg , expected_last_msg )) {
5836
+ snprintf (buf ,
5837
+ 512 ,
5838
+ "last event is '%s', expected '%s'" ,
5839
+ PyUnicode_AsUTF8 (last_msg ),
5840
+ expected_last_msg );
5841
+ raiseTestError ("test_watch_dict" , (const char * )& buf );
5842
+ return -1 ;
5843
+ }
5844
+ return 0 ;
5845
+ }
5846
+
5847
+ static int
5848
+ try_watch (PyObject * obj ) {
5849
+ if (PyDict_Watch (obj )) {
5850
+ raiseTestError ("test_watch_dict" , "PyDict_Watch() failed on dict" );
5851
+ return -1 ;
5852
+ }
5853
+ return 0 ;
5854
+ }
5855
+
5856
+ static PyObject *
5857
+ test_watch_dict (PyObject * self , PyObject * Py_UNUSED (args ))
5858
+ {
5859
+ PyObject * watched = PyDict_New ();
5860
+ PyObject * unwatched = PyDict_New ();
5861
+ PyObject * one = PyLong_FromLong (1 );
5862
+ PyObject * two = PyLong_FromLong (2 );
5863
+ PyObject * key1 = PyUnicode_FromString ("key1" );
5864
+ PyObject * key2 = PyUnicode_FromString ("key2" );
5865
+
5866
+ g_dict_watch_events = PyList_New (0 );
5867
+ g_prev_callback = PyDict_GetWatchCallback ();
5868
+
5869
+ PyDict_SetWatchCallback (dict_watch_callback );
5870
+ if (PyDict_GetWatchCallback () != dict_watch_callback ) {
5871
+ return raiseTestError ("test_watch_dict" , "GetWatchCallback did not return set callback" );
5872
+ }
5873
+ if (try_watch (watched )) {
5874
+ return NULL ;
5875
+ }
5876
+
5877
+ if (!PyDict_IsWatched (watched )) {
5878
+ return raiseTestError ("test_watch_dict" , "IsWatched returned false for watched dict" );
5879
+ }
5880
+ if (PyDict_IsWatched (unwatched )) {
5881
+ return raiseTestError ("test_watch_dict" , "IsWatched returned true for unwatched dict" );
5882
+ }
5883
+
5884
+ PyDict_SetItem (unwatched , key1 , two );
5885
+ PyDict_Merge (watched , unwatched , 1 );
5886
+
5887
+ if (dict_watch_assert (1 , "clone" )) {
5888
+ return NULL ;
5889
+ }
5890
+
5891
+ PyDict_SetItem (watched , key1 , one );
5892
+ PyDict_SetItem (unwatched , key1 , one );
5893
+
5894
+ if (dict_watch_assert (2 , "mod:key1:1" )) {
5895
+ return NULL ;
5896
+ }
5897
+
5898
+ PyDict_SetItemString (watched , "key1" , two );
5899
+ PyDict_SetItemString (unwatched , "key1" , two );
5900
+
5901
+ if (dict_watch_assert (3 , "mod:key1:2" )) {
5902
+ return NULL ;
5903
+ }
5904
+
5905
+ PyDict_SetItem (watched , key2 , one );
5906
+ PyDict_SetItem (unwatched , key2 , one );
5907
+
5908
+ if (dict_watch_assert (4 , "new:key2:1" )) {
5909
+ return NULL ;
5910
+ }
5911
+
5912
+ _PyDict_Pop (watched , key2 , Py_None );
5913
+ _PyDict_Pop (unwatched , key2 , Py_None );
5914
+
5915
+ if (dict_watch_assert (5 , "del:key2" )) {
5916
+ return NULL ;
5917
+ }
5918
+
5919
+ PyDict_DelItemString (watched , "key1" );
5920
+ PyDict_DelItemString (unwatched , "key1" );
5921
+
5922
+ if (dict_watch_assert (6 , "del:key1" )) {
5923
+ return NULL ;
5924
+ }
5925
+
5926
+ PyDict_SetDefault (watched , key1 , one );
5927
+ PyDict_SetDefault (unwatched , key1 , one );
5928
+
5929
+ if (dict_watch_assert (7 , "new:key1:1" )) {
5930
+ return NULL ;
5931
+ }
5932
+
5933
+ PyDict_Clear (watched );
5934
+ PyDict_Clear (unwatched );
5935
+
5936
+ if (dict_watch_assert (8 , "clear" )) {
5937
+ return NULL ;
5938
+ }
5939
+
5940
+ PyObject * copy = PyDict_Copy (watched );
5941
+ if (PyDict_IsWatched (copy )) {
5942
+ return raiseTestError ("test_watch_dict" , "copying a watched dict should not watch the copy" );
5943
+ }
5944
+ Py_CLEAR (copy );
5945
+
5946
+ Py_CLEAR (watched );
5947
+ Py_CLEAR (unwatched );
5948
+
5949
+ if (dict_watch_assert (9 , "dealloc" )) {
5950
+ return NULL ;
5951
+ }
5952
+
5953
+ PyDict_SetWatchCallback (g_prev_callback );
5954
+ g_prev_callback = NULL ;
5955
+
5956
+ // no events after callback unset
5957
+ watched = PyDict_New ();
5958
+ if (try_watch (watched )) {
5959
+ return NULL ;
5960
+ }
5961
+
5962
+ PyDict_SetItem (watched , key1 , one );
5963
+ Py_CLEAR (watched );
5964
+
5965
+ if (dict_watch_assert (9 , "dealloc" )) {
5966
+ return NULL ;
5967
+ }
5968
+
5969
+ // it is an error to try to watch a non-dict
5970
+ if (!PyDict_Watch (one )) {
5971
+ raiseTestError ("test_watch_dict" , "PyDict_Watch() succeeded on non-dict" );
5972
+ return NULL ;
5973
+ } else if (!PyErr_Occurred ()) {
5974
+ raiseTestError ("test_watch_dict" , "PyDict_Watch() returned error code without exception set" );
5975
+ return NULL ;
5976
+ } else {
5977
+ PyErr_Clear ();
5978
+ }
5979
+
5980
+
5981
+ Py_CLEAR (g_dict_watch_events );
5982
+ Py_DECREF (one );
5983
+ Py_DECREF (two );
5984
+ Py_DECREF (key1 );
5985
+ Py_DECREF (key2 );
5986
+ Py_RETURN_NONE ;
5987
+ }
5988
+
5989
+
5778
5990
static PyObject * negative_dictoffset (PyObject * , PyObject * );
5779
5991
static PyObject * test_buildvalue_issue38913 (PyObject * , PyObject * );
5780
5992
static PyObject * getargs_s_hash_int (PyObject * , PyObject * , PyObject * );
@@ -6061,6 +6273,7 @@ static PyMethodDef TestMethods[] = {
6061
6273
PyDoc_STR ("fatal_error(message, release_gil=False): call Py_FatalError(message)" )},
6062
6274
{"type_get_version" , type_get_version , METH_O , PyDoc_STR ("type->tp_version_tag" )},
6063
6275
{"test_tstate_capi" , test_tstate_capi , METH_NOARGS , NULL },
6276
+ {"test_watch_dict" , test_watch_dict , METH_NOARGS , NULL },
6064
6277
{NULL , NULL } /* sentinel */
6065
6278
};
6066
6279
0 commit comments