|  | // 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 <algorithm> | 
|  | #include <cstring> | 
|  | #include <map> | 
|  | #include <set> | 
|  | #include <string> | 
|  | #include <utility> | 
|  | #include <vector> | 
|  |  | 
|  | #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<uword>(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 <typename Table> | 
|  | 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<TestTraits, 2, 1> Table; | 
|  | Table table(Thread::Current()->zone(), HashTables::New<Table>(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<char>(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 <typename Set> | 
|  | void VerifyStringSetsEqual(const std::set<std::string>& 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<intptr_t>(expected.size()), keys.Length()); | 
|  | std::vector<std::string> 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<std::string> 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 <typename Map> | 
|  | void VerifyStringMapsEqual(const std::map<std::string, int>& 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<std::pair<std::string, int> > 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<std::string> 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 <typename Set> | 
|  | void TestSet(intptr_t initial_capacity, bool ordered) { | 
|  | std::set<std::string> expected; | 
|  | Set actual(HashTables::New<Set>(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 <typename Map> | 
|  | void TestMap(intptr_t initial_capacity, bool ordered) { | 
|  | std::map<std::string, int> expected; | 
|  | Map actual(HashTables::New<Map>(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<UnorderedHashSet<TestTraits> >(initial_capacity, false); | 
|  | } | 
|  | } | 
|  |  | 
|  | ISOLATE_UNIT_TEST_CASE(Maps) { | 
|  | for (intptr_t initial_capacity = 0; initial_capacity < 32; | 
|  | ++initial_capacity) { | 
|  | TestMap<UnorderedHashMap<TestTraits> >(initial_capacity, false); | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace dart |