1##############################################################################
2#
3# Copyright (c) Zope Foundation and Contributors.
4# All Rights Reserved.
5#
6# This software is subject to the provisions of the Zope Public License,
7# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
8# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
9# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
10# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
11# FOR A PARTICULAR PURPOSE.
12#
13##############################################################################
14import unittest
15
16
17from persistent.tests.utils import TrivialJar
18from persistent.tests.utils import copy_test
19
20# pylint:disable=blacklisted-name, protected-access
21
22class Test_default(unittest.TestCase):
23
24    def _getTargetClass(self):
25        from persistent.mapping import default
26        return default
27
28    def _makeOne(self, func):
29        return self._getTargetClass()(func)
30
31    def test___get___from_class(self):
32        def _test(inst):
33            raise AssertionError("Must not be caled")
34
35        descr = self._makeOne(_test)
36        class Foo(object):
37            testing = descr
38        self.assertIs(Foo.testing, descr)
39
40
41    def test___get___from_instance(self):
42        _called_with = []
43        def _test(inst):
44            _called_with.append(inst)
45            return 'TESTING'
46        descr = self._makeOne(_test)
47        class Foo(object):
48            testing = descr
49        foo = Foo()
50        self.assertEqual(foo.testing, 'TESTING')
51        self.assertEqual(_called_with, [foo])
52
53
54class PersistentMappingTests(unittest.TestCase):
55
56    def _getTargetClass(self):
57        from persistent.mapping import PersistentMapping
58        return PersistentMapping
59
60    def _makeJar(self):
61        return TrivialJar()
62
63    def _makeOne(self, *args, **kwargs):
64        inst = self._getTargetClass()(*args, **kwargs)
65        inst._p_jar = self._makeJar()
66        return inst
67
68    def test_volatile_attributes_not_persisted(self):
69        # http://www.zope.org/Collectors/Zope/2052
70        m = self._makeOne()
71        m.foo = 'bar'
72        m._v_baz = 'qux'
73        state = m.__getstate__()
74        self.assertTrue('foo' in state)
75        self.assertFalse('_v_baz' in state)
76
77    def testTheWorld(self):
78        # Test constructors
79        l0 = {}
80        l1 = {0:0}
81        l2 = {0:0, 1:1}
82        u = self._makeOne()
83        u0 = self._makeOne(l0)
84        u1 = self._makeOne(l1)
85        u2 = self._makeOne(l2)
86
87        uu = self._makeOne(u)
88        uu0 = self._makeOne(u0)
89        uu1 = self._makeOne(u1)
90        uu2 = self._makeOne(u2)
91
92        class OtherMapping(dict):
93            def __init__(self, initmapping):
94                dict.__init__(self)
95                self.__data = initmapping
96            def items(self):
97                raise AssertionError("Not called")
98        self._makeOne(OtherMapping(u0))
99        self._makeOne([(0, 0), (1, 1)])
100
101        # Test __repr__
102        eq = self.assertEqual
103
104        eq(str(u0), str(l0), "str(u0) == str(l0)")
105        eq(repr(u1), repr(l1), "repr(u1) == repr(l1)")
106
107        # Test __cmp__ and __len__
108        try:
109            cmp
110        except NameError:
111            def cmp(a, b):
112                if a == b:
113                    return 0
114                if hasattr(a, 'items'):
115                    a = sorted(a.items())
116                    b = sorted(b.items())
117                if a < b:
118                    return -1
119                return 1
120
121        def mycmp(a, b):
122            r = cmp(a, b)
123            if r < 0:
124                return -1
125            if r > 0:
126                return 1
127            return r
128
129        to_test = [l0, l1, l2, u, u0, u1, u2, uu, uu0, uu1, uu2]
130        for a in to_test:
131            for b in to_test:
132                eq(mycmp(a, b), mycmp(len(a), len(b)),
133                   "mycmp(a, b) == mycmp(len(a), len(b))")
134
135        # Test __getitem__
136
137        for i, val in enumerate(u2):
138            eq(val, i, "u2[i] == i")
139
140        # Test get
141
142        for i in range(len(u2)):
143            eq(u2.get(i), i, "u2.get(i) == i")
144            eq(u2.get(i, 5), i, "u2.get(i, 5) == i")
145
146        for i in min(u2)-1, max(u2)+1:
147            eq(u2.get(i), None, "u2.get(i) == None")
148            eq(u2.get(i, 5), 5, "u2.get(i, 5) == 5")
149
150        # Test __setitem__
151
152        uu2[0] = 0
153        uu2[1] = 100
154        uu2[2] = 200
155
156        # Test __delitem__
157
158        del uu2[1]
159        del uu2[0]
160        with self.assertRaises(KeyError):
161            del uu2[0]
162
163        # Test __contains__
164        for i in u2:
165            self.assertTrue(i in u2, "i in u2")
166        for i in min(u2)-1, max(u2)+1:
167            self.assertTrue(i not in u2, "i not in u2")
168
169        # Test update
170
171        l = {"a":"b"}
172        u = self._makeOne(l)
173        u.update(u2)
174        for i in u:
175            self.assertTrue(i in l or i in u2, "i in l or i in u2")
176        for i in l:
177            self.assertTrue(i in u, "i in u")
178        for i in u2:
179            self.assertTrue(i in u, "i in u")
180
181        # Test setdefault
182
183        x = u2.setdefault(0, 5)
184        eq(x, 0, "u2.setdefault(0, 5) == 0")
185
186        x = u2.setdefault(5, 5)
187        eq(x, 5, "u2.setdefault(5, 5) == 5")
188        self.assertTrue(5 in u2, "5 in u2")
189
190        # Test pop
191
192        x = u2.pop(1)
193        eq(x, 1, "u2.pop(1) == 1")
194        self.assertTrue(1 not in u2, "1 not in u2")
195
196        with self.assertRaises(KeyError):
197            u2.pop(1)
198
199        x = u2.pop(1, 7)
200        eq(x, 7, "u2.pop(1, 7) == 7")
201
202        # Test popitem
203
204        items = list(u2.items())
205        key, value = u2.popitem()
206        self.assertTrue((key, value) in items, "key, value in items")
207        self.assertTrue(key not in u2, "key not in u2")
208
209        # Test clear
210
211        u2.clear()
212        eq(u2, {}, "u2 == {}")
213
214    def test___repr___converts_legacy_container_attr(self):
215        # In the past, PM used a _container attribute. For some time, the
216        # implementation continued to use a _container attribute in pickles
217        # (__get/setstate__) to be compatible with older releases.  This isn't
218        # really necessary any more. In fact, releases for which this might
219        # matter can no longer share databases with current releases.  Because
220        # releases as recent as 3.9.0b5 still use _container in saved state, we
221        # need to accept such state, but we stop producing it.
222        pm = self._makeOne()
223        self.assertEqual(pm.__dict__, {'data': {}})
224        # Make it look like an older instance
225        pm.__dict__.clear()
226        pm.__dict__['_container'] = {'a': 1}
227        self.assertEqual(pm.__dict__, {'_container': {'a': 1}})
228        pm._p_changed = 0
229        self.assertEqual(repr(pm), "{'a': 1}")
230        self.assertEqual(pm.__dict__, {'data': {'a': 1}})
231        self.assertEqual(pm.__getstate__(), {'data': {'a': 1}})
232
233    def test_update_keywords(self):
234        # Prior to https://github.com/zopefoundation/persistent/issues/126,
235        # PersistentMapping didn't accept keyword arguments to update as
236        # the builtin dict and the UserDict do.
237        # Here we make sure it does. We use some names that have been
238        # seen to be special in signatures as well to make sure that
239        # we don't interpret them incorrectly.
240        pm = self._makeOne()
241        # Our older implementation was ``def update(self, b)``, so ``b``
242        # is potentially a keyword argument in the wild; the behaviour in that
243        # corner case has changed.
244        pm.update(b={'a': 42})
245        self.assertEqual(pm, {'b': {'a': 42}})
246
247        pm = self._makeOne()
248        # Our previous implementation would explode with a TypeError
249        pm.update(b=42)
250        self.assertEqual(pm, {'b': 42})
251
252        pm = self._makeOne()
253        # ``other`` shows up in a Python 3 signature.
254        pm.update(other=42)
255        self.assertEqual(pm, {'other': 42})
256        pm = self._makeOne()
257        pm.update(other={'a': 42})
258        self.assertEqual(pm, {'other': {'a': 42}})
259
260        pm = self._makeOne()
261        pm.update(a=1, b=2)
262        self.assertEqual(pm, {'a': 1, 'b': 2})
263
264    def test_clear_nonempty(self):
265        pm = self._makeOne({'a': 42})
266        self.assertFalse(pm._p_changed)
267        pm.clear()
268        self.assertTrue(pm._p_changed)
269
270    def test_clear_empty(self):
271        pm = self._makeOne()
272        self.assertFalse(pm._p_changed)
273        pm.clear()
274        self.assertFalse(pm._p_changed)
275
276    def test_clear_no_jar(self):
277        # https://github.com/zopefoundation/persistent/issues/139
278        self._makeOne = self._getTargetClass()
279        self.test_clear_empty()
280
281        pm = self._makeOne(a=42)
282        pm.clear()
283        self.assertFalse(pm._p_changed)
284
285    def test_clear_empty_legacy_container(self):
286        pm = self._makeOne()
287        pm.__dict__['_container'] = pm.__dict__.pop('data')
288        self.assertFalse(pm._p_changed)
289        pm.clear()
290        # Migration happened
291        self.assertIn('data', pm.__dict__)
292        # and we are marked as changed.
293        self.assertTrue(pm._p_changed)
294
295    def test_copy(self):
296        pm = self._makeOne()
297        pm['key'] = 42
298        copy = copy_test(self, pm)
299        self.assertEqual(42, copy['key'])
300
301    def test_copy_legacy_container(self):
302        pm = self._makeOne()
303        pm['key'] = 42
304        pm.__dict__['_container'] = pm.__dict__.pop('data')
305
306        self.assertNotIn('data', pm.__dict__)
307        self.assertIn('_container', pm.__dict__)
308
309        copy = copy_test(self, pm)
310        self.assertNotIn('_container', copy.__dict__)
311        self.assertIn('data', copy.__dict__)
312        self.assertEqual(42, copy['key'])
313
314
315class Test_legacy_PersistentDict(unittest.TestCase):
316
317    def _getTargetClass(self):
318        from persistent.dict import PersistentDict
319        return PersistentDict
320
321    def test_PD_is_alias_to_PM(self):
322        from persistent.mapping import PersistentMapping
323        self.assertIs(self._getTargetClass(), PersistentMapping)
324
325
326def test_suite():
327    return unittest.defaultTestLoader.loadTestsFromName(__name__)
328