[6211] | 1 | """Test unique field index |
---|
| 2 | """ |
---|
| 3 | import unittest |
---|
| 4 | from zope.catalog.catalog import Catalog |
---|
| 5 | from waeup.sirp.index.uniquefield import UniqueFieldIndex as FieldIndex |
---|
| 6 | from zope.catalog.tests import ( |
---|
| 7 | PlacelessSetup, IntIdsStub, stoopid, stoopidCallable,) |
---|
| 8 | from zope.component import provideUtility |
---|
| 9 | from zope.intid.interfaces import IIntIds |
---|
| 10 | |
---|
| 11 | _marker = object() |
---|
| 12 | |
---|
| 13 | class RawUniqueFieldIndexTests(unittest.TestCase): |
---|
| 14 | |
---|
| 15 | def _getTargetClass(self): |
---|
| 16 | from waeup.sirp.index.uniquefield import RawUniqueFieldIndex |
---|
| 17 | return RawUniqueFieldIndex |
---|
| 18 | |
---|
| 19 | def _makeOne(self, family=_marker): |
---|
| 20 | if family is _marker: |
---|
| 21 | return self._getTargetClass()() |
---|
| 22 | return self._getTargetClass()(family) |
---|
| 23 | |
---|
| 24 | def _populateIndex(self, index): |
---|
| 25 | index.index_doc(5, 1) # docid, obj |
---|
| 26 | index.index_doc(2, 2) |
---|
| 27 | index.index_doc(1, 3) |
---|
| 28 | index.index_doc(3, 4) |
---|
| 29 | index.index_doc(4, 5) |
---|
| 30 | index.index_doc(8, 6) |
---|
| 31 | index.index_doc(9, 7) |
---|
| 32 | index.index_doc(7, 8) |
---|
| 33 | index.index_doc(6, 9) |
---|
| 34 | index.index_doc(11, 10) |
---|
| 35 | index.index_doc(10, 11) |
---|
| 36 | |
---|
| 37 | def test_class_conforms_to_IInjection(self): |
---|
| 38 | from zope.interface.verify import verifyClass |
---|
| 39 | from zope.index.interfaces import IInjection |
---|
| 40 | verifyClass(IInjection, self._getTargetClass()) |
---|
| 41 | |
---|
| 42 | def test_instance_conforms_to_IInjection(self): |
---|
| 43 | from zope.interface.verify import verifyObject |
---|
| 44 | from zope.index.interfaces import IInjection |
---|
| 45 | verifyObject(IInjection, self._makeOne()) |
---|
| 46 | |
---|
| 47 | def test_class_conforms_to_IUniqueInjection(self): |
---|
| 48 | from zope.interface.verify import verifyClass |
---|
| 49 | from waeup.sirp.index.interfaces import IUnique |
---|
| 50 | verifyClass(IUnique, self._getTargetClass()) |
---|
| 51 | |
---|
| 52 | def test_instance_conforms_to_IUniqueInjection(self): |
---|
| 53 | from zope.interface.verify import verifyObject |
---|
| 54 | from waeup.sirp.index.interfaces import IUnique |
---|
| 55 | verifyObject(IUnique, self._makeOne()) |
---|
| 56 | |
---|
| 57 | def test_class_conforms_to_IIndexSearch(self): |
---|
| 58 | from zope.interface.verify import verifyClass |
---|
| 59 | from zope.index.interfaces import IIndexSearch |
---|
| 60 | verifyClass(IIndexSearch, self._getTargetClass()) |
---|
| 61 | |
---|
| 62 | def test_instance_conforms_to_IIndexSearch(self): |
---|
| 63 | from zope.interface.verify import verifyObject |
---|
| 64 | from zope.index.interfaces import IIndexSearch |
---|
| 65 | verifyObject(IIndexSearch, self._makeOne()) |
---|
| 66 | |
---|
| 67 | def test_class_conforms_to_IStatistics(self): |
---|
| 68 | from zope.interface.verify import verifyClass |
---|
| 69 | from zope.index.interfaces import IStatistics |
---|
| 70 | verifyClass(IStatistics, self._getTargetClass()) |
---|
| 71 | |
---|
| 72 | def test_instance_conforms_to_IStatistics(self): |
---|
| 73 | from zope.interface.verify import verifyObject |
---|
| 74 | from zope.index.interfaces import IStatistics |
---|
| 75 | verifyObject(IStatistics, self._makeOne()) |
---|
| 76 | |
---|
| 77 | def test_ctor_defaults(self): |
---|
| 78 | import BTrees |
---|
| 79 | index = self._makeOne() |
---|
| 80 | self.failUnless(index.family is BTrees.family32) |
---|
| 81 | self.assertEqual(index.documentCount(), 0) |
---|
| 82 | self.assertEqual(index.wordCount(), 0) |
---|
| 83 | |
---|
| 84 | def test_ctor_explicit_family(self): |
---|
| 85 | import BTrees |
---|
| 86 | index = self._makeOne(BTrees.family64) |
---|
| 87 | self.failUnless(index.family is BTrees.family64) |
---|
| 88 | |
---|
| 89 | def test_index_doc_new(self): |
---|
| 90 | index = self._makeOne() |
---|
| 91 | index.index_doc(1, 'value') |
---|
| 92 | self.assertEqual(index.documentCount(), 1) |
---|
| 93 | self.assertEqual(index.wordCount(), 1) |
---|
| 94 | self.failUnless(1 in index._rev_index) |
---|
| 95 | self.failUnless('value' in index._fwd_index) |
---|
| 96 | |
---|
| 97 | def test_index_doc_existing_same_value(self): |
---|
| 98 | index = self._makeOne() |
---|
| 99 | index.index_doc(1, 'value') |
---|
| 100 | index.index_doc(1, 'value') |
---|
| 101 | self.assertEqual(index.documentCount(), 1) |
---|
| 102 | self.assertEqual(index.wordCount(), 1) |
---|
| 103 | self.failUnless(1 in index._rev_index) |
---|
| 104 | self.failUnless('value' in index._fwd_index) |
---|
| 105 | self.assertEqual(list(index._fwd_index['value']), [1]) |
---|
| 106 | |
---|
| 107 | def test_index_doc_existing_new_value(self): |
---|
| 108 | index = self._makeOne() |
---|
| 109 | index.index_doc(1, 'value') |
---|
| 110 | index.index_doc(1, 'new_value') |
---|
| 111 | self.assertEqual(index.documentCount(), 1) |
---|
| 112 | self.assertEqual(index.wordCount(), 1) |
---|
| 113 | self.failUnless(1 in index._rev_index) |
---|
| 114 | self.failIf('value' in index._fwd_index) |
---|
| 115 | self.failUnless('new_value' in index._fwd_index) |
---|
| 116 | self.assertEqual(list(index._fwd_index['new_value']), [1]) |
---|
| 117 | |
---|
| 118 | def test_unindex_doc_nonesuch(self): |
---|
| 119 | index = self._makeOne() |
---|
| 120 | index.unindex_doc(1) # doesn't raise |
---|
| 121 | |
---|
| 122 | def test_unindex_doc_no_residual_fwd_values(self): |
---|
| 123 | index = self._makeOne() |
---|
| 124 | index.index_doc(1, 'value') |
---|
| 125 | index.unindex_doc(1) # doesn't raise |
---|
| 126 | self.assertEqual(index.documentCount(), 0) |
---|
| 127 | self.assertEqual(index.wordCount(), 0) |
---|
| 128 | self.failIf(1 in index._rev_index) |
---|
| 129 | self.failIf('value' in index._fwd_index) |
---|
| 130 | |
---|
| 131 | def test_unindex_doc_w_residual_fwd_values(self): |
---|
| 132 | index = self._makeOne() |
---|
| 133 | index.index_doc(1, 'value') |
---|
| 134 | self.assertRaises( |
---|
| 135 | KeyError, |
---|
| 136 | index.index_doc, 2, 'value') |
---|
| 137 | index.unindex_doc(1) # doesn't raise |
---|
| 138 | self.assertEqual(index.documentCount(), 0) |
---|
| 139 | self.assertEqual(index.wordCount(), 0) |
---|
| 140 | self.failIf(1 in index._rev_index) |
---|
| 141 | self.failIf(2 in index._rev_index) |
---|
| 142 | self.failIf('value' in index._fwd_index) |
---|
| 143 | self.assertRaises( |
---|
| 144 | KeyError, |
---|
| 145 | index._fwd_index.__getitem__, 'value') |
---|
| 146 | self.assertEqual(index._fwd_index.get('value'), None) |
---|
| 147 | |
---|
| 148 | def test_apply_non_tuple_raises(self): |
---|
| 149 | index = self._makeOne() |
---|
| 150 | self.assertRaises(TypeError, index.apply, ['a', 'b']) |
---|
| 151 | |
---|
| 152 | def test_apply_empty_tuple_raises(self): |
---|
| 153 | index = self._makeOne() |
---|
| 154 | self.assertRaises(TypeError, index.apply, ('a',)) |
---|
| 155 | |
---|
| 156 | def test_apply_one_tuple_raises(self): |
---|
| 157 | index = self._makeOne() |
---|
| 158 | self.assertRaises(TypeError, index.apply, ('a',)) |
---|
| 159 | |
---|
| 160 | def test_apply_three_tuple_raises(self): |
---|
| 161 | index = self._makeOne() |
---|
| 162 | self.assertRaises(TypeError, index.apply, ('a', 'b', 'c')) |
---|
| 163 | |
---|
| 164 | def test_apply_two_tuple_miss(self): |
---|
| 165 | index = self._makeOne() |
---|
| 166 | self.assertEqual(list(index.apply(('a', 'b'))), []) |
---|
| 167 | |
---|
| 168 | def test_apply_two_tuple_hit(self): |
---|
| 169 | index = self._makeOne() |
---|
| 170 | index.index_doc(1, 'albatross') |
---|
| 171 | self.assertEqual(list(index.apply(('a', 'b'))), [1]) |
---|
| 172 | |
---|
| 173 | def test_sort_w_limit_lt_1(self): |
---|
| 174 | index = self._makeOne() |
---|
| 175 | self.assertRaises(ValueError, |
---|
| 176 | lambda: list(index.sort([1, 2, 3], limit=0))) |
---|
| 177 | |
---|
| 178 | def test_sort_w_empty_index(self): |
---|
| 179 | index = self._makeOne() |
---|
| 180 | self.assertEqual(list(index.sort([1, 2, 3])), []) |
---|
| 181 | |
---|
| 182 | def test_sort_w_empty_docids(self): |
---|
| 183 | index = self._makeOne() |
---|
| 184 | index.index_doc(1, 'albatross') |
---|
| 185 | self.assertEqual(list(index.sort([])), []) |
---|
| 186 | |
---|
| 187 | def test_sort_w_missing_docids(self): |
---|
| 188 | index = self._makeOne() |
---|
| 189 | index.index_doc(1, 'albatross') |
---|
| 190 | self.assertEqual(list(index.sort([2, 3])), []) |
---|
| 191 | |
---|
| 192 | def test_sort_force_nbest_w_missing_docids(self): |
---|
| 193 | index = self._makeOne() |
---|
| 194 | index._use_nbest = True |
---|
| 195 | index.index_doc(1, 'albatross') |
---|
| 196 | self.assertEqual(list(index.sort([2, 3])), []) |
---|
| 197 | |
---|
| 198 | def test_sort_force_lazy_w_missing_docids(self): |
---|
| 199 | index = self._makeOne() |
---|
| 200 | index._use_lazy = True |
---|
| 201 | index.index_doc(1, 'albatross') |
---|
| 202 | self.assertEqual(list(index.sort([2, 3])), []) |
---|
| 203 | |
---|
| 204 | def test_sort_lazy_nolimit(self): |
---|
| 205 | from BTrees.IFBTree import IFSet |
---|
| 206 | index = self._makeOne() |
---|
| 207 | index._use_lazy = True |
---|
| 208 | self._populateIndex(index) |
---|
| 209 | c1 = IFSet([1, 2, 3, 4, 5]) |
---|
| 210 | result = index.sort(c1) |
---|
| 211 | self.assertEqual(list(result), [5, 2, 1, 3, 4]) |
---|
| 212 | |
---|
| 213 | def test_sort_lazy_withlimit(self): |
---|
| 214 | from BTrees.IFBTree import IFSet |
---|
| 215 | index = self._makeOne() |
---|
| 216 | index._use_lazy = True |
---|
| 217 | self._populateIndex(index) |
---|
| 218 | c1 = IFSet([1, 2, 3, 4, 5]) |
---|
| 219 | result = index.sort(c1, limit=3) |
---|
| 220 | self.assertEqual(list(result), [5, 2, 1]) |
---|
| 221 | |
---|
| 222 | def test_sort_nonlazy_nolimit(self): |
---|
| 223 | from BTrees.IFBTree import IFSet |
---|
| 224 | index = self._makeOne() |
---|
| 225 | self._populateIndex(index) |
---|
| 226 | c1 = IFSet([1, 2, 3, 4, 5]) |
---|
| 227 | result = index.sort(c1) |
---|
| 228 | self.assertEqual(list(result), [5, 2, 1, 3, 4]) |
---|
| 229 | |
---|
| 230 | def test_sort_nonlazy_missingdocid(self): |
---|
| 231 | from BTrees.IFBTree import IFSet |
---|
| 232 | index = self._makeOne() |
---|
| 233 | self._populateIndex(index) |
---|
| 234 | c1 = IFSet([1, 2, 3, 4, 5, 99]) |
---|
| 235 | result = index.sort(c1) |
---|
| 236 | self.assertEqual(list(result), [5, 2, 1, 3, 4]) # 99 not present |
---|
| 237 | |
---|
| 238 | def test_sort_nonlazy_withlimit(self): |
---|
| 239 | from BTrees.IFBTree import IFSet |
---|
| 240 | index = self._makeOne() |
---|
| 241 | self._populateIndex(index) |
---|
| 242 | c1 = IFSet([1, 2, 3, 4, 5]) |
---|
| 243 | result = index.sort(c1, limit=3) |
---|
| 244 | self.assertEqual(list(result), [5, 2, 1]) |
---|
| 245 | |
---|
| 246 | def test_sort_nonlazy_reverse_nolimit(self): |
---|
| 247 | from BTrees.IFBTree import IFSet |
---|
| 248 | index = self._makeOne() |
---|
| 249 | self._populateIndex(index) |
---|
| 250 | c1 = IFSet([1, 2, 3, 4, 5]) |
---|
| 251 | result = index.sort(c1, reverse=True) |
---|
| 252 | self.assertEqual(list(result), [4, 3, 1, 2, 5]) |
---|
| 253 | |
---|
| 254 | def test_sort_nonlazy_reverse_withlimit(self): |
---|
| 255 | from BTrees.IFBTree import IFSet |
---|
| 256 | index = self._makeOne() |
---|
| 257 | self._populateIndex(index) |
---|
| 258 | c1 = IFSet([1, 2, 3, 4, 5]) |
---|
| 259 | result = index.sort(c1, reverse=True, limit=3) |
---|
| 260 | self.assertEqual(list(result), [4, 3, 1]) |
---|
| 261 | |
---|
| 262 | def test_sort_nbest(self): |
---|
| 263 | from BTrees.IFBTree import IFSet |
---|
| 264 | index = self._makeOne() |
---|
| 265 | index._use_nbest = True |
---|
| 266 | self._populateIndex(index) |
---|
| 267 | c1 = IFSet([1, 2, 3, 4, 5]) |
---|
| 268 | result = index.sort(c1, limit=3) |
---|
| 269 | self.assertEqual(list(result), [5, 2, 1]) |
---|
| 270 | |
---|
| 271 | def test_sort_nbest_reverse(self): |
---|
| 272 | from BTrees.IFBTree import IFSet |
---|
| 273 | index = self._makeOne() |
---|
| 274 | index._use_nbest = True |
---|
| 275 | self._populateIndex(index) |
---|
| 276 | c1 = IFSet([1, 2, 3, 4, 5]) |
---|
| 277 | result = index.sort(c1, reverse=True, limit=3) |
---|
| 278 | self.assertEqual(list(result), [4, 3, 1]) |
---|
| 279 | |
---|
| 280 | def test_sort_nbest_missing(self): |
---|
| 281 | from BTrees.IFBTree import IFSet |
---|
| 282 | index = self._makeOne() |
---|
| 283 | index._use_nbest = True |
---|
| 284 | self._populateIndex(index) |
---|
| 285 | c1 = IFSet([1, 2, 3, 4, 5, 99]) |
---|
| 286 | result = index.sort(c1, limit=3) |
---|
| 287 | self.assertEqual(list(result), [5, 2, 1]) |
---|
| 288 | |
---|
| 289 | def test_sort_nbest_missing_reverse(self): |
---|
| 290 | from BTrees.IFBTree import IFSet |
---|
| 291 | index = self._makeOne() |
---|
| 292 | index._use_nbest = True |
---|
| 293 | self._populateIndex(index) |
---|
| 294 | c1 = IFSet([1, 2, 3, 4, 5, 99]) |
---|
| 295 | result = index.sort(c1, reverse=True, limit=3) |
---|
| 296 | self.assertEqual(list(result), [4, 3, 1]) |
---|
| 297 | |
---|
| 298 | def test_sort_nodocs(self): |
---|
| 299 | from BTrees.IFBTree import IFSet |
---|
| 300 | index = self._makeOne() |
---|
| 301 | c1 = IFSet([1, 2, 3, 4, 5]) |
---|
| 302 | result = index.sort(c1) |
---|
| 303 | self.assertEqual(list(result), []) |
---|
| 304 | |
---|
| 305 | def test_sort_nodocids(self): |
---|
| 306 | from BTrees.IFBTree import IFSet |
---|
| 307 | index = self._makeOne() |
---|
| 308 | self._populateIndex(index) |
---|
| 309 | c1 = IFSet() |
---|
| 310 | result = index.sort(c1) |
---|
| 311 | self.assertEqual(list(result), []) |
---|
| 312 | |
---|
| 313 | def test_sort_badlimit(self): |
---|
| 314 | from BTrees.IFBTree import IFSet |
---|
| 315 | index = self._makeOne() |
---|
| 316 | self._populateIndex(index) |
---|
| 317 | c1 = IFSet([1, 2, 3, 4, 5]) |
---|
| 318 | result = index.sort(c1, limit=0) |
---|
| 319 | self.assertRaises(ValueError, list, result) |
---|
| 320 | |
---|
| 321 | |
---|
| 322 | class TestCatalogBugs(PlacelessSetup, unittest.TestCase): |
---|
| 323 | """I found that z.a.catalog, AttributeIndex failed to remove the previous |
---|
| 324 | value/object from the index IF the NEW value is None. |
---|
| 325 | """ |
---|
| 326 | |
---|
| 327 | def test_updateIndexWithNone(self): |
---|
| 328 | uidutil = IntIdsStub() |
---|
| 329 | provideUtility(uidutil, IIntIds) |
---|
| 330 | |
---|
| 331 | catalog = Catalog() |
---|
| 332 | index = FieldIndex('author', None) |
---|
| 333 | catalog['author'] = index |
---|
| 334 | |
---|
| 335 | ob1 = stoopid(author = "joe") |
---|
| 336 | ob1id = uidutil.register(ob1) |
---|
| 337 | catalog.index_doc(ob1id, ob1) |
---|
| 338 | |
---|
| 339 | res = catalog.searchResults(author=('joe','joe')) |
---|
| 340 | names = [x.author for x in res] |
---|
| 341 | names.sort() |
---|
| 342 | self.assertEqual(len(names), 1) |
---|
| 343 | self.assertEqual(names, ['joe']) |
---|
| 344 | |
---|
| 345 | ob1.author = None |
---|
| 346 | catalog.index_doc(ob1id, ob1) |
---|
| 347 | |
---|
| 348 | #the index must be empty now because None values are never indexed |
---|
| 349 | res = catalog.searchResults(author=(None, None)) |
---|
| 350 | self.assertEqual(len(res), 0) |
---|
| 351 | |
---|
| 352 | def test_updateIndexFromCallableWithNone(self): |
---|
| 353 | uidutil = IntIdsStub() |
---|
| 354 | provideUtility(uidutil, IIntIds) |
---|
| 355 | |
---|
| 356 | catalog = Catalog() |
---|
| 357 | index = FieldIndex('getAuthor', None, field_callable=True) |
---|
| 358 | catalog['author'] = index |
---|
| 359 | |
---|
| 360 | ob1 = stoopidCallable(author = "joe") |
---|
| 361 | |
---|
| 362 | ob1id = uidutil.register(ob1) |
---|
| 363 | catalog.index_doc(ob1id, ob1) |
---|
| 364 | |
---|
| 365 | res = catalog.searchResults(author=('joe','joe')) |
---|
| 366 | names = [x.author for x in res] |
---|
| 367 | names.sort() |
---|
| 368 | self.assertEqual(len(names), 1) |
---|
| 369 | self.assertEqual(names, ['joe']) |
---|
| 370 | |
---|
| 371 | ob1.author = None |
---|
| 372 | catalog.index_doc(ob1id, ob1) |
---|
| 373 | |
---|
| 374 | #the index must be empty now because None values are never indexed |
---|
| 375 | res = catalog.searchResults(author=(None, None)) |
---|
| 376 | self.assertEqual(len(res), 0) |
---|
| 377 | |
---|
| 378 | def test_suite(): |
---|
| 379 | from zope.testing import doctest |
---|
| 380 | return unittest.TestSuite(( |
---|
| 381 | doctest.DocFileSuite('README.txt', optionflags=doctest.ELLIPSIS), |
---|
| 382 | unittest.makeSuite(RawUniqueFieldIndexTests), |
---|
| 383 | unittest.makeSuite(TestCatalogBugs), |
---|
| 384 | )) |
---|
| 385 | |
---|
| 386 | if __name__=='__main__': |
---|
| 387 | unittest.main(defaultTest='test_suite') |
---|