# Copyright (c) 2008-2009 Aryeh Leib Taurog, http://www.aryehleib.com # All rights reserved. # # Modified from original contribution by Aryeh Leib Taurog, which was # released under the New BSD license. from django.contrib.gis.geos.mutable_list import ListMixin from django.test import SimpleTestCase class UserListA(ListMixin): _mytype = tuple def __init__(self, i_list, *args, **kwargs): self._list = self._mytype(i_list) super().__init__(*args, **kwargs) def __len__(self): return len(self._list) def __str__(self): return str(self._list) def __repr__(self): return repr(self._list) def _set_list(self, length, items): # this would work: # self._list = self._mytype(items) # but then we wouldn't be testing length parameter itemList = ["x"] * length for i, v in enumerate(items): itemList[i] = v self._list = self._mytype(itemList) def _get_single_external(self, index): return self._list[index] class UserListB(UserListA): _mytype = list def _set_single(self, index, value): self._list[index] = value def nextRange(length): nextRange.start += 100 return range(nextRange.start, nextRange.start + length) nextRange.start = 0 class ListMixinTest(SimpleTestCase): """ Tests base class ListMixin by comparing a list clone which is a ListMixin subclass with a real Python list. """ limit = 3 listType = UserListA def lists_of_len(self, length=None): if length is None: length = self.limit pl = list(range(length)) return pl, self.listType(pl) def limits_plus(self, b): return range(-self.limit - b, self.limit + b) def step_range(self): return [*range(-1 - self.limit, 0), *range(1, 1 + self.limit)] def test01_getslice(self): "Slice retrieval" pl, ul = self.lists_of_len() for i in self.limits_plus(1): with self.subTest(i=i): self.assertEqual(pl[i:], ul[i:], "slice [%d:]" % (i)) self.assertEqual(pl[:i], ul[:i], "slice [:%d]" % (i)) for j in self.limits_plus(1): self.assertEqual(pl[i:j], ul[i:j], "slice [%d:%d]" % (i, j)) for k in self.step_range(): self.assertEqual( pl[i:j:k], ul[i:j:k], "slice [%d:%d:%d]" % (i, j, k) ) for k in self.step_range(): self.assertEqual(pl[i::k], ul[i::k], "slice [%d::%d]" % (i, k)) self.assertEqual(pl[:i:k], ul[:i:k], "slice [:%d:%d]" % (i, k)) for k in self.step_range(): with self.subTest(k=k): self.assertEqual(pl[::k], ul[::k], "slice [::%d]" % (k)) def test02_setslice(self): "Slice assignment" def setfcn(x, i, j, k, L): x[i:j:k] = range(L) pl, ul = self.lists_of_len() for slen in range(self.limit + 1): ssl = nextRange(slen) with self.subTest(slen=slen): ul[:] = ssl pl[:] = ssl self.assertEqual(pl, ul[:], "set slice [:]") for i in self.limits_plus(1): ssl = nextRange(slen) ul[i:] = ssl pl[i:] = ssl self.assertEqual(pl, ul[:], "set slice [%d:]" % (i)) ssl = nextRange(slen) ul[:i] = ssl pl[:i] = ssl self.assertEqual(pl, ul[:], "set slice [:%d]" % (i)) for j in self.limits_plus(1): ssl = nextRange(slen) ul[i:j] = ssl pl[i:j] = ssl self.assertEqual(pl, ul[:], "set slice [%d:%d]" % (i, j)) for k in self.step_range(): ssl = nextRange(len(ul[i:j:k])) ul[i:j:k] = ssl pl[i:j:k] = ssl self.assertEqual( pl, ul[:], "set slice [%d:%d:%d]" % (i, j, k) ) sliceLen = len(ul[i:j:k]) msg = ( f"attempt to assign sequence of size {sliceLen + 1} " f"to extended slice of size {sliceLen}" ) with self.assertRaisesMessage(ValueError, msg): setfcn(ul, i, j, k, sliceLen + 1) if sliceLen > 2: msg = ( f"attempt to assign sequence of size {sliceLen - 1}" f" to extended slice of size {sliceLen}" ) with self.assertRaisesMessage(ValueError, msg): setfcn(ul, i, j, k, sliceLen - 1) for k in self.step_range(): ssl = nextRange(len(ul[i::k])) ul[i::k] = ssl pl[i::k] = ssl self.assertEqual(pl, ul[:], "set slice [%d::%d]" % (i, k)) ssl = nextRange(len(ul[:i:k])) ul[:i:k] = ssl pl[:i:k] = ssl self.assertEqual(pl, ul[:], "set slice [:%d:%d]" % (i, k)) for k in self.step_range(): ssl = nextRange(len(ul[::k])) ul[::k] = ssl pl[::k] = ssl self.assertEqual(pl, ul[:], "set slice [::%d]" % (k)) def test03_delslice(self): "Delete slice" for Len in range(self.limit): pl, ul = self.lists_of_len(Len) with self.subTest(Len=Len): del pl[:] del ul[:] self.assertEqual(pl[:], ul[:], "del slice [:]") for i in range(-Len - 1, Len + 1): pl, ul = self.lists_of_len(Len) del pl[i:] del ul[i:] self.assertEqual(pl[:], ul[:], "del slice [%d:]" % (i)) pl, ul = self.lists_of_len(Len) del pl[:i] del ul[:i] self.assertEqual(pl[:], ul[:], "del slice [:%d]" % (i)) for j in range(-Len - 1, Len + 1): pl, ul = self.lists_of_len(Len) del pl[i:j] del ul[i:j] self.assertEqual(pl[:], ul[:], "del slice [%d:%d]" % (i, j)) for k in [*range(-Len - 1, 0), *range(1, Len)]: pl, ul = self.lists_of_len(Len) del pl[i:j:k] del ul[i:j:k] self.assertEqual( pl[:], ul[:], "del slice [%d:%d:%d]" % (i, j, k) ) for k in [*range(-Len - 1, 0), *range(1, Len)]: pl, ul = self.lists_of_len(Len) del pl[:i:k] del ul[:i:k] self.assertEqual(pl[:], ul[:], "del slice [:%d:%d]" % (i, k)) pl, ul = self.lists_of_len(Len) del pl[i::k] del ul[i::k] self.assertEqual(pl[:], ul[:], "del slice [%d::%d]" % (i, k)) for k in [*range(-Len - 1, 0), *range(1, Len)]: pl, ul = self.lists_of_len(Len) del pl[::k] del ul[::k] self.assertEqual(pl[:], ul[:], "del slice [::%d]" % (k)) def test04_get_set_del_single(self): "Get/set/delete single item" pl, ul = self.lists_of_len() for i in self.limits_plus(0): with self.subTest(i=i): self.assertEqual(pl[i], ul[i], "get single item [%d]" % i) for i in self.limits_plus(0): pl, ul = self.lists_of_len() pl[i] = 100 ul[i] = 100 with self.subTest(i=i): self.assertEqual(pl[:], ul[:], "set single item [%d]" % i) for i in self.limits_plus(0): pl, ul = self.lists_of_len() del pl[i] del ul[i] with self.subTest(i=i): self.assertEqual(pl[:], ul[:], "del single item [%d]" % i) def test05_out_of_range_exceptions(self): "Out of range exceptions" def setfcn(x, i): x[i] = 20 def getfcn(x, i): return x[i] def delfcn(x, i): del x[i] pl, ul = self.lists_of_len() for i in (-1 - self.limit, self.limit): msg = f"invalid index: {i}" with self.subTest(i=i): with self.assertRaisesMessage(IndexError, msg): setfcn(ul, i) with self.assertRaisesMessage(IndexError, msg): getfcn(ul, i) with self.assertRaisesMessage(IndexError, msg): delfcn(ul, i) def test06_list_methods(self): "List methods" pl, ul = self.lists_of_len() pl.append(40) ul.append(40) self.assertEqual(pl[:], ul[:], "append") pl.extend(range(50, 55)) ul.extend(range(50, 55)) self.assertEqual(pl[:], ul[:], "extend") pl.reverse() ul.reverse() self.assertEqual(pl[:], ul[:], "reverse") for i in self.limits_plus(1): pl, ul = self.lists_of_len() pl.insert(i, 50) ul.insert(i, 50) with self.subTest(i=i): self.assertEqual(pl[:], ul[:], "insert at %d" % i) for i in self.limits_plus(0): pl, ul = self.lists_of_len() with self.subTest(i=i): self.assertEqual(pl.pop(i), ul.pop(i), "popped value at %d" % i) self.assertEqual(pl[:], ul[:], "after pop at %d" % i) pl, ul = self.lists_of_len() self.assertEqual(pl.pop(), ul.pop(i), "popped value") self.assertEqual(pl[:], ul[:], "after pop") pl, ul = self.lists_of_len() def popfcn(x, i): x.pop(i) with self.assertRaisesMessage(IndexError, "invalid index: 3"): popfcn(ul, self.limit) with self.assertRaisesMessage(IndexError, "invalid index: -4"): popfcn(ul, -1 - self.limit) pl, ul = self.lists_of_len() for val in range(self.limit): with self.subTest(val=val): self.assertEqual(pl.index(val), ul.index(val), "index of %d" % val) for val in self.limits_plus(2): with self.subTest(val=val): self.assertEqual(pl.count(val), ul.count(val), "count %d" % val) for val in range(self.limit): pl, ul = self.lists_of_len() pl.remove(val) ul.remove(val) with self.subTest(val=val): self.assertEqual(pl[:], ul[:], "after remove val %d" % val) def indexfcn(x, v): return x.index(v) def removefcn(x, v): return x.remove(v) msg = "40 not found in object" with self.assertRaisesMessage(ValueError, msg): indexfcn(ul, 40) with self.assertRaisesMessage(ValueError, msg): removefcn(ul, 40) def test07_allowed_types(self): "Type-restricted list" pl, ul = self.lists_of_len() ul._allowed = int ul[1] = 50 ul[:2] = [60, 70, 80] def setfcn(x, i, v): x[i] = v msg = "Invalid type encountered in the arguments." with self.assertRaisesMessage(TypeError, msg): setfcn(ul, 2, "hello") with self.assertRaisesMessage(TypeError, msg): setfcn(ul, slice(0, 3, 2), ("hello", "goodbye")) def test08_min_length(self): "Length limits" pl, ul = self.lists_of_len(5) ul._minlength = 3 def delfcn(x, i): del x[:i] def setfcn(x, i): x[:i] = [] msg = "Must have at least 3 items" for i in range(len(ul) - ul._minlength + 1, len(ul)): with self.subTest(i=i): with self.assertRaisesMessage(ValueError, msg): delfcn(ul, i) with self.assertRaisesMessage(ValueError, msg): setfcn(ul, i) del ul[: len(ul) - ul._minlength] ul._maxlength = 4 for i in range(0, ul._maxlength - len(ul)): with self.subTest(i=i): ul.append(i) with self.assertRaisesMessage(ValueError, "Cannot have more than 4 items"): ul.append(10) def test09_iterable_check(self): "Error on assigning non-iterable to slice" pl, ul = self.lists_of_len(self.limit + 1) def setfcn(x, i, v): x[i] = v with self.assertRaisesMessage( TypeError, "can only assign an iterable to a slice" ): setfcn(ul, slice(0, 3, 2), 2) def test10_checkindex(self): "Index check" pl, ul = self.lists_of_len() for i in self.limits_plus(0): with self.subTest(i=i): if i < 0: self.assertEqual( ul._checkindex(i), i + self.limit, "_checkindex(neg index)" ) else: self.assertEqual(ul._checkindex(i), i, "_checkindex(pos index)") for i in (-self.limit - 1, self.limit): with ( self.subTest(i=i), self.assertRaisesMessage(IndexError, f"invalid index: {i}"), ): ul._checkindex(i) def test_11_sorting(self): "Sorting" pl, ul = self.lists_of_len() pl.insert(0, pl.pop()) ul.insert(0, ul.pop()) pl.sort() ul.sort() self.assertEqual(pl[:], ul[:], "sort") mid = pl[len(pl) // 2] pl.sort(key=lambda x: (mid - x) ** 2) ul.sort(key=lambda x: (mid - x) ** 2) self.assertEqual(pl[:], ul[:], "sort w/ key") pl.insert(0, pl.pop()) ul.insert(0, ul.pop()) pl.sort(reverse=True) ul.sort(reverse=True) self.assertEqual(pl[:], ul[:], "sort w/ reverse") mid = pl[len(pl) // 2] pl.sort(key=lambda x: (mid - x) ** 2) ul.sort(key=lambda x: (mid - x) ** 2) self.assertEqual(pl[:], ul[:], "sort w/ key") def test_12_arithmetic(self): "Arithmetic" pl, ul = self.lists_of_len() al = list(range(10, 14)) self.assertEqual(list(pl + al), list(ul + al), "add") self.assertEqual(type(ul), type(ul + al), "type of add result") self.assertEqual(list(al + pl), list(al + ul), "radd") self.assertEqual(type(al), type(al + ul), "type of radd result") objid = id(ul) pl += al ul += al self.assertEqual(pl[:], ul[:], "in-place add") self.assertEqual(objid, id(ul), "in-place add id") for n in (-1, 0, 1, 3): pl, ul = self.lists_of_len() self.assertEqual(list(pl * n), list(ul * n), "mul by %d" % n) self.assertEqual(type(ul), type(ul * n), "type of mul by %d result" % n) self.assertEqual(list(n * pl), list(n * ul), "rmul by %d" % n) self.assertEqual(type(ul), type(n * ul), "type of rmul by %d result" % n) objid = id(ul) pl *= n ul *= n self.assertEqual(pl[:], ul[:], "in-place mul by %d" % n) self.assertEqual(objid, id(ul), "in-place mul by %d id" % n) pl, ul = self.lists_of_len() self.assertEqual(pl, ul, "cmp for equal") self.assertNotEqual(ul, pl + [2], "cmp for not equal") self.assertGreaterEqual(pl, ul, "cmp for gte self") self.assertLessEqual(pl, ul, "cmp for lte self") self.assertGreaterEqual(ul, pl, "cmp for self gte") self.assertLessEqual(ul, pl, "cmp for self lte") self.assertGreater(pl + [5], ul, "cmp") self.assertGreaterEqual(pl + [5], ul, "cmp") self.assertLess(pl, ul + [2], "cmp") self.assertLessEqual(pl, ul + [2], "cmp") self.assertGreater(ul + [5], pl, "cmp") self.assertGreaterEqual(ul + [5], pl, "cmp") self.assertLess(ul, pl + [2], "cmp") self.assertLessEqual(ul, pl + [2], "cmp") pl[1] = 20 self.assertGreater(pl, ul, "cmp for gt self") self.assertLess(ul, pl, "cmp for self lt") pl[1] = -20 self.assertLess(pl, ul, "cmp for lt self") self.assertGreater(ul, pl, "cmp for gt self") class ListMixinTestSingle(ListMixinTest): listType = UserListB