1
# Copyright (C) 2009 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
"""Tests for the StaticTupleInterned type."""
28
from bzrlib import _simple_set_pyx
30
_simple_set_pyx = None
33
class _Hashable(object):
34
"""A simple object which has a fixed hash value.
36
We could have used an 'int', but it turns out that Int objects don't
37
implement tp_richcompare...
40
def __init__(self, the_hash):
46
def __eq__(self, other):
47
if not isinstance(other, _Hashable):
49
return other.hash == self.hash
52
class _BadSecondHash(_Hashable):
54
def __init__(self, the_hash):
55
_Hashable.__init__(self, the_hash)
63
raise ValueError('I can only be hashed once.')
66
class _BadCompare(_Hashable):
68
def __eq__(self, other):
69
raise RuntimeError('I refuse to play nice')
72
# Even though this is an extension, we don't permute the tests for a python
73
# version. As the plain python version is just a dict or set
75
class _CompiledSimpleSet(tests.Feature):
78
if _simple_set_pyx is None:
82
def feature_name(self):
83
return 'bzrlib._simple_set_pyx'
85
CompiledSimpleSet = _CompiledSimpleSet()
88
class TestSimpleSet(tests.TestCase):
90
_test_needs_features = [CompiledSimpleSet]
91
module = _simple_set_pyx
93
def assertIn(self, obj, container):
94
self.assertTrue(obj in container,
95
'%s not found in %s' % (obj, container))
97
def assertNotIn(self, obj, container):
98
self.assertTrue(obj not in container,
99
'We found %s in %s' % (obj, container))
101
def assertFillState(self, used, fill, mask, obj):
102
self.assertEqual((used, fill, mask), (obj.used, obj.fill, obj.mask))
104
def assertLookup(self, offset, value, obj, key):
105
self.assertEqual((offset, value), obj._test_lookup(key))
107
def assertRefcount(self, count, obj):
108
"""Assert that the refcount for obj is what we expect.
110
Note that this automatically adjusts for the fact that calling
111
assertRefcount actually creates a new pointer, as does calling
112
sys.getrefcount. So pass the expected value *before* the call.
114
# I'm not sure why the offset is 3, but I've check that in the caller,
115
# an offset of 1 works, which is expected. Not sure why assertRefcount
116
# is incrementing/decrementing 2 times
117
self.assertEqual(count, sys.getrefcount(obj)-3)
119
def test_initial(self):
120
obj = self.module.SimpleSet()
121
self.assertEqual(0, len(obj))
123
self.assertFillState(0, 0, 0x3ff, obj)
125
def test__lookup(self):
126
# These are carefully chosen integers to force hash collisions in the
127
# algorithm, based on the initial set size of 1024
128
obj = self.module.SimpleSet()
129
self.assertLookup(643, '<null>', obj, _Hashable(643))
130
self.assertLookup(643, '<null>', obj, _Hashable(643 + 1024))
131
self.assertLookup(643, '<null>', obj, _Hashable(643 + 50*1024))
133
def test__lookup_collision(self):
134
obj = self.module.SimpleSet()
136
k2 = _Hashable(643 + 1024)
137
self.assertLookup(643, '<null>', obj, k1)
138
self.assertLookup(643, '<null>', obj, k2)
140
self.assertLookup(643, k1, obj, k1)
141
self.assertLookup(644, '<null>', obj, k2)
143
def test__lookup_after_resize(self):
144
obj = self.module.SimpleSet()
146
k2 = _Hashable(643 + 1024)
149
self.assertLookup(643, k1, obj, k1)
150
self.assertLookup(644, k2, obj, k2)
151
obj._py_resize(2047) # resized to 2048
152
self.assertEqual(2048, obj.mask + 1)
153
self.assertLookup(643, k1, obj, k1)
154
self.assertLookup(643+1024, k2, obj, k2)
155
obj._py_resize(1023) # resized back to 1024
156
self.assertEqual(1024, obj.mask + 1)
157
self.assertLookup(643, k1, obj, k1)
158
self.assertLookup(644, k2, obj, k2)
160
def test_get_set_del_with_collisions(self):
161
obj = self.module.SimpleSet()
176
self.assertLookup(643, '<null>', obj, k1)
177
self.assertLookup(643, '<null>', obj, k2)
178
self.assertLookup(643, '<null>', obj, k3)
179
self.assertLookup(643, '<null>', obj, k4)
180
self.assertLookup(644, '<null>', obj, k5)
181
self.assertLookup(644, '<null>', obj, k6)
183
self.assertIn(k1, obj)
184
self.assertNotIn(k2, obj)
185
self.assertNotIn(k3, obj)
186
self.assertNotIn(k4, obj)
187
self.assertLookup(643, k1, obj, k1)
188
self.assertLookup(644, '<null>', obj, k2)
189
self.assertLookup(644, '<null>', obj, k3)
190
self.assertLookup(644, '<null>', obj, k4)
191
self.assertLookup(644, '<null>', obj, k5)
192
self.assertLookup(644, '<null>', obj, k6)
193
self.assertIs(k1, obj[k1])
194
self.assertIs(k2, obj.add(k2))
195
self.assertIs(k2, obj[k2])
196
self.assertLookup(643, k1, obj, k1)
197
self.assertLookup(644, k2, obj, k2)
198
self.assertLookup(646, '<null>', obj, k3)
199
self.assertLookup(646, '<null>', obj, k4)
200
self.assertLookup(645, '<null>', obj, k5)
201
self.assertLookup(645, '<null>', obj, k6)
202
self.assertLookup(643, k1, obj, _Hashable(h1))
203
self.assertLookup(644, k2, obj, _Hashable(h2))
204
self.assertLookup(646, '<null>', obj, _Hashable(h3))
205
self.assertLookup(646, '<null>', obj, _Hashable(h4))
206
self.assertLookup(645, '<null>', obj, _Hashable(h5))
207
self.assertLookup(645, '<null>', obj, _Hashable(h6))
209
self.assertIs(k3, obj[k3])
210
self.assertIn(k1, obj)
211
self.assertIn(k2, obj)
212
self.assertIn(k3, obj)
213
self.assertNotIn(k4, obj)
216
self.assertLookup(643, '<dummy>', obj, k1)
217
self.assertLookup(644, k2, obj, k2)
218
self.assertLookup(646, k3, obj, k3)
219
self.assertLookup(643, '<dummy>', obj, k4)
220
self.assertNotIn(k1, obj)
221
self.assertIn(k2, obj)
222
self.assertIn(k3, obj)
223
self.assertNotIn(k4, obj)
226
obj = self.module.SimpleSet()
227
self.assertFillState(0, 0, 0x3ff, obj)
228
# We use this clumsy notation, because otherwise the refcounts are off.
229
# I'm guessing the python compiler sees it is a static tuple, and adds
230
# it to the function variables, or somesuch
232
self.assertRefcount(1, k1)
233
self.assertIs(k1, obj.add(k1))
234
self.assertFillState(1, 1, 0x3ff, obj)
235
self.assertRefcount(2, k1)
237
self.assertRefcount(3, k1)
238
self.assertIs(k1, ktest)
240
self.assertRefcount(2, k1)
242
self.assertRefcount(1, k2)
243
self.assertIsNot(k1, k2)
244
# doesn't add anything, so the counters shouldn't be adjusted
245
self.assertIs(k1, obj.add(k2))
246
self.assertFillState(1, 1, 0x3ff, obj)
247
self.assertRefcount(2, k1) # not changed
248
self.assertRefcount(1, k2) # not incremented
249
self.assertIs(k1, obj[k1])
250
self.assertIs(k1, obj[k2])
251
self.assertRefcount(2, k1)
252
self.assertRefcount(1, k2)
253
# Deleting an entry should remove the fill, but not the used
255
self.assertFillState(0, 1, 0x3ff, obj)
256
self.assertRefcount(1, k1)
258
self.assertRefcount(1, k3)
259
self.assertIs(k3, obj.add(k3))
260
self.assertFillState(1, 2, 0x3ff, obj)
261
self.assertRefcount(2, k3)
262
self.assertIs(k2, obj.add(k2))
263
self.assertFillState(2, 2, 0x3ff, obj)
264
self.assertRefcount(1, k1)
265
self.assertRefcount(2, k2)
266
self.assertRefcount(2, k3)
268
def test_discard(self):
269
obj = self.module.SimpleSet()
273
self.assertRefcount(1, k1)
274
self.assertRefcount(1, k2)
275
self.assertRefcount(1, k3)
277
self.assertRefcount(2, k1)
278
self.assertEqual(0, obj.discard(k3))
279
self.assertRefcount(1, k3)
281
self.assertRefcount(2, k3)
282
self.assertEqual(1, obj.discard(k3))
283
self.assertRefcount(1, k3)
285
def test__resize(self):
286
obj = self.module.SimpleSet()
294
self.assertFillState(2, 3, 0x3ff, obj)
295
self.assertEqual(1024, obj._py_resize(500))
296
# Doesn't change the size, but does change the content
297
self.assertFillState(2, 2, 0x3ff, obj)
300
self.assertFillState(2, 3, 0x3ff, obj)
301
self.assertEqual(4096, obj._py_resize(4095))
302
self.assertFillState(2, 2, 0xfff, obj)
303
self.assertIn(k1, obj)
304
self.assertIn(k2, obj)
305
self.assertNotIn(k3, obj)
307
self.assertIn(k2, obj)
309
self.assertEqual((591, '<dummy>'), obj._test_lookup(k2))
310
self.assertFillState(1, 2, 0xfff, obj)
311
self.assertEqual(2048, obj._py_resize(1024))
312
self.assertFillState(1, 1, 0x7ff, obj)
313
self.assertEqual((591, '<null>'), obj._test_lookup(k2))
315
def test_second_hash_failure(self):
316
obj = self.module.SimpleSet()
317
k1 = _BadSecondHash(200)
319
# Should only call hash() one time
321
self.assertFalse(k1._first)
322
self.assertRaises(ValueError, obj.add, k2)
324
def test_richcompare_failure(self):
325
obj = self.module.SimpleSet()
327
k2 = _BadCompare(200)
329
# Tries to compare with k1, fails
330
self.assertRaises(RuntimeError, obj.add, k2)
332
def test_add_and_remove_lots_of_items(self):
333
obj = self.module.SimpleSet()
334
chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890'
339
num = len(chars)*len(chars)
340
self.assertFillState(num, num, 0x1fff, obj)
341
# Now delete all of the entries and it should shrink again
346
# It should be back to 1024 wide mask, though there may still be some
347
# dummy values in there
348
self.assertFillState(0, obj.fill, 0x3ff, obj)
349
# but there should be fewer than 1/5th dummy entries
350
self.assertTrue(obj.fill < 1024 / 5)
352
def test__iter__(self):
353
obj = self.module.SimpleSet()
363
self.assertEqual(sorted([k1, k2, k3]), sorted(all))
368
self.assertRaises(RuntimeError, iterator.next)
369
# And even removing an item still causes it to fail
371
self.assertRaises(RuntimeError, iterator.next)