1"""
2Test implementation of the PEP 509: dictionary versioning.
3"""
4import unittest
5from test.support import import_helper
6
7# PEP 509 is implemented in CPython but other Python implementations
8# don't require to implement it
9_testcapi = import_helper.import_module('_testcapi')
10
11
12class DictVersionTests(unittest.TestCase):
13    type2test = dict
14
15    def setUp(self):
16        self.seen_versions = set()
17        self.dict = None
18
19    def check_version_unique(self, mydict):
20        version = _testcapi.dict_get_version(mydict)
21        self.assertNotIn(version, self.seen_versions)
22        self.seen_versions.add(version)
23
24    def check_version_changed(self, mydict, method, *args, **kw):
25        result = method(*args, **kw)
26        self.check_version_unique(mydict)
27        return result
28
29    def check_version_dont_change(self, mydict, method, *args, **kw):
30        version1 = _testcapi.dict_get_version(mydict)
31        self.seen_versions.add(version1)
32
33        result = method(*args, **kw)
34
35        version2 = _testcapi.dict_get_version(mydict)
36        self.assertEqual(version2, version1, "version changed")
37
38        return  result
39
40    def new_dict(self, *args, **kw):
41        d = self.type2test(*args, **kw)
42        self.check_version_unique(d)
43        return d
44
45    def test_constructor(self):
46        # new empty dictionaries must all have an unique version
47        empty1 = self.new_dict()
48        empty2 = self.new_dict()
49        empty3 = self.new_dict()
50
51        # non-empty dictionaries must also have an unique version
52        nonempty1 = self.new_dict(x='x')
53        nonempty2 = self.new_dict(x='x', y='y')
54
55    def test_copy(self):
56        d = self.new_dict(a=1, b=2)
57
58        d2 = self.check_version_dont_change(d, d.copy)
59
60        # dict.copy() must create a dictionary with a new unique version
61        self.check_version_unique(d2)
62
63    def test_setitem(self):
64        d = self.new_dict()
65
66        # creating new keys must change the version
67        self.check_version_changed(d, d.__setitem__, 'x', 'x')
68        self.check_version_changed(d, d.__setitem__, 'y', 'y')
69
70        # changing values must change the version
71        self.check_version_changed(d, d.__setitem__, 'x', 1)
72        self.check_version_changed(d, d.__setitem__, 'y', 2)
73
74    def test_setitem_same_value(self):
75        value = object()
76        d = self.new_dict()
77
78        # setting a key must change the version
79        self.check_version_changed(d, d.__setitem__, 'key', value)
80
81        # setting a key to the same value with dict.__setitem__
82        # must change the version
83        self.check_version_dont_change(d, d.__setitem__, 'key', value)
84
85        # setting a key to the same value with dict.update
86        # must change the version
87        self.check_version_dont_change(d, d.update, key=value)
88
89        d2 = self.new_dict(key=value)
90        self.check_version_dont_change(d, d.update, d2)
91
92    def test_setitem_equal(self):
93        class AlwaysEqual:
94            def __eq__(self, other):
95                return True
96
97        value1 = AlwaysEqual()
98        value2 = AlwaysEqual()
99        self.assertTrue(value1 == value2)
100        self.assertFalse(value1 != value2)
101        self.assertIsNot(value1, value2)
102
103        d = self.new_dict()
104        self.check_version_changed(d, d.__setitem__, 'key', value1)
105        self.assertIs(d['key'], value1)
106
107        # setting a key to a value equal to the current value
108        # with dict.__setitem__() must change the version
109        self.check_version_changed(d, d.__setitem__, 'key', value2)
110        self.assertIs(d['key'], value2)
111
112        # setting a key to a value equal to the current value
113        # with dict.update() must change the version
114        self.check_version_changed(d, d.update, key=value1)
115        self.assertIs(d['key'], value1)
116
117        d2 = self.new_dict(key=value2)
118        self.check_version_changed(d, d.update, d2)
119        self.assertIs(d['key'], value2)
120
121    def test_setdefault(self):
122        d = self.new_dict()
123
124        # setting a key with dict.setdefault() must change the version
125        self.check_version_changed(d, d.setdefault, 'key', 'value1')
126
127        # don't change the version if the key already exists
128        self.check_version_dont_change(d, d.setdefault, 'key', 'value2')
129
130    def test_delitem(self):
131        d = self.new_dict(key='value')
132
133        # deleting a key with dict.__delitem__() must change the version
134        self.check_version_changed(d, d.__delitem__, 'key')
135
136        # don't change the version if the key doesn't exist
137        self.check_version_dont_change(d, self.assertRaises, KeyError,
138                                       d.__delitem__, 'key')
139
140    def test_pop(self):
141        d = self.new_dict(key='value')
142
143        # pop() must change the version if the key exists
144        self.check_version_changed(d, d.pop, 'key')
145
146        # pop() must not change the version if the key does not exist
147        self.check_version_dont_change(d, self.assertRaises, KeyError,
148                                       d.pop, 'key')
149
150    def test_popitem(self):
151        d = self.new_dict(key='value')
152
153        # popitem() must change the version if the dict is not empty
154        self.check_version_changed(d, d.popitem)
155
156        # popitem() must not change the version if the dict is empty
157        self.check_version_dont_change(d, self.assertRaises, KeyError,
158                                       d.popitem)
159
160    def test_update(self):
161        d = self.new_dict(key='value')
162
163        # update() calling with no argument must not change the version
164        self.check_version_dont_change(d, d.update)
165
166        # update() must change the version
167        self.check_version_changed(d, d.update, key='new value')
168
169        d2 = self.new_dict(key='value 3')
170        self.check_version_changed(d, d.update, d2)
171
172    def test_clear(self):
173        d = self.new_dict(key='value')
174
175        # clear() must change the version if the dict is not empty
176        self.check_version_changed(d, d.clear)
177
178        # clear() must not change the version if the dict is empty
179        self.check_version_dont_change(d, d.clear)
180
181
182class Dict(dict):
183    pass
184
185
186class DictSubtypeVersionTests(DictVersionTests):
187    type2test = Dict
188
189
190if __name__ == "__main__":
191    unittest.main()
192