Home | History | Annotate | Download | only in test
      1 from test import test_support
      2 import random
      3 import sys
      4 import unittest
      5 
      6 verbose = test_support.verbose
      7 nerrors = 0
      8 
      9 def check(tag, expected, raw, compare=None):
     10     global nerrors
     11 
     12     if verbose:
     13         print "    checking", tag
     14 
     15     orig = raw[:]   # save input in case of error
     16     if compare:
     17         raw.sort(compare)
     18     else:
     19         raw.sort()
     20 
     21     if len(expected) != len(raw):
     22         print "error in", tag
     23         print "length mismatch;", len(expected), len(raw)
     24         print expected
     25         print orig
     26         print raw
     27         nerrors += 1
     28         return
     29 
     30     for i, good in enumerate(expected):
     31         maybe = raw[i]
     32         if good is not maybe:
     33             print "error in", tag
     34             print "out of order at index", i, good, maybe
     35             print expected
     36             print orig
     37             print raw
     38             nerrors += 1
     39             return
     40 
     41 class TestBase(unittest.TestCase):
     42     def testStressfully(self):
     43         # Try a variety of sizes at and around powers of 2, and at powers of 10.
     44         sizes = [0]
     45         for power in range(1, 10):
     46             n = 2 ** power
     47             sizes.extend(range(n-1, n+2))
     48         sizes.extend([10, 100, 1000])
     49 
     50         class Complains(object):
     51             maybe_complain = True
     52 
     53             def __init__(self, i):
     54                 self.i = i
     55 
     56             def __lt__(self, other):
     57                 if Complains.maybe_complain and random.random() < 0.001:
     58                     if verbose:
     59                         print "        complaining at", self, other
     60                     raise RuntimeError
     61                 return self.i < other.i
     62 
     63             def __repr__(self):
     64                 return "Complains(%d)" % self.i
     65 
     66         class Stable(object):
     67             def __init__(self, key, i):
     68                 self.key = key
     69                 self.index = i
     70 
     71             def __cmp__(self, other):
     72                 return cmp(self.key, other.key)
     73             __hash__ = None # Silence Py3k warning
     74 
     75             def __repr__(self):
     76                 return "Stable(%d, %d)" % (self.key, self.index)
     77 
     78         for n in sizes:
     79             x = range(n)
     80             if verbose:
     81                 print "Testing size", n
     82 
     83             s = x[:]
     84             check("identity", x, s)
     85 
     86             s = x[:]
     87             s.reverse()
     88             check("reversed", x, s)
     89 
     90             s = x[:]
     91             random.shuffle(s)
     92             check("random permutation", x, s)
     93 
     94             y = x[:]
     95             y.reverse()
     96             s = x[:]
     97             check("reversed via function", y, s, lambda a, b: cmp(b, a))
     98 
     99             if verbose:
    100                 print "    Checking against an insane comparison function."
    101                 print "        If the implementation isn't careful, this may segfault."
    102             s = x[:]
    103             s.sort(lambda a, b:  int(random.random() * 3) - 1)
    104             check("an insane function left some permutation", x, s)
    105 
    106             x = [Complains(i) for i in x]
    107             s = x[:]
    108             random.shuffle(s)
    109             Complains.maybe_complain = True
    110             it_complained = False
    111             try:
    112                 s.sort()
    113             except RuntimeError:
    114                 it_complained = True
    115             if it_complained:
    116                 Complains.maybe_complain = False
    117                 check("exception during sort left some permutation", x, s)
    118 
    119             s = [Stable(random.randrange(10), i) for i in xrange(n)]
    120             augmented = [(e, e.index) for e in s]
    121             augmented.sort()    # forced stable because ties broken by index
    122             x = [e for e, i in augmented] # a stable sort of s
    123             check("stability", x, s)
    124 
    125 #==============================================================================
    126 
    127 class TestBugs(unittest.TestCase):
    128 
    129     def test_bug453523(self):
    130         # bug 453523 -- list.sort() crasher.
    131         # If this fails, the most likely outcome is a core dump.
    132         # Mutations during a list sort should raise a ValueError.
    133 
    134         class C:
    135             def __lt__(self, other):
    136                 if L and random.random() < 0.75:
    137                     L.pop()
    138                 else:
    139                     L.append(3)
    140                 return random.random() < 0.5
    141 
    142         L = [C() for i in range(50)]
    143         self.assertRaises(ValueError, L.sort)
    144 
    145     def test_cmpNone(self):
    146         # Testing None as a comparison function.
    147 
    148         L = range(50)
    149         random.shuffle(L)
    150         L.sort(None)
    151         self.assertEqual(L, range(50))
    152 
    153     def test_undetected_mutation(self):
    154         # Python 2.4a1 did not always detect mutation
    155         memorywaster = []
    156         for i in range(20):
    157             def mutating_cmp(x, y):
    158                 L.append(3)
    159                 L.pop()
    160                 return cmp(x, y)
    161             L = [1,2]
    162             self.assertRaises(ValueError, L.sort, mutating_cmp)
    163             def mutating_cmp(x, y):
    164                 L.append(3)
    165                 del L[:]
    166                 return cmp(x, y)
    167             self.assertRaises(ValueError, L.sort, mutating_cmp)
    168             memorywaster = [memorywaster]
    169 
    170 #==============================================================================
    171 
    172 class TestDecorateSortUndecorate(unittest.TestCase):
    173 
    174     def test_decorated(self):
    175         data = 'The quick Brown fox Jumped over The lazy Dog'.split()
    176         copy = data[:]
    177         random.shuffle(data)
    178         data.sort(key=str.lower)
    179         copy.sort(cmp=lambda x,y: cmp(x.lower(), y.lower()))
    180 
    181     def test_baddecorator(self):
    182         data = 'The quick Brown fox Jumped over The lazy Dog'.split()
    183         self.assertRaises(TypeError, data.sort, None, lambda x,y: 0)
    184 
    185     def test_stability(self):
    186         data = [(random.randrange(100), i) for i in xrange(200)]
    187         copy = data[:]
    188         data.sort(key=lambda x: x[0])   # sort on the random first field
    189         copy.sort()                     # sort using both fields
    190         self.assertEqual(data, copy)    # should get the same result
    191 
    192     def test_cmp_and_key_combination(self):
    193         # Verify that the wrapper has been removed
    194         def compare(x, y):
    195             self.assertEqual(type(x), str)
    196             self.assertEqual(type(x), str)
    197             return cmp(x, y)
    198         data = 'The quick Brown fox Jumped over The lazy Dog'.split()
    199         data.sort(cmp=compare, key=str.lower)
    200 
    201     def test_badcmp_with_key(self):
    202         # Verify that the wrapper has been removed
    203         data = 'The quick Brown fox Jumped over The lazy Dog'.split()
    204         self.assertRaises(TypeError, data.sort, "bad", str.lower)
    205 
    206     def test_key_with_exception(self):
    207         # Verify that the wrapper has been removed
    208         data = range(-2,2)
    209         dup = data[:]
    210         self.assertRaises(ZeroDivisionError, data.sort, None, lambda x: 1 // x)
    211         self.assertEqual(data, dup)
    212 
    213     def test_key_with_mutation(self):
    214         data = range(10)
    215         def k(x):
    216             del data[:]
    217             data[:] = range(20)
    218             return x
    219         self.assertRaises(ValueError, data.sort, key=k)
    220 
    221     def test_key_with_mutating_del(self):
    222         data = range(10)
    223         class SortKiller(object):
    224             def __init__(self, x):
    225                 pass
    226             def __del__(self):
    227                 del data[:]
    228                 data[:] = range(20)
    229         self.assertRaises(ValueError, data.sort, key=SortKiller)
    230 
    231     def test_key_with_mutating_del_and_exception(self):
    232         data = range(10)
    233         ## dup = data[:]
    234         class SortKiller(object):
    235             def __init__(self, x):
    236                 if x > 2:
    237                     raise RuntimeError
    238             def __del__(self):
    239                 del data[:]
    240                 data[:] = range(20)
    241         self.assertRaises(RuntimeError, data.sort, key=SortKiller)
    242         ## major honking subtlety: we *can't* do:
    243         ##
    244         ## self.assertEqual(data, dup)
    245         ##
    246         ## because there is a reference to a SortKiller in the
    247         ## traceback and by the time it dies we're outside the call to
    248         ## .sort() and so the list protection gimmicks are out of
    249         ## date (this cost some brain cells to figure out...).
    250 
    251     def test_reverse(self):
    252         data = range(100)
    253         random.shuffle(data)
    254         data.sort(reverse=True)
    255         self.assertEqual(data, range(99,-1,-1))
    256         self.assertRaises(TypeError, data.sort, "wrong type")
    257 
    258     def test_reverse_stability(self):
    259         data = [(random.randrange(100), i) for i in xrange(200)]
    260         copy1 = data[:]
    261         copy2 = data[:]
    262         data.sort(cmp=lambda x,y: cmp(x[0],y[0]), reverse=True)
    263         copy1.sort(cmp=lambda x,y: cmp(y[0],x[0]))
    264         self.assertEqual(data, copy1)
    265         copy2.sort(key=lambda x: x[0], reverse=True)
    266         self.assertEqual(data, copy2)
    267 
    268 #==============================================================================
    269 
    270 def test_main(verbose=None):
    271     test_classes = (
    272         TestBase,
    273         TestDecorateSortUndecorate,
    274         TestBugs,
    275     )
    276 
    277     with test_support.check_py3k_warnings(
    278             ("the cmp argument is not supported", DeprecationWarning)):
    279         test_support.run_unittest(*test_classes)
    280 
    281         # verify reference counting
    282         if verbose and hasattr(sys, "gettotalrefcount"):
    283             import gc
    284             counts = [None] * 5
    285             for i in xrange(len(counts)):
    286                 test_support.run_unittest(*test_classes)
    287                 gc.collect()
    288                 counts[i] = sys.gettotalrefcount()
    289             print counts
    290 
    291 if __name__ == "__main__":
    292     test_main(verbose=True)
    293