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")])