| // Copyright (c) 2018 Kenton Varda and contributors |
| // Licensed under the MIT License: |
| // |
| // Permission is hereby granted, free of charge, to any person obtaining a copy |
| // of this software and associated documentation files (the "Software"), to deal |
| // in the Software without restriction, including without limitation the rights |
| // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| // copies of the Software, and to permit persons to whom the Software is |
| // furnished to do so, subject to the following conditions: |
| // |
| // The above copyright notice and this permission notice shall be included in |
| // all copies or substantial portions of the Software. |
| // |
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| // THE SOFTWARE. |
| |
| #include "table.h" |
| #include <kj/test.h> |
| #include <set> |
| #include <unordered_set> |
| #include "hash.h" |
| #include "time.h" |
| #include <stdlib.h> |
| |
| namespace kj { |
| namespace _ { |
| namespace { |
| |
| #if defined(KJ_DEBUG) && !__OPTIMIZE__ |
| static constexpr uint MEDIUM_PRIME = 619; |
| static constexpr uint BIG_PRIME = 6143; |
| #else |
| static constexpr uint MEDIUM_PRIME = 6143; |
| static constexpr uint BIG_PRIME = 101363; |
| #endif |
| // Some of the tests build large tables. These numbers are used as the table sizes. We use primes |
| // to avoid any unintended aliasing affects -- this is probably just paranoia, but why not? |
| // |
| // We use smaller values for debug builds to keep runtime down. |
| |
| KJ_TEST("_::tryReserveSize() works") { |
| { |
| Vector<int> vec; |
| tryReserveSize(vec, "foo"_kj); |
| KJ_EXPECT(vec.capacity() == 3); |
| } |
| { |
| Vector<int> vec; |
| tryReserveSize(vec, 123); |
| KJ_EXPECT(vec.capacity() == 0); |
| } |
| } |
| |
| class StringHasher { |
| public: |
| StringPtr keyForRow(StringPtr s) const { return s; } |
| |
| bool matches(StringPtr a, StringPtr b) const { |
| return a == b; |
| } |
| uint hashCode(StringPtr str) const { |
| return kj::hashCode(str); |
| } |
| }; |
| |
| KJ_TEST("simple table") { |
| Table<StringPtr, HashIndex<StringHasher>> table; |
| |
| KJ_EXPECT(table.find("foo") == nullptr); |
| |
| KJ_EXPECT(table.size() == 0); |
| KJ_EXPECT(table.insert("foo") == "foo"); |
| KJ_EXPECT(table.size() == 1); |
| KJ_EXPECT(table.insert("bar") == "bar"); |
| KJ_EXPECT(table.size() == 2); |
| |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find("foo")) == "foo"); |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find("bar")) == "bar"); |
| KJ_EXPECT(table.find("fop") == nullptr); |
| KJ_EXPECT(table.find("baq") == nullptr); |
| |
| { |
| StringPtr& ref = table.insert("baz"); |
| KJ_EXPECT(ref == "baz"); |
| StringPtr& ref2 = KJ_ASSERT_NONNULL(table.find("baz")); |
| KJ_EXPECT(&ref == &ref2); |
| } |
| |
| KJ_EXPECT(table.size() == 3); |
| |
| { |
| auto iter = table.begin(); |
| KJ_EXPECT(*iter++ == "foo"); |
| KJ_EXPECT(*iter++ == "bar"); |
| KJ_EXPECT(*iter++ == "baz"); |
| KJ_EXPECT(iter == table.end()); |
| } |
| |
| KJ_EXPECT(table.eraseMatch("foo")); |
| KJ_EXPECT(table.size() == 2); |
| KJ_EXPECT(table.find("foo") == nullptr); |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find("bar")) == "bar"); |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find("baz")) == "baz"); |
| |
| { |
| auto iter = table.begin(); |
| KJ_EXPECT(*iter++ == "baz"); |
| KJ_EXPECT(*iter++ == "bar"); |
| KJ_EXPECT(iter == table.end()); |
| } |
| |
| { |
| auto& row = table.upsert("qux", [&](StringPtr&, StringPtr&&) { |
| KJ_FAIL_ASSERT("shouldn't get here"); |
| }); |
| |
| auto copy = kj::str("qux"); |
| table.upsert(StringPtr(copy), [&](StringPtr& existing, StringPtr&& param) { |
| KJ_EXPECT(param.begin() == copy.begin()); |
| KJ_EXPECT(&existing == &row); |
| }); |
| |
| auto& found = KJ_ASSERT_NONNULL(table.find("qux")); |
| KJ_EXPECT(&found == &row); |
| } |
| |
| StringPtr STRS[] = { "corge"_kj, "grault"_kj, "garply"_kj }; |
| table.insertAll(ArrayPtr<StringPtr>(STRS)); |
| KJ_EXPECT(table.size() == 6); |
| KJ_EXPECT(table.find("corge") != nullptr); |
| KJ_EXPECT(table.find("grault") != nullptr); |
| KJ_EXPECT(table.find("garply") != nullptr); |
| |
| KJ_EXPECT_THROW_MESSAGE("inserted row already exists in table", table.insert("bar")); |
| |
| KJ_EXPECT(table.size() == 6); |
| |
| KJ_EXPECT(table.insert("baa") == "baa"); |
| |
| KJ_EXPECT(table.eraseAll([](StringPtr s) { return s.startsWith("ba"); }) == 3); |
| KJ_EXPECT(table.size() == 4); |
| |
| { |
| auto iter = table.begin(); |
| KJ_EXPECT(*iter++ == "garply"); |
| KJ_EXPECT(*iter++ == "grault"); |
| KJ_EXPECT(*iter++ == "qux"); |
| KJ_EXPECT(*iter++ == "corge"); |
| KJ_EXPECT(iter == table.end()); |
| } |
| |
| auto& graultRow = table.begin()[1]; |
| kj::StringPtr origGrault = graultRow; |
| |
| KJ_EXPECT(&table.findOrCreate("grault", |
| [&]() -> kj::StringPtr { KJ_FAIL_ASSERT("shouldn't have called this"); }) == &graultRow); |
| KJ_EXPECT(graultRow.begin() == origGrault.begin()); |
| KJ_EXPECT(&KJ_ASSERT_NONNULL(table.find("grault")) == &graultRow); |
| KJ_EXPECT(table.find("waldo") == nullptr); |
| KJ_EXPECT(table.size() == 4); |
| |
| kj::String searchWaldo = kj::str("waldo"); |
| kj::String insertWaldo = kj::str("waldo"); |
| |
| auto& waldo = table.findOrCreate(searchWaldo, |
| [&]() -> kj::StringPtr { return insertWaldo; }); |
| KJ_EXPECT(waldo == "waldo"); |
| KJ_EXPECT(waldo.begin() == insertWaldo.begin()); |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find("grault")) == "grault"); |
| KJ_EXPECT(&KJ_ASSERT_NONNULL(table.find("waldo")) == &waldo); |
| KJ_EXPECT(table.size() == 5); |
| |
| { |
| auto iter = table.begin(); |
| KJ_EXPECT(*iter++ == "garply"); |
| KJ_EXPECT(*iter++ == "grault"); |
| KJ_EXPECT(*iter++ == "qux"); |
| KJ_EXPECT(*iter++ == "corge"); |
| KJ_EXPECT(*iter++ == "waldo"); |
| KJ_EXPECT(iter == table.end()); |
| } |
| } |
| |
| class BadHasher { |
| // String hash that always returns the same hash code. This should not affect correctness, only |
| // performance. |
| public: |
| StringPtr keyForRow(StringPtr s) const { return s; } |
| |
| bool matches(StringPtr a, StringPtr b) const { |
| return a == b; |
| } |
| uint hashCode(StringPtr str) const { |
| return 1234; |
| } |
| }; |
| |
| KJ_TEST("hash tables when hash is always same") { |
| Table<StringPtr, HashIndex<BadHasher>> table; |
| |
| KJ_EXPECT(table.size() == 0); |
| KJ_EXPECT(table.insert("foo") == "foo"); |
| KJ_EXPECT(table.size() == 1); |
| KJ_EXPECT(table.insert("bar") == "bar"); |
| KJ_EXPECT(table.size() == 2); |
| |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find("foo")) == "foo"); |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find("bar")) == "bar"); |
| KJ_EXPECT(table.find("fop") == nullptr); |
| KJ_EXPECT(table.find("baq") == nullptr); |
| |
| { |
| StringPtr& ref = table.insert("baz"); |
| KJ_EXPECT(ref == "baz"); |
| StringPtr& ref2 = KJ_ASSERT_NONNULL(table.find("baz")); |
| KJ_EXPECT(&ref == &ref2); |
| } |
| |
| KJ_EXPECT(table.size() == 3); |
| |
| { |
| auto iter = table.begin(); |
| KJ_EXPECT(*iter++ == "foo"); |
| KJ_EXPECT(*iter++ == "bar"); |
| KJ_EXPECT(*iter++ == "baz"); |
| KJ_EXPECT(iter == table.end()); |
| } |
| |
| KJ_EXPECT(table.eraseMatch("foo")); |
| KJ_EXPECT(table.size() == 2); |
| KJ_EXPECT(table.find("foo") == nullptr); |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find("bar")) == "bar"); |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find("baz")) == "baz"); |
| |
| { |
| auto iter = table.begin(); |
| KJ_EXPECT(*iter++ == "baz"); |
| KJ_EXPECT(*iter++ == "bar"); |
| KJ_EXPECT(iter == table.end()); |
| } |
| |
| { |
| auto& row = table.upsert("qux", [&](StringPtr&, StringPtr&&) { |
| KJ_FAIL_ASSERT("shouldn't get here"); |
| }); |
| |
| auto copy = kj::str("qux"); |
| table.upsert(StringPtr(copy), [&](StringPtr& existing, StringPtr&& param) { |
| KJ_EXPECT(param.begin() == copy.begin()); |
| KJ_EXPECT(&existing == &row); |
| }); |
| |
| auto& found = KJ_ASSERT_NONNULL(table.find("qux")); |
| KJ_EXPECT(&found == &row); |
| } |
| |
| StringPtr STRS[] = { "corge"_kj, "grault"_kj, "garply"_kj }; |
| table.insertAll(ArrayPtr<StringPtr>(STRS)); |
| KJ_EXPECT(table.size() == 6); |
| KJ_EXPECT(table.find("corge") != nullptr); |
| KJ_EXPECT(table.find("grault") != nullptr); |
| KJ_EXPECT(table.find("garply") != nullptr); |
| |
| KJ_EXPECT_THROW_MESSAGE("inserted row already exists in table", table.insert("bar")); |
| } |
| |
| class IntHasher { |
| // Dumb integer hasher that just returns the integer itself. |
| public: |
| uint keyForRow(uint i) const { return i; } |
| |
| bool matches(uint a, uint b) const { |
| return a == b; |
| } |
| uint hashCode(uint i) const { |
| return i; |
| } |
| }; |
| |
| KJ_TEST("HashIndex with many erasures doesn't keep growing") { |
| HashIndex<IntHasher> index; |
| |
| kj::ArrayPtr<uint> rows = nullptr; |
| |
| for (uint i: kj::zeroTo(1000000)) { |
| KJ_ASSERT(index.insert(rows, 0, i) == nullptr); |
| index.erase(rows, 0, i); |
| } |
| |
| KJ_ASSERT(index.capacity() < 10); |
| } |
| |
| struct SiPair { |
| kj::StringPtr str; |
| uint i; |
| |
| inline bool operator==(SiPair other) const { |
| return str == other.str && i == other.i; |
| } |
| }; |
| |
| class SiPairStringHasher { |
| public: |
| StringPtr keyForRow(SiPair s) const { return s.str; } |
| |
| bool matches(SiPair a, StringPtr b) const { |
| return a.str == b; |
| } |
| uint hashCode(StringPtr str) const { |
| return inner.hashCode(str); |
| } |
| |
| private: |
| StringHasher inner; |
| }; |
| |
| class SiPairIntHasher { |
| public: |
| uint keyForRow(SiPair s) const { return s.i; } |
| |
| bool matches(SiPair a, uint b) const { |
| return a.i == b; |
| } |
| uint hashCode(uint i) const { |
| return i; |
| } |
| }; |
| |
| KJ_TEST("double-index table") { |
| Table<SiPair, HashIndex<SiPairStringHasher>, HashIndex<SiPairIntHasher>> table; |
| |
| KJ_EXPECT(table.size() == 0); |
| KJ_EXPECT(table.insert({"foo", 123}) == (SiPair {"foo", 123})); |
| KJ_EXPECT(table.size() == 1); |
| KJ_EXPECT(table.insert({"bar", 456}) == (SiPair {"bar", 456})); |
| KJ_EXPECT(table.size() == 2); |
| |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<HashIndex<SiPairStringHasher>>("foo")) == |
| (SiPair {"foo", 123})); |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<HashIndex<SiPairIntHasher>>(123)) == |
| (SiPair {"foo", 123})); |
| |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<0>("foo")) == (SiPair {"foo", 123})); |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<1>(123)) == (SiPair {"foo", 123})); |
| |
| KJ_EXPECT_THROW_MESSAGE("inserted row already exists in table", table.insert({"foo", 111})); |
| KJ_EXPECT_THROW_MESSAGE("inserted row already exists in table", table.insert({"qux", 123})); |
| |
| KJ_EXPECT(table.size() == 2); |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<0>("foo")) == (SiPair {"foo", 123})); |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<1>(123)) == (SiPair {"foo", 123})); |
| |
| KJ_EXPECT( |
| table.findOrCreate<0>("foo", |
| []() -> SiPair { KJ_FAIL_ASSERT("shouldn't have called this"); }) |
| == (SiPair {"foo", 123})); |
| KJ_EXPECT(table.size() == 2); |
| KJ_EXPECT_THROW_MESSAGE("inserted row already exists in table", |
| table.findOrCreate<0>("corge", []() -> SiPair { return {"corge", 123}; })); |
| |
| KJ_EXPECT(table.size() == 2); |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<0>("foo")) == (SiPair {"foo", 123})); |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<1>(123)) == (SiPair {"foo", 123})); |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<0>("bar")) == (SiPair {"bar", 456})); |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<1>(456)) == (SiPair {"bar", 456})); |
| KJ_EXPECT(table.find<0>("corge") == nullptr); |
| |
| KJ_EXPECT( |
| table.findOrCreate<0>("corge", []() -> SiPair { return {"corge", 789}; }) |
| == (SiPair {"corge", 789})); |
| |
| KJ_EXPECT(table.size() == 3); |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<0>("foo")) == (SiPair {"foo", 123})); |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<1>(123)) == (SiPair {"foo", 123})); |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<0>("bar")) == (SiPair {"bar", 456})); |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<1>(456)) == (SiPair {"bar", 456})); |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<0>("corge")) == (SiPair {"corge", 789})); |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<1>(789)) == (SiPair {"corge", 789})); |
| |
| KJ_EXPECT( |
| table.findOrCreate<1>(234, []() -> SiPair { return {"grault", 234}; }) |
| == (SiPair {"grault", 234})); |
| |
| KJ_EXPECT(table.size() == 4); |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<0>("foo")) == (SiPair {"foo", 123})); |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<1>(123)) == (SiPair {"foo", 123})); |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<0>("bar")) == (SiPair {"bar", 456})); |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<1>(456)) == (SiPair {"bar", 456})); |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<0>("corge")) == (SiPair {"corge", 789})); |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<1>(789)) == (SiPair {"corge", 789})); |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<0>("grault")) == (SiPair {"grault", 234})); |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find<1>(234)) == (SiPair {"grault", 234})); |
| } |
| |
| class UintHasher { |
| public: |
| uint keyForRow(uint i) const { return i; } |
| |
| bool matches(uint a, uint b) const { |
| return a == b; |
| } |
| uint hashCode(uint i) const { |
| return i; |
| } |
| }; |
| |
| KJ_TEST("benchmark: kj::Table<uint, HashIndex>") { |
| constexpr uint SOME_PRIME = BIG_PRIME; |
| constexpr uint STEP[] = {1, 2, 4, 7, 43, 127}; |
| |
| for (auto step: STEP) { |
| KJ_CONTEXT(step); |
| Table<uint, HashIndex<UintHasher>> table; |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| uint j = (i * step) % SOME_PRIME; |
| table.insert(j * 5 + 123); |
| } |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| uint value = KJ_ASSERT_NONNULL(table.find(i * 5 + 123)); |
| KJ_ASSERT(value == i * 5 + 123); |
| KJ_ASSERT(table.find(i * 5 + 122) == nullptr); |
| KJ_ASSERT(table.find(i * 5 + 124) == nullptr); |
| } |
| |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| if (i % 2 == 0 || i % 7 == 0) { |
| table.erase(KJ_ASSERT_NONNULL(table.find(i * 5 + 123))); |
| } |
| } |
| |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| if (i % 2 == 0 || i % 7 == 0) { |
| // erased |
| KJ_ASSERT(table.find(i * 5 + 123) == nullptr); |
| } else { |
| uint value = KJ_ASSERT_NONNULL(table.find(i * 5 + 123)); |
| KJ_ASSERT(value == i * 5 + 123); |
| } |
| } |
| } |
| } |
| |
| KJ_TEST("benchmark: std::unordered_set<uint>") { |
| constexpr uint SOME_PRIME = BIG_PRIME; |
| constexpr uint STEP[] = {1, 2, 4, 7, 43, 127}; |
| |
| for (auto step: STEP) { |
| KJ_CONTEXT(step); |
| std::unordered_set<uint> table; |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| uint j = (i * step) % SOME_PRIME; |
| table.insert(j * 5 + 123); |
| } |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| auto iter = table.find(i * 5 + 123); |
| KJ_ASSERT(iter != table.end()); |
| uint value = *iter; |
| KJ_ASSERT(value == i * 5 + 123); |
| KJ_ASSERT(table.find(i * 5 + 122) == table.end()); |
| KJ_ASSERT(table.find(i * 5 + 124) == table.end()); |
| } |
| |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| if (i % 2 == 0 || i % 7 == 0) { |
| KJ_ASSERT(table.erase(i * 5 + 123) > 0); |
| } |
| } |
| |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| if (i % 2 == 0 || i % 7 == 0) { |
| // erased |
| KJ_ASSERT(table.find(i * 5 + 123) == table.end()); |
| } else { |
| auto iter = table.find(i * 5 + 123); |
| KJ_ASSERT(iter != table.end()); |
| uint value = *iter; |
| KJ_ASSERT(value == i * 5 + 123); |
| } |
| } |
| } |
| } |
| |
| KJ_TEST("benchmark: kj::Table<StringPtr, HashIndex>") { |
| constexpr uint SOME_PRIME = BIG_PRIME; |
| constexpr uint STEP[] = {1, 2, 4, 7, 43, 127}; |
| |
| kj::Vector<String> strings(SOME_PRIME); |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| strings.add(kj::str(i * 5 + 123)); |
| } |
| |
| for (auto step: STEP) { |
| KJ_CONTEXT(step); |
| Table<StringPtr, HashIndex<StringHasher>> table; |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| uint j = (i * step) % SOME_PRIME; |
| table.insert(strings[j]); |
| } |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| StringPtr value = KJ_ASSERT_NONNULL(table.find(strings[i])); |
| KJ_ASSERT(value == strings[i]); |
| } |
| |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| if (i % 2 == 0 || i % 7 == 0) { |
| table.erase(KJ_ASSERT_NONNULL(table.find(strings[i]))); |
| } |
| } |
| |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| if (i % 2 == 0 || i % 7 == 0) { |
| // erased |
| KJ_ASSERT(table.find(strings[i]) == nullptr); |
| } else { |
| StringPtr value = KJ_ASSERT_NONNULL(table.find(strings[i])); |
| KJ_ASSERT(value == strings[i]); |
| } |
| } |
| } |
| } |
| |
| struct StlStringHash { |
| inline size_t operator()(StringPtr str) const { |
| return kj::hashCode(str); |
| } |
| }; |
| |
| KJ_TEST("benchmark: std::unordered_set<StringPtr>") { |
| constexpr uint SOME_PRIME = BIG_PRIME; |
| constexpr uint STEP[] = {1, 2, 4, 7, 43, 127}; |
| |
| kj::Vector<String> strings(SOME_PRIME); |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| strings.add(kj::str(i * 5 + 123)); |
| } |
| |
| for (auto step: STEP) { |
| KJ_CONTEXT(step); |
| std::unordered_set<StringPtr, StlStringHash> table; |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| uint j = (i * step) % SOME_PRIME; |
| table.insert(strings[j]); |
| } |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| auto iter = table.find(strings[i]); |
| KJ_ASSERT(iter != table.end()); |
| StringPtr value = *iter; |
| KJ_ASSERT(value == strings[i]); |
| } |
| |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| if (i % 2 == 0 || i % 7 == 0) { |
| KJ_ASSERT(table.erase(strings[i]) > 0); |
| } |
| } |
| |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| if (i % 2 == 0 || i % 7 == 0) { |
| // erased |
| KJ_ASSERT(table.find(strings[i]) == table.end()); |
| } else { |
| auto iter = table.find(strings[i]); |
| KJ_ASSERT(iter != table.end()); |
| StringPtr value = *iter; |
| KJ_ASSERT(value == strings[i]); |
| } |
| } |
| } |
| } |
| |
| // ======================================================================================= |
| |
| KJ_TEST("B-tree internals") { |
| { |
| BTreeImpl::Leaf leaf; |
| memset(&leaf, 0, sizeof(leaf)); |
| |
| for (auto i: kj::indices(leaf.rows)) { |
| KJ_CONTEXT(i); |
| |
| KJ_EXPECT(leaf.size() == i); |
| |
| if (i < kj::size(leaf.rows) / 2) { |
| #ifdef KJ_DEBUG |
| KJ_EXPECT_THROW(FAILED, leaf.isHalfFull()); |
| #endif |
| KJ_EXPECT(!leaf.isMostlyFull()); |
| } |
| |
| if (i == kj::size(leaf.rows) / 2) { |
| KJ_EXPECT(leaf.isHalfFull()); |
| KJ_EXPECT(!leaf.isMostlyFull()); |
| } |
| |
| if (i > kj::size(leaf.rows) / 2) { |
| KJ_EXPECT(!leaf.isHalfFull()); |
| KJ_EXPECT(leaf.isMostlyFull()); |
| } |
| |
| if (i == kj::size(leaf.rows)) { |
| KJ_EXPECT(leaf.isFull()); |
| } else { |
| KJ_EXPECT(!leaf.isFull()); |
| } |
| |
| leaf.rows[i] = 1; |
| } |
| KJ_EXPECT(leaf.size() == kj::size(leaf.rows)); |
| } |
| |
| { |
| BTreeImpl::Parent parent; |
| memset(&parent, 0, sizeof(parent)); |
| |
| for (auto i: kj::indices(parent.keys)) { |
| KJ_CONTEXT(i); |
| |
| KJ_EXPECT(parent.keyCount() == i); |
| |
| if (i < kj::size(parent.keys) / 2) { |
| #ifdef KJ_DEBUG |
| KJ_EXPECT_THROW(FAILED, parent.isHalfFull()); |
| #endif |
| KJ_EXPECT(!parent.isMostlyFull()); |
| } |
| |
| if (i == kj::size(parent.keys) / 2) { |
| KJ_EXPECT(parent.isHalfFull()); |
| KJ_EXPECT(!parent.isMostlyFull()); |
| } |
| |
| if (i > kj::size(parent.keys) / 2) { |
| KJ_EXPECT(!parent.isHalfFull()); |
| KJ_EXPECT(parent.isMostlyFull()); |
| } |
| |
| if (i == kj::size(parent.keys)) { |
| KJ_EXPECT(parent.isFull()); |
| } else { |
| KJ_EXPECT(!parent.isFull()); |
| } |
| |
| parent.keys[i] = 1; |
| } |
| KJ_EXPECT(parent.keyCount() == kj::size(parent.keys)); |
| } |
| } |
| |
| class StringCompare { |
| public: |
| StringPtr keyForRow(StringPtr s) const { return s; } |
| |
| bool isBefore(StringPtr a, StringPtr b) const { |
| return a < b; |
| } |
| bool matches(StringPtr a, StringPtr b) const { |
| return a == b; |
| } |
| }; |
| |
| KJ_TEST("simple tree table") { |
| Table<StringPtr, TreeIndex<StringCompare>> table; |
| |
| KJ_EXPECT(table.find("foo") == nullptr); |
| |
| KJ_EXPECT(table.size() == 0); |
| KJ_EXPECT(table.insert("foo") == "foo"); |
| KJ_EXPECT(table.size() == 1); |
| KJ_EXPECT(table.insert("bar") == "bar"); |
| KJ_EXPECT(table.size() == 2); |
| |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find("foo")) == "foo"); |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find("bar")) == "bar"); |
| KJ_EXPECT(table.find("fop") == nullptr); |
| KJ_EXPECT(table.find("baq") == nullptr); |
| |
| { |
| StringPtr& ref = table.insert("baz"); |
| KJ_EXPECT(ref == "baz"); |
| StringPtr& ref2 = KJ_ASSERT_NONNULL(table.find("baz")); |
| KJ_EXPECT(&ref == &ref2); |
| } |
| |
| KJ_EXPECT(table.size() == 3); |
| |
| { |
| auto range = table.ordered(); |
| auto iter = range.begin(); |
| KJ_EXPECT(*iter++ == "bar"); |
| KJ_EXPECT(*iter++ == "baz"); |
| KJ_EXPECT(*iter++ == "foo"); |
| KJ_EXPECT(iter == range.end()); |
| } |
| |
| KJ_EXPECT(table.eraseMatch("foo")); |
| KJ_EXPECT(table.size() == 2); |
| KJ_EXPECT(table.find("foo") == nullptr); |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find("bar")) == "bar"); |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find("baz")) == "baz"); |
| |
| { |
| auto range = table.ordered(); |
| auto iter = range.begin(); |
| KJ_EXPECT(*iter++ == "bar"); |
| KJ_EXPECT(*iter++ == "baz"); |
| KJ_EXPECT(iter == range.end()); |
| } |
| |
| { |
| auto& row = table.upsert("qux", [&](StringPtr&, StringPtr&&) { |
| KJ_FAIL_ASSERT("shouldn't get here"); |
| }); |
| |
| auto copy = kj::str("qux"); |
| table.upsert(StringPtr(copy), [&](StringPtr& existing, StringPtr&& param) { |
| KJ_EXPECT(param.begin() == copy.begin()); |
| KJ_EXPECT(&existing == &row); |
| }); |
| |
| auto& found = KJ_ASSERT_NONNULL(table.find("qux")); |
| KJ_EXPECT(&found == &row); |
| } |
| |
| StringPtr STRS[] = { "corge"_kj, "grault"_kj, "garply"_kj }; |
| table.insertAll(ArrayPtr<StringPtr>(STRS)); |
| KJ_EXPECT(table.size() == 6); |
| KJ_EXPECT(table.find("corge") != nullptr); |
| KJ_EXPECT(table.find("grault") != nullptr); |
| KJ_EXPECT(table.find("garply") != nullptr); |
| |
| KJ_EXPECT_THROW_MESSAGE("inserted row already exists in table", table.insert("bar")); |
| |
| KJ_EXPECT(table.size() == 6); |
| |
| KJ_EXPECT(table.insert("baa") == "baa"); |
| |
| KJ_EXPECT(table.eraseAll([](StringPtr s) { return s.startsWith("ba"); }) == 3); |
| KJ_EXPECT(table.size() == 4); |
| |
| { |
| auto range = table.ordered(); |
| auto iter = range.begin(); |
| KJ_EXPECT(*iter++ == "corge"); |
| KJ_EXPECT(*iter++ == "garply"); |
| KJ_EXPECT(*iter++ == "grault"); |
| KJ_EXPECT(*iter++ == "qux"); |
| KJ_EXPECT(iter == range.end()); |
| } |
| |
| { |
| auto range = table.range("foo", "har"); |
| auto iter = range.begin(); |
| KJ_EXPECT(*iter++ == "garply"); |
| KJ_EXPECT(*iter++ == "grault"); |
| KJ_EXPECT(iter == range.end()); |
| } |
| |
| { |
| auto range = table.range("garply", "grault"); |
| auto iter = range.begin(); |
| KJ_EXPECT(*iter++ == "garply"); |
| KJ_EXPECT(iter == range.end()); |
| } |
| |
| { |
| auto iter = table.seek("garply"); |
| KJ_EXPECT(*iter++ == "garply"); |
| KJ_EXPECT(*iter++ == "grault"); |
| KJ_EXPECT(*iter++ == "qux"); |
| KJ_EXPECT(iter == table.ordered().end()); |
| } |
| |
| { |
| auto iter = table.seek("gorply"); |
| KJ_EXPECT(*iter++ == "grault"); |
| KJ_EXPECT(*iter++ == "qux"); |
| KJ_EXPECT(iter == table.ordered().end()); |
| } |
| |
| auto& graultRow = table.begin()[1]; |
| kj::StringPtr origGrault = graultRow; |
| |
| KJ_EXPECT(&table.findOrCreate("grault", |
| [&]() -> kj::StringPtr { KJ_FAIL_ASSERT("shouldn't have called this"); }) == &graultRow); |
| KJ_EXPECT(graultRow.begin() == origGrault.begin()); |
| KJ_EXPECT(&KJ_ASSERT_NONNULL(table.find("grault")) == &graultRow); |
| KJ_EXPECT(table.find("waldo") == nullptr); |
| KJ_EXPECT(table.size() == 4); |
| |
| kj::String searchWaldo = kj::str("waldo"); |
| kj::String insertWaldo = kj::str("waldo"); |
| |
| auto& waldo = table.findOrCreate(searchWaldo, |
| [&]() -> kj::StringPtr { return insertWaldo; }); |
| KJ_EXPECT(waldo == "waldo"); |
| KJ_EXPECT(waldo.begin() == insertWaldo.begin()); |
| KJ_EXPECT(KJ_ASSERT_NONNULL(table.find("grault")) == "grault"); |
| KJ_EXPECT(&KJ_ASSERT_NONNULL(table.find("waldo")) == &waldo); |
| KJ_EXPECT(table.size() == 5); |
| |
| { |
| auto iter = table.begin(); |
| KJ_EXPECT(*iter++ == "garply"); |
| KJ_EXPECT(*iter++ == "grault"); |
| KJ_EXPECT(*iter++ == "qux"); |
| KJ_EXPECT(*iter++ == "corge"); |
| KJ_EXPECT(*iter++ == "waldo"); |
| KJ_EXPECT(iter == table.end()); |
| } |
| |
| // Verify that move constructor/assignment work. |
| Table<StringPtr, TreeIndex<StringCompare>> other(kj::mv(table)); |
| KJ_EXPECT(other.size() == 5); |
| KJ_EXPECT(table.size() == 0); |
| KJ_EXPECT(table.begin() == table.end()); |
| { |
| auto iter = other.begin(); |
| KJ_EXPECT(*iter++ == "garply"); |
| KJ_EXPECT(*iter++ == "grault"); |
| KJ_EXPECT(*iter++ == "qux"); |
| KJ_EXPECT(*iter++ == "corge"); |
| KJ_EXPECT(*iter++ == "waldo"); |
| KJ_EXPECT(iter == other.end()); |
| } |
| |
| table = kj::mv(other); |
| KJ_EXPECT(other.size() == 0); |
| KJ_EXPECT(table.size() == 5); |
| { |
| auto iter = table.begin(); |
| KJ_EXPECT(*iter++ == "garply"); |
| KJ_EXPECT(*iter++ == "grault"); |
| KJ_EXPECT(*iter++ == "qux"); |
| KJ_EXPECT(*iter++ == "corge"); |
| KJ_EXPECT(*iter++ == "waldo"); |
| KJ_EXPECT(iter == table.end()); |
| } |
| KJ_EXPECT(other.begin() == other.end()); |
| } |
| |
| class UintCompare { |
| public: |
| uint keyForRow(uint i) const { return i; } |
| |
| bool isBefore(uint a, uint b) const { |
| return a < b; |
| } |
| bool matches(uint a, uint b) const { |
| return a == b; |
| } |
| }; |
| |
| KJ_TEST("large tree table") { |
| constexpr uint SOME_PRIME = MEDIUM_PRIME; |
| constexpr uint STEP[] = {1, 2, 4, 7, 43, 127}; |
| |
| for (auto step: STEP) { |
| KJ_CONTEXT(step); |
| Table<uint, TreeIndex<UintCompare>> table; |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| uint j = (i * step) % SOME_PRIME; |
| table.insert(j * 5 + 123); |
| } |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| uint value = KJ_ASSERT_NONNULL(table.find(i * 5 + 123)); |
| KJ_ASSERT(value == i * 5 + 123); |
| KJ_ASSERT(table.find(i * 5 + 122) == nullptr); |
| KJ_ASSERT(table.find(i * 5 + 124) == nullptr); |
| } |
| table.verify(); |
| |
| { |
| auto range = table.ordered(); |
| auto iter = range.begin(); |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| KJ_ASSERT(*iter++ == i * 5 + 123); |
| } |
| KJ_ASSERT(iter == range.end()); |
| } |
| |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| KJ_CONTEXT(i); |
| if (i % 2 == 0 || i % 7 == 0) { |
| table.erase(KJ_ASSERT_NONNULL(table.find(i * 5 + 123), i)); |
| table.verify(); |
| } |
| } |
| |
| { |
| auto range = table.ordered(); |
| auto iter = range.begin(); |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| if (i % 2 == 0 || i % 7 == 0) { |
| // erased |
| KJ_ASSERT(table.find(i * 5 + 123) == nullptr); |
| } else { |
| uint value = KJ_ASSERT_NONNULL(table.find(i * 5 + 123)); |
| KJ_ASSERT(value == i * 5 + 123); |
| KJ_ASSERT(*iter++ == i * 5 + 123); |
| } |
| } |
| KJ_ASSERT(iter == range.end()); |
| } |
| } |
| } |
| |
| KJ_TEST("TreeIndex fuzz test") { |
| // A test which randomly modifies a TreeIndex to try to discover buggy state changes. |
| |
| uint seed = (kj::systemPreciseCalendarClock().now() - kj::UNIX_EPOCH) / kj::NANOSECONDS; |
| KJ_CONTEXT(seed); // print the seed if the test fails |
| srand(seed); |
| |
| Table<uint, TreeIndex<UintCompare>> table; |
| |
| auto randomInsert = [&]() { |
| table.upsert(rand(), [](auto&&, auto&&) {}); |
| }; |
| auto randomErase = [&]() { |
| if (table.size() > 0) { |
| auto& row = table.begin()[rand() % table.size()]; |
| table.erase(row); |
| } |
| }; |
| auto randomLookup = [&]() { |
| if (table.size() > 0) { |
| auto& row = table.begin()[rand() % table.size()]; |
| auto& found = KJ_ASSERT_NONNULL(table.find(row)); |
| KJ_ASSERT(&found == &row); |
| } |
| }; |
| |
| // First pass: focus on insertions, aim to do 2x as many insertions as deletions. |
| for (auto i KJ_UNUSED: kj::zeroTo(1000)) { |
| switch (rand() % 4) { |
| case 0: |
| case 1: |
| randomInsert(); |
| break; |
| case 2: |
| randomErase(); |
| break; |
| case 3: |
| randomLookup(); |
| break; |
| } |
| |
| table.verify(); |
| } |
| |
| // Second pass: focus on deletions, aim to do 2x as many deletions as insertions. |
| for (auto i KJ_UNUSED: kj::zeroTo(1000)) { |
| switch (rand() % 4) { |
| case 0: |
| randomInsert(); |
| break; |
| case 1: |
| case 2: |
| randomErase(); |
| break; |
| case 3: |
| randomLookup(); |
| break; |
| } |
| |
| table.verify(); |
| } |
| } |
| |
| KJ_TEST("TreeIndex clear() leaves tree in valid state") { |
| // A test which ensures that calling clear() does not break the internal state of a TreeIndex. |
| // It used to be the case that clearing a non-empty tree would leave it thinking that it had room |
| // for one more node than it really did, causing it to write and read beyond the end of its |
| // internal array of nodes. |
| Table<uint, TreeIndex<UintCompare>> table; |
| |
| // Insert at least one value to allocate an initial set of tree nodes. |
| table.upsert(1, [](auto&&, auto&&) {}); |
| KJ_EXPECT(table.find(1) != nullptr); |
| table.clear(); |
| |
| // Insert enough values to force writes/reads beyond the end of the tree's internal node array. |
| for (uint i = 0; i < 29; ++i) { |
| table.upsert(i, [](auto&&, auto&&) {}); |
| } |
| for (uint i = 0; i < 29; ++i) { |
| KJ_EXPECT(table.find(i) != nullptr); |
| } |
| } |
| |
| KJ_TEST("benchmark: kj::Table<uint, TreeIndex>") { |
| constexpr uint SOME_PRIME = BIG_PRIME; |
| constexpr uint STEP[] = {1, 2, 4, 7, 43, 127}; |
| |
| for (auto step: STEP) { |
| KJ_CONTEXT(step); |
| Table<uint, TreeIndex<UintCompare>> table; |
| table.reserve(SOME_PRIME); |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| uint j = (i * step) % SOME_PRIME; |
| table.insert(j * 5 + 123); |
| } |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| uint value = KJ_ASSERT_NONNULL(table.find(i * 5 + 123)); |
| KJ_ASSERT(value == i * 5 + 123); |
| KJ_ASSERT(table.find(i * 5 + 122) == nullptr); |
| KJ_ASSERT(table.find(i * 5 + 124) == nullptr); |
| } |
| |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| if (i % 2 == 0 || i % 7 == 0) { |
| table.erase(KJ_ASSERT_NONNULL(table.find(i * 5 + 123))); |
| } |
| } |
| |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| if (i % 2 == 0 || i % 7 == 0) { |
| // erased |
| KJ_ASSERT(table.find(i * 5 + 123) == nullptr); |
| } else { |
| uint value = KJ_ASSERT_NONNULL(table.find(i * 5 + 123)); |
| KJ_ASSERT(value == i * 5 + 123); |
| } |
| } |
| } |
| } |
| |
| KJ_TEST("benchmark: std::set<uint>") { |
| constexpr uint SOME_PRIME = BIG_PRIME; |
| constexpr uint STEP[] = {1, 2, 4, 7, 43, 127}; |
| |
| for (auto step: STEP) { |
| KJ_CONTEXT(step); |
| std::set<uint> table; |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| uint j = (i * step) % SOME_PRIME; |
| table.insert(j * 5 + 123); |
| } |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| auto iter = table.find(i * 5 + 123); |
| KJ_ASSERT(iter != table.end()); |
| uint value = *iter; |
| KJ_ASSERT(value == i * 5 + 123); |
| KJ_ASSERT(table.find(i * 5 + 122) == table.end()); |
| KJ_ASSERT(table.find(i * 5 + 124) == table.end()); |
| } |
| |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| if (i % 2 == 0 || i % 7 == 0) { |
| KJ_ASSERT(table.erase(i * 5 + 123) > 0); |
| } |
| } |
| |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| if (i % 2 == 0 || i % 7 == 0) { |
| // erased |
| KJ_ASSERT(table.find(i * 5 + 123) == table.end()); |
| } else { |
| auto iter = table.find(i * 5 + 123); |
| KJ_ASSERT(iter != table.end()); |
| uint value = *iter; |
| KJ_ASSERT(value == i * 5 + 123); |
| } |
| } |
| } |
| } |
| |
| KJ_TEST("benchmark: kj::Table<StringPtr, TreeIndex>") { |
| constexpr uint SOME_PRIME = BIG_PRIME; |
| constexpr uint STEP[] = {1, 2, 4, 7, 43, 127}; |
| |
| kj::Vector<String> strings(SOME_PRIME); |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| strings.add(kj::str(i * 5 + 123)); |
| } |
| |
| for (auto step: STEP) { |
| KJ_CONTEXT(step); |
| Table<StringPtr, TreeIndex<StringCompare>> table; |
| table.reserve(SOME_PRIME); |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| uint j = (i * step) % SOME_PRIME; |
| table.insert(strings[j]); |
| } |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| StringPtr value = KJ_ASSERT_NONNULL(table.find(strings[i])); |
| KJ_ASSERT(value == strings[i]); |
| } |
| |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| if (i % 2 == 0 || i % 7 == 0) { |
| table.erase(KJ_ASSERT_NONNULL(table.find(strings[i]))); |
| } |
| } |
| |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| if (i % 2 == 0 || i % 7 == 0) { |
| // erased |
| KJ_ASSERT(table.find(strings[i]) == nullptr); |
| } else { |
| auto& value = KJ_ASSERT_NONNULL(table.find(strings[i])); |
| KJ_ASSERT(value == strings[i]); |
| } |
| } |
| } |
| } |
| |
| KJ_TEST("benchmark: std::set<StringPtr>") { |
| constexpr uint SOME_PRIME = BIG_PRIME; |
| constexpr uint STEP[] = {1, 2, 4, 7, 43, 127}; |
| |
| kj::Vector<String> strings(SOME_PRIME); |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| strings.add(kj::str(i * 5 + 123)); |
| } |
| |
| for (auto step: STEP) { |
| KJ_CONTEXT(step); |
| std::set<StringPtr> table; |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| uint j = (i * step) % SOME_PRIME; |
| table.insert(strings[j]); |
| } |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| auto iter = table.find(strings[i]); |
| KJ_ASSERT(iter != table.end()); |
| StringPtr value = *iter; |
| KJ_ASSERT(value == strings[i]); |
| } |
| |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| if (i % 2 == 0 || i % 7 == 0) { |
| KJ_ASSERT(table.erase(strings[i]) > 0); |
| } |
| } |
| |
| for (uint i: kj::zeroTo(SOME_PRIME)) { |
| if (i % 2 == 0 || i % 7 == 0) { |
| // erased |
| KJ_ASSERT(table.find(strings[i]) == table.end()); |
| } else { |
| auto iter = table.find(strings[i]); |
| KJ_ASSERT(iter != table.end()); |
| StringPtr value = *iter; |
| KJ_ASSERT(value == strings[i]); |
| } |
| } |
| } |
| } |
| |
| // ======================================================================================= |
| |
| KJ_TEST("insertion order index") { |
| Table<uint, InsertionOrderIndex> table; |
| |
| { |
| auto range = table.ordered(); |
| KJ_EXPECT(range.begin() == range.end()); |
| } |
| |
| table.insert(12); |
| table.insert(34); |
| table.insert(56); |
| table.insert(78); |
| |
| { |
| auto range = table.ordered(); |
| auto iter = range.begin(); |
| KJ_ASSERT(iter != range.end()); |
| KJ_EXPECT(*iter++ == 12); |
| KJ_ASSERT(iter != range.end()); |
| KJ_EXPECT(*iter++ == 34); |
| KJ_ASSERT(iter != range.end()); |
| KJ_EXPECT(*iter++ == 56); |
| KJ_ASSERT(iter != range.end()); |
| KJ_EXPECT(*iter++ == 78); |
| KJ_EXPECT(iter == range.end()); |
| KJ_EXPECT(*--iter == 78); |
| KJ_EXPECT(*--iter == 56); |
| KJ_EXPECT(*--iter == 34); |
| KJ_EXPECT(*--iter == 12); |
| KJ_EXPECT(iter == range.begin()); |
| } |
| |
| table.erase(table.begin()[1]); |
| |
| { |
| auto range = table.ordered(); |
| auto iter = range.begin(); |
| KJ_ASSERT(iter != range.end()); |
| KJ_EXPECT(*iter++ == 12); |
| KJ_ASSERT(iter != range.end()); |
| KJ_EXPECT(*iter++ == 56); |
| KJ_ASSERT(iter != range.end()); |
| KJ_EXPECT(*iter++ == 78); |
| KJ_EXPECT(iter == range.end()); |
| KJ_EXPECT(*--iter == 78); |
| KJ_EXPECT(*--iter == 56); |
| KJ_EXPECT(*--iter == 12); |
| KJ_EXPECT(iter == range.begin()); |
| } |
| |
| // Allocate enough more elements to cause a resize. |
| table.insert(111); |
| table.insert(222); |
| table.insert(333); |
| table.insert(444); |
| table.insert(555); |
| table.insert(666); |
| table.insert(777); |
| table.insert(888); |
| table.insert(999); |
| |
| { |
| auto range = table.ordered(); |
| auto iter = range.begin(); |
| KJ_ASSERT(iter != range.end()); |
| KJ_EXPECT(*iter++ == 12); |
| KJ_ASSERT(iter != range.end()); |
| KJ_EXPECT(*iter++ == 56); |
| KJ_ASSERT(iter != range.end()); |
| KJ_EXPECT(*iter++ == 78); |
| KJ_ASSERT(iter != range.end()); |
| KJ_EXPECT(*iter++ == 111); |
| KJ_ASSERT(iter != range.end()); |
| KJ_EXPECT(*iter++ == 222); |
| KJ_ASSERT(iter != range.end()); |
| KJ_EXPECT(*iter++ == 333); |
| KJ_ASSERT(iter != range.end()); |
| KJ_EXPECT(*iter++ == 444); |
| KJ_ASSERT(iter != range.end()); |
| KJ_EXPECT(*iter++ == 555); |
| KJ_ASSERT(iter != range.end()); |
| KJ_EXPECT(*iter++ == 666); |
| KJ_ASSERT(iter != range.end()); |
| KJ_EXPECT(*iter++ == 777); |
| KJ_ASSERT(iter != range.end()); |
| KJ_EXPECT(*iter++ == 888); |
| KJ_ASSERT(iter != range.end()); |
| KJ_EXPECT(*iter++ == 999); |
| KJ_EXPECT(iter == range.end()); |
| } |
| |
| // Remove everything. |
| while (table.size() > 0) { |
| table.erase(*table.begin()); |
| } |
| |
| { |
| auto range = table.ordered(); |
| KJ_EXPECT(range.begin() == range.end()); |
| } |
| } |
| |
| KJ_TEST("insertion order index is movable") { |
| using UintTable = Table<uint, InsertionOrderIndex>; |
| |
| kj::Maybe<UintTable> myTable; |
| |
| { |
| UintTable yourTable; |
| |
| yourTable.insert(12); |
| yourTable.insert(34); |
| yourTable.insert(56); |
| yourTable.insert(78); |
| yourTable.insert(111); |
| yourTable.insert(222); |
| yourTable.insert(333); |
| yourTable.insert(444); |
| yourTable.insert(555); |
| yourTable.insert(666); |
| yourTable.insert(777); |
| yourTable.insert(888); |
| yourTable.insert(999); |
| |
| myTable = kj::mv(yourTable); |
| } |
| |
| auto& table = KJ_ASSERT_NONNULL(myTable); |
| |
| // At one time the following induced a segfault/double-free, due to incorrect memory management in |
| // InsertionOrderIndex's move ctor and dtor. |
| auto range = table.ordered(); |
| auto iter = range.begin(); |
| KJ_ASSERT(iter != range.end()); |
| KJ_EXPECT(*iter++ == 12); |
| KJ_ASSERT(iter != range.end()); |
| KJ_EXPECT(*iter++ == 34); |
| KJ_ASSERT(iter != range.end()); |
| KJ_EXPECT(*iter++ == 56); |
| KJ_ASSERT(iter != range.end()); |
| KJ_EXPECT(*iter++ == 78); |
| KJ_ASSERT(iter != range.end()); |
| KJ_EXPECT(*iter++ == 111); |
| KJ_ASSERT(iter != range.end()); |
| KJ_EXPECT(*iter++ == 222); |
| KJ_ASSERT(iter != range.end()); |
| KJ_EXPECT(*iter++ == 333); |
| KJ_ASSERT(iter != range.end()); |
| KJ_EXPECT(*iter++ == 444); |
| KJ_ASSERT(iter != range.end()); |
| KJ_EXPECT(*iter++ == 555); |
| KJ_ASSERT(iter != range.end()); |
| KJ_EXPECT(*iter++ == 666); |
| KJ_ASSERT(iter != range.end()); |
| KJ_EXPECT(*iter++ == 777); |
| KJ_ASSERT(iter != range.end()); |
| KJ_EXPECT(*iter++ == 888); |
| KJ_ASSERT(iter != range.end()); |
| KJ_EXPECT(*iter++ == 999); |
| KJ_EXPECT(iter == range.end()); |
| } |
| |
| } // namespace |
| } // namespace _ |
| } // namespace kj |