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
class _NoImplementCompare(_Hashable):
74
def __eq__(self, other):
78
# Even though this is an extension, we don't permute the tests for a python
79
# version. As the plain python version is just a dict or set
81
class _CompiledSimpleSet(tests.Feature):
84
if _simple_set_pyx is None:
88
def feature_name(self):
89
return 'bzrlib._simple_set_pyx'
91
CompiledSimpleSet = _CompiledSimpleSet()
94
class TestSimpleSet(tests.TestCase):
96
_test_needs_features = [CompiledSimpleSet]
97
module = _simple_set_pyx
99
def assertIn(self, obj, container):
100
self.assertTrue(obj in container,
101
'%s not found in %s' % (obj, container))
103
def assertNotIn(self, obj, container):
104
self.assertTrue(obj not in container,
105
'We found %s in %s' % (obj, container))
107
def assertFillState(self, used, fill, mask, obj):
108
self.assertEqual((used, fill, mask), (obj.used, obj.fill, obj.mask))
110
def assertLookup(self, offset, value, obj, key):
111
self.assertEqual((offset, value), obj._test_lookup(key))
113
def assertRefcount(self, count, obj):
114
"""Assert that the refcount for obj is what we expect.
116
Note that this automatically adjusts for the fact that calling
117
assertRefcount actually creates a new pointer, as does calling
118
sys.getrefcount. So pass the expected value *before* the call.
120
# I'm not sure why the offset is 3, but I've check that in the caller,
121
# an offset of 1 works, which is expected. Not sure why assertRefcount
122
# is incrementing/decrementing 2 times
123
self.assertEqual(count, sys.getrefcount(obj)-3)
125
def test_initial(self):
126
obj = self.module.SimpleSet()
127
self.assertEqual(0, len(obj))
129
self.assertFillState(0, 0, 0x3ff, obj)
131
def test__lookup(self):
132
# These are carefully chosen integers to force hash collisions in the
133
# algorithm, based on the initial set size of 1024
134
obj = self.module.SimpleSet()
135
self.assertLookup(643, '<null>', obj, _Hashable(643))
136
self.assertLookup(643, '<null>', obj, _Hashable(643 + 1024))
137
self.assertLookup(643, '<null>', obj, _Hashable(643 + 50*1024))
139
def test__lookup_collision(self):
140
obj = self.module.SimpleSet()
142
k2 = _Hashable(643 + 1024)
143
self.assertLookup(643, '<null>', obj, k1)
144
self.assertLookup(643, '<null>', obj, k2)
146
self.assertLookup(643, k1, obj, k1)
147
self.assertLookup(644, '<null>', obj, k2)
149
def test__lookup_after_resize(self):
150
obj = self.module.SimpleSet()
152
k2 = _Hashable(643 + 1024)
155
self.assertLookup(643, k1, obj, k1)
156
self.assertLookup(644, k2, obj, k2)
157
obj._py_resize(2047) # resized to 2048
158
self.assertEqual(2048, obj.mask + 1)
159
self.assertLookup(643, k1, obj, k1)
160
self.assertLookup(643+1024, k2, obj, k2)
161
obj._py_resize(1023) # resized back to 1024
162
self.assertEqual(1024, obj.mask + 1)
163
self.assertLookup(643, k1, obj, k1)
164
self.assertLookup(644, k2, obj, k2)
166
def test_get_set_del_with_collisions(self):
167
obj = self.module.SimpleSet()
182
self.assertLookup(643, '<null>', obj, k1)
183
self.assertLookup(643, '<null>', obj, k2)
184
self.assertLookup(643, '<null>', obj, k3)
185
self.assertLookup(643, '<null>', obj, k4)
186
self.assertLookup(644, '<null>', obj, k5)
187
self.assertLookup(644, '<null>', obj, k6)
189
self.assertIn(k1, obj)
190
self.assertNotIn(k2, obj)
191
self.assertNotIn(k3, obj)
192
self.assertNotIn(k4, obj)
193
self.assertLookup(643, k1, obj, k1)
194
self.assertLookup(644, '<null>', obj, k2)
195
self.assertLookup(644, '<null>', obj, k3)
196
self.assertLookup(644, '<null>', obj, k4)
197
self.assertLookup(644, '<null>', obj, k5)
198
self.assertLookup(644, '<null>', obj, k6)
199
self.assertIs(k1, obj[k1])
200
self.assertIs(k2, obj.add(k2))
201
self.assertIs(k2, obj[k2])
202
self.assertLookup(643, k1, obj, k1)
203
self.assertLookup(644, k2, obj, k2)
204
self.assertLookup(646, '<null>', obj, k3)
205
self.assertLookup(646, '<null>', obj, k4)
206
self.assertLookup(645, '<null>', obj, k5)
207
self.assertLookup(645, '<null>', obj, k6)
208
self.assertLookup(643, k1, obj, _Hashable(h1))
209
self.assertLookup(644, k2, obj, _Hashable(h2))
210
self.assertLookup(646, '<null>', obj, _Hashable(h3))
211
self.assertLookup(646, '<null>', obj, _Hashable(h4))
212
self.assertLookup(645, '<null>', obj, _Hashable(h5))
213
self.assertLookup(645, '<null>', obj, _Hashable(h6))
215
self.assertIs(k3, obj[k3])
216
self.assertIn(k1, obj)
217
self.assertIn(k2, obj)
218
self.assertIn(k3, obj)
219
self.assertNotIn(k4, obj)
222
self.assertLookup(643, '<dummy>', obj, k1)
223
self.assertLookup(644, k2, obj, k2)
224
self.assertLookup(646, k3, obj, k3)
225
self.assertLookup(643, '<dummy>', obj, k4)
226
self.assertNotIn(k1, obj)
227
self.assertIn(k2, obj)
228
self.assertIn(k3, obj)
229
self.assertNotIn(k4, obj)
232
obj = self.module.SimpleSet()
233
self.assertFillState(0, 0, 0x3ff, obj)
234
# We use this clumsy notation, because otherwise the refcounts are off.
235
# I'm guessing the python compiler sees it is a static tuple, and adds
236
# it to the function variables, or somesuch
238
self.assertRefcount(1, k1)
239
self.assertIs(k1, obj.add(k1))
240
self.assertFillState(1, 1, 0x3ff, obj)
241
self.assertRefcount(2, k1)
243
self.assertRefcount(3, k1)
244
self.assertIs(k1, ktest)
246
self.assertRefcount(2, k1)
248
self.assertRefcount(1, k2)
249
self.assertIsNot(k1, k2)
250
# doesn't add anything, so the counters shouldn't be adjusted
251
self.assertIs(k1, obj.add(k2))
252
self.assertFillState(1, 1, 0x3ff, obj)
253
self.assertRefcount(2, k1) # not changed
254
self.assertRefcount(1, k2) # not incremented
255
self.assertIs(k1, obj[k1])
256
self.assertIs(k1, obj[k2])
257
self.assertRefcount(2, k1)
258
self.assertRefcount(1, k2)
259
# Deleting an entry should remove the fill, but not the used
261
self.assertFillState(0, 1, 0x3ff, obj)
262
self.assertRefcount(1, k1)
264
self.assertRefcount(1, k3)
265
self.assertIs(k3, obj.add(k3))
266
self.assertFillState(1, 2, 0x3ff, obj)
267
self.assertRefcount(2, k3)
268
self.assertIs(k2, obj.add(k2))
269
self.assertFillState(2, 2, 0x3ff, obj)
270
self.assertRefcount(1, k1)
271
self.assertRefcount(2, k2)
272
self.assertRefcount(2, k3)
274
def test_discard(self):
275
obj = self.module.SimpleSet()
279
self.assertRefcount(1, k1)
280
self.assertRefcount(1, k2)
281
self.assertRefcount(1, k3)
283
self.assertRefcount(2, k1)
284
self.assertEqual(0, obj.discard(k3))
285
self.assertRefcount(1, k3)
287
self.assertRefcount(2, k3)
288
self.assertEqual(1, obj.discard(k3))
289
self.assertRefcount(1, k3)
291
def test__resize(self):
292
obj = self.module.SimpleSet()
300
self.assertFillState(2, 3, 0x3ff, obj)
301
self.assertEqual(1024, obj._py_resize(500))
302
# Doesn't change the size, but does change the content
303
self.assertFillState(2, 2, 0x3ff, obj)
306
self.assertFillState(2, 3, 0x3ff, obj)
307
self.assertEqual(4096, obj._py_resize(4095))
308
self.assertFillState(2, 2, 0xfff, obj)
309
self.assertIn(k1, obj)
310
self.assertIn(k2, obj)
311
self.assertNotIn(k3, obj)
313
self.assertIn(k2, obj)
315
self.assertEqual((591, '<dummy>'), obj._test_lookup(k2))
316
self.assertFillState(1, 2, 0xfff, obj)
317
self.assertEqual(2048, obj._py_resize(1024))
318
self.assertFillState(1, 1, 0x7ff, obj)
319
self.assertEqual((591, '<null>'), obj._test_lookup(k2))
321
def test_second_hash_failure(self):
322
obj = self.module.SimpleSet()
323
k1 = _BadSecondHash(200)
325
# Should only call hash() one time
327
self.assertFalse(k1._first)
328
self.assertRaises(ValueError, obj.add, k2)
330
def test_richcompare_failure(self):
331
obj = self.module.SimpleSet()
333
k2 = _BadCompare(200)
335
# Tries to compare with k1, fails
336
self.assertRaises(RuntimeError, obj.add, k2)
338
def test_richcompare_not_implemented(self):
339
obj = self.module.SimpleSet()
340
# Even though their hashes are the same, tp_richcompare returns
341
# NotImplemented, which means we treat them as not equal
342
k1 = _NoImplementCompare(200)
343
k2 = _NoImplementCompare(200)
344
self.assertLookup(200, '<null>', obj, k1)
345
self.assertLookup(200, '<null>', obj, k2)
346
self.assertIs(k1, obj.add(k1))
347
self.assertLookup(200, k1, obj, k1)
348
self.assertLookup(201, '<null>', obj, k2)
349
self.assertIs(k2, obj.add(k2))
350
self.assertIs(k1, obj[k1])
352
def test_add_and_remove_lots_of_items(self):
353
obj = self.module.SimpleSet()
354
chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890'
359
num = len(chars)*len(chars)
360
self.assertFillState(num, num, 0x1fff, obj)
361
# Now delete all of the entries and it should shrink again
366
# It should be back to 1024 wide mask, though there may still be some
367
# dummy values in there
368
self.assertFillState(0, obj.fill, 0x3ff, obj)
369
# but there should be fewer than 1/5th dummy entries
370
self.assertTrue(obj.fill < 1024 / 5)
372
def test__iter__(self):
373
obj = self.module.SimpleSet()
383
self.assertEqual(sorted([k1, k2, k3]), sorted(all))
388
self.assertRaises(RuntimeError, iterator.next)
389
# And even removing an item still causes it to fail
391
self.assertRaises(RuntimeError, iterator.next)