blob: 860293f89a05895211df89e42027268ef2e1a81c [file] [edit]
/*
* Copyright (C) 2026 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include <wtf/OrderedHashSet.h>
#include "Helpers/Test.h"
#include "MoveOnly.h"
#include <wtf/text/StringHash.h>
#include <wtf/text/WTFString.h>
namespace TestWebKitAPI {
TEST(WTF_OrderedHashSet, EmptySet)
{
OrderedHashSet<int> set;
EXPECT_TRUE(set.isEmpty());
EXPECT_EQ(0u, set.size());
EXPECT_TRUE(set.begin() == set.end());
}
TEST(WTF_OrderedHashSet, BasicAddAndContains)
{
OrderedHashSet<int> set;
auto result = set.add(1);
EXPECT_TRUE(result.isNewEntry);
EXPECT_EQ(1u, set.size());
auto result2 = set.add(1);
EXPECT_FALSE(result2.isNewEntry);
EXPECT_EQ(1u, set.size());
EXPECT_TRUE(set.contains(1));
EXPECT_FALSE(set.contains(2));
}
TEST(WTF_OrderedHashSet, InsertionOrderPreserved)
{
OrderedHashSet<int> set;
set.add(5);
set.add(3);
set.add(1);
set.add(4);
set.add(2);
Vector<int> values;
for (auto& v : set)
values.append(v);
EXPECT_EQ(5u, values.size());
EXPECT_EQ(5, values[0]);
EXPECT_EQ(3, values[1]);
EXPECT_EQ(1, values[2]);
EXPECT_EQ(4, values[3]);
EXPECT_EQ(2, values[4]);
}
TEST(WTF_OrderedHashSet, InsertionOrderPreservedAfterDeletion)
{
OrderedHashSet<int> set;
set.add(1);
set.add(2);
set.add(3);
set.add(4);
set.add(5);
set.remove(3);
Vector<int> values;
for (auto& v : set)
values.append(v);
EXPECT_EQ(4u, values.size());
EXPECT_EQ(1, values[0]);
EXPECT_EQ(2, values[1]);
EXPECT_EQ(4, values[2]);
EXPECT_EQ(5, values[3]);
}
TEST(WTF_OrderedHashSet, Remove)
{
OrderedHashSet<int> set;
set.add(1);
set.add(2);
set.add(3);
EXPECT_TRUE(set.remove(2));
EXPECT_FALSE(set.contains(2));
EXPECT_EQ(2u, set.size());
EXPECT_FALSE(set.remove(999));
}
TEST(WTF_OrderedHashSet, RemoveByIterator)
{
OrderedHashSet<int> set;
set.add(1);
set.add(2);
set.add(3);
auto it = set.find(2);
EXPECT_TRUE(set.remove(it));
EXPECT_FALSE(set.contains(2));
EXPECT_EQ(2u, set.size());
}
TEST(WTF_OrderedHashSet, Take)
{
OrderedHashSet<int> set;
set.add(1);
set.add(2);
set.add(3);
auto value = set.take(2);
EXPECT_EQ(2, value);
EXPECT_FALSE(set.contains(2));
EXPECT_EQ(2u, set.size());
auto missing = set.take(999);
EXPECT_EQ(0, missing); // Default for missing
}
TEST(WTF_OrderedHashSet, TakeAny)
{
OrderedHashSet<int> set;
set.add(5);
set.add(3);
set.add(1);
auto value = set.takeAny();
EXPECT_EQ(5, value); // First in insertion order
EXPECT_EQ(2u, set.size());
}
TEST(WTF_OrderedHashSet, Clear)
{
OrderedHashSet<int> set;
set.add(1);
set.add(2);
set.clear();
EXPECT_TRUE(set.isEmpty());
EXPECT_EQ(0u, set.size());
EXPECT_TRUE(set.begin() == set.end());
}
TEST(WTF_OrderedHashSet, Swap)
{
OrderedHashSet<int> set1;
set1.add(1);
set1.add(2);
OrderedHashSet<int> set2;
set2.add(3);
set1.swap(set2);
EXPECT_EQ(1u, set1.size());
EXPECT_TRUE(set1.contains(3));
EXPECT_EQ(2u, set2.size());
EXPECT_TRUE(set2.contains(1));
EXPECT_TRUE(set2.contains(2));
}
TEST(WTF_OrderedHashSet, CopyConstruction)
{
OrderedHashSet<int> set1;
set1.add(3);
set1.add(1);
set1.add(2);
OrderedHashSet<int> set2(set1);
EXPECT_EQ(3u, set2.size());
Vector<int> values;
for (auto& v : set2)
values.append(v);
EXPECT_EQ(3, values[0]);
EXPECT_EQ(1, values[1]);
EXPECT_EQ(2, values[2]);
}
TEST(WTF_OrderedHashSet, MoveConstruction)
{
OrderedHashSet<int> set1;
set1.add(1);
set1.add(2);
OrderedHashSet<int> set2(WTF::move(set1));
EXPECT_EQ(2u, set2.size());
EXPECT_TRUE(set2.contains(1));
EXPECT_TRUE(set2.contains(2));
EXPECT_TRUE(set1.isEmpty());
}
TEST(WTF_OrderedHashSet, CopyAssignment)
{
OrderedHashSet<int> set1;
set1.add(1);
set1.add(2);
OrderedHashSet<int> set2;
set2.add(9);
set2 = set1;
EXPECT_EQ(2u, set2.size());
EXPECT_TRUE(set2.contains(1));
EXPECT_TRUE(set2.contains(2));
EXPECT_FALSE(set2.contains(9));
}
TEST(WTF_OrderedHashSet, MoveAssignment)
{
OrderedHashSet<int> set1;
set1.add(1);
OrderedHashSet<int> set2;
set2.add(9);
set2 = WTF::move(set1);
EXPECT_EQ(1u, set2.size());
EXPECT_TRUE(set2.contains(1));
EXPECT_TRUE(set1.isEmpty());
}
TEST(WTF_OrderedHashSet, RehashPreservesOrder)
{
OrderedHashSet<int> set;
Vector<int> expectedOrder;
for (int i = 0; i < 100; ++i) {
set.add(i);
expectedOrder.append(i);
}
Vector<int> actualOrder;
for (auto& v : set)
actualOrder.append(v);
EXPECT_EQ(expectedOrder, actualOrder);
}
TEST(WTF_OrderedHashSet, DeleteAndReinsert)
{
OrderedHashSet<int> set;
set.add(1);
set.add(2);
set.add(3);
set.remove(2);
set.add(2); // Re-add goes at end
Vector<int> values;
for (auto& v : set)
values.append(v);
EXPECT_EQ(3u, values.size());
EXPECT_EQ(1, values[0]);
EXPECT_EQ(3, values[1]);
EXPECT_EQ(2, values[2]); // Re-inserted at end
}
TEST(WTF_OrderedHashSet, ManyDeletesAndInserts)
{
OrderedHashSet<int> set;
for (int i = 0; i < 50; ++i)
set.add(i);
for (int i = 0; i < 50; i += 2)
set.remove(i);
EXPECT_EQ(25u, set.size());
Vector<int> values;
for (auto& v : set)
values.append(v);
for (int i = 0; i < 25; ++i)
EXPECT_EQ(i * 2 + 1, values[i]);
}
TEST(WTF_OrderedHashSet, StringValues)
{
OrderedHashSet<String> set;
set.add("banana"_s);
set.add("apple"_s);
set.add("cherry"_s);
Vector<String> values;
for (auto& v : set)
values.append(v);
EXPECT_EQ(3u, values.size());
EXPECT_STREQ("banana", values[0].utf8().data());
EXPECT_STREQ("apple", values[1].utf8().data());
EXPECT_STREQ("cherry", values[2].utf8().data());
}
TEST(WTF_OrderedHashSet, InitializerList)
{
OrderedHashSet<int> set { 5, 3, 1, 4, 2 };
EXPECT_EQ(5u, set.size());
Vector<int> values;
for (auto& v : set)
values.append(v);
EXPECT_EQ(5, values[0]);
EXPECT_EQ(3, values[1]);
EXPECT_EQ(1, values[2]);
EXPECT_EQ(4, values[3]);
EXPECT_EQ(2, values[4]);
}
TEST(WTF_OrderedHashSet, AddAll)
{
OrderedHashSet<int> set;
set.add(1);
Vector<int> toAdd { 2, 3, 4 };
set.addAll(toAdd);
EXPECT_EQ(4u, set.size());
Vector<int> values;
for (auto& v : set)
values.append(v);
EXPECT_EQ(1, values[0]);
EXPECT_EQ(2, values[1]);
EXPECT_EQ(3, values[2]);
EXPECT_EQ(4, values[3]);
}
TEST(WTF_OrderedHashSet, Find)
{
OrderedHashSet<int> set;
set.add(1);
set.add(2);
set.add(3);
auto it = set.find(2);
EXPECT_TRUE(it != set.end());
EXPECT_EQ(2, *it);
auto missing = set.find(999);
EXPECT_TRUE(missing == set.end());
}
TEST(WTF_OrderedHashSet, ReserveCapacity)
{
OrderedHashSet<int> set;
set.reserveInitialCapacity(100);
for (int i = 0; i < 100; ++i)
set.add(i);
EXPECT_EQ(100u, set.size());
Vector<int> values;
for (auto& v : set)
values.append(v);
for (int i = 0; i < 100; ++i)
EXPECT_EQ(i, values[i]);
}
TEST(WTF_OrderedHashSet, StressTest)
{
OrderedHashSet<int> set;
// Insert 1000 elements
for (int i = 0; i < 1000; ++i)
set.add(i);
EXPECT_EQ(1000u, set.size());
// Remove every 3rd element
for (int i = 0; i < 1000; i += 3)
set.remove(i);
// Verify remaining elements are in insertion order
Vector<int> remaining;
for (auto& v : set)
remaining.append(v);
int expectedIdx = 0;
for (int i = 0; i < 1000; ++i) {
if (i % 3) {
EXPECT_EQ(i, remaining[expectedIdx]);
++expectedIdx;
}
}
EXPECT_EQ(static_cast<unsigned>(expectedIdx), set.size());
}
TEST(WTF_OrderedHashSet, CompactInPlacePreservesOrderAndCapacity)
{
// With initialBucketCount=8, entriesCapacity starts at 6 and the table
// does not shrink below initialBucketCount, so compactInPlace is the only
// rehash path triggered here.
OrderedHashSet<int> set;
for (int i = 0; i < 6; ++i)
set.add(i);
unsigned initialCapacity = set.capacity();
set.remove(1);
set.remove(2);
set.remove(3);
set.remove(4);
EXPECT_EQ(2u, set.size());
EXPECT_EQ(initialCapacity, set.capacity());
// Adding now triggers rehashForAdd. liveCount (2) < 3/4 * capacity (6),
// so compactInPlace runs and reuses both the entries and buckets allocations.
set.add(100);
EXPECT_EQ(3u, set.size());
EXPECT_EQ(initialCapacity, set.capacity());
Vector<int> values;
for (auto& v : set)
values.append(v);
EXPECT_EQ(3u, values.size());
EXPECT_EQ(0, values[0]);
EXPECT_EQ(5, values[1]);
EXPECT_EQ(100, values[2]);
EXPECT_TRUE(set.contains(0));
EXPECT_TRUE(set.contains(5));
EXPECT_TRUE(set.contains(100));
EXPECT_FALSE(set.contains(1));
EXPECT_FALSE(set.contains(4));
}
TEST(WTF_OrderedHashSet, CompactInPlaceOnLargerTable)
{
// Force a larger table so we can verify compactInPlace works beyond the
// initial capacity without triggering a grow or shrink rehash.
OrderedHashSet<int> set;
set.reserveInitialCapacity(12);
unsigned reservedCapacity = set.capacity();
EXPECT_GE(reservedCapacity, 12u);
for (int i = 0; i < 12; ++i)
set.add(i);
EXPECT_EQ(12u, set.size());
EXPECT_EQ(reservedCapacity, set.capacity());
// shrinkIfNeeded fires when liveCount < entriesLength / 4 (= 3). Keep liveCount
// at 3 so the subsequent add takes the compactInPlace branch, not shrink.
for (int i = 0; i < 9; ++i)
set.remove(i);
EXPECT_EQ(3u, set.size());
EXPECT_EQ(reservedCapacity, set.capacity());
set.add(100);
EXPECT_EQ(4u, set.size());
EXPECT_EQ(reservedCapacity, set.capacity());
Vector<int> values;
for (auto& v : set)
values.append(v);
EXPECT_EQ(4u, values.size());
EXPECT_EQ(9, values[0]);
EXPECT_EQ(10, values[1]);
EXPECT_EQ(11, values[2]);
EXPECT_EQ(100, values[3]);
}
TEST(WTF_OrderedHashSet, CompactInPlaceWithStrings)
{
// Non-trivial value type exercises move/destroy paths inside compactInPlace.
OrderedHashSet<String> set;
set.add("zero"_s);
set.add("one"_s);
set.add("two"_s);
set.add("three"_s);
set.add("four"_s);
set.add("five"_s);
unsigned initialCapacity = set.capacity();
set.remove("one"_s);
set.remove("two"_s);
set.remove("three"_s);
set.remove("four"_s);
set.add("new"_s);
EXPECT_EQ(initialCapacity, set.capacity());
EXPECT_EQ(3u, set.size());
Vector<String> values;
for (auto& v : set)
values.append(v);
EXPECT_EQ(3u, values.size());
EXPECT_EQ("zero"_s, values[0]);
EXPECT_EQ("five"_s, values[1]);
EXPECT_EQ("new"_s, values[2]);
EXPECT_TRUE(set.contains("zero"_s));
EXPECT_TRUE(set.contains("five"_s));
EXPECT_TRUE(set.contains("new"_s));
EXPECT_FALSE(set.contains("one"_s));
EXPECT_FALSE(set.contains("four"_s));
}
TEST(WTF_OrderedHashSet, CompactInPlaceRepeatedCycles)
{
// Many cycles of "delete most, refill to capacity" keep hitting the compactInPlace
// branch. Verify the table remains consistent across all of them.
OrderedHashSet<int> set;
set.reserveInitialCapacity(12);
unsigned capacity = set.capacity();
for (int i = 0; i < 12; ++i)
set.add(i);
int nextValue = 100;
for (int cycle = 0; cycle < 20; ++cycle) {
Vector<int> snapshot;
for (auto& v : set)
snapshot.append(v);
// Remove down to 3 live entries (just above the shrink threshold).
for (unsigned i = 0; i < snapshot.size() && set.size() > 3; ++i)
set.remove(snapshot[i]);
EXPECT_EQ(3u, set.size());
// Refilling will trigger compactInPlace on the first add past capacity.
while (set.size() < 12)
set.add(nextValue++);
EXPECT_EQ(12u, set.size());
EXPECT_EQ(capacity, set.capacity()); // No growth happened.
// All values must round-trip via contains.
Vector<int> live;
for (auto& v : set)
live.append(v);
for (int v : live)
EXPECT_TRUE(set.contains(v));
}
}
TEST(WTF_OrderedHashSet, RemoveIfAcrossShrinkThreshold)
{
// Fill past initialBucketCount so shrinkIfNeeded() can actually rehash, then
// removeIf enough entries to cross the 1/4 shrink threshold. Prior to the fix
// that deferred shrink, this would rehash mid-iteration and skip entries.
OrderedHashSet<int> set;
for (int i = 0; i < 64; ++i)
set.add(i);
EXPECT_EQ(64u, set.size());
bool changed = set.removeIf([](int value) {
return value >= 8;
});
EXPECT_TRUE(changed);
EXPECT_EQ(8u, set.size());
Vector<int> remaining;
for (int v : set)
remaining.append(v);
EXPECT_EQ(8u, remaining.size());
for (unsigned i = 0; i < remaining.size(); ++i)
EXPECT_EQ(static_cast<int>(i), remaining[i]);
for (int i = 0; i < 8; ++i)
EXPECT_TRUE(set.contains(i));
for (int i = 8; i < 64; ++i)
EXPECT_FALSE(set.contains(i));
}
TEST(WTF_OrderedHashSet, MoveOnlyValue)
{
OrderedHashSet<MoveOnly> set;
auto addResult = set.add(MoveOnly(1));
EXPECT_TRUE(addResult.isNewEntry);
EXPECT_EQ(1u, addResult.iterator->value());
set.add(MoveOnly(2));
set.add(MoveOnly(3));
EXPECT_EQ(3u, set.size());
EXPECT_TRUE(set.contains(MoveOnly(1)));
EXPECT_TRUE(set.contains(MoveOnly(2)));
EXPECT_TRUE(set.contains(MoveOnly(3)));
auto duplicate = set.add(MoveOnly(2));
EXPECT_FALSE(duplicate.isNewEntry);
EXPECT_EQ(3u, set.size());
auto taken = set.take(MoveOnly(2));
EXPECT_EQ(2u, taken.value());
EXPECT_FALSE(set.contains(MoveOnly(2)));
Vector<unsigned> remainingInOrder;
for (auto& v : set)
remainingInOrder.append(v.value());
EXPECT_EQ(2u, remainingInOrder.size());
EXPECT_EQ(1u, remainingInOrder[0]);
EXPECT_EQ(3u, remainingInOrder[1]);
}
TEST(WTF_OrderedHashSet, MoveOnlyValueRehash)
{
OrderedHashSet<MoveOnly> set;
for (unsigned i = 0; i < 64; ++i)
set.add(MoveOnly(i));
EXPECT_EQ(64u, set.size());
for (unsigned i = 0; i < 64; ++i)
EXPECT_TRUE(set.contains(MoveOnly(i)));
}
TEST(WTF_OrderedHashSet, HashTranslatorFindContains)
{
OrderedHashSet<String> set;
set.add("alpha"_s);
set.add("beta"_s);
set.add("gamma"_s);
auto it = set.find<StringViewHashTranslator>(StringView { "beta"_s });
EXPECT_TRUE(it != set.end());
EXPECT_EQ("beta"_s, *it);
EXPECT_TRUE(set.contains<StringViewHashTranslator>(StringView { "alpha"_s }));
EXPECT_TRUE(set.contains<StringViewHashTranslator>(StringView { "gamma"_s }));
EXPECT_FALSE(set.contains<StringViewHashTranslator>(StringView { "delta"_s }));
auto missing = set.find<StringViewHashTranslator>(StringView { "delta"_s });
EXPECT_TRUE(missing == set.end());
}
namespace {
// Mirrors the signature OrderedHashTable::add<HashTranslator> expects:
// translate(location, key, valueFunctor).
// Uses plain assignment into `location` — the canonical WTF translator convention.
// This is the shape that will silently corrupt raw storage if the entries array
// isn't pre-initialized to an empty value before translate() runs.
struct AssigningStringViewTranslator {
static unsigned hash(StringView view) { return StringHash::hash(view.toString()); }
static bool equal(const String& a, StringView b) { return a == b; }
static void translate(String& location, StringView view, NOESCAPE const auto&)
{
location = view.toString();
}
};
using StringOrderedHashTable = WTF::OrderedHashTable<String, String, WTF::IdentityExtractor, DefaultHash<String>, HashTraits<String>, HashTraits<String>, WTF::HashTableMalloc>;
// A value type whose operator= asserts the LHS is in a constructed state
// (one of the known sentinels). Without a pre-translate constructEmptyValue,
// the translator's `location = ...` assigns into raw malloc storage and the
// m_state field will be random bits, nearly always tripping the assert.
// liveCount tracks Live-state instances only; Empty/Deleted sentinels are
// not counted, mirroring WTF's convention that sentinels hold no resources.
class TrackedValue {
public:
enum State : uint32_t { Garbage = 0xDEADBEEF, Empty = 0xE11E, Deleted = 0xDEAD, Live = 0xA11E };
static int liveCount;
TrackedValue() : m_state(Empty), m_value(0) { }
explicit TrackedValue(int v) : m_state(Live), m_value(v) { ++liveCount; }
TrackedValue(const TrackedValue& other) : m_state(other.m_state), m_value(other.m_value)
{
if (m_state == Live)
++liveCount;
}
TrackedValue(TrackedValue&& other) noexcept : m_state(other.m_state), m_value(other.m_value)
{
// Move: dst increments, src keeps its state so its destructor balances.
if (m_state == Live)
++liveCount;
}
TrackedValue(WTF::HashTableDeletedValueType) : m_state(Deleted), m_value(0) { }
~TrackedValue()
{
if (m_state == Live)
--liveCount;
m_state = Garbage;
}
TrackedValue& operator=(const TrackedValue& other)
{
// LHS must be a constructed object (Empty/Live/Deleted). Raw malloc
// storage will have random bits, which almost never match a sentinel.
EXPECT_TRUE(m_state == Empty || m_state == Live || m_state == Deleted);
if (m_state == Live)
--liveCount;
m_state = other.m_state;
m_value = other.m_value;
if (m_state == Live)
++liveCount;
return *this;
}
bool isHashTableDeletedValue() const { return m_state == Deleted; }
bool operator==(const TrackedValue& other) const { return m_state == other.m_state && m_value == other.m_value; }
int value() const { return m_value; }
private:
uint32_t m_state;
int m_value;
};
int TrackedValue::liveCount = 0;
struct TrackedValueHash {
static unsigned hash(const TrackedValue& v) { return IntHash<int>::hash(v.value()); }
static bool equal(const TrackedValue& a, const TrackedValue& b) { return a == b; }
};
struct TrackedValueTraits : WTF::SimpleClassHashTraits<TrackedValue> {
// Force emptyValueIsZero=false so constructEmptyValue runs (not zeroBytes).
static constexpr bool emptyValueIsZero = false;
};
struct TrackedValueTranslator {
static unsigned hash(int v) { return IntHash<int>::hash(v); }
static bool equal(const TrackedValue& a, int b) { return a.value() == b; }
static void translate(TrackedValue& location, int v, NOESCAPE const auto&)
{
// Standard WTF translator convention: assign. LHS must be constructed.
location = TrackedValue(v);
}
};
using TrackedOrderedHashTable = WTF::OrderedHashTable<TrackedValue, TrackedValue, WTF::IdentityExtractor, TrackedValueHash, TrackedValueTraits, TrackedValueTraits, WTF::HashTableMalloc>;
}
TEST(WTF_OrderedHashSet, HashTranslatorAddConstructsEmptyBeforeAssign)
{
// Without the pre-translate constructEmptyValue, `location = ...` inside the
// translator would be invoked on raw malloc storage. TrackedValue::operator=
// asserts its LHS is a real constructed object, so the bug would EXPECT fail.
TrackedValue::liveCount = 0;
auto noFunctor = []() ALWAYS_INLINE_LAMBDA -> TrackedValue { return TrackedValue(); };
{
TrackedOrderedHashTable table;
for (int i = 0; i < 32; ++i) {
auto r = table.add<TrackedValueTranslator>(i, noFunctor);
EXPECT_TRUE(r.isNewEntry);
}
EXPECT_EQ(32u, table.size());
// Force deletions then re-add; after internal compact/rehash the translator
// path writes into slots whose prior occupants were destructed/moved-from —
// each such slot must again be re-constructed-empty before translate.
for (int i = 0; i < 32; i += 2)
table.remove(TrackedValue(i));
EXPECT_EQ(16u, table.size());
for (int i = 0; i < 32; i += 2) {
auto r = table.add<TrackedValueTranslator>(i, noFunctor);
EXPECT_TRUE(r.isNewEntry);
}
EXPECT_EQ(32u, table.size());
}
// Every constructed instance must have been destructed.
EXPECT_EQ(0, TrackedValue::liveCount);
}
TEST(WTF_OrderedHashSet, HashTranslatorAddIntoRawStorage)
{
// Exercises OrderedHashTable::add<HashTranslator>. A correctly-implemented WTF
// translator assigns into `location`; our entries array is raw malloc storage,
// so without an in-place empty-value construction before translate, String's
// assignment operator would deref garbage from the freshly malloc'd slot.
StringOrderedHashTable table;
auto noFunctor = []() ALWAYS_INLINE_LAMBDA -> String { return String(); };
auto a = table.add<AssigningStringViewTranslator>(StringView { "alpha"_s }, noFunctor);
EXPECT_TRUE(a.isNewEntry);
auto b = table.add<AssigningStringViewTranslator>(StringView { "beta"_s }, noFunctor);
EXPECT_TRUE(b.isNewEntry);
auto aAgain = table.add<AssigningStringViewTranslator>(StringView { "alpha"_s }, noFunctor);
EXPECT_FALSE(aAgain.isNewEntry);
EXPECT_EQ(2u, table.size());
auto it = table.begin();
EXPECT_EQ("alpha"_s, *it);
++it;
EXPECT_EQ("beta"_s, *it);
++it;
EXPECT_TRUE(it == table.end());
}
TEST(WTF_OrderedHashSet, HashTranslatorAddTriggersRehashAndCompact)
{
// Adding through the translator path past initial capacity must rehash (growing
// the entries array and re-placement-newing into it) without disturbing any
// in-flight empty-value state for the slot still being populated.
StringOrderedHashTable table;
auto noFunctor = []() ALWAYS_INLINE_LAMBDA -> String { return String(); };
Vector<String> keys;
for (unsigned i = 0; i < 64; ++i)
keys.append(String::number(i));
for (const auto& k : keys)
table.add<AssigningStringViewTranslator>(StringView { k }, noFunctor);
EXPECT_EQ(64u, table.size());
// Delete half, then add again via the translator — forces compactInPlace to
// run, after which the next translator add lands in a slot that was previously
// a deleted entry. The fix must still apply in that path.
for (unsigned i = 0; i < 64; i += 2)
table.remove(keys[i]);
EXPECT_EQ(32u, table.size());
for (unsigned i = 0; i < 64; i += 2) {
auto result = table.add<AssigningStringViewTranslator>(StringView { keys[i] }, noFunctor);
EXPECT_TRUE(result.isNewEntry);
}
EXPECT_EQ(64u, table.size());
// Verify all keys are present and iterable.
unsigned count = 0;
for (auto it = table.begin(); it != table.end(); ++it) {
EXPECT_TRUE(table.contains(*it));
++count;
}
EXPECT_EQ(64u, count);
}
TEST(WTF_OrderedHashSet, EqualIgnoringOrder)
{
// OrderedHashSet intentionally does not define operator==, so that the
// order-sensitive vs order-insensitive choice is explicit at the call
// site. equalIgnoringOrder compares contents, matching HashSet::operator==
// semantics.
OrderedHashSet<String> a;
a.add("alpha"_s);
a.add("beta"_s);
a.add("gamma"_s);
OrderedHashSet<String> b;
b.add("gamma"_s);
b.add("alpha"_s);
b.add("beta"_s);
EXPECT_TRUE(equalIgnoringOrder(a, b));
EXPECT_TRUE(equalIgnoringOrder(b, a));
auto itA = a.begin();
auto itB = b.begin();
EXPECT_NE(*itA, *itB);
}
TEST(WTF_OrderedHashSet, EqualIgnoringOrderDifferentSize)
{
OrderedHashSet<String> a;
a.add("alpha"_s);
a.add("beta"_s);
OrderedHashSet<String> b;
b.add("alpha"_s);
EXPECT_FALSE(equalIgnoringOrder(a, b));
EXPECT_FALSE(equalIgnoringOrder(b, a));
}
TEST(WTF_OrderedHashSet, EqualIgnoringOrderDifferentElements)
{
OrderedHashSet<String> a;
a.add("alpha"_s);
a.add("beta"_s);
OrderedHashSet<String> b;
b.add("alpha"_s);
b.add("delta"_s);
EXPECT_FALSE(equalIgnoringOrder(a, b));
EXPECT_FALSE(equalIgnoringOrder(b, a));
}
TEST(WTF_OrderedHashSet, EqualIgnoringOrderEmpty)
{
OrderedHashSet<String> a;
OrderedHashSet<String> b;
EXPECT_TRUE(equalIgnoringOrder(a, b));
a.add("key"_s);
EXPECT_FALSE(equalIgnoringOrder(a, b));
}
} // namespace TestWebKitAPI