diff options
author | embg <elliot.gorokhovsky@gmail.com> | 2018-01-29 03:03:23 (GMT) |
---|---|---|
committer | Raymond Hettinger <rhettinger@users.noreply.github.com> | 2018-01-29 03:03:23 (GMT) |
commit | 1e34da49ef22004ca25c517b3f07c6d25f083ece (patch) | |
tree | 7266686f1ae6545a9501b18daaec69baa8e9fe0c /Lib/test/test_sort.py | |
parent | 6c6ddf97c402709713d668d0ed53836a7749ba99 (diff) | |
download | cpython-1e34da49ef22004ca25c517b3f07c6d25f083ece.zip cpython-1e34da49ef22004ca25c517b3f07c6d25f083ece.tar.gz cpython-1e34da49ef22004ca25c517b3f07c6d25f083ece.tar.bz2 |
bpo-28685: Optimize sorted() list.sort() with type-specialized comparisons (#582)
Diffstat (limited to 'Lib/test/test_sort.py')
-rw-r--r-- | Lib/test/test_sort.py | 114 |
1 files changed, 114 insertions, 0 deletions
diff --git a/Lib/test/test_sort.py b/Lib/test/test_sort.py index 98ccab5..f2f53cb 100644 --- a/Lib/test/test_sort.py +++ b/Lib/test/test_sort.py @@ -260,6 +260,120 @@ class TestDecorateSortUndecorate(unittest.TestCase): self.assertEqual(data, copy2) #============================================================================== +def check_against_PyObject_RichCompareBool(self, L): + ## The idea here is to exploit the fact that unsafe_tuple_compare uses + ## PyObject_RichCompareBool for the second elements of tuples. So we have, + ## for (most) L, sorted(L) == [y[1] for y in sorted([(0,x) for x in L])] + ## This will work as long as __eq__ => not __lt__ for all the objects in L, + ## which holds for all the types used below. + ## + ## Testing this way ensures that the optimized implementation remains consistent + ## with the naive implementation, even if changes are made to any of the + ## richcompares. + ## + ## This function tests sorting for three lists (it randomly shuffles each one): + ## 1. L + ## 2. [(x,) for x in L] + ## 3. [((x,),) for x in L] + + random.seed(0) + random.shuffle(L) + L_1 = L[:] + L_2 = [(x,) for x in L] + L_3 = [((x,),) for x in L] + for L in [L_1, L_2, L_3]: + optimized = sorted(L) + reference = [y[1] for y in sorted([(0,x) for x in L])] + for (opt, ref) in zip(optimized, reference): + self.assertIs(opt, ref) + #note: not assertEqual! We want to ensure *identical* behavior. + +class TestOptimizedCompares(unittest.TestCase): + def test_safe_object_compare(self): + heterogeneous_lists = [[0, 'foo'], + [0.0, 'foo'], + [('foo',), 'foo']] + for L in heterogeneous_lists: + self.assertRaises(TypeError, L.sort) + self.assertRaises(TypeError, [(x,) for x in L].sort) + self.assertRaises(TypeError, [((x,),) for x in L].sort) + + float_int_lists = [[1,1.1], + [1<<70,1.1], + [1.1,1], + [1.1,1<<70]] + for L in float_int_lists: + check_against_PyObject_RichCompareBool(self, L) + + def test_unsafe_object_compare(self): + + # This test is by ppperry. It ensures that unsafe_object_compare is + # verifying ms->key_richcompare == tp->richcompare before comparing. + + class WackyComparator(int): + def __lt__(self, other): + elem.__class__ = WackyList2 + return int.__lt__(self, other) + + class WackyList1(list): + pass + + class WackyList2(list): + def __lt__(self, other): + raise ValueError + + L = [WackyList1([WackyComparator(i), i]) for i in range(10)] + elem = L[-1] + with self.assertRaises(ValueError): + L.sort() + + L = [WackyList1([WackyComparator(i), i]) for i in range(10)] + elem = L[-1] + with self.assertRaises(ValueError): + [(x,) for x in L].sort() + + # The following test is also by ppperry. It ensures that + # unsafe_object_compare handles Py_NotImplemented appropriately. + class PointlessComparator: + def __lt__(self, other): + return NotImplemented + L = [PointlessComparator(), PointlessComparator()] + self.assertRaises(TypeError, L.sort) + self.assertRaises(TypeError, [(x,) for x in L].sort) + + # The following tests go through various types that would trigger + # ms->key_compare = unsafe_object_compare + lists = [list(range(100)) + [(1<<70)], + [str(x) for x in range(100)] + ['\uffff'], + [bytes(x) for x in range(100)], + [cmp_to_key(lambda x,y: x<y)(x) for x in range(100)]] + for L in lists: + check_against_PyObject_RichCompareBool(self, L) + + def test_unsafe_latin_compare(self): + check_against_PyObject_RichCompareBool(self, [str(x) for + x in range(100)]) + + def test_unsafe_long_compare(self): + check_against_PyObject_RichCompareBool(self, [x for + x in range(100)]) + + def test_unsafe_float_compare(self): + check_against_PyObject_RichCompareBool(self, [float(x) for + x in range(100)]) + + def test_unsafe_tuple_compare(self): + # This test was suggested by Tim Peters. It verifies that the tuple + # comparison respects the current tuple compare semantics, which do not + # guarantee that x < x <=> (x,) < (x,) + # + # Note that we don't have to put anything in tuples here, because + # the check function does a tuple test automatically. + + check_against_PyObject_RichCompareBool(self, [float('nan')]*100) + check_against_PyObject_RichCompareBool(self, [float('nan') for + _ in range(100)]) +#============================================================================== if __name__ == "__main__": unittest.main() |