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