1"""
2Test implementation of the PEP 509: dictionary versionning.
3"""
4import unittest
5from test import support
6
7# PEP 509 is implemented in CPython but other Python implementations
8# don't require to implement it
9_testcapi = support.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_changed(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_changed(d, d.update, key=value)
88
89        d2 = self.new_dict(key=value)
90        self.check_version_changed(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
102        d = self.new_dict()
103        self.check_version_changed(d, d.__setitem__, 'key', value1)
104
105        # setting a key to a value equal to the current value
106        # with dict.__setitem__() must change the version
107        self.check_version_changed(d, d.__setitem__, 'key', value2)
108
109        # setting a key to a value equal to the current value
110        # with dict.update() must change the version
111        self.check_version_changed(d, d.update, key=value1)
112
113        d2 = self.new_dict(key=value2)
114        self.check_version_changed(d, d.update, d2)
115
116    def test_setdefault(self):
117        d = self.new_dict()
118
119        # setting a key with dict.setdefault() must change the version
120        self.check_version_changed(d, d.setdefault, 'key', 'value1')
121
122        # don't change the version if the key already exists
123        self.check_version_dont_change(d, d.setdefault, 'key', 'value2')
124
125    def test_delitem(self):
126        d = self.new_dict(key='value')
127
128        # deleting a key with dict.__delitem__() must change the version
129        self.check_version_changed(d, d.__delitem__, 'key')
130
131        # don't change the version if the key doesn't exist
132        self.check_version_dont_change(d, self.assertRaises, KeyError,
133                                       d.__delitem__, 'key')
134
135    def test_pop(self):
136        d = self.new_dict(key='value')
137
138        # pop() must change the version if the key exists
139        self.check_version_changed(d, d.pop, 'key')
140
141        # pop() must not change the version if the key does not exist
142        self.check_version_dont_change(d, self.assertRaises, KeyError,
143                                       d.pop, 'key')
144
145    def test_popitem(self):
146        d = self.new_dict(key='value')
147
148        # popitem() must change the version if the dict is not empty
149        self.check_version_changed(d, d.popitem)
150
151        # popitem() must not change the version if the dict is empty
152        self.check_version_dont_change(d, self.assertRaises, KeyError,
153                                       d.popitem)
154
155    def test_update(self):
156        d = self.new_dict(key='value')
157
158        # update() calling with no argument must not change the version
159        self.check_version_dont_change(d, d.update)
160
161        # update() must change the version
162        self.check_version_changed(d, d.update, key='new value')
163
164        d2 = self.new_dict(key='value 3')
165        self.check_version_changed(d, d.update, d2)
166
167    def test_clear(self):
168        d = self.new_dict(key='value')
169
170        # clear() must change the version if the dict is not empty
171        self.check_version_changed(d, d.clear)
172
173        # clear() must not change the version if the dict is empty
174        self.check_version_dont_change(d, d.clear)
175
176
177class Dict(dict):
178    pass
179
180
181class DictSubtypeVersionTests(DictVersionTests):
182    type2test = Dict
183
184
185if __name__ == "__main__":
186    unittest.main()
187