==================
Regression Testing
==================

We'll use a `matches` function to compare a bucket sequence with a standard
Python list to which the same modifications have made.  This also checks for
bucket health.

    >>> import zc.blist
    >>> from zc.blist.testing import matches, checkIndex
    >>> b = zc.blist.BList(bucket_size=5, index_size=4) # we want > 3 so min is > 1
    >>> matches(b, [])
    True
    >>> b.append(0)
    >>> matches(b, [0])
    True
    >>> del b[0]
    >>> matches(b, [])
    True
    >>> b.extend(range(10))
    >>> comparison = range(10)
    >>> matches(b, comparison)
    True
    >>> b.reverse()
    >>> comparison.reverse()
    >>> matches(b, comparison)
    True
    >>> for i in range(10):
    ...     b[i] = i+10
    ...     comparison[i] = i+10
    ...
    >>> matches(b, comparison)
    True
    >>> b[5:10] = [9, 8, 7, 6, 5]
    >>> comparison[5:10] = [9, 8, 7, 6, 5]
    >>> matches(b, comparison)
    True
    >>> b[0:0] = [-3, -2, -1]
    >>> comparison[0:0] = [-3, -2, -1]
    >>> matches(b, comparison)
    True
    >>> b.extend(range(90, 100))
    >>> comparison.extend(range(90,100))
    >>> matches(b, comparison)
    True
    >>> b[10:10] = range(20, 90)
    >>> comparison[10:10] = range(20, 90)
    >>> matches(b, comparison)
    True
    >>> b[b.index(82)]
    82
    >>> del b[:4]
    >>> del comparison[:4]
    >>> matches(b, comparison)
    True
    >>> comparison[2:10:2] = [100, 102, 104, 106]
    >>> b[2:10:2] = [100, 102, 104, 106]
    >>> matches(b, comparison)
    True
    >>> del b[1:88]
    >>> del comparison[1:88]
    >>> matches(b, comparison)
    True
    >>> list(b[:])
    [11, 99]
    >>> b[0] = 0
    >>> b.append(100)
    >>> b.append(101)
    >>> b.append(102)
    >>> matches(b, [0, 99, 100, 101, 102])
    True

Switching two values is most efficiently done with slice notation.

    >>> b[:] = range(1000)
    >>> b[5:996:990] = (b[995], b[5])
    >>> list(b[:7])
    [0, 1, 2, 3, 4, 995, 6]
    >>> list(b[994:])
    [994, 5, 996, 997, 998, 999]
    >>> comparison = range(1000)
    >>> comparison[5] = 995
    >>> comparison[995] = 5
    >>> matches(b, comparison)
    True

We'll test some of the other methods

    >>> b.pop(995) == comparison.pop(995)
    True
    >>> matches(b, comparison)
    True
    >>> b.insert(995, 5)
    >>> comparison.insert(995, 5)
    >>> matches(b, comparison)
    True
    >>> 999 in b
    True
    >>> 1000 in b
    False
    >>> b == zc.blist.BList(range(1000))
    False
    >>> b.sort()
    >>> comparison.sort()
    >>> matches(b, comparison)
    True
    >>> b == range(1000)
    False
    >>> b == zc.blist.BList(range(1000))
    True
    >>> b != range(1000)
    True
    >>> b != zc.blist.BList(range(1000))
    False
    >>> b.remove(999)
    >>> comparison.remove(999)
    >>> matches(b, comparison)
    True
    >>> b.count(999)
    0
    >>> b.append(999)
    >>> b.count(999)
    1
    >>> b.append(999)
    >>> b.count(999)
    2

Regression tests for accessing the last list item, which would fail in 1.0b1:

    >>> len(b)
    1001

    >>> b[-1] = 1000
    >>> b[-1]
    1000
    >>> del b[-1]
    >>> b[-1]
    999
    >>> len(b)
    1000

Regression tests for accessing items at the edges of the valid index range
(which would also fail in 1.0b1):

    >>> b[1000]
    Traceback (most recent call last):
    IndexError: list index out of range
    >>> b[1000] = None
    Traceback (most recent call last):
    IndexError: list assignment index out of range
    >>> del b[1000]
    Traceback (most recent call last):
    IndexError: list assignment index out of range

    >>> b[-1001]
    Traceback (most recent call last):
    IndexError: list index out of range
    >>> b[-1001] = None
    Traceback (most recent call last):
    IndexError: list assignment index out of range
    >>> del b[-1001]
    Traceback (most recent call last):
    IndexError: list assignment index out of range

