"""Bug demonstrating a problem with hash collisions in dict subclasses in Jython 2.7.0. Caleb Levy, 2015. """ import unittest class HashDict(dict): """Dict with __hash__ and __eq__ that checks both type and dict values.""" def __hash__(self): return hash(frozenset(self.items())) def __eq__(self, other): if type(self) is type(other): return dict.__eq__(self, other) return False def __ne__(self, other): return not(self == other) class Dict1(HashDict): pass class Dict2(HashDict): pass class HashCollisionEqualityTests(unittest.TestCase): """Tests to demonstrate that Jython does not call custom __eq__ from dict subclasses with __hash__ when encountering collisions.""" a = Dict2() b = Dict1() def test_equal_hashes(self): """Ensure that we are in fact dealing with a hash collision. PASSES: All """ self.assertEqual(hash(self.a), hash(self.b)) def test_unequal_objects(self): """Test that the objects do in fact compare unequal, and should be simultaneously usable as keys. PASSES: All """ # Ensures no funny business is going on with __ne__ and __eq__ self.assertTrue(self.a != self.b) self.assertTrue(self.b != self.a) self.assertFalse(self.a == self.b) self.assertFalse(self.b == self.a) def test_distinct_as_keys(self): """Test that both elements may be used as different keys in a dict. PASSES: python, python3, pypy, pypy3 FAILS: jython 2.7.0 """ # Python should first compare by hash. As the hashes are the same, it # should proceed to compare for equality. As Dict1 and Dict2 have # different types, their __eq__ methods will return false, thus we # should have a hash collision, and both elements should be in the # dict. # # In CPython and PyPy, this is what happens. Jython seems to ignore # the custom __eq__. d = {} d[self.a] = 1 d[self.b] = 2 self.assertEqual(len(d), 2) def test_both_in_set(self): """That both objects may be placed in a set. PASSES: All. """ s = {self.a, self.b} self.assertIn(self.a, s) self.assertIn(self.b, s) def test_distinct_in_set(self): """Test that both elements are viewd as distinct in a set. PASSES: python, python3, pypy, pypy3 FAILS: jython 2.7.10. """ s = {self.a, self.b} self.assertEqual(2, len(s)) def test_hash_collisions_on_same_type(self): """Test that hash collisions on same type are resolved normally. PASSES: All """ d1 = Dict1({2**128: 1}) d2 = Dict1({hash(2**128): 1}) self.assertEqual(hash(d1), hash(d2)) d = {} d[d1] = 1 d[d2] = 2 self.assertEqual(2, len(d)) # We can conclude that Jython is ignoring custom __eq__ methods defined on # objects inheriting from dict. if __name__ == '__main__': unittest.main()