runtime/dict-builtins-test.cpp (886 lines of code) (raw):

// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) #include "dict-builtins.h" #include "gtest/gtest.h" #include "builtins-module.h" #include "builtins.h" #include "int-builtins.h" #include "runtime.h" #include "str-builtins.h" #include "test-utils.h" namespace py { namespace testing { using DictBuiltinsTest = RuntimeFixture; using DictItemIteratorBuiltinsTest = RuntimeFixture; using DictItemsBuiltinsTest = RuntimeFixture; using DictKeyIteratorBuiltinsTest = RuntimeFixture; using DictKeysBuiltinsTest = RuntimeFixture; using DictValueIteratorBuiltinsTest = RuntimeFixture; using DictValuesBuiltinsTest = RuntimeFixture; TEST_F(DictBuiltinsTest, EmptyDictInvariants) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDict()); EXPECT_EQ(dict.numItems(), 0); ASSERT_TRUE(isIntEqualsWord(dict.data(), 0)); } TEST_F(DictBuiltinsTest, DictAtPutRetainsExistingKeyObject) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDict()); Str key0(&scope, runtime_->newStrFromCStr("foobarbazbam")); word key0_hash = strHash(thread_, *key0); Object value0(&scope, SmallInt::fromWord(123)); Str key1(&scope, runtime_->newStrFromCStr("foobarbazbam")); word key1_hash = strHash(thread_, *key1); Object value1(&scope, SmallInt::fromWord(456)); ASSERT_NE(key0, key1); ASSERT_EQ(key0_hash, key1_hash); ASSERT_TRUE(dictAtPut(thread_, dict, key0, key0_hash, value0).isNoneType()); ASSERT_EQ(dict.numItems(), 1); ASSERT_EQ(dictAt(thread_, dict, key0, key0_hash), *value0); // Overwrite the stored value ASSERT_TRUE(dictAtPut(thread_, dict, key1, key1_hash, value1).isNoneType()); ASSERT_EQ(dict.numItems(), 1); ASSERT_EQ(dictAt(thread_, dict, key1, key1_hash), *value1); word i = 0; Object key(&scope, NoneType::object()); ASSERT_TRUE(dictNextKey(dict, &i, &key)); EXPECT_EQ(key, key0); } TEST_F(DictBuiltinsTest, GetSet) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDict()); Object key(&scope, SmallInt::fromWord(12345)); word hash = intHash(*key); // Looking up a key that doesn't exist should fail EXPECT_TRUE(dictAt(thread_, dict, key, hash).isError()); // Store a value Object stored(&scope, SmallInt::fromWord(67890)); ASSERT_TRUE(dictAtPut(thread_, dict, key, hash, stored).isNoneType()); EXPECT_EQ(dict.numItems(), 1); // Retrieve the stored value RawObject retrieved = dictAt(thread_, dict, key, hash); EXPECT_EQ(retrieved, *stored); // Overwrite the stored value Object new_value(&scope, SmallInt::fromWord(5555)); ASSERT_TRUE(dictAtPut(thread_, dict, key, hash, new_value).isNoneType()); EXPECT_EQ(dict.numItems(), 1); // Get the new value retrieved = dictAt(thread_, dict, key, hash); EXPECT_EQ(retrieved, *new_value); } TEST_F(DictBuiltinsTest, Remove) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDict()); Object key(&scope, SmallInt::fromWord(12345)); word hash = intHash(*key); // Removing a key that doesn't exist should fail bool is_missing = dictRemove(thread_, dict, key, hash).isError(); EXPECT_TRUE(is_missing); // Removing a key that exists should succeed and return the value that was // stored. Object stored(&scope, SmallInt::fromWord(54321)); ASSERT_TRUE(dictAtPut(thread_, dict, key, hash, stored).isNoneType()); EXPECT_EQ(dict.numItems(), 1); RawObject retrieved = dictRemove(thread_, dict, key, hash); ASSERT_FALSE(retrieved.isError()); ASSERT_EQ(SmallInt::cast(retrieved).value(), SmallInt::cast(*stored).value()); // Looking up a key that was deleted should fail EXPECT_TRUE(dictAt(thread_, dict, key, hash).isError()); EXPECT_EQ(dict.numItems(), 0); } TEST_F(DictBuiltinsTest, Length) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDict()); // Add 10 items and make sure length reflects it for (int i = 0; i < 10; i++) { Object key(&scope, SmallInt::fromWord(i)); word hash = intHash(*key); ASSERT_TRUE(dictAtPut(thread_, dict, key, hash, key).isNoneType()); } EXPECT_EQ(dict.numItems(), 10); // Remove half the items for (int i = 0; i < 5; i++) { Object key(&scope, SmallInt::fromWord(i)); word hash = intHash(*key); ASSERT_FALSE(dictRemove(thread_, dict, key, hash).isError()); } EXPECT_EQ(dict.numItems(), 5); } TEST_F(DictBuiltinsTest, DictAtPutInValueCellByStrCreatesValueCell) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDict()); Object name(&scope, Runtime::internStrFromCStr(thread_, "foo")); Object value(&scope, Runtime::internStrFromCStr(thread_, "bar")); Object result(&scope, dictAtPutInValueCellByStr(thread_, dict, name, value)); ASSERT_TRUE(result.isValueCell()); EXPECT_EQ(ValueCell::cast(*result).value(), value); EXPECT_EQ(dictAtByStr(thread_, dict, name), result); } TEST_F(DictBuiltinsTest, DictAtPutInValueCellByStrReusesExistingValueCell) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDict()); Object name(&scope, Runtime::internStrFromCStr(thread_, "foo")); Object value0(&scope, Runtime::internStrFromCStr(thread_, "bar")); Object result0(&scope, dictAtPutInValueCellByStr(thread_, dict, name, value0)); ASSERT_TRUE(result0.isValueCell()); EXPECT_EQ(ValueCell::cast(*result0).value(), value0); Object value1(&scope, Runtime::internStrFromCStr(thread_, "baz")); Object result1(&scope, dictAtPutInValueCellByStr(thread_, dict, name, value1)); EXPECT_EQ(result0, result1); EXPECT_EQ(dictAtByStr(thread_, dict, name), result1); EXPECT_EQ(ValueCell::cast(*result1).value(), value1); } // Should be synced with dict-builtins.cpp. static const word kInitialDictIndicesLength = 8; static const word kItemNumPointers = 3; TEST_F(DictBuiltinsTest, DictAtPutGrowsDictWhenDictIsEmpty) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDict()); EXPECT_EQ(dict.numIndices(), 0); Object first_key(&scope, SmallInt::fromWord(0)); word hash = intHash(*first_key); Object first_value(&scope, SmallInt::fromWord(1)); ASSERT_TRUE( dictAtPut(thread_, dict, first_key, hash, first_value).isNoneType()); word initial_capacity = kInitialDictIndicesLength; EXPECT_EQ(dict.numItems(), 1); EXPECT_EQ(dict.numIndices(), initial_capacity); } TEST_F(DictBuiltinsTest, DictAtPutGrowsDictWhenTwoThirdsUsed) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDict()); // Fill in one fewer keys than would require growing the underlying object // array again. word threshold = ((kInitialDictIndicesLength * 2) / 3) - 1; for (word i = 0; i < threshold; i++) { Object key(&scope, SmallInt::fromWord(i)); word hash = intHash(*key); Object value(&scope, SmallInt::fromWord(-i)); ASSERT_TRUE(dictAtPut(thread_, dict, key, hash, value).isNoneType()); } EXPECT_EQ(dict.numItems(), threshold); EXPECT_EQ(dict.firstEmptyItemIndex() / kItemNumPointers, threshold); word initial_capacity = kInitialDictIndicesLength; EXPECT_EQ(dict.numIndices(), initial_capacity); // Add another key which should force us to double the capacity Object last_key(&scope, SmallInt::fromWord(threshold)); word last_key_hash = intHash(*last_key); Object last_value(&scope, SmallInt::fromWord(-threshold)); ASSERT_TRUE(dictAtPut(thread_, dict, last_key, last_key_hash, last_value) .isNoneType()); EXPECT_EQ(dict.numItems(), threshold + 1); // 2 == kDictGrowthFactor. EXPECT_EQ(dict.numIndices(), initial_capacity * 2); EXPECT_EQ(dict.firstEmptyItemIndex() / kItemNumPointers, threshold + 1); // Make sure we can still read all the stored keys/values. for (word i = 0; i <= threshold; i++) { Object key(&scope, SmallInt::fromWord(i)); word hash = intHash(*key); RawObject value = dictAt(thread_, dict, key, hash); ASSERT_FALSE(value.isError()); EXPECT_TRUE(isIntEqualsWord(value, -i)); } } TEST_F(DictBuiltinsTest, CollidingKeys) { HandleScope scope(thread_); ASSERT_FALSE(runFromCStr(runtime_, R"( class C: def __eq__(self, other): return self is other def __hash__(self): return 0 i0 = C() i1 = C() )") .isError()); Object i0(&scope, mainModuleAt(runtime_, "i0")); Object i0_hash_obj(&scope, Interpreter::hash(thread_, i0)); ASSERT_FALSE(i0_hash_obj.isErrorException()); word i0_hash = SmallInt::cast(*i0_hash_obj).value(); Object i1(&scope, mainModuleAt(runtime_, "i1")); Object i1_hash_obj(&scope, Interpreter::hash(thread_, i1)); ASSERT_FALSE(i1_hash_obj.isErrorException()); word i1_hash = SmallInt::cast(*i1_hash_obj).value(); ASSERT_EQ(i0_hash, i1_hash); Dict dict(&scope, runtime_->newDict()); // Add two different keys with different values using the same hash ASSERT_TRUE(dictAtPut(thread_, dict, i0, i0_hash, i0).isNoneType()); ASSERT_TRUE(dictAtPut(thread_, dict, i1, i1_hash, i1).isNoneType()); // Make sure we get both back Object retrieved(&scope, dictAt(thread_, dict, i0, i0_hash)); EXPECT_EQ(retrieved, i0); retrieved = dictAt(thread_, dict, i1, i1_hash); EXPECT_EQ(retrieved, i1); } TEST_F(DictBuiltinsTest, MixedKeys) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDict()); // Add keys of different type Object int_key(&scope, SmallInt::fromWord(100)); word int_key_hash = intHash(*int_key); ASSERT_TRUE( dictAtPut(thread_, dict, int_key, int_key_hash, int_key).isNoneType()); Object str_key(&scope, runtime_->newStrFromCStr("testing 123")); word str_key_hash = strHash(thread_, *str_key); ASSERT_TRUE( dictAtPut(thread_, dict, str_key, str_key_hash, str_key).isNoneType()); // Make sure we get the appropriate values back out RawObject retrieved = dictAt(thread_, dict, int_key, int_key_hash); EXPECT_EQ(retrieved, *int_key); retrieved = dictAt(thread_, dict, str_key, str_key_hash); ASSERT_TRUE(retrieved.isStr()); EXPECT_EQ(*str_key, retrieved); } TEST_F(DictBuiltinsTest, GetKeys) { HandleScope scope(thread_); // Create keys Object obj1(&scope, SmallInt::fromWord(100)); Object obj2(&scope, runtime_->newStrFromCStr("testing 123")); Object obj3(&scope, Bool::trueObj()); Object obj4(&scope, NoneType::object()); Tuple keys(&scope, runtime_->newTupleWith4(obj1, obj2, obj3, obj4)); // Add keys to dict Dict dict(&scope, runtime_->newDict()); for (word i = 0; i < keys.length(); i++) { Object key(&scope, keys.at(i)); Object hash_obj(&scope, Interpreter::hash(thread_, key)); ASSERT_FALSE(hash_obj.isErrorException()); word hash = SmallInt::cast(*hash_obj).value(); ASSERT_TRUE(dictAtPut(thread_, dict, key, hash, key).isNoneType()); } // Grab the keys and verify everything is there List retrieved(&scope, dictKeys(thread_, dict)); ASSERT_EQ(retrieved.numItems(), keys.length()); for (word i = 0; i < keys.length(); i++) { Object key(&scope, keys.at(i)); EXPECT_TRUE(listContains(retrieved, key)) << " missing key " << i; } } TEST_F(DictBuiltinsTest, CanCreateDictItems) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDict()); RawObject iter = runtime_->newDictItemIterator(thread_, dict); ASSERT_TRUE(iter.isDictItemIterator()); } TEST_F(DictBuiltinsTest, DictAtGrowsToInitialCapacity) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDict()); EXPECT_EQ(dict.numIndices(), 0); Object key(&scope, runtime_->newInt(123)); word hash = intHash(*key); Object value(&scope, runtime_->newInt(456)); ASSERT_TRUE(dictAtPut(thread_, dict, key, hash, value).isNoneType()); int expected = kInitialDictIndicesLength; EXPECT_EQ(dict.numIndices(), expected); } TEST_F(DictBuiltinsTest, ClearWithEmptyDictIsNoop) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDict()); EXPECT_EQ(runBuiltin(METH(dict, clear), dict), NoneType::object()); } TEST_F(DictBuiltinsTest, ClearWithNonEmptyDictRemovesAllElements) { ASSERT_FALSE(runFromCStr(runtime_, R"( class C: pass d = {'a': C()} )") .isError()); HandleScope scope(thread_); Dict dict(&scope, mainModuleAt(runtime_, "d")); Object ref_obj(&scope, NoneType::object()); { Str key(&scope, runtime_->newStrFromCStr("a")); Object c(&scope, dictAtByStr(thread_, dict, key)); ref_obj = runtime_->newWeakRef(thread_, c); } WeakRef ref(&scope, *ref_obj); EXPECT_NE(ref.referent(), NoneType::object()); runBuiltin(METH(dict, clear), dict); runtime_->collectGarbage(); EXPECT_EQ(ref.referent(), NoneType::object()); } TEST_F(DictBuiltinsTest, CopyWithDictReturnsNewInstance) { ASSERT_FALSE(runFromCStr(runtime_, R"( d = {'a': 3} result = dict.copy(d) )") .isError()); HandleScope scope(thread_); Object dict(&scope, mainModuleAt(runtime_, "d")); EXPECT_TRUE(dict.isDict()); Object result_obj(&scope, mainModuleAt(runtime_, "result")); EXPECT_TRUE(result_obj.isDict()); Dict result(&scope, *result_obj); EXPECT_NE(*dict, *result); EXPECT_EQ(result.numItems(), 1); EXPECT_EQ(result.firstEmptyItemIndex() / kItemNumPointers, 1); } TEST_F(DictBuiltinsTest, DunderContainsWithExistingKeyReturnsTrue) { ASSERT_FALSE(runFromCStr(runtime_, "result = {'foo': 0}.__contains__('foo')") .isError()); HandleScope scope(thread_); Object result(&scope, mainModuleAt(runtime_, "result")); ASSERT_TRUE(result.isBool()); EXPECT_TRUE(Bool::cast(*result).value()); } TEST_F(DictBuiltinsTest, DunderContainsWithNonexistentKeyReturnsFalse) { ASSERT_FALSE( runFromCStr(runtime_, "result = {}.__contains__('foo')").isError()); HandleScope scope(thread_); Object result(&scope, mainModuleAt(runtime_, "result")); ASSERT_TRUE(result.isBool()); EXPECT_FALSE(Bool::cast(*result).value()); } TEST_F(DictBuiltinsTest, DunderContainsWithUnhashableTypeRaisesTypeError) { ASSERT_FALSE(runFromCStr(runtime_, R"( class C: __hash__ = None c = C() )") .isError()); EXPECT_TRUE(raised(runFromCStr(runtime_, "{}.__contains__(C())"), LayoutId::kTypeError)); } TEST_F(DictBuiltinsTest, DunderContainsWithNonCallableDunderHashRaisesTypeError) { ASSERT_FALSE(runFromCStr(runtime_, R"( class C: __hash__ = 4 )") .isError()); EXPECT_TRUE(raised(runFromCStr(runtime_, "{}.__contains__(C())"), LayoutId::kTypeError)); } TEST_F(DictBuiltinsTest, DunderContainsWithTypeWithDunderHashReturningNonIntRaisesTypeError) { ASSERT_FALSE(runFromCStr(runtime_, R"( class C: def __hash__(self): return "boo" )") .isError()); EXPECT_TRUE(raised(runFromCStr(runtime_, "{}.__contains__(C())"), LayoutId::kTypeError)); } TEST_F(DictBuiltinsTest, InWithExistingKeyReturnsTrue) { ASSERT_FALSE(runFromCStr(runtime_, R"( d = {"foo": 1} foo_in_d = "foo" in d )") .isError()); HandleScope scope(thread_); Bool foo_in_d(&scope, mainModuleAt(runtime_, "foo_in_d")); EXPECT_TRUE(foo_in_d.value()); } TEST_F(DictBuiltinsTest, InWithNonexistentKeyReturnsFalse) { ASSERT_FALSE(runFromCStr(runtime_, R"( d = {} foo_in_d = "foo" in d )") .isError()); HandleScope scope(thread_); Bool foo_in_d(&scope, mainModuleAt(runtime_, "foo_in_d")); EXPECT_FALSE(foo_in_d.value()); } TEST_F(DictBuiltinsTest, DunderDelitemOnExistingKeyReturnsNone) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDictWithSize(1)); Str key(&scope, runtime_->newStrFromCStr("foo")); Object val(&scope, runtime_->newInt(0)); dictAtPutByStr(thread_, dict, key, val); RawObject result = runBuiltin(METH(dict, __delitem__), dict, key); EXPECT_TRUE(result.isNoneType()); } TEST_F(DictBuiltinsTest, DunderDelitemOnNonexistentKeyRaisesKeyError) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDictWithSize(1)); Str key(&scope, runtime_->newStrFromCStr("foo")); Object val(&scope, runtime_->newInt(0)); dictAtPutByStr(thread_, dict, key, val); // "bar" doesn't exist in this dictionary, attempting to delete it should // cause a KeyError. Object key2(&scope, runtime_->newStrFromCStr("bar")); RawObject result = runBuiltin(METH(dict, __delitem__), dict, key2); ASSERT_TRUE(result.isError()); } TEST_F(DictBuiltinsTest, DelOnObjectHashReturningNonIntRaisesTypeError) { EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"( class E: def __hash__(self): return "non int" d = {} del d[E()] )"), LayoutId::kTypeError, "__hash__ method should return an integer")); } TEST_F(DictBuiltinsTest, DelOnExistingKeyDeletesKey) { ASSERT_FALSE(runFromCStr(runtime_, R"( d = {"foo": 1} del d["foo"] )") .isError()); HandleScope scope(thread_); Dict d(&scope, mainModuleAt(runtime_, "d")); Str foo(&scope, runtime_->newStrFromCStr("foo")); EXPECT_EQ(dictIncludes(thread_, d, foo, strHash(thread_, *foo)), Bool::falseObj()); } TEST_F(DictBuiltinsTest, DelOnNonexistentKeyRaisesKeyError) { EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"( d = {} del d["foo"] )"), LayoutId::kKeyError, "foo")); } TEST_F(DictBuiltinsTest, NonTypeInDunderNew) { EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"( dict.__new__(1) )"), LayoutId::kTypeError, "not a type object")); } TEST_F(DictBuiltinsTest, NonSubclassInDunderNew) { EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"( class Foo: pass dict.__new__(Foo) )"), LayoutId::kTypeError, "not a subtype of dict")); } TEST_F(DictBuiltinsTest, DunderNewConstructsDict) { HandleScope scope(thread_); Type type(&scope, runtime_->typeAt(LayoutId::kDict)); Object result(&scope, runBuiltin(METH(dict, __new__), type)); ASSERT_TRUE(result.isDict()); } TEST_F(DictBuiltinsTest, DunderIterReturnsDictKeyIter) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDict()); Object iter(&scope, runBuiltin(METH(dict, __iter__), dict)); ASSERT_TRUE(iter.isDictKeyIterator()); } TEST_F(DictBuiltinsTest, DunderItemsReturnsDictItems) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDict()); Object items(&scope, runBuiltin(METH(dict, items), dict)); ASSERT_TRUE(items.isDictItems()); } TEST_F(DictBuiltinsTest, KeysReturnsDictKeys) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDict()); Object keys(&scope, runBuiltin(METH(dict, keys), dict)); ASSERT_TRUE(keys.isDictKeys()); } TEST_F(DictBuiltinsTest, ValuesReturnsDictValues) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDict()); Object values(&scope, runBuiltin(METH(dict, values), dict)); ASSERT_TRUE(values.isDictValues()); } TEST_F(DictBuiltinsTest, UpdateWithNoArgumentsRaisesTypeError) { EXPECT_TRUE(raisedWithStr( runFromCStr(runtime_, "dict.update()"), LayoutId::kTypeError, "'dict.update' takes min 1 positional arguments but 0 given")); } TEST_F(DictBuiltinsTest, UpdateWithNonDictRaisesTypeError) { EXPECT_TRUE(raised(runFromCStr(runtime_, "dict.update([], None)"), LayoutId::kTypeError)); } TEST_F(DictBuiltinsTest, UpdateWithNonMappingTypeRaisesTypeError) { EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, "dict.update({}, 1)"), LayoutId::kTypeError, "'int' object is not iterable")); } TEST_F(DictBuiltinsTest, UpdateWithListContainerWithObjectHashReturningNonIntRaisesTypeError) { EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"( class E: def __hash__(self): return "non int" class C: def __init__(self): self.item = E() def __getitem__(self, idx): return self.item def keys(self): return [self.item] dict.update({1:4}, C()) )"), LayoutId::kTypeError, "__hash__ method should return an integer")); } TEST_F(DictBuiltinsTest, UpdateWithTupleContainerWithObjectHashReturningNonIntRaisesTypeError) { EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"( class E: def __hash__(self): return "non int" class C: def __init__(self): self.item = E() def __getitem__(self, idx): return self.item def keys(self): return (self.item,) dict.update({1:4}, C()) )"), LayoutId::kTypeError, "__hash__ method should return an integer")); } TEST_F(DictBuiltinsTest, UpdateWithIterContainerWithObjectHashReturningNonIntRaisesTypeError) { EXPECT_TRUE(raisedWithStr(runFromCStr(runtime_, R"( class E: def __hash__(self): return "non int" class C: def __init__(self): self.item = E() def __getitem__(self, idx): return self.item def keys(self): return iter([self.item]) dict.update({1:4}, C()) )"), LayoutId::kTypeError, "__hash__ method should return an integer")); } TEST_F(DictBuiltinsTest, UpdateWithDictReturnsUpdatedDict) { ASSERT_FALSE(runFromCStr(runtime_, R"( d1 = {"a": 1, "b": 2} d2 = {"c": 3, "d": 4} d3 = {"a": 123} )") .isError()); HandleScope scope(thread_); Dict d1(&scope, mainModuleAt(runtime_, "d1")); Dict d2(&scope, mainModuleAt(runtime_, "d2")); ASSERT_EQ(d1.numItems(), 2); EXPECT_EQ(d1.firstEmptyItemIndex() / kItemNumPointers, 2); ASSERT_EQ(d2.numItems(), 2); EXPECT_EQ(d2.firstEmptyItemIndex() / kItemNumPointers, 2); ASSERT_TRUE(runFromCStr(runtime_, "d1.update(d2)").isNoneType()); EXPECT_EQ(d1.numItems(), 4); EXPECT_EQ(d1.firstEmptyItemIndex() / kItemNumPointers, 4); EXPECT_EQ(d2.numItems(), 2); EXPECT_EQ(d2.firstEmptyItemIndex() / kItemNumPointers, 2); ASSERT_TRUE(runFromCStr(runtime_, "d1.update(d3)").isNoneType()); EXPECT_EQ(d1.numItems(), 4); EXPECT_EQ(d1.firstEmptyItemIndex() / kItemNumPointers, 4); Str a(&scope, runtime_->newStrFromCStr("a")); Object a_val(&scope, dictAtByStr(thread_, d1, a)); EXPECT_TRUE(isIntEqualsWord(*a_val, 123)); } TEST_F(DictItemsBuiltinsTest, DunderIterReturnsIter) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDict()); DictItems items(&scope, runtime_->newDictItems(thread_, dict)); Object iter(&scope, runBuiltin(METH(dict_items, __iter__), items)); ASSERT_TRUE(iter.isDictItemIterator()); } TEST_F(DictKeysBuiltinsTest, DunderIterReturnsIter) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDict()); DictKeys keys(&scope, runtime_->newDictKeys(thread_, dict)); Object iter(&scope, runBuiltin(METH(dict_keys, __iter__), keys)); ASSERT_TRUE(iter.isDictKeyIterator()); } TEST_F(DictValuesBuiltinsTest, DunderIterReturnsIter) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDict()); DictValues values(&scope, runtime_->newDictValues(thread_, dict)); Object iter(&scope, runBuiltin(METH(dict_values, __iter__), values)); ASSERT_TRUE(iter.isDictValueIterator()); } TEST_F(DictItemIteratorBuiltinsTest, CallDunderIterReturnsSelf) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDict()); DictItemIterator iter(&scope, runtime_->newDictItemIterator(thread_, dict)); // Now call __iter__ on the iterator object Object result(&scope, runBuiltin(METH(dict_itemiterator, __iter__), iter)); ASSERT_EQ(*result, *iter); } TEST_F(DictKeyIteratorBuiltinsTest, CallDunderIterReturnsSelf) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDict()); DictKeyIterator iter(&scope, runtime_->newDictKeyIterator(thread_, dict)); // Now call __iter__ on the iterator object Object result(&scope, runBuiltin(METH(dict_keyiterator, __iter__), iter)); ASSERT_EQ(*result, *iter); } TEST_F(DictValueIteratorBuiltinsTest, CallDunderIterReturnsSelf) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDict()); DictValueIterator iter(&scope, runtime_->newDictValueIterator(thread_, dict)); // Now call __iter__ on the iterator object Object result(&scope, runBuiltin(METH(dict_valueiterator, __iter__), iter)); ASSERT_EQ(*result, *iter); } TEST_F(DictItemIteratorBuiltinsTest, DunderLengthHintOnEmptyDictItemIteratorReturnsZero) { HandleScope scope(thread_); Dict empty_dict(&scope, runtime_->newDict()); DictItemIterator iter(&scope, runtime_->newDictItemIterator(thread_, empty_dict)); Object length_hint( &scope, runBuiltin(METH(dict_itemiterator, __length_hint__), iter)); EXPECT_TRUE(isIntEqualsWord(*length_hint, 0)); } TEST_F(DictKeyIteratorBuiltinsTest, DunderLengthHintOnEmptyDictKeyIteratorReturnsZero) { HandleScope scope(thread_); Dict empty_dict(&scope, runtime_->newDict()); DictKeyIterator iter(&scope, runtime_->newDictKeyIterator(thread_, empty_dict)); Object length_hint(&scope, runBuiltin(METH(dict_keyiterator, __length_hint__), iter)); EXPECT_TRUE(isIntEqualsWord(*length_hint, 0)); } TEST_F(DictValueIteratorBuiltinsTest, DunderLengthHintOnEmptyDictValueIteratorReturnsZero) { HandleScope scope(thread_); Dict empty_dict(&scope, runtime_->newDict()); DictValueIterator iter(&scope, runtime_->newDictValueIterator(thread_, empty_dict)); Object length_hint( &scope, runBuiltin(METH(dict_valueiterator, __length_hint__), iter)); EXPECT_TRUE(isIntEqualsWord(*length_hint, 0)); } TEST_F(DictItemIteratorBuiltinsTest, CallDunderNextReadsItemsSequentially) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDictWithSize(5)); Str hello(&scope, runtime_->newStrFromCStr("hello")); Object world(&scope, runtime_->newStrFromCStr("world")); Str goodbye(&scope, runtime_->newStrFromCStr("goodbye")); Object moon(&scope, runtime_->newStrFromCStr("moon")); dictAtPutByStr(thread_, dict, hello, world); dictAtPutByStr(thread_, dict, goodbye, moon); DictItemIterator iter(&scope, runtime_->newDictItemIterator(thread_, dict)); Object item1(&scope, runBuiltin(METH(dict_itemiterator, __next__), iter)); ASSERT_TRUE(item1.isTuple()); EXPECT_EQ(Tuple::cast(*item1).at(0), hello); EXPECT_EQ(Tuple::cast(*item1).at(1), world); Object item2(&scope, runBuiltin(METH(dict_itemiterator, __next__), iter)); ASSERT_TRUE(item2.isTuple()); EXPECT_EQ(Tuple::cast(*item2).at(0), goodbye); EXPECT_EQ(Tuple::cast(*item2).at(1), moon); Object item3(&scope, runBuiltin(METH(dict_itemiterator, __next__), iter)); ASSERT_TRUE(item3.isError()); } TEST_F(DictKeyIteratorBuiltinsTest, CallDunderNextReadsKeysSequentially) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDictWithSize(5)); Str hello(&scope, runtime_->newStrFromCStr("hello")); Object world(&scope, runtime_->newStrFromCStr("world")); Str goodbye(&scope, runtime_->newStrFromCStr("goodbye")); Object moon(&scope, runtime_->newStrFromCStr("moon")); dictAtPutByStr(thread_, dict, hello, world); dictAtPutByStr(thread_, dict, goodbye, moon); DictKeyIterator iter(&scope, runtime_->newDictKeyIterator(thread_, dict)); Object item1(&scope, runBuiltin(METH(dict_keyiterator, __next__), iter)); ASSERT_TRUE(item1.isStr()); EXPECT_EQ(Str::cast(*item1), hello); Object item2(&scope, runBuiltin(METH(dict_keyiterator, __next__), iter)); ASSERT_TRUE(item2.isStr()); EXPECT_EQ(Str::cast(*item2), goodbye); Object item3(&scope, runBuiltin(METH(dict_keyiterator, __next__), iter)); ASSERT_TRUE(item3.isError()); } TEST_F(DictValueIteratorBuiltinsTest, CallDunderNextReadsValuesSequentially) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDictWithSize(5)); Str hello(&scope, runtime_->newStrFromCStr("hello")); Object world(&scope, runtime_->newStrFromCStr("world")); Str goodbye(&scope, runtime_->newStrFromCStr("goodbye")); Object moon(&scope, runtime_->newStrFromCStr("moon")); dictAtPutByStr(thread_, dict, hello, world); dictAtPutByStr(thread_, dict, goodbye, moon); DictValueIterator iter(&scope, runtime_->newDictValueIterator(thread_, dict)); Object item1(&scope, runBuiltin(METH(dict_valueiterator, __next__), iter)); ASSERT_TRUE(item1.isStr()); EXPECT_EQ(Str::cast(*item1), world); Object item2(&scope, runBuiltin(METH(dict_valueiterator, __next__), iter)); ASSERT_TRUE(item2.isStr()); EXPECT_EQ(Str::cast(*item2), moon); Object item3(&scope, runBuiltin(METH(dict_valueiterator, __next__), iter)); ASSERT_TRUE(item3.isError()); } TEST_F(DictItemIteratorBuiltinsTest, DunderLengthHintOnConsumedDictItemIteratorReturnsZero) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDict()); Str hello(&scope, runtime_->newStrFromCStr("hello")); Object world(&scope, runtime_->newStrFromCStr("world")); dictAtPutByStr(thread_, dict, hello, world); DictItemIterator iter(&scope, runtime_->newDictItemIterator(thread_, dict)); Object item1(&scope, runBuiltin(METH(dict_itemiterator, __next__), iter)); ASSERT_FALSE(item1.isError()); Object length_hint( &scope, runBuiltin(METH(dict_itemiterator, __length_hint__), iter)); EXPECT_TRUE(isIntEqualsWord(*length_hint, 0)); } TEST_F(DictKeyIteratorBuiltinsTest, DunderLengthHintOnConsumedDictKeyIteratorReturnsZero) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDict()); Str hello(&scope, runtime_->newStrFromCStr("hello")); Object world(&scope, runtime_->newStrFromCStr("world")); dictAtPutByStr(thread_, dict, hello, world); DictKeyIterator iter(&scope, runtime_->newDictKeyIterator(thread_, dict)); Object item1(&scope, runBuiltin(METH(dict_keyiterator, __next__), iter)); ASSERT_FALSE(item1.isError()); Object length_hint(&scope, runBuiltin(METH(dict_keyiterator, __length_hint__), iter)); EXPECT_TRUE(isIntEqualsWord(*length_hint, 0)); } TEST_F(DictValueIteratorBuiltinsTest, DunderLengthHintOnConsumedDictValueIteratorReturnsZero) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDict()); Str hello(&scope, runtime_->newStrFromCStr("hello")); Object world(&scope, runtime_->newStrFromCStr("world")); dictAtPutByStr(thread_, dict, hello, world); DictValueIterator iter(&scope, runtime_->newDictValueIterator(thread_, dict)); Object item1(&scope, runBuiltin(METH(dict_valueiterator, __next__), iter)); ASSERT_FALSE(item1.isError()); Object length_hint( &scope, runBuiltin(METH(dict_valueiterator, __length_hint__), iter)); EXPECT_TRUE(isIntEqualsWord(*length_hint, 0)); } TEST_F(DictBuiltinsTest, ItemIteratorNextOnOneElementDictReturnsElement) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDict()); Str key(&scope, runtime_->newStrFromCStr("hello")); Object value(&scope, runtime_->newStrFromCStr("world")); dictAtPutByStr(thread_, dict, key, value); DictItemIterator iter(&scope, runtime_->newDictItemIterator(thread_, dict)); Object next(&scope, dictItemIteratorNext(thread_, iter)); ASSERT_TRUE(next.isTuple()); EXPECT_EQ(Tuple::cast(*next).at(0), key); EXPECT_EQ(Tuple::cast(*next).at(1), value); next = dictItemIteratorNext(thread_, iter); ASSERT_TRUE(next.isError()); } TEST_F(DictBuiltinsTest, KeyIteratorNextOnOneElementDictReturnsElement) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDict()); Str key(&scope, runtime_->newStrFromCStr("hello")); Object value(&scope, runtime_->newStrFromCStr("world")); dictAtPutByStr(thread_, dict, key, value); DictKeyIterator iter(&scope, runtime_->newDictKeyIterator(thread_, dict)); Object next(&scope, dictKeyIteratorNext(thread_, iter)); EXPECT_EQ(next, key); next = dictKeyIteratorNext(thread_, iter); ASSERT_TRUE(next.isError()); } TEST_F(DictBuiltinsTest, ValueIteratorNextOnOneElementDictReturnsElement) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDict()); Str key(&scope, runtime_->newStrFromCStr("hello")); Object value(&scope, runtime_->newStrFromCStr("world")); dictAtPutByStr(thread_, dict, key, value); DictValueIterator iter(&scope, runtime_->newDictValueIterator(thread_, dict)); Object next(&scope, dictValueIteratorNext(thread_, iter)); EXPECT_EQ(next, value); next = dictValueIteratorNext(thread_, iter); ASSERT_TRUE(next.isError()); } TEST_F(DictBuiltinsTest, NextOnDictWithOnlyTombstonesReturnsFalse) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDict()); Str key(&scope, runtime_->newStrFromCStr("hello")); Object value(&scope, runtime_->newStrFromCStr("world")); dictAtPutByStr(thread_, dict, key, value); ASSERT_FALSE(dictRemoveByStr(thread_, dict, key).isError()); word i = 0; Object dict_key(&scope, NoneType::object()); Object dict_value(&scope, NoneType::object()); EXPECT_FALSE(dictNextItem(dict, &i, &dict_key, &dict_value)); } TEST_F(DictBuiltinsTest, RecursiveDictPrintsEllipsis) { ASSERT_FALSE(runFromCStr(runtime_, R"( class C: def __init__(self, obj): self.val = obj def __repr__(self): return self.val.__repr__() def __hash__(self): return 5 d = dict() c = C(d) d['hello'] = c result = d.__repr__() )") .isError()); EXPECT_TRUE( isStrEqualsCStr(mainModuleAt(runtime_, "result"), "{'hello': {...}}")); } TEST_F(DictBuiltinsTest, PopWithKeyPresentReturnsValue) { ASSERT_FALSE(runFromCStr(runtime_, R"( d = {"hello": "world"} result = d.pop("hello") )") .isError()); HandleScope scope(thread_); EXPECT_TRUE(isStrEqualsCStr(mainModuleAt(runtime_, "result"), "world")); Dict dict(&scope, mainModuleAt(runtime_, "d")); EXPECT_EQ(dict.numItems(), 0); EXPECT_EQ(dict.firstEmptyItemIndex() / kItemNumPointers, 1); } TEST_F(DictBuiltinsTest, PopWithMissingKeyAndDefaultReturnsDefault) { ASSERT_FALSE(runFromCStr(runtime_, R"( d = {} result = d.pop("hello", "world") )") .isError()); HandleScope scope(thread_); Dict dict(&scope, mainModuleAt(runtime_, "d")); EXPECT_EQ(dict.numItems(), 0); EXPECT_EQ(dict.firstEmptyItemIndex() / kItemNumPointers, 0); EXPECT_TRUE(isStrEqualsCStr(mainModuleAt(runtime_, "result"), "world")); } TEST_F(DictBuiltinsTest, PopitemAfterInsert) { HandleScope scope(thread_); Dict dict(&scope, runtime_->newDict()); Object key(&scope, SmallInt::fromWord(0)); Object key1(&scope, SmallInt::fromWord(1)); word hash = intHash(*key); word hash1 = intHash(*key1); ASSERT_TRUE(dictAtPut(thread_, dict, key, hash, key).isNoneType()); ASSERT_TRUE(dictAtPut(thread_, dict, key1, hash1, key1).isNoneType()); for (int i = 0; i < 2; i++) { runBuiltin(METH(dict, popitem), dict); } ASSERT_EQ(dict.numItems(), 0); } TEST_F(DictBuiltinsTest, PopWithMisingKeyRaisesKeyError) { EXPECT_TRUE( raised(runFromCStr(runtime_, "{}.pop('hello')"), LayoutId::kKeyError)); } TEST_F(DictBuiltinsTest, PopWithSubclassDoesNotCallDunderDelitem) { ASSERT_FALSE(runFromCStr(runtime_, R"( class C(dict): def __delitem__(self, key): raise Exception(key) c = C({'hello': 'world'}) result = c.pop('hello') )") .isError()); ASSERT_FALSE(thread_->hasPendingException()); HandleScope scope(thread_); Dict dict(&scope, mainModuleAt(runtime_, "c")); EXPECT_EQ(dict.numItems(), 0); EXPECT_EQ(dict.firstEmptyItemIndex() / kItemNumPointers, 1); EXPECT_TRUE(isStrEqualsCStr(mainModuleAt(runtime_, "result"), "world")); } TEST_F(DictBuiltinsTest, DictInitWithSubclassInitializesElements) { ASSERT_FALSE(runFromCStr(runtime_, R"( class C(dict): pass c = C({'hello': 'world'}) )") .isError()); HandleScope scope(thread_); Dict dict(&scope, mainModuleAt(runtime_, "c")); EXPECT_EQ(dict.numItems(), 1); } TEST_F(DictBuiltinsTest, SetDefaultWithNoDefaultSetsToNone) { ASSERT_FALSE(runFromCStr(runtime_, R"( d = {} d.setdefault("hello") result = d["hello"] )") .isError()); EXPECT_EQ(mainModuleAt(runtime_, "result"), NoneType::object()); } TEST_F(DictBuiltinsTest, SetDefaultWithNotKeyInDictSetsDefault) { ASSERT_FALSE(runFromCStr(runtime_, R"( d = {} d.setdefault("hello", 4) result = d["hello"] )") .isError()); EXPECT_TRUE(isIntEqualsWord(mainModuleAt(runtime_, "result"), 4)); } TEST_F(DictBuiltinsTest, SetDefaultWithKeyInDictReturnsValue) { ASSERT_FALSE(runFromCStr(runtime_, R"( d = {"hello": 5} d.setdefault("hello", 4) result = d["hello"] )") .isError()); EXPECT_TRUE(isIntEqualsWord(mainModuleAt(runtime_, "result"), 5)); } TEST_F(DictBuiltinsTest, NumAttributesMatchesObjectSize) { HandleScope scope(thread_); Layout layout(&scope, runtime_->layoutAt(LayoutId::kDict)); EXPECT_EQ(layout.numInObjectAttributes(), (RawDict::kSize - RawHeapObject::kSize) / kPointerSize); } } // namespace testing } // namespace py