These are some more stress and regression tests.

    >>> del b[900:]
    >>> del comparison[900:]
    >>> matches(b, comparison)
    True

    >>> del comparison[::2]
    >>> del b[::2] # 1
    >>> matches(b, comparison)
    True
    >>> del b[::2] # 2
    >>> del comparison[::2]
    >>> matches(b, comparison)
    True
    >>> del b[::2] # 3
    >>> del comparison[::2]
    >>> matches(b, comparison)
    True

    >>> alt = zc.blist.BList(b)
    >>> alt_comp = comparison[:]
    >>> matches(b, comparison)
    True
    >>> del alt[::3]
    >>> del alt_comp[::3]
    >>> matches(alt, alt_comp)
    True
    >>> del alt[::3]
    >>> del alt_comp[::3]
    >>> matches(alt, alt_comp)
    True
    >>> del alt[-1:5:-2]
    >>> del alt_comp[-1:5:-2]
    >>> matches(alt, alt_comp)
    True

The ``copy`` method gives a complete copy, reusing buckets and indexes.

    >>> from zc.blist.testing import checkCopies
    >>> old_comparison = comparison[:]
    >>> new = b.copy()
    >>> new == b
    True
    >>> def check():
    ...     assert matches(new, comparison)
    ...     assert matches(b, old_comparison)
    ...     return checkCopies(b, new)
    ...

So, ``checkCopies`` and ``check`` return three lists: the bucket
identifiers that are only in b, the bucket identifiers that are only in
new, and the bucket identifiers that are in both, but different. Initially,
all three lists are empty, because the two blists share all buckets and
indexes.

    >>> check()
    ([], [], [])
    >>> del new[4]
    >>> del comparison[4]
    >>> [len(v) for v in check()] # 4 = 1 bucket, 3 indexes
    [0, 0, 4]
    >>> del old_comparison[10]
    >>> del b[10]
    >>> [len(v) for v in check()]
    [0, 1, 9]
    >>> new.append(999999999)
    >>> comparison.append(999999999)
    >>> [len(v) for v in check()]
    [0, 1, 10]
    >>> new.extend(range(5000, 5100))
    >>> comparison.extend(range(5000, 5100))
    >>> [len(v) for v in check()]
    [0, 27, 13]
    >>> del new[15:50]
    >>> del comparison[15:50]
    >>> [len(v) for v in check()]
    [20, 27, 16]
    >>> del new[::3]
    >>> del comparison[::3]
    >>> [len(v) for v in check()]
    [35, 26, 28]
    >>> del new[::2]
    >>> del comparison[::2]
    >>> [len(v) for v in check()]
    [56, 19, 10]
    >>> del new[-1:5:-2]
    >>> del comparison[-1:5:-2]
    >>> [len(v) for v in check()]
    [56, 7, 10]

Let's make sure that BLists are correctly stored in the ZODB (which hadn't
been the case with zc.blist 1.0b1):

>>> import tempfile
>>> import ZODB.FileStorage
>>> import ZODB
>>> import transaction
>>> handle, filename = tempfile.mkstemp()
>>> fs = ZODB.FileStorage.FileStorage(filename)
>>> db = ZODB.DB(fs)
>>> conn = db.open()
>>> root = conn.root()
>>> root['foo'] = zc.blist.BList(range(60))
>>> root['bar'] = zc.blist.BList(range(31))
>>> transaction.commit()

The sequence with a length of 60 (the default bucket size being 30) lost the
collections attribute of its index:

>>> db = ZODB.DB(fs)
>>> conn = db.open()
>>> root = conn.root()
>>> root['foo'].append(0)

The sequence with a length of 31 produced a TypeError when accessing its
bucket-and-index mapping internally:

>>> db = ZODB.DB(fs)
>>> conn = db.open()
>>> root = conn.root()
>>> root['bar'].append(0)

Clean up:

>>> import os
>>> os.unlink(filename)
