from pyrsistent._compat import Mapping, Hashable import six from operator import add import pytest from pyrsistent import pmap, m, PVector import pickle def test_instance_of_hashable(): assert isinstance(m(), Hashable) def test_instance_of_map(): assert isinstance(m(), Mapping) def test_literalish_works(): assert m() is pmap() assert m(a=1, b=2) == pmap({'a': 1, 'b': 2}) def test_empty_initialization(): map = pmap() assert len(map) == 0 def test_initialization_with_one_element(): the_map = pmap({'a': 2}) assert len(the_map) == 1 assert the_map['a'] == 2 assert the_map.a == 2 assert 'a' in the_map assert the_map is the_map.discard('b') empty_map = the_map.remove('a') assert len(empty_map) == 0 assert 'a' not in empty_map def test_get_non_existing_raises_key_error(): m1 = m() with pytest.raises(KeyError) as error: m1['foo'] assert str(error.value) == "'foo'" def test_remove_non_existing_element_raises_key_error(): m1 = m(a=1) with pytest.raises(KeyError) as error: m1.remove('b') assert str(error.value) == "'b'" def test_various_iterations(): assert set(['a', 'b']) == set(m(a=1, b=2)) assert ['a', 'b'] == sorted(m(a=1, b=2).keys()) assert isinstance(m().keys(), PVector) assert set([1, 2]) == set(m(a=1, b=2).itervalues()) assert [1, 2] == sorted(m(a=1, b=2).values()) assert isinstance(m().values(), PVector) assert set([('a', 1), ('b', 2)]) == set(m(a=1, b=2).iteritems()) assert set([('a', 1), ('b', 2)]) == set(m(a=1, b=2).items()) assert isinstance(m().items(), PVector) def test_initialization_with_two_elements(): map = pmap({'a': 2, 'b': 3}) assert len(map) == 2 assert map['a'] == 2 assert map['b'] == 3 map2 = map.remove('a') assert 'a' not in map2 assert map2['b'] == 3 def test_initialization_with_many_elements(): init_dict = dict([(str(x), x) for x in range(1700)]) the_map = pmap(init_dict) assert len(the_map) == 1700 assert the_map['16'] == 16 assert the_map['1699'] == 1699 assert the_map.set('256', 256) is the_map new_map = the_map.remove('1600') assert len(new_map) == 1699 assert '1600' not in new_map assert new_map['1601'] == 1601 # Some NOP properties assert new_map.discard('18888') is new_map assert '19999' not in new_map assert new_map['1500'] == 1500 assert new_map.set('1500', new_map['1500']) is new_map def test_access_non_existing_element(): map1 = pmap() assert len(map1) == 0 map2 = map1.set('1', 1) assert '1' not in map1 assert map2['1'] == 1 assert '2' not in map2 def test_overwrite_existing_element(): map1 = pmap({'a': 2}) map2 = map1.set('a', 3) assert len(map2) == 1 assert map2['a'] == 3 def test_hash(): x = m(a=1, b=2, c=3) y = m(a=1, b=2, c=3) assert hash(x) == hash(y) def test_same_hash_when_content_the_same_but_underlying_vector_size_differs(): x = pmap(dict((x, x) for x in range(1000))) y = pmap({10: 10, 200: 200, 700: 700}) for z in x: if z not in y: x = x.remove(z) assert x == y assert hash(x) == hash(y) class HashabilityControlled(object): hashable = True def __hash__(self): if self.hashable: return 4 # Proven random raise ValueError("I am not currently hashable.") def test_map_does_not_hash_values_on_second_hash_invocation(): hashable = HashabilityControlled() x = pmap(dict(el=hashable)) hash(x) hashable.hashable = False hash(x) def test_equal(): x = m(a=1, b=2, c=3) y = m(a=1, b=2, c=3) assert x == y assert not (x != y) assert y == x assert not (y != x) def test_equal_to_dict(): x = m(a=1, b=2, c=3) y = dict(a=1, b=2, c=3) assert x == y assert not (x != y) assert y == x assert not (y != x) def test_equal_with_different_bucket_sizes(): x = pmap({'a': 1, 'b': 2}, 50) y = pmap({'a': 1, 'b': 2}, 10) assert x == y assert not (x != y) assert y == x assert not (y != x) def test_equal_with_different_insertion_order(): x = pmap([(i, i) for i in range(50)], 10) y = pmap([(i, i) for i in range(49, -1, -1)], 10) assert x == y assert not (x != y) assert y == x assert not (y != x) def test_not_equal(): x = m(a=1, b=2, c=3) y = m(a=1, b=2) assert x != y assert not (x == y) assert y != x assert not (y == x) def test_not_equal_to_dict(): x = m(a=1, b=2, c=3) y = dict(a=1, b=2, d=4) assert x != y assert not (x == y) assert y != x assert not (y == x) def test_update_with_multiple_arguments(): # If same value is present in multiple sources, the rightmost is used. x = m(a=1, b=2, c=3) y = x.update(m(b=4, c=5), {'c': 6}) assert y == m(a=1, b=4, c=6) def test_update_one_argument(): x = m(a=1) assert x.update(m(b=2)) == m(a=1, b=2) def test_update_no_arguments(): x = m(a=1) assert x.update() is x def test_addition(): assert m(x=1, y=2) + m(y=3, z=4) == m(x=1, y=3, z=4) def test_transform_base_case(): # Works as set when called with only one key x = m(a=1, b=2) assert x.transform(['a'], 3) == m(a=3, b=2) def test_transform_nested_maps(): x = m(a=1, b=m(c=3, d=m(e=6, f=7))) assert x.transform(['b', 'd', 'e'], 999) == m(a=1, b=m(c=3, d=m(e=999, f=7))) def test_transform_levels_missing(): x = m(a=1, b=m(c=3)) assert x.transform(['b', 'd', 'e'], 999) == m(a=1, b=m(c=3, d=m(e=999))) class HashDummy(object): def __hash__(self): return 6528039219058920 # Hash of '33' def __eq__(self, other): return self is other def test_hash_collision_is_correctly_resolved(): dummy1 = HashDummy() dummy2 = HashDummy() dummy3 = HashDummy() dummy4 = HashDummy() map = pmap({dummy1: 1, dummy2: 2, dummy3: 3}) assert map[dummy1] == 1 assert map[dummy2] == 2 assert map[dummy3] == 3 assert dummy4 not in map keys = set() values = set() for k, v in map.iteritems(): keys.add(k) values.add(v) assert keys == set([dummy1, dummy2, dummy3]) assert values == set([1, 2, 3]) map2 = map.set(dummy1, 11) assert map2[dummy1] == 11 # Re-use existing structure when inserted element is the same assert map2.set(dummy1, 11) is map2 map3 = map.set('a', 22) assert map3['a'] == 22 assert map3[dummy3] == 3 # Remove elements map4 = map.discard(dummy2) assert len(map4) == 2 assert map4[dummy1] == 1 assert dummy2 not in map4 assert map4[dummy3] == 3 assert map.discard(dummy4) is map # Empty map handling empty_map = map4.remove(dummy1).remove(dummy3) assert len(empty_map) == 0 assert empty_map.discard(dummy1) is empty_map def test_bitmap_indexed_iteration(): map = pmap({'a': 2, 'b': 1}) keys = set() values = set() count = 0 for k, v in map.iteritems(): count += 1 keys.add(k) values.add(v) assert count == 2 assert keys == set(['a', 'b']) assert values == set([2, 1]) def test_iteration_with_many_elements(): values = list(range(0, 2000)) keys = [str(x) for x in values] init_dict = dict(zip(keys, values)) hash_dummy1 = HashDummy() hash_dummy2 = HashDummy() # Throw in a couple of hash collision nodes to tests # those properly as well init_dict[hash_dummy1] = 12345 init_dict[hash_dummy2] = 54321 map = pmap(init_dict) actual_values = set() actual_keys = set() for k, v in map.iteritems(): actual_values.add(v) actual_keys.add(k) assert actual_keys == set(keys + [hash_dummy1, hash_dummy2]) assert actual_values == set(values + [12345, 54321]) def test_str(): assert str(pmap({1: 2, 3: 4})) == "pmap({1: 2, 3: 4})" def test_empty_truthiness(): assert m(a=1) assert not m() def test_update_with(): assert m(a=1).update_with(add, m(a=2, b=4)) == m(a=3, b=4) assert m(a=1).update_with(lambda l, r: l, m(a=2, b=4)) == m(a=1, b=4) def map_add(l, r): return dict(list(l.items()) + list(r.items())) assert m(a={'c': 3}).update_with(map_add, m(a={'d': 4})) == m(a={'c': 3, 'd': 4}) def test_pickling_empty_map(): assert pickle.loads(pickle.dumps(m(), -1)) == m() def test_pickling_non_empty_map(): assert pickle.loads(pickle.dumps(m(a=1, b=2), -1)) == m(a=1, b=2) def test_set_with_relocation(): x = pmap({'a':1000}, pre_size=1) x = x.set('b', 3000) x = x.set('c', 4000) x = x.set('d', 5000) x = x.set('d', 6000) assert len(x) == 4 assert x == pmap({'a': 1000, 'b': 3000, 'c': 4000, 'd': 6000}) def test_evolver_simple_update(): x = m(a=1000, b=2000) e = x.evolver() e['b'] = 3000 assert e['b'] == 3000 assert e.persistent()['b'] == 3000 assert x['b'] == 2000 def test_evolver_update_with_relocation(): x = pmap({'a':1000}, pre_size=1) e = x.evolver() e['b'] = 3000 e['c'] = 4000 e['d'] = 5000 e['d'] = 6000 assert len(e) == 4 assert e.persistent() == pmap({'a': 1000, 'b': 3000, 'c': 4000, 'd': 6000}) def test_evolver_set_with_reallocation_edge_case(): # Demonstrates a bug in evolver that also affects updates. Under certain # circumstances, the result of `x.update(y)` will **not** have all the # keys from `y`. foo = object() x = pmap({'a': foo}, pre_size=1) e = x.evolver() e['b'] = 3000 # Bug is triggered when we do a reallocation and the new value is # identical to the old one. e['a'] = foo y = e.persistent() assert 'b' in y assert y is e.persistent() def test_evolver_remove_element(): e = m(a=1000, b=2000).evolver() assert 'a' in e del e['a'] assert 'a' not in e def test_evolver_remove_element_not_present(): e = m(a=1000, b=2000).evolver() with pytest.raises(KeyError) as error: del e['c'] assert str(error.value) == "'c'" def test_copy_returns_reference_to_self(): m1 = m(a=10) assert m1.copy() is m1 def test_dot_access_of_non_existing_element_raises_attribute_error(): m1 = m(a=10) with pytest.raises(AttributeError) as error: m1.b error_message = str(error.value) assert "'b'" in error_message assert type(m1).__name__ in error_message def test_pmap_unorderable(): with pytest.raises(TypeError): _ = m(a=1) < m(b=2) with pytest.raises(TypeError): _ = m(a=1) <= m(b=2) with pytest.raises(TypeError): _ = m(a=1) > m(b=2) with pytest.raises(TypeError): _ = m(a=1) >= m(b=2) def test_supports_weakref(): import weakref weakref.ref(m(a=1)) def test_iterable(): """ PMaps can be created from iterables even though they can't be len() hinted. """ assert pmap(iter([("a", "b")])) == pmap([("a", "b")])