[Checkins] SVN: Products.ZCatalog/trunk/src/Products/PluginIndexes/ avoid deep tests nesting for single test files

Hano Schlichting cvs-admin at zope.org
Sun Mar 25 13:40:06 UTC 2012


Log message for revision 124721:
  avoid deep tests nesting for single test files
  

Changed:
  D   Products.ZCatalog/trunk/src/Products/PluginIndexes/DateIndex/tests/
  A   Products.ZCatalog/trunk/src/Products/PluginIndexes/DateIndex/tests.py
  D   Products.ZCatalog/trunk/src/Products/PluginIndexes/DateRangeIndex/tests/
  A   Products.ZCatalog/trunk/src/Products/PluginIndexes/DateRangeIndex/tests.py
  D   Products.ZCatalog/trunk/src/Products/PluginIndexes/FieldIndex/tests/
  A   Products.ZCatalog/trunk/src/Products/PluginIndexes/FieldIndex/tests.py
  D   Products.ZCatalog/trunk/src/Products/PluginIndexes/KeywordIndex/tests/
  A   Products.ZCatalog/trunk/src/Products/PluginIndexes/KeywordIndex/tests.py
  D   Products.ZCatalog/trunk/src/Products/PluginIndexes/PathIndex/tests/
  A   Products.ZCatalog/trunk/src/Products/PluginIndexes/PathIndex/tests.py
  D   Products.ZCatalog/trunk/src/Products/PluginIndexes/TopicIndex/tests/
  A   Products.ZCatalog/trunk/src/Products/PluginIndexes/TopicIndex/tests.py

