map_test.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497
  1. from pyrsistent._compat import Mapping, Hashable
  2. import six
  3. from operator import add
  4. import pytest
  5. from pyrsistent import pmap, m, PVector
  6. import pickle
  7. def test_instance_of_hashable():
  8. assert isinstance(m(), Hashable)
  9. def test_instance_of_map():
  10. assert isinstance(m(), Mapping)
  11. def test_literalish_works():
  12. assert m() is pmap()
  13. assert m(a=1, b=2) == pmap({'a': 1, 'b': 2})
  14. def test_empty_initialization():
  15. map = pmap()
  16. assert len(map) == 0
  17. def test_initialization_with_one_element():
  18. the_map = pmap({'a': 2})
  19. assert len(the_map) == 1
  20. assert the_map['a'] == 2
  21. assert the_map.a == 2
  22. assert 'a' in the_map
  23. assert the_map is the_map.discard('b')
  24. empty_map = the_map.remove('a')
  25. assert len(empty_map) == 0
  26. assert 'a' not in empty_map
  27. def test_get_non_existing_raises_key_error():
  28. m1 = m()
  29. with pytest.raises(KeyError) as error:
  30. m1['foo']
  31. assert str(error.value) == "'foo'"
  32. def test_remove_non_existing_element_raises_key_error():
  33. m1 = m(a=1)
  34. with pytest.raises(KeyError) as error:
  35. m1.remove('b')
  36. assert str(error.value) == "'b'"
  37. def test_various_iterations():
  38. assert set(['a', 'b']) == set(m(a=1, b=2))
  39. assert ['a', 'b'] == sorted(m(a=1, b=2).keys())
  40. assert isinstance(m().keys(), PVector)
  41. assert set([1, 2]) == set(m(a=1, b=2).itervalues())
  42. assert [1, 2] == sorted(m(a=1, b=2).values())
  43. assert isinstance(m().values(), PVector)
  44. assert set([('a', 1), ('b', 2)]) == set(m(a=1, b=2).iteritems())
  45. assert set([('a', 1), ('b', 2)]) == set(m(a=1, b=2).items())
  46. assert isinstance(m().items(), PVector)
  47. def test_initialization_with_two_elements():
  48. map = pmap({'a': 2, 'b': 3})
  49. assert len(map) == 2
  50. assert map['a'] == 2
  51. assert map['b'] == 3
  52. map2 = map.remove('a')
  53. assert 'a' not in map2
  54. assert map2['b'] == 3
  55. def test_initialization_with_many_elements():
  56. init_dict = dict([(str(x), x) for x in range(1700)])
  57. the_map = pmap(init_dict)
  58. assert len(the_map) == 1700
  59. assert the_map['16'] == 16
  60. assert the_map['1699'] == 1699
  61. assert the_map.set('256', 256) is the_map
  62. new_map = the_map.remove('1600')
  63. assert len(new_map) == 1699
  64. assert '1600' not in new_map
  65. assert new_map['1601'] == 1601
  66. # Some NOP properties
  67. assert new_map.discard('18888') is new_map
  68. assert '19999' not in new_map
  69. assert new_map['1500'] == 1500
  70. assert new_map.set('1500', new_map['1500']) is new_map
  71. def test_access_non_existing_element():
  72. map1 = pmap()
  73. assert len(map1) == 0
  74. map2 = map1.set('1', 1)
  75. assert '1' not in map1
  76. assert map2['1'] == 1
  77. assert '2' not in map2
  78. def test_overwrite_existing_element():
  79. map1 = pmap({'a': 2})
  80. map2 = map1.set('a', 3)
  81. assert len(map2) == 1
  82. assert map2['a'] == 3
  83. def test_hash():
  84. x = m(a=1, b=2, c=3)
  85. y = m(a=1, b=2, c=3)
  86. assert hash(x) == hash(y)
  87. def test_same_hash_when_content_the_same_but_underlying_vector_size_differs():
  88. x = pmap(dict((x, x) for x in range(1000)))
  89. y = pmap({10: 10, 200: 200, 700: 700})
  90. for z in x:
  91. if z not in y:
  92. x = x.remove(z)
  93. assert x == y
  94. assert hash(x) == hash(y)
  95. class HashabilityControlled(object):
  96. hashable = True
  97. def __hash__(self):
  98. if self.hashable:
  99. return 4 # Proven random
  100. raise ValueError("I am not currently hashable.")
  101. def test_map_does_not_hash_values_on_second_hash_invocation():
  102. hashable = HashabilityControlled()
  103. x = pmap(dict(el=hashable))
  104. hash(x)
  105. hashable.hashable = False
  106. hash(x)
  107. def test_equal():
  108. x = m(a=1, b=2, c=3)
  109. y = m(a=1, b=2, c=3)
  110. assert x == y
  111. assert not (x != y)
  112. assert y == x
  113. assert not (y != x)
  114. def test_equal_to_dict():
  115. x = m(a=1, b=2, c=3)
  116. y = dict(a=1, b=2, c=3)
  117. assert x == y
  118. assert not (x != y)
  119. assert y == x
  120. assert not (y != x)
  121. def test_equal_with_different_bucket_sizes():
  122. x = pmap({'a': 1, 'b': 2}, 50)
  123. y = pmap({'a': 1, 'b': 2}, 10)
  124. assert x == y
  125. assert not (x != y)
  126. assert y == x
  127. assert not (y != x)
  128. def test_equal_with_different_insertion_order():
  129. x = pmap([(i, i) for i in range(50)], 10)
  130. y = pmap([(i, i) for i in range(49, -1, -1)], 10)
  131. assert x == y
  132. assert not (x != y)
  133. assert y == x
  134. assert not (y != x)
  135. def test_not_equal():
  136. x = m(a=1, b=2, c=3)
  137. y = m(a=1, b=2)
  138. assert x != y
  139. assert not (x == y)
  140. assert y != x
  141. assert not (y == x)
  142. def test_not_equal_to_dict():
  143. x = m(a=1, b=2, c=3)
  144. y = dict(a=1, b=2, d=4)
  145. assert x != y
  146. assert not (x == y)
  147. assert y != x
  148. assert not (y == x)
  149. def test_update_with_multiple_arguments():
  150. # If same value is present in multiple sources, the rightmost is used.
  151. x = m(a=1, b=2, c=3)
  152. y = x.update(m(b=4, c=5), {'c': 6})
  153. assert y == m(a=1, b=4, c=6)
  154. def test_update_one_argument():
  155. x = m(a=1)
  156. assert x.update(m(b=2)) == m(a=1, b=2)
  157. def test_update_no_arguments():
  158. x = m(a=1)
  159. assert x.update() is x
  160. def test_addition():
  161. assert m(x=1, y=2) + m(y=3, z=4) == m(x=1, y=3, z=4)
  162. def test_transform_base_case():
  163. # Works as set when called with only one key
  164. x = m(a=1, b=2)
  165. assert x.transform(['a'], 3) == m(a=3, b=2)
  166. def test_transform_nested_maps():
  167. x = m(a=1, b=m(c=3, d=m(e=6, f=7)))
  168. assert x.transform(['b', 'd', 'e'], 999) == m(a=1, b=m(c=3, d=m(e=999, f=7)))
  169. def test_transform_levels_missing():
  170. x = m(a=1, b=m(c=3))
  171. assert x.transform(['b', 'd', 'e'], 999) == m(a=1, b=m(c=3, d=m(e=999)))
  172. class HashDummy(object):
  173. def __hash__(self):
  174. return 6528039219058920 # Hash of '33'
  175. def __eq__(self, other):
  176. return self is other
  177. def test_hash_collision_is_correctly_resolved():
  178. dummy1 = HashDummy()
  179. dummy2 = HashDummy()
  180. dummy3 = HashDummy()
  181. dummy4 = HashDummy()
  182. map = pmap({dummy1: 1, dummy2: 2, dummy3: 3})
  183. assert map[dummy1] == 1
  184. assert map[dummy2] == 2
  185. assert map[dummy3] == 3
  186. assert dummy4 not in map
  187. keys = set()
  188. values = set()
  189. for k, v in map.iteritems():
  190. keys.add(k)
  191. values.add(v)
  192. assert keys == set([dummy1, dummy2, dummy3])
  193. assert values == set([1, 2, 3])
  194. map2 = map.set(dummy1, 11)
  195. assert map2[dummy1] == 11
  196. # Re-use existing structure when inserted element is the same
  197. assert map2.set(dummy1, 11) is map2
  198. map3 = map.set('a', 22)
  199. assert map3['a'] == 22
  200. assert map3[dummy3] == 3
  201. # Remove elements
  202. map4 = map.discard(dummy2)
  203. assert len(map4) == 2
  204. assert map4[dummy1] == 1
  205. assert dummy2 not in map4
  206. assert map4[dummy3] == 3
  207. assert map.discard(dummy4) is map
  208. # Empty map handling
  209. empty_map = map4.remove(dummy1).remove(dummy3)
  210. assert len(empty_map) == 0
  211. assert empty_map.discard(dummy1) is empty_map
  212. def test_bitmap_indexed_iteration():
  213. map = pmap({'a': 2, 'b': 1})
  214. keys = set()
  215. values = set()
  216. count = 0
  217. for k, v in map.iteritems():
  218. count += 1
  219. keys.add(k)
  220. values.add(v)
  221. assert count == 2
  222. assert keys == set(['a', 'b'])
  223. assert values == set([2, 1])
  224. def test_iteration_with_many_elements():
  225. values = list(range(0, 2000))
  226. keys = [str(x) for x in values]
  227. init_dict = dict(zip(keys, values))
  228. hash_dummy1 = HashDummy()
  229. hash_dummy2 = HashDummy()
  230. # Throw in a couple of hash collision nodes to tests
  231. # those properly as well
  232. init_dict[hash_dummy1] = 12345
  233. init_dict[hash_dummy2] = 54321
  234. map = pmap(init_dict)
  235. actual_values = set()
  236. actual_keys = set()
  237. for k, v in map.iteritems():
  238. actual_values.add(v)
  239. actual_keys.add(k)
  240. assert actual_keys == set(keys + [hash_dummy1, hash_dummy2])
  241. assert actual_values == set(values + [12345, 54321])
  242. def test_str():
  243. assert str(pmap({1: 2, 3: 4})) == "pmap({1: 2, 3: 4})"
  244. def test_empty_truthiness():
  245. assert m(a=1)
  246. assert not m()
  247. def test_update_with():
  248. assert m(a=1).update_with(add, m(a=2, b=4)) == m(a=3, b=4)
  249. assert m(a=1).update_with(lambda l, r: l, m(a=2, b=4)) == m(a=1, b=4)
  250. def map_add(l, r):
  251. return dict(list(l.items()) + list(r.items()))
  252. assert m(a={'c': 3}).update_with(map_add, m(a={'d': 4})) == m(a={'c': 3, 'd': 4})
  253. def test_pickling_empty_map():
  254. assert pickle.loads(pickle.dumps(m(), -1)) == m()
  255. def test_pickling_non_empty_map():
  256. assert pickle.loads(pickle.dumps(m(a=1, b=2), -1)) == m(a=1, b=2)
  257. def test_set_with_relocation():
  258. x = pmap({'a':1000}, pre_size=1)
  259. x = x.set('b', 3000)
  260. x = x.set('c', 4000)
  261. x = x.set('d', 5000)
  262. x = x.set('d', 6000)
  263. assert len(x) == 4
  264. assert x == pmap({'a': 1000, 'b': 3000, 'c': 4000, 'd': 6000})
  265. def test_evolver_simple_update():
  266. x = m(a=1000, b=2000)
  267. e = x.evolver()
  268. e['b'] = 3000
  269. assert e['b'] == 3000
  270. assert e.persistent()['b'] == 3000
  271. assert x['b'] == 2000
  272. def test_evolver_update_with_relocation():
  273. x = pmap({'a':1000}, pre_size=1)
  274. e = x.evolver()
  275. e['b'] = 3000
  276. e['c'] = 4000
  277. e['d'] = 5000
  278. e['d'] = 6000
  279. assert len(e) == 4
  280. assert e.persistent() == pmap({'a': 1000, 'b': 3000, 'c': 4000, 'd': 6000})
  281. def test_evolver_set_with_reallocation_edge_case():
  282. # Demonstrates a bug in evolver that also affects updates. Under certain
  283. # circumstances, the result of `x.update(y)` will **not** have all the
  284. # keys from `y`.
  285. foo = object()
  286. x = pmap({'a': foo}, pre_size=1)
  287. e = x.evolver()
  288. e['b'] = 3000
  289. # Bug is triggered when we do a reallocation and the new value is
  290. # identical to the old one.
  291. e['a'] = foo
  292. y = e.persistent()
  293. assert 'b' in y
  294. assert y is e.persistent()
  295. def test_evolver_remove_element():
  296. e = m(a=1000, b=2000).evolver()
  297. assert 'a' in e
  298. del e['a']
  299. assert 'a' not in e
  300. def test_evolver_remove_element_not_present():
  301. e = m(a=1000, b=2000).evolver()
  302. with pytest.raises(KeyError) as error:
  303. del e['c']
  304. assert str(error.value) == "'c'"
  305. def test_copy_returns_reference_to_self():
  306. m1 = m(a=10)
  307. assert m1.copy() is m1
  308. def test_dot_access_of_non_existing_element_raises_attribute_error():
  309. m1 = m(a=10)
  310. with pytest.raises(AttributeError) as error:
  311. m1.b
  312. error_message = str(error.value)
  313. assert "'b'" in error_message
  314. assert type(m1).__name__ in error_message
  315. def test_pmap_unorderable():
  316. with pytest.raises(TypeError):
  317. _ = m(a=1) < m(b=2)
  318. with pytest.raises(TypeError):
  319. _ = m(a=1) <= m(b=2)
  320. with pytest.raises(TypeError):
  321. _ = m(a=1) > m(b=2)
  322. with pytest.raises(TypeError):
  323. _ = m(a=1) >= m(b=2)
  324. def test_supports_weakref():
  325. import weakref
  326. weakref.ref(m(a=1))
  327. def test_iterable():
  328. """
  329. PMaps can be created from iterables even though they can't be len() hinted.
  330. """
  331. assert pmap(iter([("a", "b")])) == pmap([("a", "b")])