// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. #include #include #include #include #include #include #include #include "platform/assert.h" #include "vm/hash_table.h" #include "vm/unit_test.h" namespace dart { // Various ways to look up strings. Uses length as the hash code to make it // easy to engineer collisions. class TestTraits { public: static const char* Name() { return "TestTraits"; } static bool ReportStats() { return false; } static bool IsMatch(const char* key, const Object& obj) { return String::Cast(obj).Equals(key); } static uword Hash(const char* key) { return static_cast(strlen(key)); } static bool IsMatch(const Object& a, const Object& b) { return a.IsString() && b.IsString() && String::Cast(a).Equals(String::Cast(b)); } static uword Hash(const Object& obj) { return String::Cast(obj).Length(); } static ObjectPtr NewKey(const char* key) { return String::New(key); } }; template void Validate(const Table& table) { // Verify consistency of entry state tracking. intptr_t num_entries = table.NumEntries(); intptr_t num_unused = table.NumUnused(); intptr_t num_occupied = table.NumOccupied(); intptr_t num_deleted = table.NumDeleted(); for (intptr_t i = 0; i < num_entries; ++i) { EXPECT_EQ(1, table.IsUnused(i) + table.IsOccupied(i) + table.IsDeleted(i)); num_unused -= table.IsUnused(i); num_occupied -= table.IsOccupied(i); num_deleted -= table.IsDeleted(i); } EXPECT_EQ(0, num_unused); EXPECT_EQ(0, num_occupied); EXPECT_EQ(0, num_deleted); } ISOLATE_UNIT_TEST_CASE(HashTable) { typedef HashTable Table; Table table(Thread::Current()->zone(), HashTables::New(5)); // Ensure that we did get at least 5 entries. EXPECT_LE(5, table.NumEntries()); EXPECT_EQ(0, table.NumOccupied()); Validate(table); EXPECT_EQ(-1, table.FindKey("a")); // Insertion and lookup. intptr_t a_entry = -1; EXPECT(!table.FindKeyOrDeletedOrUnused("a", &a_entry)); EXPECT_NE(-1, a_entry); String& a = String::Handle(String::New("a")); table.InsertKey(a_entry, a); EXPECT_EQ(1, table.NumOccupied()); Validate(table); EXPECT_EQ(a_entry, table.FindKey("a")); EXPECT_EQ(-1, table.FindKey("b")); intptr_t a_entry_again = -1; EXPECT(table.FindKeyOrDeletedOrUnused("a", &a_entry_again)); EXPECT_EQ(a_entry, a_entry_again); intptr_t b_entry = -1; EXPECT(!table.FindKeyOrDeletedOrUnused("b", &b_entry)); String& b = String::Handle(String::New("b")); table.InsertKey(b_entry, b); EXPECT_EQ(2, table.NumOccupied()); Validate(table); // Deletion. table.DeleteEntry(a_entry); EXPECT_EQ(1, table.NumOccupied()); Validate(table); EXPECT_EQ(-1, table.FindKey("a")); EXPECT_EQ(b_entry, table.FindKey("b")); intptr_t c_entry = -1; EXPECT(!table.FindKeyOrDeletedOrUnused("c", &c_entry)); String& c = String::Handle(String::New("c")); table.InsertKey(c_entry, c); EXPECT_EQ(2, table.NumOccupied()); Validate(table); EXPECT_EQ(c_entry, table.FindKey("c")); // Ensure we can actually reach 5 occupied entries (without expansion). { intptr_t entry = -1; EXPECT(!table.FindKeyOrDeletedOrUnused("d", &entry)); String& k = String::Handle(String::New("d")); table.InsertKey(entry, k); EXPECT(!table.FindKeyOrDeletedOrUnused("e", &entry)); k = String::New("e"); table.InsertKey(entry, k); EXPECT(!table.FindKeyOrDeletedOrUnused("f", &entry)); k = String::New("f"); table.InsertKey(entry, k); EXPECT_EQ(5, table.NumOccupied()); } table.Release(); } std::string ToStdString(const String& str) { EXPECT(str.IsOneByteString()); std::string result; for (intptr_t i = 0; i < str.Length(); ++i) { result += static_cast(str.CharAt(i)); } return result; } // Checks that 'expected' and 'actual' are equal sets. If 'ordered' is true, // it also verifies that their iteration orders match, i.e., that actual's // insertion order coincides with lexicographic order. template void VerifyStringSetsEqual(const std::set& expected, const Set& actual, bool ordered) { // Get actual keys in iteration order. Array& keys = Array::Handle(HashTables::ToArray(actual, true)); // Cardinality must match. EXPECT_EQ(static_cast(expected.size()), keys.Length()); std::vector expected_vec(expected.begin(), expected.end()); // Check containment. for (uintptr_t i = 0; i < expected_vec.size(); ++i) { EXPECT(actual.ContainsKey(expected_vec[i].c_str())); } // Equality, including order, if requested. std::vector actual_vec; String& key = String::Handle(); for (int i = 0; i < keys.Length(); ++i) { key ^= keys.At(i); actual_vec.push_back(ToStdString(key)); } if (!ordered) { std::sort(actual_vec.begin(), actual_vec.end()); } EXPECT( std::equal(actual_vec.begin(), actual_vec.end(), expected_vec.begin())); } // Checks that 'expected' and 'actual' are equal maps. If 'ordered' is true, // it also verifies that their iteration orders match, i.e., that actual's // insertion order coincides with lexicographic order. template void VerifyStringMapsEqual(const std::map& expected, const Map& actual, bool ordered) { intptr_t expected_size = expected.size(); // Get actual concatenated (key, value) pairs in iteration order. Array& entries = Array::Handle(HashTables::ToArray(actual, true)); // Cardinality must match. EXPECT_EQ(expected_size * 2, entries.Length()); std::vector > expected_vec(expected.begin(), expected.end()); // Check containment. Smi& value = Smi::Handle(); for (uintptr_t i = 0; i < expected_vec.size(); ++i) { std::string key = expected_vec[i].first; EXPECT(actual.ContainsKey(key.c_str())); value ^= actual.GetOrNull(key.c_str()); EXPECT_EQ(expected_vec[i].second, value.Value()); } if (!ordered) { return; } // Equality including order. std::vector actual_vec; String& key = String::Handle(); for (int i = 0; i < expected_size; ++i) { key ^= entries.At(2 * i); value ^= entries.At(2 * i + 1); EXPECT(expected_vec[i].first == ToStdString(key)); EXPECT_EQ(expected_vec[i].second, value.Value()); } } template void TestSet(intptr_t initial_capacity, bool ordered) { std::set expected; Set actual(HashTables::New(initial_capacity)); // Insert the following strings twice: // aaa...aaa (length 26) // bbb..bbb // ... // yy // z for (int i = 0; i < 2; ++i) { for (char ch = 'a'; ch <= 'z'; ++ch) { std::string key('z' - ch + 1, ch); expected.insert(key); bool present = actual.Insert(String::Handle(String::New(key.c_str()))); EXPECT_EQ((i != 0), present); Validate(actual); VerifyStringSetsEqual(expected, actual, ordered); } } actual.Clear(); EXPECT_EQ(0, actual.NumOccupied()); actual.Release(); } template void TestMap(intptr_t initial_capacity, bool ordered) { std::map expected; Map actual(HashTables::New(initial_capacity)); // Insert the following (strings, int) mapping: // aaa...aaa -> 26 // bbb..bbb -> 25 // ... // yy -> 2 // z -> 1 for (int i = 0; i < 2; ++i) { for (char ch = 'a'; ch <= 'z'; ++ch) { int length = 'z' - ch + 1; std::string key(length, ch); // Map everything to zero initially, then update to their final values. int value = length * i; expected[key] = value; bool present = actual.UpdateOrInsert(String::Handle(String::New(key.c_str())), Smi::Handle(Smi::New(value))); EXPECT_EQ((i != 0), present); Validate(actual); VerifyStringMapsEqual(expected, actual, ordered); } } actual.Clear(); EXPECT_EQ(0, actual.NumOccupied()); actual.Release(); } ISOLATE_UNIT_TEST_CASE(Sets) { for (intptr_t initial_capacity = 0; initial_capacity < 32; ++initial_capacity) { TestSet >(initial_capacity, false); } } ISOLATE_UNIT_TEST_CASE(Maps) { for (intptr_t initial_capacity = 0; initial_capacity < 32; ++initial_capacity) { TestMap >(initial_capacity, false); } } } // namespace dart