-=-
Copied: Products.ZCatalog/trunk/src/Products/PluginIndexes/DateIndex/tests.py (from rev 124720, Products.ZCatalog/trunk/src/Products/PluginIndexes/DateIndex/tests/test_DateIndex.py)
===================================================================
--- Products.ZCatalog/trunk/src/Products/PluginIndexes/DateIndex/tests.py	                        (rev 0)
+++ Products.ZCatalog/trunk/src/Products/PluginIndexes/DateIndex/tests.py	2012-03-25 13:40:03 UTC (rev 124721)
@@ -0,0 +1,276 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Foundation and Contributors.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""DateIndex unit tests.
+"""
+
+import unittest
+
+
+class Dummy:
+
+    def __init__(self, name, date):
+        self._name = name
+        self._date = date
+
+    def name(self):
+        return self._name
+
+    def date(self):
+        return self._date
+
+    def __str__(self):
+        return "<Dummy %s, date %s>" % (self._name, str(self._date))
+
+###############################################################################
+# excerpted from the Python module docs
+###############################################################################
+
+def _getEastern():
+    from datetime import date
+    from datetime import datetime
+    from datetime import timedelta
+    from datetime import tzinfo
+    ZERO = timedelta(0)
+    HOUR = timedelta(hours=1)
+    def first_sunday_on_or_after(dt):
+        days_to_go = 6 - dt.weekday()
+        if days_to_go:
+            dt += timedelta(days_to_go)
+        return dt
+
+    # In the US, DST starts at 2am (standard time) on the first Sunday in
+    # April...
+    DSTSTART = datetime(1, 4, 1, 2)
+    # and ends at 2am (DST time; 1am standard time) on the last Sunday of
+    # October, which is the first Sunday on or after Oct 25.
+    DSTEND = datetime(1, 10, 25, 1)
+
+    class USTimeZone(tzinfo):
+
+        def __init__(self, hours, reprname, stdname, dstname):
+            self.stdoffset = timedelta(hours=hours)
+            self.reprname = reprname
+            self.stdname = stdname
+            self.dstname = dstname
+
+        def __repr__(self):
+            return self.reprname
+
+        def tzname(self, dt):
+            if self.dst(dt):
+                return self.dstname
+            else:
+                return self.stdname
+
+        def utcoffset(self, dt):
+            return self.stdoffset + self.dst(dt)
+
+        def dst(self, dt):
+            if dt is None or dt.tzinfo is None:
+                # An exception may be sensible here, in one or both cases.
+                # It depends on how you want to treat them.  The default
+                # fromutc() implementation (called by the default astimezone()
+                # implementation) passes a datetime with dt.tzinfo is self.
+                return ZERO
+            assert dt.tzinfo is self
+
+            # Find first Sunday in April & the last in October.
+            start = first_sunday_on_or_after(DSTSTART.replace(year=dt.year))
+            end = first_sunday_on_or_after(DSTEND.replace(year=dt.year))
+
+            # Can't compare naive to aware objects, so strip the timezone from
+            # dt first.
+            if start <= dt.replace(tzinfo=None) < end:
+                return HOUR
+            else:
+                return ZERO
+
+    return USTimeZone(-5, "Eastern",  "EST", "EDT")
+
+###############################################################################
+
+
+class DI_Tests(unittest.TestCase):
+
+    def _getTargetClass(self):
+        from Products.PluginIndexes.DateIndex.DateIndex import DateIndex
+        return DateIndex
+
+    def _makeOne(self, id='date'):
+        return self._getTargetClass()(id)
+
+    def _getValues(self):
+        from DateTime import DateTime
+        from datetime import date
+        from datetime import datetime
+        return [
+            (0, Dummy('a', None)),                            # None
+            (1, Dummy('b', DateTime(0))),                     # 1055335680
+            (2, Dummy('c', DateTime('2002-05-08 15:16:17'))), # 1072667236
+            (3, Dummy('d', DateTime('2032-05-08 15:16:17'))), # 1088737636
+            (4, Dummy('e', DateTime('2062-05-08 15:16:17'))), # 1018883325
+            (5, Dummy('e', DateTime('2062-05-08 15:16:17'))), # 1018883325
+            (6, Dummy('f', 1072742620.0)),                    # 1073545923
+            (7, Dummy('f', 1072742900)),                      # 1073545928
+            (8, Dummy('g', date(2034,2,5))),                  # 1073599200
+            (9, Dummy('h', datetime(2034,2,5,15,20,5))),      # (varies)
+            (10, Dummy('i', datetime(2034,2,5,10,17,5,
+                                     tzinfo=_getEastern()))), # 1073600117
+        ]
+
+    def _populateIndex(self, index):
+        for k, v in self._getValues():
+            index.index_object(k, v)
+
+    def _checkApply(self, index, req, expectedValues):
+        result, used = index._apply_index(req)
+        if hasattr(result, 'keys'):
+            result = result.keys()
+        self.assertEqual(used, ('date',))
+        self.assertEqual(len(result), len(expectedValues),
+            '%s | %s' % (result, expectedValues))
+        for k, v in expectedValues:
+            self.assertTrue(k in result)
+
+    def _convert(self, dt):
+        from time import gmtime
+        from datetime import date
+        from datetime import datetime
+        from Products.PluginIndexes.DateIndex.DateIndex import Local
+        if isinstance(dt, (float, int)):
+            yr, mo, dy, hr, mn = gmtime(dt)[:5]
+        elif type(dt) is date:
+            yr, mo, dy, hr, mn = dt.timetuple()[:5]
+        elif type(dt) is datetime:
+            if dt.tzinfo is None: # default behavior of index
+                dt = dt.replace(tzinfo=Local)
+            yr, mo, dy, hr, mn = dt.utctimetuple()[:5]
+        else:
+            yr, mo, dy, hr, mn = dt.toZone('UTC').parts()[:5]
+        return (((yr * 12 + mo) * 31 + dy) * 24 + hr) * 60 + mn
+
+    def test_interfaces(self):
+        from Products.PluginIndexes.interfaces import IDateIndex
+        from Products.PluginIndexes.interfaces import IPluggableIndex
+        from Products.PluginIndexes.interfaces import ISortIndex
+        from Products.PluginIndexes.interfaces import IUniqueValueIndex
+        from zope.interface.verify import verifyClass
+
+        verifyClass(IDateIndex, self._getTargetClass())
+        verifyClass(IPluggableIndex, self._getTargetClass())
+        verifyClass(ISortIndex, self._getTargetClass())
+        verifyClass(IUniqueValueIndex, self._getTargetClass())
+
+    def test_empty(self):
+        from DateTime import DateTime
+        index = self._makeOne()
+
+        self.assertEqual(len(index), 0)
+        self.assertEqual(len(index.referencedObjects()), 0)
+
+        self.assertTrue(index.getEntryForObject(1234) is None)
+        marker = []
+        self.assertTrue(index.getEntryForObject(1234, marker) is marker)
+        index.unindex_object(1234) # shouldn't throw
+
+        self.assertTrue(index.hasUniqueValuesFor('date'))
+        self.assertFalse(index.hasUniqueValuesFor('foo'))
+        self.assertEqual(len(index.uniqueValues('date')), 0)
+
+        self.assertTrue(index._apply_index({'zed': 12345}) is None)
+
+        self._checkApply(index,
+                         {'date': DateTime(0)}, [])
+        self._checkApply(index,
+                         {'date': {'query': DateTime('2032-05-08 15:16:17'),
+                                   'range': 'min'}},
+                         [])
+        self._checkApply(index,
+                         {'date': {'query': DateTime('2032-05-08 15:16:17'),
+                                   'range': 'max'}},
+                         [])
+        self._checkApply(index,
+                         {'date': {'query':(DateTime('2002-05-08 15:16:17'),
+                                            DateTime('2062-05-08 15:16:17')),
+                                   'range': 'min:max'}},
+                         [])
+
+    def test_retrieval( self ):
+        from DateTime import DateTime
+        index = self._makeOne()
+        self._populateIndex(index)
+        values = self._getValues()
+
+        self.assertEqual(len(index), len(values) - 2) # One dupe, one empty
+        self.assertEqual(len(index.referencedObjects()), len(values) - 1)
+            # One empty
+
+        self.assertTrue(index.getEntryForObject(1234) is None)
+        marker = []
+        self.assertTrue(index.getEntryForObject(1234, marker) is marker)
+        index.unindex_object(1234) # shouldn't throw
+
+        for k, v in values:
+            if v.date():
+                self.assertEqual(index.getEntryForObject(k),
+                    self._convert(v.date()))
+
+        self.assertEqual(len(index.uniqueValues('date')), len(values) - 2)
+        self.assertTrue(index._apply_index({'bar': 123}) is None)
+
+        self._checkApply(index,
+                         {'date': DateTime(0)}, values[1:2])
+        self._checkApply(index,
+                         {'date': {'query': DateTime('2032-05-08 15:16:17'),
+                                   'range': 'min'}},
+                         values[3:6] + values[8:])
+        self._checkApply(index,
+                         {'date': {'query': DateTime('2032-05-08 15:16:17'),
+                                   'range': 'max'}},
+                         values[1:4] + values[6:8])
+        self._checkApply(index,
+                         {'date': {'query':(DateTime('2002-05-08 15:16:17'),
+                                            DateTime('2062-05-08 15:16:17')),
+                                   'range': 'min:max'}},
+                         values[2:] )
+        self._checkApply(index,
+                         {'date': 1072742620.0}, [values[6]])
+        self._checkApply(index,
+                         {'date': 1072742900}, [values[7]])
+
+    def test_naive_convert_to_utc(self):
+        index = self._makeOne()
+        values = self._getValues()
+        index.index_naive_time_as_local = False
+        self._populateIndex(index)
+        for k, v in values[9:]:
+            # assert that the timezone is effectively UTC for item 9,
+            # and still correct for item 10
+            yr, mo, dy, hr, mn = v.date().utctimetuple()[:5]
+            val = (((yr * 12 + mo) * 31 + dy) * 24 + hr) * 60 + mn
+            self.assertEqual(index.getEntryForObject(k), val)
+
+    def test_removal(self):
+        """ DateIndex would hand back spurious entries when used as a
+            sort_index, because it previously was not removing entries
+            from the _unindex when indexing an object with a value of
+            None. The catalog consults a sort_index's
+            documentToKeyMap() to build the brains.
+        """
+        values = self._getValues()
+        index = self._makeOne()
+        self._populateIndex(index)
+        self._checkApply(index,
+                         {'date': 1072742900}, [values[7]])
+        index.index_object(7, None)
+        self.assertFalse(7 in index.documentToKeyMap().keys())

Copied: Products.ZCatalog/trunk/src/Products/PluginIndexes/DateRangeIndex/tests.py (from rev 124720, Products.ZCatalog/trunk/src/Products/PluginIndexes/DateRangeIndex/tests/test_DateRangeIndex.py)
===================================================================
--- Products.ZCatalog/trunk/src/Products/PluginIndexes/DateRangeIndex/tests.py	                        (rev 0)
+++ Products.ZCatalog/trunk/src/Products/PluginIndexes/DateRangeIndex/tests.py	2012-03-25 13:40:03 UTC (rev 124721)
@@ -0,0 +1,247 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Foundation and Contributors.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+
+import unittest
+
+
+class Dummy(object):
+
+    def __init__(self, name, start, stop):
+        self._name = name
+        self._start = start
+        self._stop = stop
+
+    def name(self):
+        return self._name
+
+    def start(self):
+        return self._start
+
+    def stop(self):
+        return self._stop
+
+    def datum(self):
+        return (self._start, self._stop)
+
+
+dummies = [ Dummy( 'a', None,   None )
+          , Dummy( 'b', None,   None )
+          , Dummy( 'c', 0,      None )
+          , Dummy( 'd', 10,     None )
+          , Dummy( 'e', None,   4    )
+          , Dummy( 'f', None,   11   )
+          , Dummy( 'g', 0,      11   )
+          , Dummy( 'h', 2,      9    )
+          ]
+
+
+def matchingDummies(value):
+    result = []
+    for dummy in dummies:
+        if ((dummy.start() is None or dummy.start() <= value)
+            and (dummy.stop() is None or dummy.stop() >= value)):
+            result.append(dummy)
+    return result
+
+
+class DRI_Tests(unittest.TestCase):
+
+    def _getTargetClass(self):
+        from Products.PluginIndexes.DateRangeIndex.DateRangeIndex \
+            import DateRangeIndex
+        return DateRangeIndex
+
+    def _makeOne(self, id, since_field=None, until_field=None, caller=None,
+                 extra=None):
+        klass = self._getTargetClass()
+        return klass(id, since_field, until_field, caller, extra)
+
+    def test_interfaces(self):
+        from Products.PluginIndexes.interfaces import IDateRangeIndex
+        from Products.PluginIndexes.interfaces import IPluggableIndex
+        from Products.PluginIndexes.interfaces import ISortIndex
+        from Products.PluginIndexes.interfaces import IUniqueValueIndex
+        from zope.interface.verify import verifyClass
+
+        verifyClass(IDateRangeIndex, self._getTargetClass())
+        verifyClass(IPluggableIndex, self._getTargetClass())
+        verifyClass(ISortIndex, self._getTargetClass())
+        verifyClass(IUniqueValueIndex, self._getTargetClass())
+
+    def test_empty(self):
+        empty = self._makeOne('empty')
+
+        self.assertTrue(empty.getEntryForObject(1234) is None)
+        empty.unindex_object(1234) # shouldn't throw
+
+        self.assertFalse(empty.uniqueValues('foo'))
+        self.assertFalse(empty.uniqueValues('foo', 1))
+        self.assertTrue(empty._apply_index({'zed': 12345}) is None)
+
+        result, used = empty._apply_index({'empty': 12345})
+        self.assertFalse(result)
+        self.assertEqual(used, (None, None))
+
+    def test_retrieval(self):
+        index = self._makeOne('work', 'start', 'stop')
+
+        for i in range(len(dummies)):
+            index.index_object(i, dummies[i])
+
+        for i in range(len(dummies)):
+            self.assertEqual(index.getEntryForObject(i), dummies[i].datum())
+
+        for value in range(-1, 15):
+            matches = matchingDummies(value)
+            results, used = index._apply_index({'work': value})
+            self.assertEqual(used, ('start', 'stop'))
+            self.assertEqual(len(matches), len(results))
+
+            matches.sort(lambda x, y: cmp(x.name(), y.name()))
+
+            for result, match in map(None, results, matches):
+                self.assertEqual(index.getEntryForObject(result), match.datum())
+
+    def test_longdates(self):
+        too_large = long(2**31)
+        too_small = - long(2**31)
+        index = self._makeOne('work', 'start', 'stop')
+        bad = Dummy('bad', too_large, too_large)
+        self.assertRaises(OverflowError, index.index_object, 0, bad)
+        bad = Dummy('bad', too_small, too_small)
+        self.assertRaises(OverflowError, index.index_object, 0, bad)
+
+    def test_floor_date(self):
+        index = self._makeOne('work', 'start', 'stop')
+        floor = index.floor_value - 1
+        bad = Dummy('bad', floor, None)
+        index.index_object(0, bad)
+        self.assertTrue(0 in index._always.keys())
+
+    def test_ceiling_date(self):
+        index = self._makeOne('work', 'start', 'stop')
+        ceiling = index.ceiling_value + 1
+        bad = Dummy('bad', None, ceiling)
+        index.index_object(1, bad)
+        self.assertTrue(1 in index._always.keys())
+
+    def test_datetime(self):
+        from datetime import datetime
+        from DateTime.DateTime import DateTime
+        from Products.PluginIndexes.DateIndex.tests import _getEastern
+        before = datetime(2009, 7, 11, 0, 0, tzinfo=_getEastern())
+        start = datetime(2009, 7, 13, 5, 15, tzinfo=_getEastern())
+        between = datetime(2009, 7, 13, 5, 45, tzinfo=_getEastern())
+        stop = datetime(2009, 7, 13, 6, 30, tzinfo=_getEastern())
+        after = datetime(2009, 7, 14, 0, 0, tzinfo=_getEastern())
+
+        dummy = Dummy('test', start, stop)
+        index = self._makeOne('work', 'start', 'stop')
+        index.index_object(0, dummy)
+
+        self.assertEqual(index.getEntryForObject(0),
+                        (DateTime(start).millis() / 60000,
+                         DateTime(stop).millis() / 60000))
+
+        results, used = index._apply_index({'work': before})
+        self.assertEqual(len(results), 0)
+
+        results, used = index._apply_index({'work': start})
+        self.assertEqual(len(results), 1)
+
+        results, used = index._apply_index({'work': between})
+        self.assertEqual(len(results), 1)
+
+        results, used = index._apply_index({'work': stop})
+        self.assertEqual(len(results), 1)
+
+        results, used = index._apply_index({'work': after})
+        self.assertEqual(len(results), 0)
+
+    def test_datetime_naive_timezone(self):
+        from datetime import datetime
+        from DateTime.DateTime import DateTime
+        from Products.PluginIndexes.DateIndex.DateIndex import Local
+        before = datetime(2009, 7, 11, 0, 0)
+        start = datetime(2009, 7, 13, 5, 15)
+        start_local = datetime(2009, 7, 13, 5, 15, tzinfo=Local)
+        between = datetime(2009, 7, 13, 5, 45)
+        stop = datetime(2009, 7, 13, 6, 30)
+        stop_local = datetime(2009, 7, 13, 6, 30, tzinfo=Local)
+        after = datetime(2009, 7, 14, 0, 0)
+
+        dummy = Dummy('test', start, stop)
+        index = self._makeOne('work', 'start', 'stop')
+        index.index_object(0, dummy)
+
+        self.assertEqual(index.getEntryForObject(0),
+                        (DateTime(start_local).millis() / 60000,
+                         DateTime(stop_local).millis() / 60000))
+
+        results, used = index._apply_index({'work': before})
+        self.assertEqual(len(results), 0)
+
+        results, used = index._apply_index({'work': start})
+        self.assertEqual(len(results), 1)
+
+        results, used = index._apply_index({'work': between})
+        self.assertEqual(len(results), 1)
+
+        results, used = index._apply_index({'work': stop})
+        self.assertEqual(len(results), 1)
+
+        results, used = index._apply_index({'work': after})
+        self.assertEqual(len(results), 0)
+
+    def test_resultset(self):
+        from BTrees.IIBTree import IISet
+
+        index = self._makeOne('work', 'start', 'stop')
+        for i in range(len(dummies)):
+            index.index_object(i, dummies[i])
+
+        results, used = index._apply_index({'work': 20})
+        self.assertEqual(set(results), set([0, 1, 2, 3]))
+
+        # a resultset with everything doesn't actually limit
+        results, used = index._apply_index({'work': 20},
+            resultset=IISet(range(len(dummies))))
+        self.assertEqual(set(results), set([0, 1, 2, 3]))
+
+        # a small resultset limits
+        results, used = index._apply_index({'work': 20},
+            resultset=IISet([1, 2]))
+        self.assertEqual(set(results), set([1, 2]))
+
+        # the specified value is included
+        results, used = index._apply_index({'work': 11})
+        self.assertEqual(set(results), set([0, 1, 2, 3, 5, 6]))
+
+        # also for _since_only
+        results, used = index._apply_index({'work': 10})
+        self.assertEqual(set(results), set([0, 1, 2, 3, 5, 6]))
+
+        # the specified value is included with a large resultset
+        results, used = index._apply_index({'work': 11},
+            resultset=IISet(range(len(dummies))))
+        self.assertEqual(set(results), set([0, 1, 2, 3, 5, 6]))
+
+        # this also works for _since_only
+        results, used = index._apply_index({'work': 10},
+            resultset=IISet(range(len(dummies))))
+        self.assertEqual(set(results), set([0, 1, 2, 3, 5, 6]))
+
+        # the specified value is included with a small resultset
+        results, used = index._apply_index({'work': 11},
+            resultset=IISet([0, 5, 7]))
+        self.assertEqual(set(results), set([0, 5]))

Copied: Products.ZCatalog/trunk/src/Products/PluginIndexes/FieldIndex/tests.py (from rev 124720, Products.ZCatalog/trunk/src/Products/PluginIndexes/FieldIndex/tests/testFieldIndex.py)
===================================================================
--- Products.ZCatalog/trunk/src/Products/PluginIndexes/FieldIndex/tests.py	                        (rev 0)
+++ Products.ZCatalog/trunk/src/Products/PluginIndexes/FieldIndex/tests.py	2012-03-25 13:40:03 UTC (rev 124721)
@@ -0,0 +1,231 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Foundation and Contributors.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""FieldIndex unit tests.
+"""
+
+import unittest
+
+from Products.PluginIndexes.FieldIndex.FieldIndex import FieldIndex
+
+
+class Dummy:
+
+    def __init__(self, foo):
+        self._foo = foo
+
+    def foo(self):
+        return self._foo
+
+    def __str__(self):
+        return '<Dummy: %s>' % self._foo
+
+    __repr__ = __str__
+
+
+class FieldIndexTests(unittest.TestCase):
+    """Test FieldIndex objects.
+    """
+
+    def setUp(self):
+        self._index = FieldIndex('foo')
+        self._marker = []
+        self._values = [(0, Dummy('a')),
+                        (1, Dummy('ab')),
+                        (2, Dummy('abc')),
+                        (3, Dummy('abca')),
+                        (4, Dummy('abcd')),
+                        (5, Dummy('abce')),
+                        (6, Dummy('abce')),
+                        (7, Dummy(0)),  # Collector #1959
+                        (8, Dummy(None))]
+        self._forward = {}
+        self._backward = {}
+        for k, v in self._values:
+            self._backward[k] = v
+            keys = self._forward.get(v, [])
+            self._forward[v] = keys
+
+        self._noop_req  = {'bar': 123}
+        self._request   = {'foo': 'abce'}
+        self._min_req   = {'foo':
+            {'query': 'abc', 'range': 'min'}}
+        self._min_req_n = {'foo':
+            {'query': 'abc', 'range': 'min', 'not': 'abca'}}
+        self._max_req   = {'foo':
+            {'query': 'abc', 'range': 'max'}}
+        self._max_req_n = {'foo':
+            {'query': 'abc', 'range': 'max', 'not': ['a', 'b', None, 0]}}
+        self._range_req = {'foo':
+            {'query': ('abc', 'abcd'), 'range': 'min:max'}}
+        self._range_ren = {'foo':
+            {'query': ('abc', 'abcd'), 'range': 'min:max', 'not': 'abcd'}}
+        self._range_non = {'foo':
+            {'query': ('a', 'aa'), 'range': 'min:max', 'not': 'a'}}
+        self._zero_req  = {'foo': 0 }
+        self._none_req  = {'foo': None }
+        self._not_1     = {'foo': {'query': 'a', 'not': 'a'}}
+        self._not_2     = {'foo': {'query': ['a', 'ab'], 'not': 'a'}}
+        self._not_3     = {'foo': {'not': 'a'}}
+        self._not_4     = {'foo': {'not': [0, None]}}
+        self._not_5     = {'foo': {'not': ['a', 'b']}}
+        self._not_6     = {'foo': 'a', 'bar': {'query': 123, 'not': 1}}
+
+    def _populateIndex(self):
+        for k, v in self._values:
+            self._index.index_object(k, v)
+
+    def _checkApply(self, req, expectedValues):
+        result, used = self._index._apply_index(req)
+        if hasattr(result, 'keys'):
+            result = result.keys()
+        assert used == ('foo', )
+        assert len(result) == len(expectedValues), \
+          '%s | %s' % (map(None, result), expectedValues)
+        for k, v in expectedValues:
+            assert k in result
+
+    def test_interfaces(self):
+        from Products.PluginIndexes.interfaces import IPluggableIndex
+        from Products.PluginIndexes.interfaces import ISortIndex
+        from Products.PluginIndexes.interfaces import IUniqueValueIndex
+        from zope.interface.verify import verifyClass
+
+        verifyClass(IPluggableIndex, FieldIndex)
+        verifyClass(ISortIndex, FieldIndex)
+        verifyClass(IUniqueValueIndex, FieldIndex)
+
+    def testEmpty(self):
+        "Test an empty FieldIndex."
+
+        assert len(self._index) == 0
+        assert len(self._index.referencedObjects()) == 0
+        self.assertEqual(self._index.numObjects(), 0)
+
+        assert self._index.getEntryForObject(1234) is None
+        assert (self._index.getEntryForObject(1234, self._marker)
+                  is self._marker)
+        self._index.unindex_object(1234)  # nothrow
+
+        assert self._index.hasUniqueValuesFor('foo')
+        assert not self._index.hasUniqueValuesFor('bar')
+        assert len(self._index.uniqueValues('foo')) == 0
+
+        assert self._index._apply_index(self._noop_req) is None
+        self._checkApply(self._request, [])
+        self._checkApply(self._min_req, [])
+        self._checkApply(self._min_req_n, [])
+        self._checkApply(self._max_req, [])
+        self._checkApply(self._max_req_n, [])
+        self._checkApply(self._range_req, [])
+        self._checkApply(self._range_ren, [])
+        self._checkApply(self._range_non, [])
+
+    def testPopulated(self):
+        """ Test a populated FieldIndex """
+        self._populateIndex()
+        values = self._values
+
+        assert len(self._index) == len(values) - 1  # 'abce' is duplicate
+        assert len(self._index.referencedObjects() ) == len(values)
+        self.assertEqual(self._index.indexSize(), len(values) - 1)
+
+        assert self._index.getEntryForObject(1234) is None
+        assert (self._index.getEntryForObject(1234, self._marker)
+                  is self._marker)
+        self._index.unindex_object(1234)  # nothrow
+
+        for k, v in values:
+            assert self._index.getEntryForObject(k) == v.foo()
+
+        assert len(self._index.uniqueValues('foo')) == len(values) - 1
+
+        assert self._index._apply_index(self._noop_req) is None
+
+        self._checkApply(self._request, values[-4:-2])
+        self._checkApply(self._min_req, values[2:-2])
+        self._checkApply(self._min_req_n, values[2:3] + values[4:-2])
+        self._checkApply(self._max_req, values[:3] + values[-2:])
+        self._checkApply(self._max_req_n, values[1:3])
+        self._checkApply(self._range_req, values[2:5])
+        self._checkApply(self._range_ren, values[2:4])
+        self._checkApply(self._range_non, [])
+
+        self._checkApply(self._not_1, [])
+        self._checkApply(self._not_2, values[1:2])
+        self._checkApply(self._not_3, values[1:])
+        self._checkApply(self._not_4, values[:7])
+        self._checkApply(self._not_5, values[1:])
+        self._checkApply(self._not_6, values[0:1])
+
+    def testZero(self):
+        """ Make sure 0 gets indexed """
+        self._populateIndex()
+        values = self._values
+        self._checkApply(self._zero_req, values[-2:-1])
+        assert 0 in self._index.uniqueValues('foo')
+
+    def testNone(self):
+        """ make sure None gets indexed """
+        self._populateIndex()
+        values = self._values
+        self._checkApply(self._none_req, values[-1:])
+        assert None in self._index.uniqueValues('foo')
+
+    def testReindex(self):
+        self._populateIndex()
+        result, used = self._index._apply_index({'foo': 'abc'})
+        assert list(result) == [2]
+        assert self._index.keyForDocument(2) == 'abc'
+        d = Dummy('world')
+        self._index.index_object(2, d)
+        result, used = self._index._apply_index({'foo': 'world'})
+        assert list(result) == [2]
+        assert self._index.keyForDocument(2) == 'world'
+        del d._foo
+        self._index.index_object(2, d)
+        result, used = self._index._apply_index({'foo': 'world'})
+        assert list(result) == []
+        try:
+            should_not_be = self._index.keyForDocument(2)
+        except KeyError:
+            # As expected, we deleted that attribute.
+            pass
+        else:
+            # before Collector #291 this would be 'world'
+            raise ValueError(repr(should_not_be))
+
+    def testRange(self):
+        """Test a range search"""
+        index = FieldIndex('foo')
+        for i in range(100):
+            index.index_object(i, Dummy(i % 10))
+
+        record = {'foo': {'query': [-99, 3], 'range': 'min:max'}}
+        r = index._apply_index(record)
+
+        assert tuple(r[1]) == ('foo', ), r[1]
+        r = list(r[0].keys())
+
+        expect = [
+            0, 1, 2, 3, 10, 11, 12, 13, 20, 21, 22, 23, 30, 31, 32, 33,
+            40, 41, 42, 43, 50, 51, 52, 53, 60, 61, 62, 63, 70, 71, 72, 73,
+            80, 81, 82, 83, 90, 91, 92, 93
+            ]
+        assert r == expect, r
+
+        # Make sure that range tests with incompatible paramters
+        # don't return empty sets.
+        record['foo']['operator'] = 'and'
+        r2, ignore = index._apply_index(record)
+        r2 = list(r2.keys())
+        assert r2 == r

