launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #04692
[Merge] lp:~bac/lazr.batchnavigator/bug-826839 into lp:lazr.batchnavigator
Brad Crittenden has proposed merging lp:~bac/lazr.batchnavigator/bug-826839 into lp:lazr.batchnavigator.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #826839 in Launchpad itself: "DataError: OFFSET must not be negative"
https://bugs.launchpad.net/launchpad/+bug/826839
For more details, see:
https://code.launchpad.net/~bac/lazr.batchnavigator/bug-826839/+merge/72247
Negative slice indexing into Storm/Postgresql empty result sets causes an exception to be raised. In that instance, prevent the index from being negative.
--
https://code.launchpad.net/~bac/lazr.batchnavigator/bug-826839/+merge/72247
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~bac/lazr.batchnavigator/bug-826839 into lp:lazr.batchnavigator.
=== modified file 'src/lazr/batchnavigator/_batchnavigator.py'
--- src/lazr/batchnavigator/_batchnavigator.py 2011-08-17 14:34:35 +0000
+++ src/lazr/batchnavigator/_batchnavigator.py 2011-08-19 18:57:23 +0000
@@ -363,7 +363,8 @@
self.results = IFiniteSequence(self.results)
total_length = len(self.results)
offset = total_length
- offset_plus_size = offset + offset_plus_size
+ # Storm raises an exception for a negative initial offset.
+ offset_plus_size = max(offset + offset_plus_size, 0)
result = list(self.results[offset_plus_size:offset])
result.reverse()
return result
=== modified file 'src/lazr/batchnavigator/tests/test_batchnavigator.py'
--- src/lazr/batchnavigator/tests/test_batchnavigator.py 2011-08-17 14:34:35 +0000
+++ src/lazr/batchnavigator/tests/test_batchnavigator.py 2011-08-19 18:57:23 +0000
@@ -66,6 +66,35 @@
return BatchNavigator(collection, request, range_factory=range_factory)
+class PickyListLikeCollection:
+ """Collection that really hates slices with negative start values.
+
+ Some database-backed collections, e.g. Postgres via Storm, throw
+ exceptions if a slice is used that starts with a negative value. When
+ batch navigator is going backwards it is easy to pass such a slice. This
+ collection is used to ensure it is noticed if that happens.
+ """
+ def __init__(self, collection):
+ self._collection = collection
+ def __iter__(self):
+ return iter(self._collection)
+ def __getitem__(self, index):
+ if (isinstance(index, slice) and
+ index.start is not None and index.start < 0):
+ raise RuntimeError
+ return self._collection.__getitem__(index)
+ def __len__(self):
+ return self._collection.__len__()
+
+
+def empty_batchnav(start=None, batch=None, memo=None, direction=None):
+ collection = PickyListLikeCollection([])
+ request = batchnav_request(start=start, batch=batch, memo=memo,
+ direction=direction)
+ range_factory = RecordingFactory(collection)
+ return BatchNavigator(collection, request, range_factory=range_factory)
+
+
class EqualsQuery(Equals):
def __init__(self, start=None, batch=None, direction=None, memo=None):
@@ -80,7 +109,8 @@
results = [1, 2, 3, 4]
range_factory = ListRangeFactory(results)
request = batchnav_request()
- batchnav = BatchNavigator(results, request, range_factory=range_factory)
+ batchnav = BatchNavigator(
+ results, request, range_factory=range_factory)
self.assertEqual(batchnav.batch.range_factory, range_factory)
def test_without_memo_direction_gets_non_factory_batch(self):
@@ -167,6 +197,16 @@
batchnav.nextBatchURL(),
Equals(SERVER_URL + '?batch=3&memo=end%26%2F2&start=9'))
+ def test_empty_collection(self):
+ batchnav = empty_batchnav(
+ start=2, batch=2)
+ self.assertEqual([], list(batchnav.currentBatch()))
+
+ def test_empty_collection_backwards(self):
+ batchnav = empty_batchnav(
+ start=2, batch=2, direction='backwards')
+ self.assertEqual([], list(batchnav.currentBatch()))
+
class TestListRangeFactory(testtools.TestCase):
@@ -221,42 +261,70 @@
def test_getslice_next_end(self):
# at the end, crickets...
results = [0, 1, 2, 3, 4, 5]
- slice = ListRangeFactory(results).getSlice(3, '6')
- self.assertEqual([], slice)
+ _slice = ListRangeFactory(results).getSlice(3, '6')
+ self.assertEqual([], _slice)
def test_getslice_before_start(self):
# at the end, crickets...
results = [0, 1, 2, 3, 4, 5]
- slice = list(ListRangeFactory(results).getSlice(3, '0', forwards=False))
- self.assertEqual([], slice)
+ _slice = list(ListRangeFactory(results).getSlice(3, '0', forwards=False))
+ self.assertEqual([], _slice)
def test_getslice_before_end(self):
results = [0, 1, 2, 3, 4, 5]
- slice = list(ListRangeFactory(results).getSlice(3, '6', forwards=False))
- self.assertEqual([5, 4, 3], slice)
+ _slice = list(ListRangeFactory(results).getSlice(3, '6', forwards=False))
+ self.assertEqual([5, 4, 3], _slice)
def test_getslice_next(self):
# The slice returned starts where indicated but continues on.
results = [0, 1, 2, 3, 4, 5]
- slice = ListRangeFactory(results).getSlice(3, '3')
- self.assertEqual([3, 4, 5], slice)
+ _slice = ListRangeFactory(results).getSlice(3, '3')
+ self.assertEqual([3, 4, 5], _slice)
def test_getslice_before_middle(self):
# Going backwards does not include the anchor (because going forwards
# includes it)
results = [0, 1, 2, 3, 4, 5]
- slice = list(ListRangeFactory(results).getSlice(3, '3', forwards=False))
- self.assertEqual([2, 1, 0], slice)
+ _slice = list(ListRangeFactory(results).getSlice(3, '3', forwards=False))
+ self.assertEqual([2, 1, 0], _slice)
def test_getSliceByIndex(self):
# getSliceByIndex returns the slice of the result limited by
# the indices start, end.
results = [0, 1, 2, 3, 4, 5]
- slice = ListRangeFactory(results).getSliceByIndex(2, 5)
- self.assertEqual([2, 3, 4], slice)
+ _slice = ListRangeFactory(results).getSliceByIndex(2, 5)
+ self.assertEqual([2, 3, 4], _slice)
def test_getSliceByIndex__no_result_set(self):
# If no result set is present, getSliceByIndex() returns an
# empty list.
- slice = ListRangeFactory(None).getSliceByIndex(2, 5)
- self.assertEqual([], slice)
+ _slice = ListRangeFactory(None).getSliceByIndex(2, 5)
+ self.assertEqual([], _slice)
+
+ def test_picky_collection_ok(self):
+ p = PickyListLikeCollection(range(5))
+ self.assertEqual(range(5)[0:2], p[0:2])
+
+ def test_picky_collection_bad(self):
+ p = PickyListLikeCollection([])
+ # It would be nice to demonstrate p[-10:2] but it cannot be done
+ # directly since assertRaises needs individual parameters. The
+ # following is the equivalent.
+ self.assertRaises(RuntimeError,
+ p.__getitem__, slice(-10, 2, None))
+
+ def test_getSlice_empty_result_set_forwards(self):
+ results = PickyListLikeCollection([])
+ _slice = ListRangeFactory(results).getSlice(5, forwards=True)
+ self.assertEqual([], _slice)
+
+ def test_getSlice_empty_result_set_backwards(self):
+ results = PickyListLikeCollection([])
+ _slice = ListRangeFactory(results).getSlice(5, forwards=False)
+ self.assertEqual([], _slice)
+
+ def test_getSliceByIndex_empty_result_set(self):
+ results = PickyListLikeCollection([])
+ self.assertRaises(
+ RuntimeError,
+ ListRangeFactory(results).getSliceByIndex, -1, 1)