Fixed some functionalities and added more tests

This commit is contained in:
S. Mahmudul Hasan 2023-10-18 01:45:18 -04:00
parent cb76ade912
commit 14cd420cfb
2 changed files with 512 additions and 52 deletions

View File

@ -12,6 +12,7 @@ namespace pkpy
bool bounded = false; // if true, maxlen is not -1 bool bounded = false; // if true, maxlen is not -1
void insertObj(bool front, bool back, int index, PyObject *item); // insert at index, used purely for internal purposes: append, appendleft, insert methods void insertObj(bool front, bool back, int index, PyObject *item); // insert at index, used purely for internal purposes: append, appendleft, insert methods
PyObject *popObj(bool front, bool back, PyObject *item, VM *vm); // pop at index, used purely for internal purposes: pop, popleft, remove methods PyObject *popObj(bool front, bool back, PyObject *item, VM *vm); // pop at index, used purely for internal purposes: pop, popleft, remove methods
int findIndex(VM *vm, PyObject *obj, int start, int stop); // find the index of the given object in the deque
std::stringstream getRepr(VM *vm); // get the string representation of the deque std::stringstream getRepr(VM *vm); // get the string representation of the deque
// Special methods // Special methods
static void _register(VM *vm, PyObject *mod, PyObject *type); // register the type static void _register(VM *vm, PyObject *mod, PyObject *type); // register the type
@ -235,8 +236,8 @@ namespace pkpy
{ {
if (vm->py_equals((*it), obj)) if (vm->py_equals((*it), obj))
cnt++; cnt++;
if (sz != self.dequeItems.size()) // mutating the deque during iteration is not allowed if (sz != self.dequeItems.size()) // mutating the deque during iteration is not allowed
vm->RuntimeError("deque mutated during iteration");//TODO replace this with RuntimeError vm->RuntimeError("deque mutated during iteration"); // TODO replace this with RuntimeError
} }
return VAR(cnt); return VAR(cnt);
}); });
@ -267,31 +268,26 @@ namespace pkpy
start = CAST(int, args[2]); start = CAST(int, args[2]);
if (!vm->py_equals(args[3], vm->None)) if (!vm->py_equals(args[3], vm->None))
stop = CAST(int, args[3]); stop = CAST(int, args[3]);
// the following code is special purpose normalization for this method, taken from CPython: _collectionsmodule.c file int index = self.findIndex(vm, obj, start, stop);
if (start < 0) if (index != -1)
{ return VAR(index);
start = self.dequeItems.size() + start; // try to fix for negative indices else
if (start < 0) vm->ValueError(_CAST(Str &, vm->py_repr(obj)) + " is not in deque");
start = 0;
}
if (stop < 0)
{
stop = self.dequeItems.size() + stop; // try to fix for negative indices
if (stop < 0)
stop = 0;
}
if (stop > self.dequeItems.size())
stop = self.dequeItems.size();
if (start > stop)
start = stop; // end of normalization
PK_ASSERT(start >= 0 && start <= self.dequeItems.size() && stop >= 0 && stop <= self.dequeItems.size() && start <= stop); // sanity check
int loopSize = std::min((int)self.dequeItems.size(), stop);
for (int i = start; i < loopSize; i++)
if (vm->py_equals(self.dequeItems[i], obj))
return VAR(i);
vm->ValueError(_CAST(Str &, vm->py_repr(obj)) + " is not in deque");
return vm->None; return vm->None;
}); });
// NEW: returns the index of the given object in the deque
vm->bind(type, "__contains__(self, obj) -> bool",
[](VM *vm, ArgsView args)
{
// Return the position of x in the deque (at or after index start and before index stop). Returns the first match or raises ValueError if not found.
PyDeque &self = _CAST(PyDeque &, args[0]);
PyObject *obj = args[1];
int start = 0, stop = self.dequeItems.size(); // default values
int index = self.findIndex(vm, obj, start, stop);
if (index != -1)
return VAR(true);
return VAR(false);
});
// NEW: inserts the given object at the given index // NEW: inserts the given object at the given index
vm->bind(type, "insert(self, index, obj) -> None", vm->bind(type, "insert(self, index, obj) -> None",
[](VM *vm, ArgsView args) [](VM *vm, ArgsView args)
@ -300,7 +296,7 @@ namespace pkpy
int index = CAST(int, args[1]); int index = CAST(int, args[1]);
PyObject *obj = args[2]; PyObject *obj = args[2];
if (self.bounded && self.dequeItems.size() == self.maxlen) if (self.bounded && self.dequeItems.size() == self.maxlen)
vm->ValueError("deque already at its maximum size"); vm->IndexError("deque already at its maximum size");
else else
self.insertObj(false, false, index, obj); // this index shouldn't be fixed using vm->normalized_index, pass as is self.insertObj(false, false, index, obj); // this index shouldn't be fixed using vm->normalized_index, pass as is
return vm->None; return vm->None;
@ -338,7 +334,8 @@ namespace pkpy
{ {
PyDeque &self = _CAST(PyDeque &, args[0]); PyDeque &self = _CAST(PyDeque &, args[0]);
int n = CAST(int, args[1]); int n = CAST(int, args[1]);
if (n != 0) // trivial case
if (n != 0 && !self.dequeItems.empty()) // trivial case
{ {
PyObject *tmp; // holds the object to be rotated PyObject *tmp; // holds the object to be rotated
int direction = n > 0 ? 1 : -1; int direction = n > 0 ? 1 : -1;
@ -400,6 +397,39 @@ namespace pkpy
PyDeque::PyDeque(VM *vm, PyObject *iterable, PyObject *maxlen) PyDeque::PyDeque(VM *vm, PyObject *iterable, PyObject *maxlen)
{ {
} }
int PyDeque::findIndex(VM *vm, PyObject *obj, int start, int stop)
{
// the following code is special purpose normalization for this method, taken from CPython: _collectionsmodule.c file
if (start < 0)
{
start = this->dequeItems.size() + start; // try to fix for negative indices
if (start < 0)
start = 0;
}
if (stop < 0)
{
stop = this->dequeItems.size() + stop; // try to fix for negative indices
if (stop < 0)
stop = 0;
}
if (stop > this->dequeItems.size())
stop = this->dequeItems.size();
if (start > stop)
start = stop; // end of normalization
PK_ASSERT(start >= 0 && start <= this->dequeItems.size() && stop >= 0 && stop <= this->dequeItems.size() && start <= stop); // sanity check
int loopSize = std::min((int)(this->dequeItems.size()), stop);
int sz = this->dequeItems.size();
for (int i = start; i < loopSize; i++)
{
if (vm->py_equals(this->dequeItems[i], obj))
return i;
if (sz != this->dequeItems.size()) // mutating the deque during iteration is not allowed
vm->RuntimeError("deque mutated during iteration"); // TODO replace this with RuntimeError
}
return -1;
}
/// @brief pops or removes an item from the deque /// @brief pops or removes an item from the deque
/// @param front if true, pop from the front of the deque /// @param front if true, pop from the front of the deque
/// @param back if true, pop from the back of the deque /// @param back if true, pop from the back of the deque
@ -432,13 +462,19 @@ namespace pkpy
else else
{ {
// front and back are not set, we care about item, this is a remove operation and we return the removed item or nullptr // front and back are not set, we care about item, this is a remove operation and we return the removed item or nullptr
int sz = this->dequeItems.size();
for (auto it = this->dequeItems.begin(); it != this->dequeItems.end(); ++it) for (auto it = this->dequeItems.begin(); it != this->dequeItems.end(); ++it)
if (vm->py_equals((*it), item)) {
bool found = vm->py_equals((*it), item);
if (sz != this->dequeItems.size()) // mutating the deque during iteration is not allowed
vm->IndexError("deque mutated during iteration");
if (found)
{ {
PyObject *obj = *it; // keep a reference to the object for returning PyObject *obj = *it; // keep a reference to the object for returning
this->dequeItems.erase(it); this->dequeItems.erase(it);
return obj; return obj;
} }
}
return nullptr; // not found return nullptr; // not found
} }
} }

View File

@ -17,9 +17,13 @@ assert q == deque([1, 2])
## ADDING TESTS FROM CPYTHON's test_deque.py file ## ADDING TESTS FROM CPYTHON's test_deque.py file
############TEST BASICS############### ############TEST basics###############
def assertEqual(a, b): def assertEqual(a, b):
assert a == b assert a == b
BIG = 100000
def fail():
raise SyntaxError
yield 1
d = deque(range(-5125, -5000)) d = deque(range(-5125, -5000))
d.__init__(range(200)) d.__init__(range(200))
@ -40,7 +44,7 @@ right.reverse()
assertEqual(right, list(range(150, 400))) assertEqual(right, list(range(150, 400)))
assertEqual(list(d), list(range(50, 150))) assertEqual(list(d), list(range(50, 150)))
#######TEST MAXLEN############### #######TEST maxlen###############
try: try:
dq = deque() dq = deque()
dq.maxlen = -1 dq.maxlen = -1
@ -73,7 +77,7 @@ assertEqual(repr(d)[-30:], ', 198, 199, [...]], maxlen=10)')
d = deque(range(10), maxlen=None) d = deque(range(10), maxlen=None)
assertEqual(repr(d), 'deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])') assertEqual(repr(d), 'deque([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])')
#######TEST MAXLEN SIZE 0############### #######TEST maxlen = 0###############
it = iter(range(100)) it = iter(range(100))
deque(it, maxlen=0) deque(it, maxlen=0)
assertEqual(list(it), []) assertEqual(list(it), [])
@ -89,7 +93,7 @@ d.extendleft(it)
assertEqual(list(it), []) assertEqual(list(it), [])
#######TEST MAXLEN ATTRIBUTE############# #######TEST maxlen attribute #############
assertEqual(deque().maxlen, None) assertEqual(deque().maxlen, None)
assertEqual(deque('abc').maxlen, None) assertEqual(deque('abc').maxlen, None)
@ -99,12 +103,12 @@ assertEqual(deque('abc', maxlen=0).maxlen, 0)
try: try:
d = deque('abc') d = deque('abc')
d.maxlen = 10 d.maxlen = 10
print("Failed Tests!!") print("X Failed Tests!!")
exit(1) exit(1)
except AttributeError: except AttributeError:
pass pass
######### TEST COUNT################# ######### TEST count()#################
for s in ('', 'abracadabra', 'simsalabim'*500+'abc'): for s in ('', 'abracadabra', 'simsalabim'*500+'abc'):
s = list(s) s = list(s)
d = deque(s) d = deque(s)
@ -112,14 +116,14 @@ for s in ('', 'abracadabra', 'simsalabim'*500+'abc'):
assertEqual(s.count(letter), d.count(letter)) assertEqual(s.count(letter), d.count(letter))
try: try:
d.count() d.count()
print("Failed Tests!!") print("X Failed Tests!!")
exit(1) exit(1)
except TypeError: except TypeError:
pass pass
try: try:
d.count(1,2) d.count(1,2)
print("Failed Tests!!") print("X Failed Tests!!")
exit(1) exit(1)
except TypeError: except TypeError:
pass pass
@ -131,7 +135,7 @@ d = deque([1, 2, BadCompare(), 3])
try: try:
d.count(2) d.count(2)
print("Failed Tests!!") print("X Failed Tests!!")
exit(1) exit(1)
except ArithmeticError: except ArithmeticError:
pass pass
@ -139,7 +143,7 @@ except ArithmeticError:
d = deque([1, 2, 3]) d = deque([1, 2, 3])
try: try:
d.count(BadCompare()) d.count(BadCompare())
print("Failed Tests!!") print("X Failed Tests!!")
exit(1) exit(1)
except ArithmeticError: except ArithmeticError:
pass pass
@ -154,7 +158,7 @@ m.d = d
try: try:
d.count(3) d.count(3)
print("Failed Tests!") print("X Failed Tests!")
exit(1) exit(1)
except RuntimeError: except RuntimeError:
pass pass
@ -166,7 +170,7 @@ d.rotate(1)
assertEqual(d.count(1), 0) assertEqual(d.count(1), 0)
assertEqual(d.count(None), 16) assertEqual(d.count(None), 16)
#### TEST COMPARISONS##### #### TEST comparisons == #####
d = deque('xabc') d = deque('xabc')
d.popleft() d.popleft()
@ -184,7 +188,9 @@ for x in args:
# assertEqual(x > y, list(x) > list(y)) # not currently supported # assertEqual(x > y, list(x) > list(y)) # not currently supported
# assertEqual(x >= y, list(x) >= list(y)) # not currently supported # assertEqual(x >= y, list(x) >= list(y)) # not currently supported
###############TEST CONTAINS#################
###############TEST contains()#################
n = 200 n = 200
@ -202,23 +208,441 @@ class MutateCmp:
self.deque.clear() self.deque.clear()
return self.result return self.result
# Test detection of mutation during iteration # # Test detection of mutation during iteration
# d = deque(range(n)) d = deque(range(n))
# d[n//2] = MutateCmp(d, False) d[n//2] = MutateCmp(d, False)
# print(n in d) try:
# with assertRaises(RuntimeError): n in d
# n in d print("X Failed Tests!")
exit(1)
except RuntimeError:
pass
class BadCmp:
def __eq__(self, other):
raise RuntimeError
# # Test detection of comparison exceptions # # Test detection of comparison exceptions
# d = deque(range(n)) d = deque(range(n))
# d[n//2] = BadCmp() d[n//2] = BadCmp()
# with assertRaises(RuntimeError): try:
# n in d n in d
print("X Failed Tests!")
exit(1)
except RuntimeError:
pass
#####test_contains_count_stop_crashes#####
class A:
def __eq__(self, other):
d.clear()
return NotImplemented
d = deque([A(), A()])
try:
_ = 3 in d
print("X Failed Tests!")
exit(1)
except RuntimeError:
pass
d = deque([A(), A()])
try:
_ = d.count(3)
print("X Failed Tests!")
exit(1)
except RuntimeError:
pass
########TEST extend()################
d = deque('a')
try:
d.extend(1)
print("X Failed Tests!")
exit(1)
except TypeError:
pass
d.extend('bcd')
assertEqual(list(d), list('abcd'))
d.extend(d)
assertEqual(list(d), list('abcdabcd'))
######TEST extend_left() ################
d = deque('a')
try:
d.extendleft(1)
print("X Failed Tests!")
exit(1)
except TypeError:
pass
d.extendleft('bcd')
assertEqual(list(d), list(reversed('abcd')))
d.extendleft(d)
assertEqual(list(d), list('abcddcba'))
d = deque()
d.extendleft(range(1000))
assertEqual(list(d), list(reversed(range(1000))))
try:
d.extendleft(fail())
print("X Failed Tests!")
exit(1)
except SyntaxError:
pass
##### TEST get_item ################
n = 200
d = deque(range(n))
l = list(range(n))
for i in range(n):
d.popleft()
l.pop(0)
if random.random() < 0.5:
d.append(i)
l.append(i)
for j in range(1-len(l), len(l)):
assert d[j] == l[j]
d = deque('superman')
assertEqual(d[0], 's')
assertEqual(d[-1], 'n')
d = deque()
try:
d.__getitem__(0)
print("X Failed Tests!")
exit(1)
except IndexError:
pass
try:
d.__getitem__(-1)
print("X Failed Tests!")
exit(1)
except IndexError:
pass
#########TEST index()###############
for n in 1, 2, 30, 40, 200:
d = deque(range(n))
for i in range(n):
assertEqual(d.index(i), i)
try:
d.index(n+1)
print("X Failed Tests!")
exit(1)
except ValueError:
pass
# Test detection of mutation during iteration
d = deque(range(n))
d[n//2] = MutateCmp(d, False)
try:
d.index(n)
print("X Failed Tests!")
exit(1)
except RuntimeError:
pass
# Test detection of comparison exceptions
d = deque(range(n))
d[n//2] = BadCmp()
try:
d.index(n)
print("X Failed Tests!")
exit(1)
except RuntimeError:
pass
# Test start and stop arguments behavior matches list.index()
#COMMENT: Current List behavior doesn't support start and stop arguments, so this test is not supported
# elements = 'ABCDEFGHI'
# nonelement = 'Z'
# d = deque(elements * 2)
# s = list(elements * 2)
# for start in range(-5 - len(s)*2, 5 + len(s) * 2):
# for stop in range(-5 - len(s)*2, 5 + len(s) * 2):
# for element in elements + 'Z':
# try:
# print(element, start, stop)
# target = s.index(element, start, stop)
# except ValueError:
# try:
# d.index(element, start, stop)
# print("X Failed Tests!")
# exit(1)
# except ValueError:
# continue
# # with assertRaises(ValueError):
# # d.index(element, start, stop)
# assertEqual(d.index(element, start, stop), target)
# Test large start argument
d = deque(range(0, 10000, 10))
for step in range(100):
i = d.index(8500, 700)
assertEqual(d[i], 8500)
# Repeat test with a different internal offset
d.rotate()
###########test_index_bug_24913#############
d = deque('A' * 3)
try:
d.index('A', 1, 0)
print("X Failed Tests!")
exit(1)
except ValueError:
pass
###########test_insert#############
# Test to make sure insert behaves like lists
elements = 'ABCDEFGHI'
for i in range(-5 - len(elements)*2, 5 + len(elements) * 2):
d = deque('ABCDEFGHI')
s = list('ABCDEFGHI')
d.insert(i, 'Z')
s.insert(i, 'Z')
assertEqual(list(d), s)
###########test_insert_bug_26194#############
data = 'ABC'
d = deque(data, maxlen=len(data))
try:
d.insert(0, 'Z')
print("X Failed Tests!")
exit(1)
except IndexError:
pass
elements = 'ABCDEFGHI'
for i in range(-len(elements), len(elements)):
d = deque(elements, maxlen=len(elements)+1)
d.insert(i, 'Z')
if i >= 0:
assertEqual(d[i], 'Z')
else:
assertEqual(d[i-1], 'Z')
######### test set_item #############
n = 200
d = deque(range(n))
for i in range(n):
d[i] = 10 * i
assertEqual(list(d), [10*i for i in range(n)])
l = list(d)
for i in range(1-n, 0, -1):
d[i] = 7*i
l[i] = 7*i
assertEqual(list(d), l)
########## test del_item #############
n = 500 # O(n**2) test, don't make this too big
d = deque(range(n))
try:
d.__delitem__(-n-1)
print("X Failed Tests!")
exit(1)
except IndexError:
pass
try:
d.__delitem__(n)
print("X Failed Tests!")
exit(1)
except IndexError:
pass
for i in range(n):
assertEqual(len(d), n-i)
j = random.randint(0, len(d)-1)
val = d[j]
assertEqual(val in d, True)
del d[j]
assertEqual(val in d, False)
assertEqual(len(d), 0)
print("ALL TEST PASSED!!") #########test reverse()###############
n = 500 # O(n**2) test, don't make this too big
data = [random.random() for i in range(n)]
for i in range(n):
d = deque(data[:i])
r = d.reverse()
assertEqual(list(d), list(reversed(data[:i])))
assertEqual(r, None)
d.reverse()
assertEqual(list(d), data[:i])
try:
d.reverse(1)
print("X Failed Tests!")
exit(1)
except TypeError:
pass
############test rotate#############
s = tuple('abcde')
n = len(s)
d = deque(s)
d.rotate(1) # verify rot(1)
assertEqual(''.join(d), 'eabcd')
d = deque(s)
d.rotate(-1) # verify rot(-1)
assertEqual(''.join(d), 'bcdea')
d.rotate() # check default to 1
assertEqual(tuple(d), s)
for i in range(n*3):
d = deque(s)
e = deque(d)
d.rotate(i) # check vs. rot(1) n times
for j in range(i):
e.rotate(1)
assertEqual(tuple(d), tuple(e))
d.rotate(-i) # check that it works in reverse
assertEqual(tuple(d), s)
e.rotate(n-i) # check that it wraps forward
assertEqual(tuple(e), s)
for i in range(n*3):
d = deque(s)
e = deque(d)
d.rotate(-i)
for j in range(i):
e.rotate(-1) # check vs. rot(-1) n times
assertEqual(tuple(d), tuple(e))
d.rotate(i) # check that it works in reverse
assertEqual(tuple(d), s)
e.rotate(i-n) # check that it wraps backaround
assertEqual(tuple(e), s)
d = deque(s)
e = deque(s)
e.rotate(BIG+17) # verify on long series of rotates
dr = d.rotate
for i in range(BIG+17):
dr()
assertEqual(tuple(d), tuple(e))
try:
d.rotate(1, 2)
print("X Failed Tests!")
exit(1)
except TypeError:
pass
try:
d.rotate(1,10)
print("X Failed Tests!")
exit(1)
except TypeError:
pass
d = deque()
d.rotate() # rotate an empty deque
assertEqual(d, deque())
##########test len#############
d = deque('ab')
assertEqual(len(d), 2)
d.popleft()
assertEqual(len(d), 1)
d.pop()
assertEqual(len(d), 0)
try:
d.pop()
print("X Failed Tests!")
exit(1)
except IndexError:
pass
assertEqual(len(d), 0)
d.append('c')
assertEqual(len(d), 1)
d.appendleft('d')
assertEqual(len(d), 2)
d.clear()
assertEqual(len(d), 0)
##############test underflow#############
d = deque()
try:
d.pop()
print("X Failed Tests!")
exit(1)
except IndexError:
pass
try:
d.popleft()
print("X Failed Tests!")
exit(1)
except IndexError:
pass
##############test clear#############
d = deque(range(100))
assertEqual(len(d), 100)
d.clear()
assertEqual(len(d), 0)
assertEqual(list(d), [])
d.clear() # clear an empty deque
assertEqual(list(d), [])
#############test remove#############
d = deque('abcdefghcij')
d.remove('c')
assertEqual(d, deque('abdefghcij'))
d.remove('c')
assertEqual(d, deque('abdefghij'))
try:
d.remove('c')
print("X Failed Tests!")
exit(1)
except ValueError:
pass
assertEqual(d, deque('abdefghij'))
# Handle comparison errors
d = deque(['a', 'b', BadCmp(), 'c'])
e = deque(d)
try:
d.remove('c')
print("X Failed Tests!")
exit(1)
except RuntimeError:
pass
for x, y in zip(d, e):
# verify that original order and values are retained.
assertEqual(x is y, True)
# Handle evil mutator
for match in (True, False):
d = deque(['ab'])
d.extend([MutateCmp(d, match), 'c'])
try:
d.remove('c')
print("X Failed Tests!")
exit(1)
except IndexError:
pass
assertEqual(d, deque())
print('',"ALL TEST PASSED!!")