Copied: Products.ZCatalog/trunk/src/Products/PluginIndexes/KeywordIndex/tests.py (from rev 124720, Products.ZCatalog/trunk/src/Products/PluginIndexes/KeywordIndex/tests/testKeywordIndex.py)
===================================================================
--- Products.ZCatalog/trunk/src/Products/PluginIndexes/KeywordIndex/tests.py	                        (rev 0)
+++ Products.ZCatalog/trunk/src/Products/PluginIndexes/KeywordIndex/tests.py	2012-03-25 13:40:03 UTC (rev 124721)
@@ -0,0 +1,241 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Foundation and Contributors.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+
+import unittest
+
+from Products.PluginIndexes.KeywordIndex.KeywordIndex import KeywordIndex
+
+
+class Dummy:
+
+    def __init__(self, foo):
+        self._foo = foo
+
+    def foo(self):
+        return self._foo
+
+    def __str__(self):
+        return '<Dummy: %s>' % self._foo
+
+    __repr__ = __str__
+
+
+def sortedUnique(seq):
+    unique = {}
+    for i in seq:
+        unique[i] = None
+    unique = unique.keys()
+    unique.sort()
+    return unique
+
+
+class TestKeywordIndex(unittest.TestCase):
+
+    _old_log_write = None
+
+    def setUp(self):
+        self._index = KeywordIndex('foo')
+        self._marker = []
+        self._values = [(0, Dummy(['a'])),
+                        (1, Dummy(['a', 'b'])),
+                        (2, Dummy(['a', 'b', 'c'])),
+                        (3, Dummy(['a', 'b', 'c', 'a'])),
+                        (4, Dummy(['a', 'b', 'c', 'd'])),
+                        (5, Dummy(['a', 'b', 'c', 'e'])),
+                        (6, Dummy(['a', 'b', 'c', 'e', 'f'])),
+                        (7, Dummy([0])),
+                       ]
+        self._noop_req = {'bar': 123}
+        self._all_req = {'foo': ['a']}
+        self._some_req = {'foo': ['e']}
+        self._overlap_req = {'foo': ['c', 'e']}
+        self._string_req = {'foo': 'a'}
+        self._zero_req = {'foo': [0]}
+
+    def _populateIndex(self):
+        for k, v in self._values:
+            self._index.index_object(k, v)
+
+    def _checkApply(self, req, expectedValues):
+        result, used = self._index._apply_index(req)
+        assert used == ('foo', )
+        assert len(result) == len(expectedValues), \
+          '%s | %s' % (map(None, result),
+                       map(lambda x: x[0], expectedValues))
+
+        if hasattr(result, 'keys'):
+            result = result.keys()
+        for k, v in expectedValues:
+            assert k in result
+
+    def test_interfaces(self):
+        from Products.PluginIndexes.interfaces import IPluggableIndex
+        from Products.PluginIndexes.interfaces import ISortIndex
+        from Products.PluginIndexes.interfaces import IUniqueValueIndex
+        from zope.interface.verify import verifyClass
+
+        verifyClass(IPluggableIndex, KeywordIndex)
+        verifyClass(ISortIndex, KeywordIndex)
+        verifyClass(IUniqueValueIndex, KeywordIndex)
+
+    def testAddObjectWOKeywords(self):
+        self._populateIndex()
+        self._index.index_object(999, None)
+
+    def testEmpty(self):
+        assert len(self._index) == 0
+        assert len(self._index.referencedObjects()) == 0
+        self.assertEqual(self._index.numObjects(), 0)
+
+        assert self._index.getEntryForObject(1234) is None
+        assert (self._index.getEntryForObject(1234, self._marker)
+                  is self._marker), self._index.getEntryForObject(1234)
+        self._index.unindex_object(1234)  # nothrow
+
+        assert self._index.hasUniqueValuesFor('foo')
+        assert not self._index.hasUniqueValuesFor('bar')
+        assert len(self._index.uniqueValues('foo')) == 0
+
+        assert self._index._apply_index(self._noop_req) is None
+        self._checkApply(self._all_req, [])
+        self._checkApply(self._some_req, [])
+        self._checkApply(self._overlap_req, [])
+        self._checkApply(self._string_req, [])
+
+    def testPopulated(self):
+        self._populateIndex()
+        values = self._values
+
+        assert len(self._index.referencedObjects()) == len(values)
+        assert self._index.getEntryForObject(1234) is None
+        assert (self._index.getEntryForObject(1234, self._marker)
+            is self._marker)
+        self._index.unindex_object(1234)  # nothrow
+        self.assertEqual(self._index.indexSize(), len(values) - 1)
+
+        for k, v in values:
+            entry = self._index.getEntryForObject(k)
+            entry.sort()
+            kw = sortedUnique(v.foo())
+            self.assertEqual(entry, kw)
+
+        assert len(self._index.uniqueValues('foo')) == len(values) - 1
+        assert self._index._apply_index(self._noop_req) is None
+
+        self._checkApply(self._all_req, values[:-1])
+        self._checkApply(self._some_req, values[5:7])
+        self._checkApply(self._overlap_req, values[2:7])
+        self._checkApply(self._string_req, values[:-1])
+
+    def testZero(self):
+        self._populateIndex()
+        values = self._values
+        self._checkApply(self._zero_req, values[-1:])
+        assert 0 in self._index.uniqueValues('foo')
+
+    def testReindexChange(self):
+        self._populateIndex()
+        expected = Dummy(['x', 'y'])
+        self._index.index_object(6, expected)
+        result, used = self._index._apply_index({'foo': ['x', 'y']})
+        result = result.keys()
+        assert len(result) == 1
+        assert result[0] == 6
+        result, used = self._index._apply_index(
+            {'foo': ['a', 'b', 'c', 'e', 'f']})
+        result = result.keys()
+        assert 6 not in result
+
+    def testReindexNoChange(self):
+        self._populateIndex()
+        expected = Dummy(['foo', 'bar'])
+        self._index.index_object(8, expected)
+        result, used = self._index._apply_index(
+            {'foo': ['foo', 'bar']})
+        result = result.keys()
+        assert len(result) == 1
+        assert result[0] == 8
+        self._index.index_object(8, expected)
+        result, used = self._index._apply_index(
+            {'foo': ['foo', 'bar']})
+        result = result.keys()
+        assert len(result) == 1
+        assert result[0] == 8
+
+    def testIntersectionWithRange(self):
+        # Test an 'and' search, ensuring that 'range' doesn't mess it up.
+        self._populateIndex()
+
+        record = {'foo': {'query': ['e', 'f'], 'operator': 'and'}}
+        self._checkApply(record, self._values[6:7])
+
+        # Make sure that 'and' tests with incompatible parameters
+        # don't return empty sets.
+        record['foo']['range'] = 'min:max'
+        self._checkApply(record, self._values[6:7])
+
+    def testDuplicateKeywords(self):
+        self._index.index_object(0, Dummy(['a', 'a', 'b', 'b']))
+        self._index.unindex_object(0)
+
+    def testCollectorIssue889(self):
+        # Test that collector issue 889 is solved
+        values = self._values
+        nonexistent = 'foo-bar-baz'
+        self._populateIndex()
+        # make sure key is not indexed
+        result = self._index._index.get(nonexistent, self._marker)
+        assert result is self._marker
+        # patched _apply_index now works as expected
+        record = {'foo': {'query': [nonexistent], 'operator': 'and'}}
+        self._checkApply(record, [])
+        record = {'foo': {'query': [nonexistent, 'a'], 'operator': 'and'}}
+        # and does not break anything
+        self._checkApply(record, [])
+        record = {'foo': {'query': ['d'], 'operator': 'and'}}
+        self._checkApply(record, values[4:5])
+        record = {'foo': {'query': ['a', 'e'], 'operator': 'and'}}
+        self._checkApply(record, values[5:7])
+
+    def test_noindexing_when_noattribute(self):
+        to_index = Dummy(['hello'])
+        self._index._index_object(10, to_index, attr='UNKNOWN')
+        self.assertFalse(self._index._unindex.get(10))
+        self.assertFalse(self._index.getEntryForObject(10))
+
+    def test_noindexing_when_raising_attribute(self):
+        class FauxObject:
+            def foo(self):
+                raise AttributeError
+        to_index = FauxObject()
+        self._index._index_object(10, to_index, attr='foo')
+        self.assertFalse(self._index._unindex.get(10))
+        self.assertFalse(self._index.getEntryForObject(10))
+
+    def test_noindexing_when_raising_typeeror(self):
+        class FauxObject:
+            def foo(self, name):
+                return 'foo'
+        to_index = FauxObject()
+        self._index._index_object(10, to_index, attr='foo')
+        self.assertFalse(self._index._unindex.get(10))
+        self.assertFalse(self._index.getEntryForObject(10))
+
+    def test_value_removes(self):
+        to_index = Dummy(['hello'])
+        self._index._index_object(10, to_index, attr='foo')
+        self.assertTrue(self._index._unindex.get(10))
+
+        to_index = Dummy('')
+        self._index._index_object(10, to_index, attr='foo')
+        self.assertFalse(self._index._unindex.get(10))

