Skip to content

Commit 0878b8e

Browse files
committed
[GR-27027] New HashingStorage: HashMapStorage & tests & some fixes in dicts builtins.
PullRequest: graalpython/1368
2 parents aa50351 + 79c348f commit 0878b8e

File tree

13 files changed

+871
-35
lines changed

13 files changed

+871
-35
lines changed
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
# Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
2+
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
3+
#
4+
# The Universal Permissive License (UPL), Version 1.0
5+
#
6+
# Subject to the condition set forth below, permission is hereby granted to any
7+
# person obtaining a copy of this software, associated documentation and/or
8+
# data (collectively the "Software"), free of charge and under any and all
9+
# copyright rights in the Software, and any and all patent rights owned or
10+
# freely licensable by each licensor hereunder covering either (i) the
11+
# unmodified Software as contributed to or provided by such licensor, or (ii)
12+
# the Larger Works (as defined below), to deal in both
13+
#
14+
# (a) the Software, and
15+
#
16+
# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
17+
# one is included with the Software each a "Larger Work" to which the Software
18+
# is contributed by such licensors),
19+
#
20+
# without restriction, including without limitation the rights to copy, create
21+
# derivative works of, display, perform, and distribute the Software and make,
22+
# use, sell, offer for sale, import, export, have made, and have sold the
23+
# Software and the Larger Work(s), and to sublicense the foregoing rights on
24+
# either these or other terms.
25+
#
26+
# This license is subject to the following condition:
27+
#
28+
# The above copyright notice and either this complete permission notice or at a
29+
# minimum a reference to the UPL must be included in all copies or substantial
30+
# portions of the Software.
31+
#
32+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
33+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
34+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
35+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
36+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
37+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
38+
# SOFTWARE.
39+
40+
41+
class MyCustomString(str):
42+
def __hash__(self):
43+
return super(MyCustomString, self).__hash__() | 2
44+
def __eq__(self, other):
45+
return super(MyCustomString, self).__eq__(other)
46+
47+
48+
try:
49+
set_strategy = __graalpython__.set_storage_strategy
50+
FACTORIES = [
51+
lambda: set_strategy(dict(), 'empty'),
52+
lambda: set_strategy(dict(), 'hashmap'),
53+
lambda: set_strategy(dict(), 'dynamicobject'),
54+
lambda: set_strategy(dict(), 'economicmap'),
55+
]
56+
except NameError:
57+
# For CPython, just to verify the test results
58+
FACTORIES = [lambda: dict()]
59+
60+
61+
ALL_KEYS = [1, 1.5, 'foo', MyCustomString()]
62+
ALL_VALUES = list(range(len(ALL_KEYS)))
63+
64+
65+
# Several interesting combinations of keys to trigger the transitions at different points
66+
KEYS = [
67+
ALL_KEYS,
68+
['foo', MyCustomString(), 1],
69+
[MyCustomString(), 'foo'],
70+
['foo1', 'foo2'],
71+
]
72+
VALUES = [list(range(len(k))) for k in KEYS]
73+
KEYS_VALUES = [list(zip(k, v)) for (k, v) in zip(KEYS, VALUES)]
74+
75+
76+
def assert_raises_keyerror(d, key):
77+
raised = False
78+
try:
79+
d[key]
80+
except KeyError:
81+
raised = True
82+
assert raised
83+
84+
85+
def test_add_contains_one_key():
86+
keys_values = list(zip(ALL_KEYS, ALL_VALUES))
87+
combinations = [(f, kv) for f in FACTORIES for kv in keys_values]
88+
for (f, (k, v)) in combinations:
89+
d = f()
90+
d[k] = v
91+
assert d[k] == v
92+
for (k2, v2) in keys_values + [(k, v)]:
93+
# the key we just inserted is set and all other keys are not set
94+
assert (k2 == k) == (k2 in d)
95+
assert d[k] == v
96+
97+
98+
def test_add_contains_copy():
99+
for i in range(len(KEYS)):
100+
keys = KEYS[i]
101+
keys_values = KEYS_VALUES[i]
102+
for f in FACTORIES:
103+
d = f()
104+
105+
# check that it's really empty
106+
assert len(d) == 0
107+
for k in keys:
108+
assert k not in d
109+
assert_raises_keyerror(d, k)
110+
111+
# inset the items one by one, check that
112+
expected_len = 0
113+
for (k, v) in keys_values:
114+
d[k] = v
115+
expected_len += 1
116+
assert k in d
117+
assert d[k] == v
118+
assert 2 not in d
119+
assert_raises_keyerror(d, 2)
120+
assert_raises_keyerror(d, 'bar')
121+
assert len(d) == expected_len
122+
123+
# insertion order is preserved
124+
assert keys == [k for k in d]
125+
assert keys == list(d.keys())
126+
assert keys_values == list(d.items())
127+
128+
cpy = d.copy()
129+
cpy[42] = 'answer'
130+
assert_raises_keyerror(d, 42)
131+
assert cpy[42] == 'answer'
132+
133+
d.clear()
134+
assert len(d) == 0
135+
assert len(cpy) > 0
136+
for (k, v) in keys_values:
137+
assert cpy[k] == v
138+
assert_raises_keyerror(d, k)
139+
140+
141+
def test_pop():
142+
for keys_values in KEYS_VALUES:
143+
for f in FACTORIES:
144+
d = f()
145+
for (k, v) in keys_values:
146+
d[k] = v
147+
assert d.pop('bar', 'default') == 'default'
148+
expected_len = len(d)
149+
for (k, v) in keys_values:
150+
assert d.pop(k) == v
151+
expected_len -= 1
152+
assert expected_len == len(d)
153+
154+
155+
def test_popitem():
156+
for keys_values in KEYS_VALUES:
157+
for f in FACTORIES:
158+
d = f()
159+
for (k, v) in keys_values:
160+
d[k] = v
161+
162+
expected_len = len(d)
163+
reversed_key_values = keys_values
164+
reversed_key_values.reverse()
165+
for (k, v) in reversed_key_values:
166+
(actual_k, actual_v) = d.popitem()
167+
assert actual_k == k
168+
assert actual_v == v
169+
expected_len -= 1
170+
assert expected_len == len(d)
171+
172+
173+
def test_delete():
174+
for keys_values in KEYS_VALUES:
175+
for f in FACTORIES:
176+
for (k, v) in keys_values:
177+
d = f()
178+
for (k2, v2) in keys_values:
179+
d[k2] = v2
180+
del d[k]
181+
assert_raises_keyerror(d, k)
182+
for (k2, v2) in keys_values:
183+
if k2 != k:
184+
assert d[k2] == v2
185+
186+
187+
def test_update():
188+
# take all possible combinations of two dict storages, setting one key and value to each,
189+
# then updating one with the other and then checking the result
190+
factories2 = [(f1, f2) for f1 in FACTORIES for f2 in FACTORIES]
191+
all_keys_vals = list(zip(ALL_KEYS, ALL_VALUES))
192+
all_keys_values2 = [(a, b) for a in all_keys_vals for b in all_keys_vals]
193+
combinations = [(f, k) for f in factories2 for k in all_keys_values2]
194+
for ((f1, f2), ((k1, v1), (k2, v2))) in combinations:
195+
d1 = f1()
196+
d2 = f2()
197+
d1[k1] = v1
198+
d2[k2] = v2
199+
d1.update(d2)
200+
assert k1 in d1
201+
assert k2 in d2
202+
if k1 == k2:
203+
assert len(d1) == 1
204+
assert d1[k1] == v2
205+
else:
206+
assert len(d1) == 2
207+
assert d1[k1] == v1
208+
assert d1[k2] == v2
209+
210+
211+
log = []
212+
class LoggingStr(str):
213+
def __hash__(self):
214+
log.append('Hash on %r' % self)
215+
return 1
216+
def __eq__(self, other):
217+
log.append('Eq on %r and %r' % (self, other))
218+
return super(LoggingStr, self).__eq__(other)
219+
220+
def test_side_effects():
221+
for (f, key) in zip(FACTORIES, ['a', 'b', 'c', 'd', 'e', 'f', 'g']):
222+
log.clear()
223+
d = f()
224+
d[LoggingStr(key)] = 42
225+
assert log == ["Hash on '%s'" % key]
226+
log.clear()
227+
assert d[LoggingStr(key)] == 42
228+
assert log == [
229+
"Hash on '%s'" % key,
230+
"Eq on '%s' and '%s'" % (key, key)]
231+
log.clear()
232+
assert_raises_keyerror(d, LoggingStr('foo'))
233+
assert log == [
234+
"Hash on 'foo'",
235+
"Eq on '%s' and 'foo'" % key]

0 commit comments

Comments
 (0)