unittests/VMRuntime/DictPropertyMapTest.cpp (134 lines of code) (raw):

/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #include "hermes/VM/DictPropertyMap.h" #include "TestHelpers.h" #include "gtest/gtest.h" using namespace hermes::vm; namespace { using DictPropertyMapTest = LargeHeapRuntimeTestFixture; TEST_F(DictPropertyMapTest, SmokeTest) { auto id1 = SymbolID::unsafeCreate(1); auto id2 = SymbolID::unsafeCreate(2); auto id3 = SymbolID::unsafeCreate(3); auto id4 = SymbolID::unsafeCreate(4); NamedPropertyDescriptor desc1{}; auto res = DictPropertyMap::create(runtime, 2); ASSERT_FALSE(isException(res)); MutableHandle<DictPropertyMap> map{runtime, res->get()}; auto saveMap = map.get(); // Try to find a property in the empty map. ASSERT_FALSE(DictPropertyMap::find(map.get(), id1)); // Add prop1. DictPropertyMap::add(map, runtime, id1, desc1); ASSERT_EQ(1u, map->size()); auto found = DictPropertyMap::find(*map, id1); ASSERT_TRUE(found); ASSERT_EQ(id1, DictPropertyMap::getDescriptorPair(*map, *found)->first); // Add prop2. DictPropertyMap::add(map, runtime, id2, desc1); ASSERT_EQ(2u, map->size()); // Find prop1, prop2. found = DictPropertyMap::find(*map, id1); ASSERT_TRUE(found); ASSERT_EQ(id1, DictPropertyMap::getDescriptorPair(*map, *found)->first); found = DictPropertyMap::find(*map, id2); ASSERT_TRUE(found); ASSERT_EQ(id2, DictPropertyMap::getDescriptorPair(*map, *found)->first); // Make sure we haven't reallocated. ASSERT_EQ(saveMap, map.get()); // Add a prop3, causing a reallocation. DictPropertyMap::add(map, runtime, id3, desc1); // Make sure we reallocated. ASSERT_NE(saveMap, map.get()); saveMap = map.get(); for (unsigned i = 0; i < 3; ++i) { auto sym = SymbolID::unsafeCreate(id1.unsafeGetIndex() + i); found = DictPropertyMap::find(*map, sym); ASSERT_TRUE(found); ASSERT_EQ(sym, DictPropertyMap::getDescriptorPair(*map, *found)->first); } // Add a prop4. DictPropertyMap::add(map, runtime, id4, desc1); ASSERT_EQ(saveMap, map.get()); for (unsigned i = 0; i < 4; ++i) { auto sym = SymbolID::unsafeCreate(id1.unsafeGetIndex() + i); found = DictPropertyMap::find(*map, sym); ASSERT_TRUE(found); ASSERT_EQ(sym, DictPropertyMap::getDescriptorPair(*map, *found)->first); } // Verify the enumeration order. { unsigned symIndex = 1; DictPropertyMap::forEachProperty( map, runtime, [&](SymbolID id, NamedPropertyDescriptor desc) { ASSERT_EQ(symIndex, id.unsafeGetIndex()); ++symIndex; }); } // Delete prop2. found = DictPropertyMap::find(*map, id2); DictPropertyMap::erase(*map, runtime, *found); ASSERT_EQ(saveMap, map.get()); ASSERT_EQ(3u, map->size()); { static const unsigned symbols[] = {1, 3, 4}; for (unsigned i = 0; i < 3; ++i) { auto sym = SymbolID::unsafeCreate(symbols[i]); found = DictPropertyMap::find(*map, sym); ASSERT_TRUE(found); ASSERT_EQ(sym, DictPropertyMap::getDescriptorPair(*map, *found)->first); } unsigned i = 0; DictPropertyMap::forEachProperty( map, runtime, [&](SymbolID id, NamedPropertyDescriptor desc) { ASSERT_EQ(symbols[i], id.unsafeGetIndex()); ++i; }); } // Add prop2 again. DictPropertyMap::add(map, runtime, id2, desc1); ASSERT_EQ(4u, map->size()); { static const unsigned symbols[] = {1, 3, 4, 2}; for (unsigned i = 0; i < 4; ++i) { auto sym = SymbolID::unsafeCreate(symbols[i]); found = DictPropertyMap::find(*map, sym); ASSERT_TRUE(found); ASSERT_EQ(sym, DictPropertyMap::getDescriptorPair(*map, *found)->first); } unsigned i = 0; DictPropertyMap::forEachProperty( map, runtime, [&](SymbolID id, NamedPropertyDescriptor desc) { ASSERT_EQ(symbols[i], id.unsafeGetIndex()); ++i; }); } } TEST_F(DictPropertyMapTest, CreateOverCapacityTest) { (void)DictPropertyMap::create(runtime); ASSERT_EQ( ExecutionStatus::EXCEPTION, DictPropertyMap::create(runtime, DictPropertyMap::getMaxCapacity() + 1)); } TEST_F(DictPropertyMapTest, GrowOverCapacityTest) { // Hades can't handle doing a span of large allocations, because it has // fragmentation in its heap space. #if !defined(HERMESVM_GC_HADES) && !defined(HERMESVM_GC_RUNTIME) // Don't do the test if it requires too many properties. Just cross our // fingers and hope it works. auto const maxCapacity = DictPropertyMap::getMaxCapacity(); if (maxCapacity > 500000) return; auto res = DictPropertyMap::create(runtime); ASSERT_RETURNED(res); MutableHandle<DictPropertyMap> map{runtime, res->get()}; MutableHandle<> value{runtime}; NamedPropertyDescriptor desc(PropertyFlags{}, 0); auto marker = gcScope.createMarker(); for (unsigned i = 0; i < maxCapacity; ++i) { value.set(HermesValue::encodeNumberValue(i)); auto symRes = valueToSymbolID(runtime, value); ASSERT_RETURNED(symRes); ASSERT_RETURNED(DictPropertyMap::add(map, runtime, **symRes, desc)); gcScope.flushToMarker(marker); } value.set(HermesValue::encodeNumberValue(maxCapacity)); auto symRes = valueToSymbolID(runtime, value); ASSERT_RETURNED(symRes); ASSERT_EQ( ExecutionStatus::EXCEPTION, DictPropertyMap::add(map, runtime, **symRes, desc)); runtime.clearThrownValue(); // Try it again. value.set(HermesValue::encodeNumberValue(maxCapacity + 1)); symRes = valueToSymbolID(runtime, value); ASSERT_RETURNED(symRes); ASSERT_EQ( ExecutionStatus::EXCEPTION, DictPropertyMap::add(map, runtime, **symRes, desc)); runtime.clearThrownValue(); #endif } } // namespace