this repo has no description
1// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com)
2#include "dict-builtins.h"
3
4#include "gtest/gtest.h"
5
6#include "builtins-module.h"
7#include "builtins.h"
8#include "int-builtins.h"
9#include "runtime.h"
10#include "str-builtins.h"
11#include "test-utils.h"
12
13namespace py {
14namespace testing {
15
16using DictBuiltinsTest = RuntimeFixture;
17using DictItemIteratorBuiltinsTest = RuntimeFixture;
18using DictItemsBuiltinsTest = RuntimeFixture;
19using DictKeyIteratorBuiltinsTest = RuntimeFixture;
20using DictKeysBuiltinsTest = RuntimeFixture;
21using DictValueIteratorBuiltinsTest = RuntimeFixture;
22using DictValuesBuiltinsTest = RuntimeFixture;
23
24TEST_F(DictBuiltinsTest, EmptyDictInvariants) {
25 HandleScope scope(thread_);
26 Dict dict(&scope, runtime_->newDict());
27
28 EXPECT_EQ(dict.numItems(), 0);
29 ASSERT_TRUE(isIntEqualsWord(dict.data(), 0));
30}
31
32TEST_F(DictBuiltinsTest, DictAtPutRetainsExistingKeyObject) {
33 HandleScope scope(thread_);
34 Dict dict(&scope, runtime_->newDict());
35 Str key0(&scope, runtime_->newStrFromCStr("foobarbazbam"));
36 word key0_hash = strHash(thread_, *key0);
37 Object value0(&scope, SmallInt::fromWord(123));
38 Str key1(&scope, runtime_->newStrFromCStr("foobarbazbam"));
39 word key1_hash = strHash(thread_, *key1);
40 Object value1(&scope, SmallInt::fromWord(456));
41 ASSERT_NE(key0, key1);
42 ASSERT_EQ(key0_hash, key1_hash);
43
44 ASSERT_TRUE(dictAtPut(thread_, dict, key0, key0_hash, value0).isNoneType());
45 ASSERT_EQ(dict.numItems(), 1);
46 ASSERT_EQ(dictAt(thread_, dict, key0, key0_hash), *value0);
47
48 // Overwrite the stored value
49 ASSERT_TRUE(dictAtPut(thread_, dict, key1, key1_hash, value1).isNoneType());
50 ASSERT_EQ(dict.numItems(), 1);
51 ASSERT_EQ(dictAt(thread_, dict, key1, key1_hash), *value1);
52
53 word i = 0;
54 Object key(&scope, NoneType::object());
55 ASSERT_TRUE(dictNextKey(dict, &i, &key));
56 EXPECT_EQ(key, key0);
57}
58
59TEST_F(DictBuiltinsTest, GetSet) {
60 HandleScope scope(thread_);
61 Dict dict(&scope, runtime_->newDict());
62 Object key(&scope, SmallInt::fromWord(12345));
63 word hash = intHash(*key);
64
65 // Looking up a key that doesn't exist should fail
66 EXPECT_TRUE(dictAt(thread_, dict, key, hash).isError());
67
68 // Store a value
69 Object stored(&scope, SmallInt::fromWord(67890));
70 ASSERT_TRUE(dictAtPut(thread_, dict, key, hash, stored).isNoneType());
71 EXPECT_EQ(dict.numItems(), 1);
72
73 // Retrieve the stored value
74 RawObject retrieved = dictAt(thread_, dict, key, hash);
75 EXPECT_EQ(retrieved, *stored);
76
77 // Overwrite the stored value
78 Object new_value(&scope, SmallInt::fromWord(5555));
79 ASSERT_TRUE(dictAtPut(thread_, dict, key, hash, new_value).isNoneType());
80 EXPECT_EQ(dict.numItems(), 1);
81
82 // Get the new value
83 retrieved = dictAt(thread_, dict, key, hash);
84 EXPECT_EQ(retrieved, *new_value);
85}
86
87TEST_F(DictBuiltinsTest, Remove) {
88 HandleScope scope(thread_);
89 Dict dict(&scope, runtime_->newDict());
90 Object key(&scope, SmallInt::fromWord(12345));
91 word hash = intHash(*key);
92
93 // Removing a key that doesn't exist should fail
94 bool is_missing = dictRemove(thread_, dict, key, hash).isError();
95 EXPECT_TRUE(is_missing);
96
97 // Removing a key that exists should succeed and return the value that was
98 // stored.
99 Object stored(&scope, SmallInt::fromWord(54321));
100
101 ASSERT_TRUE(dictAtPut(thread_, dict, key, hash, stored).isNoneType());
102 EXPECT_EQ(dict.numItems(), 1);
103
104 RawObject retrieved = dictRemove(thread_, dict, key, hash);
105 ASSERT_FALSE(retrieved.isError());
106 ASSERT_EQ(SmallInt::cast(retrieved).value(), SmallInt::cast(*stored).value());
107
108 // Looking up a key that was deleted should fail
109 EXPECT_TRUE(dictAt(thread_, dict, key, hash).isError());
110 EXPECT_EQ(dict.numItems(), 0);
111}
112
113TEST_F(DictBuiltinsTest, Length) {
114 HandleScope scope(thread_);
115 Dict dict(&scope, runtime_->newDict());
116
117 // Add 10 items and make sure length reflects it
118 for (int i = 0; i < 10; i++) {
119 Object key(&scope, SmallInt::fromWord(i));
120 word hash = intHash(*key);
121 ASSERT_TRUE(dictAtPut(thread_, dict, key, hash, key).isNoneType());
122 }
123 EXPECT_EQ(dict.numItems(), 10);
124
125 // Remove half the items
126 for (int i = 0; i < 5; i++) {
127 Object key(&scope, SmallInt::fromWord(i));
128 word hash = intHash(*key);
129 ASSERT_FALSE(dictRemove(thread_, dict, key, hash).isError());
130 }
131 EXPECT_EQ(dict.numItems(), 5);
132}
133
134TEST_F(DictBuiltinsTest, DictAtPutInValueCellByStrCreatesValueCell) {
135 HandleScope scope(thread_);
136 Dict dict(&scope, runtime_->newDict());
137 Object name(&scope, Runtime::internStrFromCStr(thread_, "foo"));
138 Object value(&scope, Runtime::internStrFromCStr(thread_, "bar"));
139 Object result(&scope, dictAtPutInValueCellByStr(thread_, dict, name, value));
140 ASSERT_TRUE(result.isValueCell());
141 EXPECT_EQ(ValueCell::cast(*result).value(), value);
142 EXPECT_EQ(dictAtByStr(thread_, dict, name), result);
143}
144
145TEST_F(DictBuiltinsTest, DictAtPutInValueCellByStrReusesExistingValueCell) {
146 HandleScope scope(thread_);
147 Dict dict(&scope, runtime_->newDict());
148 Object name(&scope, Runtime::internStrFromCStr(thread_, "foo"));
149 Object value0(&scope, Runtime::internStrFromCStr(thread_, "bar"));
150 Object result0(&scope,
151 dictAtPutInValueCellByStr(thread_, dict, name, value0));
152 ASSERT_TRUE(result0.isValueCell());
153 EXPECT_EQ(ValueCell::cast(*result0).value(), value0);
154
155 Object value1(&scope, Runtime::internStrFromCStr(thread_, "baz"));
156 Object result1(&scope,
157 dictAtPutInValueCellByStr(thread_, dict, name, value1));
158 EXPECT_EQ(result0, result1);
159 EXPECT_EQ(dictAtByStr(thread_, dict, name), result1);
160 EXPECT_EQ(ValueCell::cast(*result1).value(), value1);
161}
162
163// Should be synced with dict-builtins.cpp.
164static const word kInitialDictIndicesLength = 8;
165static const word kItemNumPointers = 3;
166
167TEST_F(DictBuiltinsTest, DictAtPutGrowsDictWhenDictIsEmpty) {
168 HandleScope scope(thread_);
169 Dict dict(&scope, runtime_->newDict());
170 EXPECT_EQ(dict.numIndices(), 0);
171
172 Object first_key(&scope, SmallInt::fromWord(0));
173 word hash = intHash(*first_key);
174 Object first_value(&scope, SmallInt::fromWord(1));
175 ASSERT_TRUE(
176 dictAtPut(thread_, dict, first_key, hash, first_value).isNoneType());
177
178 word initial_capacity = kInitialDictIndicesLength;
179 EXPECT_EQ(dict.numItems(), 1);
180 EXPECT_EQ(dict.numIndices(), initial_capacity);
181}
182
183TEST_F(DictBuiltinsTest, DictAtPutGrowsDictWhenTwoThirdsUsed) {
184 HandleScope scope(thread_);
185 Dict dict(&scope, runtime_->newDict());
186
187 // Fill in one fewer keys than would require growing the underlying object
188 // array again.
189 word threshold = ((kInitialDictIndicesLength * 2) / 3) - 1;
190 for (word i = 0; i < threshold; i++) {
191 Object key(&scope, SmallInt::fromWord(i));
192 word hash = intHash(*key);
193 Object value(&scope, SmallInt::fromWord(-i));
194 ASSERT_TRUE(dictAtPut(thread_, dict, key, hash, value).isNoneType());
195 }
196 EXPECT_EQ(dict.numItems(), threshold);
197 EXPECT_EQ(dict.firstEmptyItemIndex() / kItemNumPointers, threshold);
198 word initial_capacity = kInitialDictIndicesLength;
199 EXPECT_EQ(dict.numIndices(), initial_capacity);
200
201 // Add another key which should force us to double the capacity
202 Object last_key(&scope, SmallInt::fromWord(threshold));
203 word last_key_hash = intHash(*last_key);
204 Object last_value(&scope, SmallInt::fromWord(-threshold));
205 ASSERT_TRUE(dictAtPut(thread_, dict, last_key, last_key_hash, last_value)
206 .isNoneType());
207 EXPECT_EQ(dict.numItems(), threshold + 1);
208 // 2 == kDictGrowthFactor.
209 EXPECT_EQ(dict.numIndices(), initial_capacity * 2);
210 EXPECT_EQ(dict.firstEmptyItemIndex() / kItemNumPointers, threshold + 1);
211
212 // Make sure we can still read all the stored keys/values.
213 for (word i = 0; i <= threshold; i++) {
214 Object key(&scope, SmallInt::fromWord(i));
215 word hash = intHash(*key);
216 RawObject value = dictAt(thread_, dict, key, hash);
217 ASSERT_FALSE(value.isError());
218 EXPECT_TRUE(isIntEqualsWord(value, -i));
219 }
220}
221
222TEST_F(DictBuiltinsTest, CollidingKeys) {
223 HandleScope scope(thread_);
224 ASSERT_FALSE(runFromCStr(runtime_, R"(
225class C:
226 def __eq__(self, other):
227 return self is other
228 def __hash__(self):
229 return 0
230i0 = C()
231i1 = C()
232)")
233 .isError());
234 Object i0(&scope, mainModuleAt(runtime_, "i0"));
235 Object i0_hash_obj(&scope, Interpreter::hash(thread_, i0));
236 ASSERT_FALSE(i0_hash_obj.isErrorException());
237 word i0_hash = SmallInt::cast(*i0_hash_obj).value();
238 Object i1(&scope, mainModuleAt(runtime_, "i1"));
239 Object i1_hash_obj(&scope, Interpreter::hash(thread_, i1));
240 ASSERT_FALSE(i1_hash_obj.isErrorException());
241 word i1_hash = SmallInt::cast(*i1_hash_obj).value();
242 ASSERT_EQ(i0_hash, i1_hash);
243
244 Dict dict(&scope, runtime_->newDict());
245
246 // Add two different keys with different values using the same hash
247 ASSERT_TRUE(dictAtPut(thread_, dict, i0, i0_hash, i0).isNoneType());
248 ASSERT_TRUE(dictAtPut(thread_, dict, i1, i1_hash, i1).isNoneType());
249
250 // Make sure we get both back
251 Object retrieved(&scope, dictAt(thread_, dict, i0, i0_hash));
252 EXPECT_EQ(retrieved, i0);
253
254 retrieved = dictAt(thread_, dict, i1, i1_hash);
255 EXPECT_EQ(retrieved, i1);
256}
257
258TEST_F(DictBuiltinsTest, MixedKeys) {
259 HandleScope scope(thread_);
260 Dict dict(&scope, runtime_->newDict());
261
262 // Add keys of different type
263 Object int_key(&scope, SmallInt::fromWord(100));
264 word int_key_hash = intHash(*int_key);
265 ASSERT_TRUE(
266 dictAtPut(thread_, dict, int_key, int_key_hash, int_key).isNoneType());
267
268 Object str_key(&scope, runtime_->newStrFromCStr("testing 123"));
269 word str_key_hash = strHash(thread_, *str_key);
270 ASSERT_TRUE(
271 dictAtPut(thread_, dict, str_key, str_key_hash, str_key).isNoneType());
272
273 // Make sure we get the appropriate values back out
274 RawObject retrieved = dictAt(thread_, dict, int_key, int_key_hash);
275 EXPECT_EQ(retrieved, *int_key);
276
277 retrieved = dictAt(thread_, dict, str_key, str_key_hash);
278 ASSERT_TRUE(retrieved.isStr());
279 EXPECT_EQ(*str_key, retrieved);
280}
281
282TEST_F(DictBuiltinsTest, GetKeys) {
283 HandleScope scope(thread_);
284
285 // Create keys
286 Object obj1(&scope, SmallInt::fromWord(100));
287 Object obj2(&scope, runtime_->newStrFromCStr("testing 123"));
288 Object obj3(&scope, Bool::trueObj());
289 Object obj4(&scope, NoneType::object());
290 Tuple keys(&scope, runtime_->newTupleWith4(obj1, obj2, obj3, obj4));
291
292 // Add keys to dict
293 Dict dict(&scope, runtime_->newDict());
294 for (word i = 0; i < keys.length(); i++) {
295 Object key(&scope, keys.at(i));
296 Object hash_obj(&scope, Interpreter::hash(thread_, key));
297 ASSERT_FALSE(hash_obj.isErrorException());
298 word hash = SmallInt::cast(*hash_obj).value();
299 ASSERT_TRUE(dictAtPut(thread_, dict, key, hash, key).isNoneType());
300 }
301
302 // Grab the keys and verify everything is there
303 List retrieved(&scope, dictKeys(thread_, dict));
304 ASSERT_EQ(retrieved.numItems(), keys.length());
305 for (word i = 0; i < keys.length(); i++) {
306 Object key(&scope, keys.at(i));
307 EXPECT_TRUE(listContains(retrieved, key)) << " missing key " << i;
308 }
309}
310
311TEST_F(DictBuiltinsTest, CanCreateDictItems) {
312 HandleScope scope(thread_);
313 Dict dict(&scope, runtime_->newDict());
314 RawObject iter = runtime_->newDictItemIterator(thread_, dict);
315 ASSERT_TRUE(iter.isDictItemIterator());
316}
317
318TEST_F(DictBuiltinsTest, DictAtGrowsToInitialCapacity) {
319 HandleScope scope(thread_);
320 Dict dict(&scope, runtime_->newDict());
321 EXPECT_EQ(dict.numIndices(), 0);
322
323 Object key(&scope, runtime_->newInt(123));
324 word hash = intHash(*key);
325 Object value(&scope, runtime_->newInt(456));
326 ASSERT_TRUE(dictAtPut(thread_, dict, key, hash, value).isNoneType());
327 int expected = kInitialDictIndicesLength;
328 EXPECT_EQ(dict.numIndices(), expected);
329}
330
331TEST_F(DictBuiltinsTest, ClearWithEmptyDictIsNoop) {
332 HandleScope scope(thread_);
333 Dict dict(&scope, runtime_->newDict());
334 EXPECT_EQ(runBuiltin(METH(dict, clear), dict), NoneType::object());
335}
336
337TEST_F(DictBuiltinsTest, ClearWithNonEmptyDictRemovesAllElements) {
338 ASSERT_FALSE(runFromCStr(runtime_, R"(
339class C:
340 pass
341d = {'a': C()}
342)")
343 .isError());
344
345 HandleScope scope(thread_);
346 Dict dict(&scope, mainModuleAt(runtime_, "d"));
347 Object ref_obj(&scope, NoneType::object());
348 {
349 Str key(&scope, runtime_->newStrFromCStr("a"));
350 Object c(&scope, dictAtByStr(thread_, dict, key));
351 ref_obj = runtime_->newWeakRef(thread_, c);
352 }
353 WeakRef ref(&scope, *ref_obj);
354 EXPECT_NE(ref.referent(), NoneType::object());
355 runBuiltin(METH(dict, clear), dict);
356 runtime_->collectGarbage();
357 EXPECT_EQ(ref.referent(), NoneType::object());
358}
359
360TEST_F(DictBuiltinsTest, CopyWithDictReturnsNewInstance) {
361 ASSERT_FALSE(runFromCStr(runtime_, R"(
362d = {'a': 3}
363result = dict.copy(d)
364)")
365 .isError());
366 HandleScope scope(thread_);
367 Object dict(&scope, mainModuleAt(runtime_, "d"));
368 EXPECT_TRUE(dict.isDict());
369 Object result_obj(&scope, mainModuleAt(runtime_, "result"));
370 EXPECT_TRUE(result_obj.isDict());
371 Dict result(&scope, *result_obj);
372 EXPECT_NE(*dict, *result);
373 EXPECT_EQ(result.numItems(), 1);
374 EXPECT_EQ(result.firstEmptyItemIndex() / kItemNumPointers, 1);
375}
376
377TEST_F(DictBuiltinsTest, DunderContainsWithExistingKeyReturnsTrue) {
378 ASSERT_FALSE(runFromCStr(runtime_, "result = {'foo': 0}.__contains__('foo')")
379 .isError());
380 HandleScope scope(thread_);
381 Object result(&scope, mainModuleAt(runtime_, "result"));
382 ASSERT_TRUE(result.isBool());
383 EXPECT_TRUE(Bool::cast(*result).value());
384}
385
386TEST_F(DictBuiltinsTest, DunderContainsWithNonexistentKeyReturnsFalse) {
387 ASSERT_FALSE(
388 runFromCStr(runtime_, "result = {}.__contains__('foo')").isError());
389 HandleScope scope(thread_);
390 Object result(&scope, mainModuleAt(runtime_, "result"));
391 ASSERT_TRUE(result.isBool());
392 EXPECT_FALSE(Bool::cast(*result).value());
393}
394
395TEST_F(DictBuiltinsTest, DunderContainsWithUnhashableTypeRaisesTypeError) {
396 ASSERT_FALSE(runFromCStr(runtime_, R"(
397class C:
398 __hash__ = None
399c = C()
400)")
401 .isError());
402 EXPECT_TRUE(raised(runFromCStr(runtime_, "{}.__contains__(C())"),
403 LayoutId::kTypeError));
404}
405
406TEST_F(DictBuiltinsTest,
407 DunderContainsWithNonCallableDunderHashRaisesTypeError) {
408 ASSERT_FALSE(runFromCStr(runtime_, R"(
409class C:
410 __hash__ = 4
411)")
412 .isError());
413 EXPECT_TRUE(raised(runFromCStr(runtime_, "{}.__contains__(C())"),
414 LayoutId::kTypeError));
415}
416
417TEST_F(DictBuiltinsTest,
418 DunderContainsWithTypeWithDunderHashReturningNonIntRaisesTypeError) {
419 ASSERT_FALSE(runFromCStr(runtime_, R"(
420class C:
421 def __hash__(self):
422 return "boo"
423)")
424 .isError());
425 EXPECT_TRUE(raised(runFromCStr(runtime_, "{}.__contains__(C())"),
426 LayoutId::kTypeError));
427}
428
429TEST_F(DictBuiltinsTest, InWithExistingKeyReturnsTrue) {
430 ASSERT_FALSE(runFromCStr(runtime_, R"(
431d = {"foo": 1}
432foo_in_d = "foo" in d
433)")
434 .isError());
435 HandleScope scope(thread_);
436 Bool foo_in_d(&scope, mainModuleAt(runtime_, "foo_in_d"));
437
438 EXPECT_TRUE(foo_in_d.value());
439}
440
441TEST_F(DictBuiltinsTest, InWithNonexistentKeyReturnsFalse) {
442 ASSERT_FALSE(runFromCStr(runtime_, R"(
443d = {}
444foo_in_d = "foo" in d
445)")
446 .isError());
447 HandleScope scope(thread_);
448 Bool foo_in_d(&scope, mainModuleAt(runtime_, "foo_in_d"));
449
450 EXPECT_FALSE(foo_in_d.value());
451}
452
453TEST_F(DictBuiltinsTest, DunderDelitemOnExistingKeyReturnsNone) {
454 HandleScope scope(thread_);
455 Dict dict(&scope, runtime_->newDictWithSize(1));
456 Str key(&scope, runtime_->newStrFromCStr("foo"));
457 Object val(&scope, runtime_->newInt(0));
458 dictAtPutByStr(thread_, dict, key, val);
459 RawObject result = runBuiltin(METH(dict, __delitem__), dict, key);
460 EXPECT_TRUE(result.isNoneType());
461}
462
463TEST_F(DictBuiltinsTest, DunderDelitemOnNonexistentKeyRaisesKeyError) {
464 HandleScope scope(thread_);
465 Dict dict(&scope, runtime_->newDictWithSize(1));
466 Str key(&scope, runtime_->newStrFromCStr("foo"));
467 Object val(&scope, runtime_->newInt(0));
468 dictAtPutByStr(thread_, dict, key, val);
469
470 // "bar" doesn't exist in this dictionary, attempting to delete it should
471 // cause a KeyError.
472 Object key2(&scope, runtime_->newStrFromCStr("bar"));
473 RawObject result = runBuiltin(METH(dict, __delitem__), dict, key2);
474 ASSERT_TRUE(result.isError());
475}
476
477TEST_F(DictBuiltinsTest, DelOnObjectHashReturningNonIntRaisesTypeError) {
478 EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
479class E:
480 def __hash__(self): return "non int"
481
482d = {}
483del d[E()]
484)"),
485 LayoutId::kTypeError,
486 "__hash__ method should return an integer"));
487}
488
489TEST_F(DictBuiltinsTest, DelOnExistingKeyDeletesKey) {
490 ASSERT_FALSE(runFromCStr(runtime_, R"(
491d = {"foo": 1}
492del d["foo"]
493)")
494 .isError());
495 HandleScope scope(thread_);
496 Dict d(&scope, mainModuleAt(runtime_, "d"));
497 Str foo(&scope, runtime_->newStrFromCStr("foo"));
498
499 EXPECT_EQ(dictIncludes(thread_, d, foo, strHash(thread_, *foo)),
500 Bool::falseObj());
501}
502
503TEST_F(DictBuiltinsTest, DelOnNonexistentKeyRaisesKeyError) {
504 EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
505d = {}
506del d["foo"]
507)"),
508 LayoutId::kKeyError, "foo"));
509}
510
511TEST_F(DictBuiltinsTest, NonTypeInDunderNew) {
512 EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
513dict.__new__(1)
514)"),
515 LayoutId::kTypeError, "not a type object"));
516}
517
518TEST_F(DictBuiltinsTest, NonSubclassInDunderNew) {
519 EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
520class Foo: pass
521dict.__new__(Foo)
522)"),
523 LayoutId::kTypeError, "not a subtype of dict"));
524}
525
526TEST_F(DictBuiltinsTest, DunderNewConstructsDict) {
527 HandleScope scope(thread_);
528 Type type(&scope, runtime_->typeAt(LayoutId::kDict));
529 Object result(&scope, runBuiltin(METH(dict, __new__), type));
530 ASSERT_TRUE(result.isDict());
531}
532
533TEST_F(DictBuiltinsTest, DunderIterReturnsDictKeyIter) {
534 HandleScope scope(thread_);
535 Dict dict(&scope, runtime_->newDict());
536 Object iter(&scope, runBuiltin(METH(dict, __iter__), dict));
537 ASSERT_TRUE(iter.isDictKeyIterator());
538}
539
540TEST_F(DictBuiltinsTest, DunderItemsReturnsDictItems) {
541 HandleScope scope(thread_);
542 Dict dict(&scope, runtime_->newDict());
543 Object items(&scope, runBuiltin(METH(dict, items), dict));
544 ASSERT_TRUE(items.isDictItems());
545}
546
547TEST_F(DictBuiltinsTest, KeysReturnsDictKeys) {
548 HandleScope scope(thread_);
549 Dict dict(&scope, runtime_->newDict());
550 Object keys(&scope, runBuiltin(METH(dict, keys), dict));
551 ASSERT_TRUE(keys.isDictKeys());
552}
553
554TEST_F(DictBuiltinsTest, ValuesReturnsDictValues) {
555 HandleScope scope(thread_);
556 Dict dict(&scope, runtime_->newDict());
557 Object values(&scope, runBuiltin(METH(dict, values), dict));
558 ASSERT_TRUE(values.isDictValues());
559}
560
561TEST_F(DictBuiltinsTest, UpdateWithNoArgumentsRaisesTypeError) {
562 EXPECT_TRUE(raisedWithStr(
563 runFromCStr(runtime_, "dict.update()"), LayoutId::kTypeError,
564 "'dict.update' takes min 1 positional arguments but 0 given"));
565}
566
567TEST_F(DictBuiltinsTest, UpdateWithNonDictRaisesTypeError) {
568 EXPECT_TRUE(raised(runFromCStr(runtime_, "dict.update([], None)"),
569 LayoutId::kTypeError));
570}
571
572TEST_F(DictBuiltinsTest, UpdateWithNonMappingTypeRaisesTypeError) {
573 EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, "dict.update({}, 1)"),
574 LayoutId::kTypeError,
575 "'int' object is not iterable"));
576}
577
578TEST_F(DictBuiltinsTest,
579 UpdateWithListContainerWithObjectHashReturningNonIntRaisesTypeError) {
580 EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
581class E:
582 def __hash__(self): return "non int"
583
584class C:
585 def __init__(self):
586 self.item = E()
587
588 def __getitem__(self, idx):
589 return self.item
590
591 def keys(self):
592 return [self.item]
593
594dict.update({1:4}, C())
595)"),
596 LayoutId::kTypeError,
597 "__hash__ method should return an integer"));
598}
599
600TEST_F(DictBuiltinsTest,
601 UpdateWithTupleContainerWithObjectHashReturningNonIntRaisesTypeError) {
602 EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
603class E:
604 def __hash__(self): return "non int"
605
606class C:
607 def __init__(self):
608 self.item = E()
609
610 def __getitem__(self, idx):
611 return self.item
612
613 def keys(self):
614 return (self.item,)
615
616dict.update({1:4}, C())
617)"),
618 LayoutId::kTypeError,
619 "__hash__ method should return an integer"));
620}
621
622TEST_F(DictBuiltinsTest,
623 UpdateWithIterContainerWithObjectHashReturningNonIntRaisesTypeError) {
624 EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"(
625class E:
626 def __hash__(self): return "non int"
627
628class C:
629 def __init__(self):
630 self.item = E()
631
632 def __getitem__(self, idx):
633 return self.item
634
635 def keys(self):
636 return iter([self.item])
637
638dict.update({1:4}, C())
639)"),
640 LayoutId::kTypeError,
641 "__hash__ method should return an integer"));
642}
643
644TEST_F(DictBuiltinsTest, UpdateWithDictReturnsUpdatedDict) {
645 ASSERT_FALSE(runFromCStr(runtime_, R"(
646d1 = {"a": 1, "b": 2}
647d2 = {"c": 3, "d": 4}
648d3 = {"a": 123}
649)")
650 .isError());
651 HandleScope scope(thread_);
652 Dict d1(&scope, mainModuleAt(runtime_, "d1"));
653 Dict d2(&scope, mainModuleAt(runtime_, "d2"));
654 ASSERT_EQ(d1.numItems(), 2);
655 EXPECT_EQ(d1.firstEmptyItemIndex() / kItemNumPointers, 2);
656 ASSERT_EQ(d2.numItems(), 2);
657 EXPECT_EQ(d2.firstEmptyItemIndex() / kItemNumPointers, 2);
658 ASSERT_TRUE(runFromCStr(runtime_, "d1.update(d2)").isNoneType());
659 EXPECT_EQ(d1.numItems(), 4);
660 EXPECT_EQ(d1.firstEmptyItemIndex() / kItemNumPointers, 4);
661 EXPECT_EQ(d2.numItems(), 2);
662 EXPECT_EQ(d2.firstEmptyItemIndex() / kItemNumPointers, 2);
663
664 ASSERT_TRUE(runFromCStr(runtime_, "d1.update(d3)").isNoneType());
665 EXPECT_EQ(d1.numItems(), 4);
666 EXPECT_EQ(d1.firstEmptyItemIndex() / kItemNumPointers, 4);
667 Str a(&scope, runtime_->newStrFromCStr("a"));
668 Object a_val(&scope, dictAtByStr(thread_, d1, a));
669 EXPECT_TRUE(isIntEqualsWord(*a_val, 123));
670}
671
672TEST_F(DictItemsBuiltinsTest, DunderIterReturnsIter) {
673 HandleScope scope(thread_);
674 Dict dict(&scope, runtime_->newDict());
675 DictItems items(&scope, runtime_->newDictItems(thread_, dict));
676 Object iter(&scope, runBuiltin(METH(dict_items, __iter__), items));
677 ASSERT_TRUE(iter.isDictItemIterator());
678}
679
680TEST_F(DictKeysBuiltinsTest, DunderIterReturnsIter) {
681 HandleScope scope(thread_);
682 Dict dict(&scope, runtime_->newDict());
683 DictKeys keys(&scope, runtime_->newDictKeys(thread_, dict));
684 Object iter(&scope, runBuiltin(METH(dict_keys, __iter__), keys));
685 ASSERT_TRUE(iter.isDictKeyIterator());
686}
687
688TEST_F(DictValuesBuiltinsTest, DunderIterReturnsIter) {
689 HandleScope scope(thread_);
690 Dict dict(&scope, runtime_->newDict());
691 DictValues values(&scope, runtime_->newDictValues(thread_, dict));
692 Object iter(&scope, runBuiltin(METH(dict_values, __iter__), values));
693 ASSERT_TRUE(iter.isDictValueIterator());
694}
695
696TEST_F(DictItemIteratorBuiltinsTest, CallDunderIterReturnsSelf) {
697 HandleScope scope(thread_);
698 Dict dict(&scope, runtime_->newDict());
699 DictItemIterator iter(&scope, runtime_->newDictItemIterator(thread_, dict));
700 // Now call __iter__ on the iterator object
701 Object result(&scope, runBuiltin(METH(dict_itemiterator, __iter__), iter));
702 ASSERT_EQ(*result, *iter);
703}
704
705TEST_F(DictKeyIteratorBuiltinsTest, CallDunderIterReturnsSelf) {
706 HandleScope scope(thread_);
707 Dict dict(&scope, runtime_->newDict());
708 DictKeyIterator iter(&scope, runtime_->newDictKeyIterator(thread_, dict));
709 // Now call __iter__ on the iterator object
710 Object result(&scope, runBuiltin(METH(dict_keyiterator, __iter__), iter));
711 ASSERT_EQ(*result, *iter);
712}
713
714TEST_F(DictValueIteratorBuiltinsTest, CallDunderIterReturnsSelf) {
715 HandleScope scope(thread_);
716 Dict dict(&scope, runtime_->newDict());
717 DictValueIterator iter(&scope, runtime_->newDictValueIterator(thread_, dict));
718 // Now call __iter__ on the iterator object
719 Object result(&scope, runBuiltin(METH(dict_valueiterator, __iter__), iter));
720 ASSERT_EQ(*result, *iter);
721}
722
723TEST_F(DictItemIteratorBuiltinsTest,
724 DunderLengthHintOnEmptyDictItemIteratorReturnsZero) {
725 HandleScope scope(thread_);
726 Dict empty_dict(&scope, runtime_->newDict());
727 DictItemIterator iter(&scope,
728 runtime_->newDictItemIterator(thread_, empty_dict));
729 Object length_hint(
730 &scope, runBuiltin(METH(dict_itemiterator, __length_hint__), iter));
731 EXPECT_TRUE(isIntEqualsWord(*length_hint, 0));
732}
733
734TEST_F(DictKeyIteratorBuiltinsTest,
735 DunderLengthHintOnEmptyDictKeyIteratorReturnsZero) {
736 HandleScope scope(thread_);
737 Dict empty_dict(&scope, runtime_->newDict());
738 DictKeyIterator iter(&scope,
739 runtime_->newDictKeyIterator(thread_, empty_dict));
740 Object length_hint(&scope,
741 runBuiltin(METH(dict_keyiterator, __length_hint__), iter));
742 EXPECT_TRUE(isIntEqualsWord(*length_hint, 0));
743}
744
745TEST_F(DictValueIteratorBuiltinsTest,
746 DunderLengthHintOnEmptyDictValueIteratorReturnsZero) {
747 HandleScope scope(thread_);
748 Dict empty_dict(&scope, runtime_->newDict());
749 DictValueIterator iter(&scope,
750 runtime_->newDictValueIterator(thread_, empty_dict));
751 Object length_hint(
752 &scope, runBuiltin(METH(dict_valueiterator, __length_hint__), iter));
753 EXPECT_TRUE(isIntEqualsWord(*length_hint, 0));
754}
755
756TEST_F(DictItemIteratorBuiltinsTest, CallDunderNextReadsItemsSequentially) {
757 HandleScope scope(thread_);
758 Dict dict(&scope, runtime_->newDictWithSize(5));
759 Str hello(&scope, runtime_->newStrFromCStr("hello"));
760 Object world(&scope, runtime_->newStrFromCStr("world"));
761 Str goodbye(&scope, runtime_->newStrFromCStr("goodbye"));
762 Object moon(&scope, runtime_->newStrFromCStr("moon"));
763 dictAtPutByStr(thread_, dict, hello, world);
764 dictAtPutByStr(thread_, dict, goodbye, moon);
765 DictItemIterator iter(&scope, runtime_->newDictItemIterator(thread_, dict));
766
767 Object item1(&scope, runBuiltin(METH(dict_itemiterator, __next__), iter));
768 ASSERT_TRUE(item1.isTuple());
769 EXPECT_EQ(Tuple::cast(*item1).at(0), hello);
770 EXPECT_EQ(Tuple::cast(*item1).at(1), world);
771
772 Object item2(&scope, runBuiltin(METH(dict_itemiterator, __next__), iter));
773 ASSERT_TRUE(item2.isTuple());
774 EXPECT_EQ(Tuple::cast(*item2).at(0), goodbye);
775 EXPECT_EQ(Tuple::cast(*item2).at(1), moon);
776
777 Object item3(&scope, runBuiltin(METH(dict_itemiterator, __next__), iter));
778 ASSERT_TRUE(item3.isError());
779}
780
781TEST_F(DictKeyIteratorBuiltinsTest, CallDunderNextReadsKeysSequentially) {
782 HandleScope scope(thread_);
783 Dict dict(&scope, runtime_->newDictWithSize(5));
784 Str hello(&scope, runtime_->newStrFromCStr("hello"));
785 Object world(&scope, runtime_->newStrFromCStr("world"));
786 Str goodbye(&scope, runtime_->newStrFromCStr("goodbye"));
787 Object moon(&scope, runtime_->newStrFromCStr("moon"));
788 dictAtPutByStr(thread_, dict, hello, world);
789 dictAtPutByStr(thread_, dict, goodbye, moon);
790 DictKeyIterator iter(&scope, runtime_->newDictKeyIterator(thread_, dict));
791
792 Object item1(&scope, runBuiltin(METH(dict_keyiterator, __next__), iter));
793 ASSERT_TRUE(item1.isStr());
794 EXPECT_EQ(Str::cast(*item1), hello);
795
796 Object item2(&scope, runBuiltin(METH(dict_keyiterator, __next__), iter));
797 ASSERT_TRUE(item2.isStr());
798 EXPECT_EQ(Str::cast(*item2), goodbye);
799
800 Object item3(&scope, runBuiltin(METH(dict_keyiterator, __next__), iter));
801 ASSERT_TRUE(item3.isError());
802}
803
804TEST_F(DictValueIteratorBuiltinsTest, CallDunderNextReadsValuesSequentially) {
805 HandleScope scope(thread_);
806 Dict dict(&scope, runtime_->newDictWithSize(5));
807 Str hello(&scope, runtime_->newStrFromCStr("hello"));
808 Object world(&scope, runtime_->newStrFromCStr("world"));
809 Str goodbye(&scope, runtime_->newStrFromCStr("goodbye"));
810 Object moon(&scope, runtime_->newStrFromCStr("moon"));
811 dictAtPutByStr(thread_, dict, hello, world);
812 dictAtPutByStr(thread_, dict, goodbye, moon);
813 DictValueIterator iter(&scope, runtime_->newDictValueIterator(thread_, dict));
814
815 Object item1(&scope, runBuiltin(METH(dict_valueiterator, __next__), iter));
816 ASSERT_TRUE(item1.isStr());
817 EXPECT_EQ(Str::cast(*item1), world);
818
819 Object item2(&scope, runBuiltin(METH(dict_valueiterator, __next__), iter));
820 ASSERT_TRUE(item2.isStr());
821 EXPECT_EQ(Str::cast(*item2), moon);
822
823 Object item3(&scope, runBuiltin(METH(dict_valueiterator, __next__), iter));
824 ASSERT_TRUE(item3.isError());
825}
826
827TEST_F(DictItemIteratorBuiltinsTest,
828 DunderLengthHintOnConsumedDictItemIteratorReturnsZero) {
829 HandleScope scope(thread_);
830 Dict dict(&scope, runtime_->newDict());
831 Str hello(&scope, runtime_->newStrFromCStr("hello"));
832 Object world(&scope, runtime_->newStrFromCStr("world"));
833 dictAtPutByStr(thread_, dict, hello, world);
834 DictItemIterator iter(&scope, runtime_->newDictItemIterator(thread_, dict));
835
836 Object item1(&scope, runBuiltin(METH(dict_itemiterator, __next__), iter));
837 ASSERT_FALSE(item1.isError());
838
839 Object length_hint(
840 &scope, runBuiltin(METH(dict_itemiterator, __length_hint__), iter));
841 EXPECT_TRUE(isIntEqualsWord(*length_hint, 0));
842}
843
844TEST_F(DictKeyIteratorBuiltinsTest,
845 DunderLengthHintOnConsumedDictKeyIteratorReturnsZero) {
846 HandleScope scope(thread_);
847 Dict dict(&scope, runtime_->newDict());
848 Str hello(&scope, runtime_->newStrFromCStr("hello"));
849 Object world(&scope, runtime_->newStrFromCStr("world"));
850 dictAtPutByStr(thread_, dict, hello, world);
851 DictKeyIterator iter(&scope, runtime_->newDictKeyIterator(thread_, dict));
852
853 Object item1(&scope, runBuiltin(METH(dict_keyiterator, __next__), iter));
854 ASSERT_FALSE(item1.isError());
855
856 Object length_hint(&scope,
857 runBuiltin(METH(dict_keyiterator, __length_hint__), iter));
858 EXPECT_TRUE(isIntEqualsWord(*length_hint, 0));
859}
860
861TEST_F(DictValueIteratorBuiltinsTest,
862 DunderLengthHintOnConsumedDictValueIteratorReturnsZero) {
863 HandleScope scope(thread_);
864 Dict dict(&scope, runtime_->newDict());
865 Str hello(&scope, runtime_->newStrFromCStr("hello"));
866 Object world(&scope, runtime_->newStrFromCStr("world"));
867 dictAtPutByStr(thread_, dict, hello, world);
868 DictValueIterator iter(&scope, runtime_->newDictValueIterator(thread_, dict));
869
870 Object item1(&scope, runBuiltin(METH(dict_valueiterator, __next__), iter));
871 ASSERT_FALSE(item1.isError());
872
873 Object length_hint(
874 &scope, runBuiltin(METH(dict_valueiterator, __length_hint__), iter));
875 EXPECT_TRUE(isIntEqualsWord(*length_hint, 0));
876}
877
878TEST_F(DictBuiltinsTest, ItemIteratorNextOnOneElementDictReturnsElement) {
879 HandleScope scope(thread_);
880 Dict dict(&scope, runtime_->newDict());
881 Str key(&scope, runtime_->newStrFromCStr("hello"));
882 Object value(&scope, runtime_->newStrFromCStr("world"));
883 dictAtPutByStr(thread_, dict, key, value);
884 DictItemIterator iter(&scope, runtime_->newDictItemIterator(thread_, dict));
885 Object next(&scope, dictItemIteratorNext(thread_, iter));
886 ASSERT_TRUE(next.isTuple());
887 EXPECT_EQ(Tuple::cast(*next).at(0), key);
888 EXPECT_EQ(Tuple::cast(*next).at(1), value);
889
890 next = dictItemIteratorNext(thread_, iter);
891 ASSERT_TRUE(next.isError());
892}
893
894TEST_F(DictBuiltinsTest, KeyIteratorNextOnOneElementDictReturnsElement) {
895 HandleScope scope(thread_);
896 Dict dict(&scope, runtime_->newDict());
897 Str key(&scope, runtime_->newStrFromCStr("hello"));
898 Object value(&scope, runtime_->newStrFromCStr("world"));
899 dictAtPutByStr(thread_, dict, key, value);
900 DictKeyIterator iter(&scope, runtime_->newDictKeyIterator(thread_, dict));
901 Object next(&scope, dictKeyIteratorNext(thread_, iter));
902 EXPECT_EQ(next, key);
903
904 next = dictKeyIteratorNext(thread_, iter);
905 ASSERT_TRUE(next.isError());
906}
907
908TEST_F(DictBuiltinsTest, ValueIteratorNextOnOneElementDictReturnsElement) {
909 HandleScope scope(thread_);
910 Dict dict(&scope, runtime_->newDict());
911 Str key(&scope, runtime_->newStrFromCStr("hello"));
912 Object value(&scope, runtime_->newStrFromCStr("world"));
913 dictAtPutByStr(thread_, dict, key, value);
914 DictValueIterator iter(&scope, runtime_->newDictValueIterator(thread_, dict));
915 Object next(&scope, dictValueIteratorNext(thread_, iter));
916 EXPECT_EQ(next, value);
917
918 next = dictValueIteratorNext(thread_, iter);
919 ASSERT_TRUE(next.isError());
920}
921
922TEST_F(DictBuiltinsTest, NextOnDictWithOnlyTombstonesReturnsFalse) {
923 HandleScope scope(thread_);
924 Dict dict(&scope, runtime_->newDict());
925 Str key(&scope, runtime_->newStrFromCStr("hello"));
926 Object value(&scope, runtime_->newStrFromCStr("world"));
927 dictAtPutByStr(thread_, dict, key, value);
928 ASSERT_FALSE(dictRemoveByStr(thread_, dict, key).isError());
929
930 word i = 0;
931 Object dict_key(&scope, NoneType::object());
932 Object dict_value(&scope, NoneType::object());
933 EXPECT_FALSE(dictNextItem(dict, &i, &dict_key, &dict_value));
934}
935
936TEST_F(DictBuiltinsTest, RecursiveDictPrintsEllipsis) {
937 ASSERT_FALSE(runFromCStr(runtime_, R"(
938class C:
939 def __init__(self, obj):
940 self.val = obj
941 def __repr__(self):
942 return self.val.__repr__()
943 def __hash__(self):
944 return 5
945
946d = dict()
947c = C(d)
948d['hello'] = c
949result = d.__repr__()
950)")
951 .isError());
952 EXPECT_TRUE(
953 isStrEqualsCStr(mainModuleAt(runtime_, "result"), "{'hello': {...}}"));
954}
955
956TEST_F(DictBuiltinsTest, PopWithKeyPresentReturnsValue) {
957 ASSERT_FALSE(runFromCStr(runtime_, R"(
958d = {"hello": "world"}
959result = d.pop("hello")
960)")
961 .isError());
962 HandleScope scope(thread_);
963 EXPECT_TRUE(isStrEqualsCStr(mainModuleAt(runtime_, "result"), "world"));
964 Dict dict(&scope, mainModuleAt(runtime_, "d"));
965 EXPECT_EQ(dict.numItems(), 0);
966 EXPECT_EQ(dict.firstEmptyItemIndex() / kItemNumPointers, 1);
967}
968
969TEST_F(DictBuiltinsTest, PopWithMissingKeyAndDefaultReturnsDefault) {
970 ASSERT_FALSE(runFromCStr(runtime_, R"(
971d = {}
972result = d.pop("hello", "world")
973)")
974 .isError());
975 HandleScope scope(thread_);
976 Dict dict(&scope, mainModuleAt(runtime_, "d"));
977 EXPECT_EQ(dict.numItems(), 0);
978 EXPECT_EQ(dict.firstEmptyItemIndex() / kItemNumPointers, 0);
979 EXPECT_TRUE(isStrEqualsCStr(mainModuleAt(runtime_, "result"), "world"));
980}
981
982TEST_F(DictBuiltinsTest, PopitemAfterInsert) {
983 HandleScope scope(thread_);
984 Dict dict(&scope, runtime_->newDict());
985
986 Object key(&scope, SmallInt::fromWord(0));
987 Object key1(&scope, SmallInt::fromWord(1));
988 word hash = intHash(*key);
989 word hash1 = intHash(*key1);
990 ASSERT_TRUE(dictAtPut(thread_, dict, key, hash, key).isNoneType());
991 ASSERT_TRUE(dictAtPut(thread_, dict, key1, hash1, key1).isNoneType());
992
993 for (int i = 0; i < 2; i++) {
994 runBuiltin(METH(dict, popitem), dict);
995 }
996 ASSERT_EQ(dict.numItems(), 0);
997}
998
999TEST_F(DictBuiltinsTest, PopWithMisingKeyRaisesKeyError) {
1000 EXPECT_TRUE(
1001 raised(runFromCStr(runtime_, "{}.pop('hello')"), LayoutId::kKeyError));
1002}
1003
1004TEST_F(DictBuiltinsTest, PopWithSubclassDoesNotCallDunderDelitem) {
1005 ASSERT_FALSE(runFromCStr(runtime_, R"(
1006class C(dict):
1007 def __delitem__(self, key):
1008 raise Exception(key)
1009c = C({'hello': 'world'})
1010result = c.pop('hello')
1011)")
1012 .isError());
1013 ASSERT_FALSE(thread_->hasPendingException());
1014 HandleScope scope(thread_);
1015 Dict dict(&scope, mainModuleAt(runtime_, "c"));
1016 EXPECT_EQ(dict.numItems(), 0);
1017 EXPECT_EQ(dict.firstEmptyItemIndex() / kItemNumPointers, 1);
1018 EXPECT_TRUE(isStrEqualsCStr(mainModuleAt(runtime_, "result"), "world"));
1019}
1020
1021TEST_F(DictBuiltinsTest, DictInitWithSubclassInitializesElements) {
1022 ASSERT_FALSE(runFromCStr(runtime_, R"(
1023class C(dict):
1024 pass
1025c = C({'hello': 'world'})
1026)")
1027 .isError());
1028 HandleScope scope(thread_);
1029 Dict dict(&scope, mainModuleAt(runtime_, "c"));
1030 EXPECT_EQ(dict.numItems(), 1);
1031}
1032
1033TEST_F(DictBuiltinsTest, SetDefaultWithNoDefaultSetsToNone) {
1034 ASSERT_FALSE(runFromCStr(runtime_, R"(
1035d = {}
1036d.setdefault("hello")
1037result = d["hello"]
1038)")
1039 .isError());
1040 EXPECT_EQ(mainModuleAt(runtime_, "result"), NoneType::object());
1041}
1042
1043TEST_F(DictBuiltinsTest, SetDefaultWithNotKeyInDictSetsDefault) {
1044 ASSERT_FALSE(runFromCStr(runtime_, R"(
1045d = {}
1046d.setdefault("hello", 4)
1047result = d["hello"]
1048)")
1049 .isError());
1050 EXPECT_TRUE(isIntEqualsWord(mainModuleAt(runtime_, "result"), 4));
1051}
1052
1053TEST_F(DictBuiltinsTest, SetDefaultWithKeyInDictReturnsValue) {
1054 ASSERT_FALSE(runFromCStr(runtime_, R"(
1055d = {"hello": 5}
1056d.setdefault("hello", 4)
1057result = d["hello"]
1058)")
1059 .isError());
1060 EXPECT_TRUE(isIntEqualsWord(mainModuleAt(runtime_, "result"), 5));
1061}
1062
1063TEST_F(DictBuiltinsTest, NumAttributesMatchesObjectSize) {
1064 HandleScope scope(thread_);
1065 Layout layout(&scope, runtime_->layoutAt(LayoutId::kDict));
1066 EXPECT_EQ(layout.numInObjectAttributes(),
1067 (RawDict::kSize - RawHeapObject::kSize) / kPointerSize);
1068}
1069
1070} // namespace testing
1071} // namespace py