Copied: Products.ZCatalog/trunk/src/Products/PluginIndexes/PathIndex/tests.py (from rev 124720, Products.ZCatalog/trunk/src/Products/PluginIndexes/PathIndex/tests/testPathIndex.py)
===================================================================
--- Products.ZCatalog/trunk/src/Products/PluginIndexes/PathIndex/tests.py	                        (rev 0)
+++ Products.ZCatalog/trunk/src/Products/PluginIndexes/PathIndex/tests.py	2012-03-25 13:40:03 UTC (rev 124721)
@@ -0,0 +1,529 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Foundation and Contributors.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""PathIndex unit tests.
+"""
+
+import unittest
+
+
+class Dummy:
+
+    def __init__(self, path):
+        self.path = path
+
+    def getPhysicalPath(self):
+        return self.path.split('/')
+
+DUMMIES = {
+    1 : Dummy("/aa/aa/aa/1.html"),
+    2 : Dummy("/aa/aa/bb/2.html"),
+    3 : Dummy("/aa/aa/cc/3.html"),
+    4 : Dummy("/aa/bb/aa/4.html"),
+    5 : Dummy("/aa/bb/bb/5.html"),
+    6 : Dummy("/aa/bb/cc/6.html"),
+    7 : Dummy("/aa/cc/aa/7.html"),
+    8 : Dummy("/aa/cc/bb/8.html"),
+    9 : Dummy("/aa/cc/cc/9.html"),
+    10 : Dummy("/bb/aa/aa/10.html"),
+    11 : Dummy("/bb/aa/bb/11.html"),
+    12 : Dummy("/bb/aa/cc/12.html"),
+    13 : Dummy("/bb/bb/aa/13.html"),
+    14 : Dummy("/bb/bb/bb/14.html"),
+    15 : Dummy("/bb/bb/cc/15.html"),
+    16 : Dummy("/bb/cc/aa/16.html"),
+    17 : Dummy("/bb/cc/bb/17.html"),
+    18 : Dummy("/bb/cc/cc/18.html")
+}
+
+def _populateIndex(index):
+    for k, v in DUMMIES.items():
+        index.index_object(k, v)
+
+_marker = object()
+
+class PathIndexTests(unittest.TestCase):
+    """ Test PathIndex objects """
+
+    def _getTargetClass(self):
+        from Products.PluginIndexes.PathIndex.PathIndex import PathIndex
+        return PathIndex
+
+    def _makeOne(self, id='path', caller=_marker):
+        if caller is not _marker:
+            return self._getTargetClass()(id, caller)
+        return self._getTargetClass()(id)
+
+    def test_class_conforms_to_IPluggableIndex(self):
+        from Products.PluginIndexes.interfaces import IPluggableIndex
+        from zope.interface.verify import verifyClass
+        verifyClass(IPluggableIndex, self._getTargetClass())
+
+    def test_instance_conforms_to_IPluggableIndex(self):
+        from Products.PluginIndexes.interfaces import IPluggableIndex
+        from zope.interface.verify import verifyObject
+        verifyObject(IPluggableIndex, self._makeOne())
+
+    def test_class_conforms_to_IUniqueValueIndex(self):
+        from Products.PluginIndexes.interfaces import IUniqueValueIndex
+        from zope.interface.verify import verifyClass
+        verifyClass(IUniqueValueIndex, self._getTargetClass())
+
+    def test_instance_conforms_to_IUniqueValueIndex(self):
+        from Products.PluginIndexes.interfaces import IUniqueValueIndex
+        from zope.interface.verify import verifyObject
+        verifyObject(IUniqueValueIndex, self._makeOne())
+
+    def test_class_conforms_to_ISortIndex(self):
+        from Products.PluginIndexes.interfaces import ISortIndex
+        from zope.interface.verify import verifyClass
+        verifyClass(ISortIndex, self._getTargetClass())
+
+    def test_instance_conforms_to_ISortIndex(self):
+        from Products.PluginIndexes.interfaces import ISortIndex
+        from zope.interface.verify import verifyObject
+        verifyObject(ISortIndex, self._makeOne())
+
+    def test_class_conforms_to_IPathIndex(self):
+        from Products.PluginIndexes.interfaces import IPathIndex
+        from zope.interface.verify import verifyClass
+        verifyClass(IPathIndex, self._getTargetClass())
+
+    def test_instance_conforms_to_IPathIndex(self):
+        from Products.PluginIndexes.interfaces import IPathIndex
+        from zope.interface.verify import verifyObject
+        verifyObject(IPathIndex, self._makeOne())
+
+    def test_ctor(self):
+        index = self._makeOne()
+        self.assertEqual(index.id, 'path')
+        self.assertEqual(index.operators, ('or', 'and'))
+        self.assertEqual(index.useOperator, 'or')
+        self.assertEqual(len(index), 0)
+        self.assertEqual(index._depth, 0)
+        self.assertEqual(len(index._index), 0)
+        self.assertEqual(len(index._unindex), 0)
+        self.assertEqual(index._length(), 0)
+
+    def test_getEntryForObject_miss_no_default(self):
+        index = self._makeOne()
+        self.assertEqual(index.getEntryForObject(1234), None)
+
+    def test_getEntryForObject_miss_w_default(self):
+        index = self._makeOne()
+        default = object()
+        self.assertTrue(index.getEntryForObject(1234, default) is default)
+
+    def test_getEntryForObject_hit(self):
+        index = self._makeOne()
+        _populateIndex(index)
+        self.assertEqual(index.getEntryForObject(1), DUMMIES[1].path)
+
+    def test_getIndexSourceNames(self):
+        index = self._makeOne('foo')
+        self.assertEqual(list(index.getIndexSourceNames()),
+                         ['foo', 'getPhysicalPath'])
+
+    def test_index_object_broken_path_raises_TypeError(self):
+        index = self._makeOne()
+        doc = Dummy({})
+        self.assertRaises(TypeError, index.index_object, 1, doc)
+
+    def test_index_object_broken_callable(self):
+        index = self._makeOne()
+        doc = Dummy(lambda: self.nonesuch)
+        rc = index.index_object(1, doc)
+        self.assertEqual(rc, 0)
+        self.assertEqual(len(index), 0)
+        self.assertEqual(index._depth, 0)
+        self.assertEqual(len(index._index), 0)
+        self.assertEqual(len(index._unindex), 0)
+        self.assertEqual(index._length(), 0)
+
+    def test_index_object_at_root(self):
+        index = self._makeOne()
+        doc = Dummy('/xx')
+        rc = index.index_object(1, doc)
+        self.assertEqual(len(index), 1)
+        self.assertEqual(rc, 1)
+        self.assertEqual(index._depth, 0)
+        self.assertEqual(len(index._index), 1)
+        self.assertEqual(list(index._index['xx'][0]), [1])
+        self.assertEqual(len(index._unindex), 1)
+        self.assertEqual(index._unindex[1], '/xx')
+        self.assertEqual(index._length(), 1)
+
+    def test_index_object_at_root_callable_attr(self):
+        index = self._makeOne()
+        doc = Dummy(lambda: '/xx')
+        rc = index.index_object(1, doc)
+        self.assertEqual(len(index), 1)
+        self.assertEqual(rc, 1)
+        self.assertEqual(index._depth, 0)
+        self.assertEqual(len(index._index), 1)
+        self.assertEqual(list(index._index['xx'][0]), [1])
+        self.assertEqual(len(index._unindex), 1)
+        self.assertEqual(index._unindex[1], '/xx')
+        self.assertEqual(index._length(), 1)
+
+    def test_index_object_at_root_no_attr_but_getPhysicalPath(self):
+        class Other:
+            def getPhysicalPath(self):
+                return '/xx'
+        index = self._makeOne()
+        doc = Other()
+        rc = index.index_object(1, doc)
+        self.assertEqual(rc, 1)
+        self.assertEqual(len(index), 1)
+        self.assertEqual(index._depth, 0)
+        self.assertEqual(len(index._index), 1)
+        self.assertEqual(list(index._index['xx'][0]), [1])
+        self.assertEqual(len(index._unindex), 1)
+        self.assertEqual(index._unindex[1], '/xx')
+        self.assertEqual(index._length(), 1)
+
+    def test_index_object_at_root_attr_as_tuple(self):
+        index = self._makeOne()
+        doc = Dummy(('', 'xx'))
+        rc = index.index_object(1, doc)
+        self.assertEqual(rc, 1)
+        self.assertEqual(len(index), 1)
+        self.assertEqual(index._depth, 0)
+        self.assertEqual(len(index._index), 1)
+        self.assertEqual(list(index._index['xx'][0]), [1])
+        self.assertEqual(len(index._unindex), 1)
+        self.assertEqual(index._unindex[1], '/xx')
+        self.assertEqual(index._length(), 1)
+
+    def test_index_object_strips_empty_path_elements(self):
+        index = self._makeOne()
+        doc = Dummy('////xx//')
+        rc = index.index_object(1, doc)
+        self.assertEqual(rc, 1)
+        self.assertEqual(len(index), 1)
+        self.assertEqual(index._depth, 0)
+        self.assertEqual(len(index._index), 1)
+        self.assertEqual(list(index._index['xx'][0]), [1])
+        self.assertEqual(len(index._unindex), 1)
+        self.assertEqual(index._unindex[1], '////xx//')
+        self.assertEqual(index._length(), 1)
+
+    def test_index_object_below_root(self):
+        index = self._makeOne()
+        doc = Dummy('/xx/yy/zz')
+        rc = index.index_object(1, doc)
+        self.assertEqual(rc, 1)
+        self.assertEqual(len(index), 1)
+        self.assertEqual(index._depth, 2)
+        self.assertEqual(len(index._index), 3)
+        self.assertEqual(list(index._index['xx'][0]), [1])
+        self.assertEqual(list(index._index['yy'][1]), [1])
+        self.assertEqual(list(index._index['zz'][2]), [1])
+        self.assertEqual(len(index._unindex), 1)
+        self.assertEqual(index._unindex[1], '/xx/yy/zz')
+        self.assertEqual(index._length(), 1)
+
+    def test_index_object_again(self):
+        index = self._makeOne()
+        o = Dummy('/foo/bar')
+        index.index_object(1234, o)
+        self.assertEqual(len(index), 1)
+        self.assertEqual(index.numObjects(), 1)
+        index.index_object(1234, o)
+        self.assertEqual(len(index), 1)
+        self.assertEqual(index.numObjects(), 1)
+
+    def test_unindex_object_nonesuch(self):
+        index = self._makeOne()
+        index.unindex_object( 1234 ) # nothrow
+
+    def test_unindex_object_broken_path(self):
+        index = self._makeOne()
+        _populateIndex(index)
+        index._unindex[1] = "/broken/thing"
+        index.unindex_object(1) # nothrow
+
+    def test_unindex_object_found(self):
+        index = self._makeOne()
+        _populateIndex(index)
+
+        for k in DUMMIES.keys():
+            index.unindex_object(k)
+
+        self.assertEqual(index.numObjects(), 0)
+        self.assertEqual(len(index._index), 0)
+        self.assertEqual(len(index._unindex), 0)
+
+    def test__apply_index_no_match_in_query(self):
+        index = self._makeOne()
+        self.assertEqual(index._apply_index({'foo': 'xxx'}), None)
+
+    def test__apply_index_nonesuch(self):
+        index = self._makeOne()
+        res = index._apply_index({'path': 'xxx'})
+        self.assertEqual(len(res[0]), 0)
+        self.assertEqual(res[1], ('path',))
+
+    def test___apply_index_root_levelO_dict(self):
+        index = self._makeOne()
+        _populateIndex(index)
+        query = {'path': {'query': '/', 'level': 0}}
+        res = index._apply_index(query)
+        self.assertEqual(list(res[0].keys()), range(1,19))
+
+    def test___apply_index_root_levelO_tuple(self):
+        index = self._makeOne()
+        _populateIndex(index)
+        query = {'path': (('/', 0),)}
+        res = index._apply_index(query)
+        self.assertEqual(list(res[0].keys()), range(1,19))
+
+    def test__apply_index_simple(self):
+        index = self._makeOne()
+        _populateIndex(index)
+        tests = [
+            # component, level, expected results
+            ("aa", 0, [1,2,3,4,5,6,7,8,9]),
+            ("aa", 1, [1,2,3,10,11,12] ),
+            ("bb", 0, [10,11,12,13,14,15,16,17,18]),
+            ("bb", 1, [4,5,6,13,14,15]),
+            ("bb/cc", 0, [16,17,18]),
+            ("bb/cc", 1, [6,15]),
+            ("bb/aa", 0, [10,11,12]),
+            ("bb/aa", 1, [4,13]),
+            ("aa/cc", -1, [3,7,8,9,12]),
+            ("bb/bb", -1, [5,13,14,15]),
+            ("18.html", 3, [18]),
+            ("18.html", -1, [18]),
+            ("cc/18.html", -1, [18]),
+            ("cc/18.html", 2, [18]),
+        ]
+
+        for comp, level, expected in tests:
+            for path in [comp, "/"+comp, "/"+comp+"/"]:
+                # Test with the level passed in as separate parameter
+                query = {'path': {'query':path, 'level': level}}
+                res = index._apply_index(query)
+                self.assertEqual(list(res[0].keys()), expected)
+
+                # Test with the level passed in as part of the path parameter
+                query = {'path': ((path, level),)}
+                res = index._apply_index(query)
+                self.assertEqual(list(res[0].keys()), expected)
+
+    def test__apply_index_ComplexOrTests(self):
+        index = self._makeOne()
+        _populateIndex(index)
+        tests = [
+            (['aa','bb'],1,[1,2,3,4,5,6,10,11,12,13,14,15]),
+            (['aa','bb','xx'],1,[1,2,3,4,5,6,10,11,12,13,14,15]),
+            ([('cc',1),('cc',2)],0,[3,6,7,8,9,12,15,16,17,18]),
+        ]
+
+        for lst, level, expected in tests:
+            query = {'path': {'query': lst, 'level': level, 'operator': 'or'}}
+            res = index._apply_index(query)
+            lst = list(res[0].keys())
+            self.assertEqual(lst, expected)
+
+    def test__apply_index_ComplexANDTests(self):
+        index = self._makeOne()
+        _populateIndex(index)
+        tests = [
+            # Path query (as list or (path, level) tuple), level, expected
+            (['aa','bb'], 1, []),
+            ([('aa',0), ('bb',1)], 0, [4,5,6]),
+            ([('aa',0), ('cc',2)], 0, [3,6,9]),
+        ]
+
+        for lst, level, expected in tests:
+            query = {'path': {'query': lst, 'level': level, 'operator': 'and'}}
+            res = index._apply_index(query)
+            lst = list(res[0].keys())
+            self.assertEqual(lst, expected)
+
+    def test__apply_index_QueryPathReturnedInResult(self):
+        index = self._makeOne()
+        index.index_object(1, Dummy("/ff"))
+        index.index_object(2, Dummy("/ff/gg"))
+        index.index_object(3, Dummy("/ff/gg/3.html"))
+        index.index_object(4, Dummy("/ff/gg/4.html"))
+        res = index._apply_index({'path': {'query': '/ff/gg'}})
+        lst = list(res[0].keys())
+        self.assertEqual(lst, [2, 3, 4])
+
+    def test_numObjects_empty(self):
+        index = self._makeOne()
+        self.assertEqual(index.numObjects(), 0)
+
+    def test_numObjects_filled(self):
+        index = self._makeOne()
+        _populateIndex(index)
+        self.assertEqual(index.numObjects(), len(DUMMIES))
+
+    def test_indexSize_empty(self):
+        index = self._makeOne()
+        self.assertEqual(index.indexSize(), 0)
+
+    def test_indexSize_filled(self):
+        index = self._makeOne()
+        _populateIndex(index)
+        self.assertEqual(index.indexSize(), len(DUMMIES))
+
+    def test_indexSize_multiple_items_same_path(self):
+        index = self._makeOne()
+        doc1 = Dummy('/shared')
+        doc2 = Dummy('/shared')
+        index.index_object(1, doc1)
+        index.index_object(2, doc2)
+        self.assertEqual(len(index._index), 1)
+        self.assertEqual(len(index), 2)
+        self.assertEqual(index.numObjects(), 2)
+        self.assertEqual(index.indexSize(), 2)
+
+    def test_clear(self):
+        index = self._makeOne()
+        _populateIndex(index)
+        index.clear()
+        self.assertEqual(len(index), 0)
+        self.assertEqual(index._depth, 0)
+        self.assertEqual(len(index._index), 0)
+        self.assertEqual(len(index._unindex), 0)
+        self.assertEqual(index._length(), 0)
+
+    def test_hasUniqueValuesFor_miss(self):
+        index = self._makeOne()
+        self.assertFalse(index.hasUniqueValuesFor('miss'))
+
+    def test_hasUniqueValuesFor_hit(self):
+        index = self._makeOne()
+        self.assertTrue(index.hasUniqueValuesFor('path'))
+
+    def test_uniqueValues_empty(self):
+        index = self._makeOne()
+        self.assertEqual(len(list(index.uniqueValues())), 0)
+
+    def test_uniqueValues_miss(self):
+        index = self._makeOne('foo')
+        _populateIndex(index)
+        self.assertEqual(len(list(index.uniqueValues('bar'))), 0)
+
+    def test_uniqueValues_hit(self):
+        index = self._makeOne('foo')
+        _populateIndex(index)
+        self.assertEqual(len(list(index.uniqueValues('foo'))),
+                         len(DUMMIES) + 3)
+
+    def test_uniqueValues_hit_w_withLength(self):
+        index = self._makeOne('foo')
+        _populateIndex(index)
+        results = dict(index.uniqueValues('foo', True))
+        self.assertEqual(len(results), len(DUMMIES) + 3)
+        for i in range(1, 19):
+            self.assertEqual(results['%s.html' % i], 1)
+        self.assertEqual(results['aa'],
+                         len([x for x in DUMMIES.values() if 'aa' in x.path]))
+        self.assertEqual(results['bb'],
+                         len([x for x in DUMMIES.values() if 'bb' in x.path]))
+        self.assertEqual(results['cc'],
+                         len([x for x in DUMMIES.values() if 'cc' in x.path]))
+
+    def test_keyForDocument_miss(self):
+        index = self._makeOne()
+        self.assertEqual(index.keyForDocument(1), None)
+
+    def test_keyForDocument_hit(self):
+        index = self._makeOne()
+        _populateIndex(index)
+        self.assertEqual(index.keyForDocument(1), DUMMIES[1].path)
+
+    def test_documentToKeyMap_empty(self):
+        index = self._makeOne()
+        self.assertEqual(dict(index.documentToKeyMap()), {})
+
+    def test_documentToKeyMap_filled(self):
+        index = self._makeOne()
+        _populateIndex(index)
+        self.assertEqual(dict(index.documentToKeyMap()),
+                         dict([(k, v.path) for k, v in DUMMIES.items()]))
+
+    def test_insertEntry_empty_depth_0(self):
+        index = self._makeOne()
+        index.insertEntry('xx', 123, level=0)
+        self.assertEqual(index._depth, 0)
+        self.assertEqual(len(index._index), 1)
+        self.assertEqual(list(index._index['xx'][0]), [123])
+
+        # insertEntry oesn't update the length or the reverse index.
+        self.assertEqual(len(index), 0)
+        self.assertEqual(len(index._unindex), 0)
+        self.assertEqual(index._length(), 0)
+
+    def test_insertEntry_empty_depth_1(self):
+        index = self._makeOne()
+        index.insertEntry('xx', 123, level=0)
+        index.insertEntry('yy', 123, level=1)
+        self.assertEqual(index._depth, 1)
+        self.assertEqual(len(index._index), 2)
+        self.assertEqual(list(index._index['xx'][0]), [123])
+        self.assertEqual(list(index._index['yy'][1]), [123])
+
+    def test_insertEntry_multiple(self):
+        index = self._makeOne()
+        index.insertEntry('xx', 123, level=0)
+        index.insertEntry('yy', 123, level=1)
+        index.insertEntry('aa', 456, level=0)
+        index.insertEntry('bb', 456, level=1)
+        index.insertEntry('cc', 456, level=2)
+        self.assertEqual(index._depth, 2)
+        self.assertEqual(len(index._index), 5)
+        self.assertEqual(list(index._index['xx'][0]), [123])
+        self.assertEqual(list(index._index['yy'][1]), [123])
+        self.assertEqual(list(index._index['aa'][0]), [456])
+        self.assertEqual(list(index._index['bb'][1]), [456])
+        self.assertEqual(list(index._index['cc'][2]), [456])
+
+    def test__search_empty_index_string_query(self):
+        index = self._makeOne()
+        self.assertEqual(list(index._search('/xxx')), [])
+
+    def test__search_empty_index_tuple_query(self):
+        index = self._makeOne()
+        self.assertEqual(list(index._search(('/xxx', 0))), [])
+
+    def test__search_empty_path(self):
+        index = self._makeOne()
+        doc = Dummy('/aa')
+        index.index_object(1, doc)
+        self.assertEqual(list(index._search('/')), [1])
+
+    def test__search_matching_path(self):
+        index = self._makeOne()
+        doc = Dummy('/aa')
+        index.index_object(1, doc)
+        self.assertEqual(list(index._search('/aa')), [1])
+
+    def test__search_mismatched_path(self):
+        index = self._makeOne()
+        doc = Dummy('/aa')
+        index.index_object(1, doc)
+        self.assertEqual(list(index._search('/bb')), [])
+
+    def test__search_w_level_0(self):
+        index = self._makeOne()
+        doc = Dummy('/aa/bb')
+        index.index_object(1, doc)
+        self.assertEqual(list(index._search('aa', 0)), [1])
+        self.assertEqual(list(index._search('aa', 1)), [])
+        self.assertEqual(list(index._search('bb', 1)), [1])
+        self.assertEqual(list(index._search('aa/bb', 0)), [1])
+        self.assertEqual(list(index._search('aa/bb', 1)), [])

Copied: Products.ZCatalog/trunk/src/Products/PluginIndexes/TopicIndex/tests.py (from rev 124720, Products.ZCatalog/trunk/src/Products/PluginIndexes/TopicIndex/tests/testTopicIndex.py)
===================================================================
--- Products.ZCatalog/trunk/src/Products/PluginIndexes/TopicIndex/tests.py	                        (rev 0)
+++ Products.ZCatalog/trunk/src/Products/PluginIndexes/TopicIndex/tests.py	2012-03-25 13:40:03 UTC (rev 124721)
@@ -0,0 +1,88 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Foundation and Contributors.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""TopicIndex unit tests.
+"""
+
+import unittest
+
+from Products.PluginIndexes.TopicIndex.TopicIndex import TopicIndex
+
+
+class Obj:
+
+    def __init__(self,id,meta_type=''):
+        self.id = id
+        self.meta_type = meta_type
+
+    def getId(self): return self.id
+    def getPhysicalPath(self):  return self.id
+
+
+class TestBase(unittest.TestCase):
+
+    def _searchAnd(self,query,expected):
+        return self._search(query,'and',expected)
+
+    def _searchOr(self,query,expected):
+        return self._search(query,'or',expected)
+
+    def _search(self,query,operator,expected):
+        res = self.TI._apply_index({'topic':{'query':query,'operator':operator}})
+        rows = list(res[0].keys())
+        rows.sort()
+        expected.sort()
+        self.assertEqual(rows,expected,query)
+        return rows
+
+
+class TestTopicIndex(TestBase):
+
+    def setUp(self):
+        self.TI = TopicIndex("topic")
+        self.TI.addFilteredSet("doc1","PythonFilteredSet","o.meta_type=='doc1'")
+        self.TI.addFilteredSet("doc2","PythonFilteredSet","o.meta_type=='doc2'")
+
+        self.TI.index_object(0 , Obj('0',))
+        self.TI.index_object(1 , Obj('1','doc1'))
+        self.TI.index_object(2 , Obj('2','doc1'))
+        self.TI.index_object(3 , Obj('3','doc2'))
+        self.TI.index_object(4 , Obj('4','doc2'))
+        self.TI.index_object(5 , Obj('5','doc3'))
+        self.TI.index_object(6 , Obj('6','doc3'))
+
+    def test_interfaces(self):
+        from Products.PluginIndexes.interfaces import ITopicIndex
+        from Products.PluginIndexes.interfaces import IPluggableIndex
+        from zope.interface.verify import verifyClass
+
+        verifyClass(ITopicIndex, TopicIndex)
+        verifyClass(IPluggableIndex, TopicIndex)
+
+    def testOr(self):
+        self._searchOr('doc1',[1,2])
+        self._searchOr(['doc1'],[1,2])
+        self._searchOr('doc2',[3,4]),
+        self._searchOr(['doc2'],[3,4])
+        self._searchOr(['doc1','doc2'], [1,2,3,4])
+
+    def testAnd(self):
+        self._searchAnd('doc1',[1,2])
+        self._searchAnd(['doc1'],[1,2])
+        self._searchAnd('doc2',[3,4])
+        self._searchAnd(['doc2'],[3,4])
+        self._searchAnd(['doc1','doc2'],[])
+
+    def testRemoval(self):
+        self.TI.index_object(1, Obj('1','doc2'))
+        self._searchOr('doc1',[2])
+        self._searchOr('doc2', [1,3,4])



More information about the checkins mailing list