| Index: runtime/vm/hash_table_test.cc
|
| ===================================================================
|
| --- runtime/vm/hash_table_test.cc (revision 0)
|
| +++ runtime/vm/hash_table_test.cc (revision 0)
|
| @@ -0,0 +1,287 @@
|
| +// 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/unit_test.h"
|
| +#include "vm/hash_table.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 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();
|
| + }
|
| +};
|
| +
|
| +
|
| +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);
|
| +}
|
| +
|
| +
|
| +TEST_CASE(HashTable) {
|
| + typedef HashTable<TestTraits, 2, 1> Table;
|
| + Table table(Array::Handle(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();
|
| +}
|
| +
|
| +
|
| +TEST_CASE(EnumIndexHashMap) {
|
| + typedef EnumIndexHashMap<TestTraits> Table;
|
| + Table table(Array::Handle(HashTables::New<Table>(5)));
|
| + table.UpdateOrInsert(String::Handle(String::New("a")),
|
| + String::Handle(String::New("A")));
|
| + EXPECT(table.ContainsKey("a"));
|
| + table.UpdateValue("a", String::Handle(String::New("AAA")));
|
| + String& a_value = String::Handle();
|
| + a_value ^= table.GetOrNull("a");
|
| + EXPECT(a_value.Equals("AAA"));
|
| + Object& null_value = Object::Handle(table.GetOrNull("0"));
|
| + EXPECT(null_value.IsNull());
|
| + 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(Array::Handle(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);
|
| + }
|
| + }
|
| + // TODO(koda): Delete all entries.
|
| + actual.Release();
|
| +}
|
| +
|
| +
|
| +template<typename Map>
|
| +void TestMap(intptr_t initial_capacity, bool ordered) {
|
| + std::map<std::string, int> expected;
|
| + Map actual(Array::Handle(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);
|
| + }
|
| + }
|
| + // TODO(koda): Delete all entries.
|
| + actual.Release();
|
| +}
|
| +
|
| +
|
| +TEST_CASE(Sets) {
|
| + for (intptr_t initial_capacity = 0;
|
| + initial_capacity < 32;
|
| + ++initial_capacity) {
|
| + TestSet<UnorderedHashSet<TestTraits> >(initial_capacity, false);
|
| + TestSet<EnumIndexHashSet<TestTraits> >(initial_capacity, true);
|
| + }
|
| +}
|
| +
|
| +
|
| +TEST_CASE(Maps) {
|
| + for (intptr_t initial_capacity = 0;
|
| + initial_capacity < 32;
|
| + ++initial_capacity) {
|
| + TestMap<UnorderedHashMap<TestTraits> >(initial_capacity, false);
|
| + TestMap<EnumIndexHashMap<TestTraits> >(initial_capacity, true);
|
| + }
|
| +}
|
| +
|
| +} // namespace dart
|
|
|