mirror of
https://github.com/python/cpython.git
synced 2025-07-12 13:55:34 +00:00

*ordering* between objects; there is only a default equality test (defined by an object being equal to itself only). Read the comment in object.c. The current implementation never uses a three-way comparison to compute a rich comparison, but it does use a rich comparison to compute a three-way comparison. I'm not quite done ripping out all the calls to PyObject_Compare/Cmp, or replacing tp_compare implementations with tp_richcompare implementations; but much of that has happened (to make most unit tests pass). The following tests still fail, because I need help deciding or understanding: test_codeop -- depends on comparing code objects test_datetime -- need Tim Peters' opinion test_marshal -- depends on comparing code objects test_mutants -- need help understanding it The problem with test_codeop and test_marshal is this: these tests compare two different code objects and expect them to be equal. Is that still a feature we'd like to support? I've temporarily removed the comparison and hash code from code objects, so they use the default (equality by pointer only) comparison. For the other two tests, run them to see for yourself. (There may be more failing test with "-u all".) A general problem with getting lots of these tests to pass is the reality that for object types that have a natural total ordering, implementing __cmp__ is much more convenient than implementing __eq__, __ne__, __lt__, and so on. Should we go back to allowing __cmp__ to provide a total ordering? Should we provide some other way to implement rich comparison with a single method override? Alex proposed a __key__() method; I've considered a __richcmp__() method. Or perhaps __cmp__() just shouldn't be killed off...
418 lines
7.4 KiB
Python
418 lines
7.4 KiB
Python
"Test the functionality of Python classes implementing operators."
|
|
|
|
from test.test_support import TestFailed
|
|
|
|
testmeths = [
|
|
|
|
# Binary operations
|
|
"add",
|
|
"radd",
|
|
"sub",
|
|
"rsub",
|
|
"mul",
|
|
"rmul",
|
|
"truediv",
|
|
"rtruediv",
|
|
"mod",
|
|
"rmod",
|
|
"divmod",
|
|
"rdivmod",
|
|
"pow",
|
|
"rpow",
|
|
"rshift",
|
|
"rrshift",
|
|
"lshift",
|
|
"rlshift",
|
|
"and",
|
|
"rand",
|
|
"or",
|
|
"ror",
|
|
"xor",
|
|
"rxor",
|
|
|
|
# List/dict operations
|
|
"contains",
|
|
"getitem",
|
|
"getslice",
|
|
"setitem",
|
|
"setslice",
|
|
"delitem",
|
|
"delslice",
|
|
|
|
# Unary operations
|
|
"neg",
|
|
"pos",
|
|
"abs",
|
|
|
|
# generic operations
|
|
"init",
|
|
]
|
|
|
|
# These need to return something other than None
|
|
# "hash",
|
|
# "str",
|
|
# "repr",
|
|
# "int",
|
|
# "long",
|
|
# "float",
|
|
# "oct",
|
|
# "hex",
|
|
|
|
# These are separate because they can influence the test of other methods.
|
|
# "getattr",
|
|
# "setattr",
|
|
# "delattr",
|
|
|
|
class AllTests:
|
|
def __hash__(self, *args):
|
|
print "__hash__:", args
|
|
return hash(id(self))
|
|
|
|
def __str__(self, *args):
|
|
print "__str__:", args
|
|
return "AllTests"
|
|
|
|
def __repr__(self, *args):
|
|
print "__repr__:", args
|
|
return "AllTests"
|
|
|
|
def __int__(self, *args):
|
|
print "__int__:", args
|
|
return 1
|
|
|
|
def __float__(self, *args):
|
|
print "__float__:", args
|
|
return 1.0
|
|
|
|
def __long__(self, *args):
|
|
print "__long__:", args
|
|
return 1L
|
|
|
|
def __oct__(self, *args):
|
|
print "__oct__:", args
|
|
return '01'
|
|
|
|
def __hex__(self, *args):
|
|
print "__hex__:", args
|
|
return '0x1'
|
|
|
|
def __cmp__(self, *args):
|
|
print "__cmp__:", args
|
|
return 0
|
|
|
|
def __eq__(self, *args):
|
|
print "__eq__:", args
|
|
return True
|
|
|
|
def __ne__(self, *args):
|
|
print "__ne__:", args
|
|
return False
|
|
|
|
def __lt__(self, *args):
|
|
print "__lt__:", args
|
|
return False
|
|
|
|
def __le__(self, *args):
|
|
print "__le__:", args
|
|
return True
|
|
|
|
def __gt__(self, *args):
|
|
print "__gt__:", args
|
|
return False
|
|
|
|
def __ge__(self, *args):
|
|
print "__ge__:", args
|
|
return True
|
|
|
|
def __del__(self, *args):
|
|
print "__del__:", args
|
|
|
|
# Synthesize AllTests methods from the names in testmeths.
|
|
|
|
method_template = """\
|
|
def __%(method)s__(self, *args):
|
|
print "__%(method)s__:", args
|
|
"""
|
|
|
|
d = {}
|
|
for method in testmeths:
|
|
exec method_template % locals() in d
|
|
for k in d:
|
|
setattr(AllTests, k, d[k])
|
|
del d, k
|
|
del method, method_template
|
|
|
|
# this also tests __init__ of course.
|
|
testme = AllTests()
|
|
|
|
# Binary operations
|
|
|
|
testme + 1
|
|
1 + testme
|
|
|
|
testme - 1
|
|
1 - testme
|
|
|
|
testme * 1
|
|
1 * testme
|
|
|
|
testme / 1
|
|
1 / testme
|
|
|
|
testme % 1
|
|
1 % testme
|
|
|
|
divmod(testme,1)
|
|
divmod(1, testme)
|
|
|
|
testme ** 1
|
|
1 ** testme
|
|
|
|
testme >> 1
|
|
1 >> testme
|
|
|
|
testme << 1
|
|
1 << testme
|
|
|
|
testme & 1
|
|
1 & testme
|
|
|
|
testme | 1
|
|
1 | testme
|
|
|
|
testme ^ 1
|
|
1 ^ testme
|
|
|
|
|
|
# List/dict operations
|
|
|
|
1 in testme
|
|
|
|
testme[1]
|
|
testme[1] = 1
|
|
del testme[1]
|
|
|
|
testme[:42]
|
|
testme[:42] = "The Answer"
|
|
del testme[:42]
|
|
|
|
testme[2:1024:10]
|
|
testme[2:1024:10] = "A lot"
|
|
del testme[2:1024:10]
|
|
|
|
testme[:42, ..., :24:, 24, 100]
|
|
testme[:42, ..., :24:, 24, 100] = "Strange"
|
|
del testme[:42, ..., :24:, 24, 100]
|
|
|
|
|
|
# Now remove the slice hooks to see if converting normal slices to slice
|
|
# object works.
|
|
|
|
del AllTests.__getslice__
|
|
del AllTests.__setslice__
|
|
del AllTests.__delslice__
|
|
|
|
import sys
|
|
if sys.platform[:4] != 'java':
|
|
testme[:42]
|
|
testme[:42] = "The Answer"
|
|
del testme[:42]
|
|
else:
|
|
# This works under Jython, but the actual slice values are
|
|
# different.
|
|
print "__getitem__: (slice(0, 42, None),)"
|
|
print "__setitem__: (slice(0, 42, None), 'The Answer')"
|
|
print "__delitem__: (slice(0, 42, None),)"
|
|
|
|
# Unary operations
|
|
|
|
-testme
|
|
+testme
|
|
abs(testme)
|
|
int(testme)
|
|
long(testme)
|
|
float(testme)
|
|
oct(testme)
|
|
hex(testme)
|
|
|
|
# And the rest...
|
|
|
|
hash(testme)
|
|
repr(testme)
|
|
str(testme)
|
|
|
|
testme == 1
|
|
testme < 1
|
|
testme > 1
|
|
testme <> 1
|
|
testme != 1
|
|
1 == testme
|
|
1 < testme
|
|
1 > testme
|
|
1 <> testme
|
|
1 != testme
|
|
|
|
# This test has to be last (duh.)
|
|
|
|
del testme
|
|
if sys.platform[:4] == 'java':
|
|
import java
|
|
java.lang.System.gc()
|
|
|
|
# Interfering tests
|
|
|
|
class ExtraTests:
|
|
def __getattr__(self, *args):
|
|
print "__getattr__:", args
|
|
return "SomeVal"
|
|
|
|
def __setattr__(self, *args):
|
|
print "__setattr__:", args
|
|
|
|
def __delattr__(self, *args):
|
|
print "__delattr__:", args
|
|
|
|
testme = ExtraTests()
|
|
testme.spam
|
|
testme.eggs = "spam, spam, spam and ham"
|
|
del testme.cardinal
|
|
|
|
|
|
# return values of some method are type-checked
|
|
class BadTypeClass:
|
|
def __int__(self):
|
|
return None
|
|
__float__ = __int__
|
|
__long__ = __int__
|
|
__str__ = __int__
|
|
__repr__ = __int__
|
|
__oct__ = __int__
|
|
__hex__ = __int__
|
|
|
|
def check_exc(stmt, exception):
|
|
"""Raise TestFailed if executing 'stmt' does not raise 'exception'
|
|
"""
|
|
try:
|
|
exec stmt
|
|
except exception:
|
|
pass
|
|
else:
|
|
raise TestFailed, "%s should raise %s" % (stmt, exception)
|
|
|
|
check_exc("int(BadTypeClass())", TypeError)
|
|
check_exc("float(BadTypeClass())", TypeError)
|
|
check_exc("long(BadTypeClass())", TypeError)
|
|
check_exc("str(BadTypeClass())", TypeError)
|
|
check_exc("repr(BadTypeClass())", TypeError)
|
|
check_exc("oct(BadTypeClass())", TypeError)
|
|
check_exc("hex(BadTypeClass())", TypeError)
|
|
|
|
# mixing up ints and longs is okay
|
|
class IntLongMixClass:
|
|
def __int__(self):
|
|
return 0L
|
|
|
|
def __long__(self):
|
|
return 0
|
|
|
|
try:
|
|
int(IntLongMixClass())
|
|
except TypeError:
|
|
raise TestFailed, "TypeError should not be raised"
|
|
|
|
try:
|
|
long(IntLongMixClass())
|
|
except TypeError:
|
|
raise TestFailed, "TypeError should not be raised"
|
|
|
|
|
|
# Test correct errors from hash() on objects with comparisons but no __hash__
|
|
|
|
class C0:
|
|
pass
|
|
|
|
hash(C0()) # This should work; the next two should raise TypeError
|
|
|
|
class C1:
|
|
def __cmp__(self, other): return 0
|
|
|
|
check_exc("hash(C1())", TypeError)
|
|
|
|
class C2:
|
|
def __eq__(self, other): return 1
|
|
|
|
check_exc("hash(C2())", TypeError)
|
|
|
|
# Test for SF bug 532646
|
|
|
|
class A:
|
|
pass
|
|
A.__call__ = A()
|
|
a = A()
|
|
try:
|
|
a() # This should not segfault
|
|
except RuntimeError:
|
|
pass
|
|
else:
|
|
raise TestFailed, "how could this not have overflowed the stack?"
|
|
|
|
|
|
# Tests for exceptions raised in instance_getattr2().
|
|
|
|
def booh(self):
|
|
raise AttributeError, "booh"
|
|
|
|
class A:
|
|
a = property(booh)
|
|
try:
|
|
A().a # Raised AttributeError: A instance has no attribute 'a'
|
|
except AttributeError, x:
|
|
if str(x) != "booh":
|
|
print "attribute error for A().a got masked:", str(x)
|
|
|
|
class E:
|
|
__eq__ = property(booh)
|
|
E() == E() # In debug mode, caused a C-level assert() to fail
|
|
|
|
class I:
|
|
__init__ = property(booh)
|
|
try:
|
|
I() # In debug mode, printed XXX undetected error and raises AttributeError
|
|
except AttributeError, x:
|
|
pass
|
|
else:
|
|
print "attribute error for I.__init__ got masked"
|
|
|
|
|
|
# Test comparison and hash of methods
|
|
class A:
|
|
def __init__(self, x):
|
|
self.x = x
|
|
def f(self):
|
|
pass
|
|
def g(self):
|
|
pass
|
|
def __eq__(self, other):
|
|
return self.x == other.x
|
|
def __hash__(self):
|
|
return self.x
|
|
class B(A):
|
|
pass
|
|
|
|
a1 = A(1)
|
|
a2 = A(2)
|
|
assert a1.f == a1.f
|
|
assert a1.f != a2.f
|
|
assert a1.f != a1.g
|
|
assert a1.f == A(1).f
|
|
assert hash(a1.f) == hash(a1.f)
|
|
assert hash(a1.f) == hash(A(1).f)
|
|
|
|
assert A.f != a1.f
|
|
assert A.f != A.g
|
|
assert B.f == A.f
|
|
assert hash(B.f) == hash(A.f)
|
|
|
|
# the following triggers a SystemError in 2.4
|
|
a = A(hash(A.f.im_func)^(-1))
|
|
hash(a.f)
|