/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include #include "gtest/gtest.h" #include "mozilla/ReverseIterator.h" #include "mozilla/dom/ECMACollections.h" #include "nsHashKeys.h" #include "nsISupportsImpl.h" #include "nsString.h" using mozilla::dom::ECMAMap; using mozilla::dom::ECMASet; using mozilla::dom::binding_detail::ECMAStorable; static_assert(ECMAStorable::value); static_assert(ECMAStorable::value); static_assert(!ECMAStorable::value); static_assert(!ECMAStorable::value); static_assert(!ECMAStorable::value); static_assert(!ECMAStorable>::value); static_assert(!ECMAStorable>::value); // The collections under test, with the length of their entry array (live // entries plus tombstones) exposed so that tests can observe compaction. template class TestSet : public ECMASet { public: uint32_t PhysicalLength() const { return this->mEntries.Length(); } }; template class TestMap : public ECMAMap { public: uint32_t PhysicalLength() const { return this->mEntries.Length(); } }; // ========================================================================== // ECMASet -- basic operations // ========================================================================== TEST(ECMASet, EmptySet) { TestSet set; EXPECT_EQ(set.Size(), 0u); EXPECT_TRUE(set.IsEmpty()); EXPECT_FALSE(set.Has(42)); } TEST(ECMASet, AddAndHas) { TestSet set; EXPECT_TRUE(set.Add(1)); EXPECT_TRUE(set.Add(2)); EXPECT_TRUE(set.Add(3)); EXPECT_EQ(set.Size(), 3u); EXPECT_TRUE(set.Has(1)); EXPECT_TRUE(set.Has(2)); EXPECT_TRUE(set.Has(3)); EXPECT_FALSE(set.Has(4)); } TEST(ECMASet, AddDuplicate) { TestSet set; EXPECT_TRUE(set.Add(1)); EXPECT_FALSE(set.Add(1)); EXPECT_EQ(set.Size(), 1u); } TEST(ECMASet, Delete) { TestSet set; set.Add(1); set.Add(2); set.Add(3); EXPECT_TRUE(set.Delete(2)); EXPECT_EQ(set.Size(), 2u); EXPECT_TRUE(set.Has(1)); EXPECT_FALSE(set.Has(2)); EXPECT_TRUE(set.Has(3)); } TEST(ECMASet, DeleteNonExistent) { TestSet set; set.Add(1); EXPECT_FALSE(set.Delete(99)); EXPECT_EQ(set.Size(), 1u); } TEST(ECMASet, DeleteAll) { TestSet set; set.Add(1); set.Add(2); set.Delete(1); set.Delete(2); EXPECT_EQ(set.Size(), 0u); EXPECT_TRUE(set.IsEmpty()); EXPECT_FALSE(set.Has(1)); EXPECT_FALSE(set.Has(2)); } TEST(ECMASet, DeleteTwice) { TestSet set; set.Add(1); EXPECT_TRUE(set.Delete(1)); EXPECT_FALSE(set.Delete(1)); EXPECT_EQ(set.Size(), 0u); } TEST(ECMASet, Clear) { TestSet set; set.Add(1); set.Add(2); set.Add(3); set.Clear(); EXPECT_EQ(set.Size(), 0u); EXPECT_TRUE(set.IsEmpty()); EXPECT_FALSE(set.Has(1)); } TEST(ECMASet, DoubleClear) { TestSet set; set.Add(1); set.Add(2); set.Clear(); EXPECT_EQ(set.Size(), 0u); set.Clear(); EXPECT_EQ(set.Size(), 0u); } TEST(ECMASet, ReAddAfterDelete) { TestSet set; set.Add(1); set.Add(2); set.Delete(1); EXPECT_TRUE(set.Add(1)); EXPECT_EQ(set.Size(), 2u); EXPECT_TRUE(set.Has(1)); } TEST(ECMASet, ReAddAfterClear) { TestSet set; set.Add(1); set.Add(2); set.Clear(); EXPECT_TRUE(set.Add(3)); EXPECT_EQ(set.Size(), 1u); EXPECT_TRUE(set.Has(3)); EXPECT_FALSE(set.Has(1)); } // ========================================================================== // ECMASet -- insertion-order iteration // ========================================================================== TEST(ECMASet, InsertionOrder) { TestSet set; set.Add(30); set.Add(10); set.Add(20); nsTArray result; for (const auto& val : set) { result.AppendElement(val); } ASSERT_EQ(result.Length(), 3u); EXPECT_EQ(result[0], 30); EXPECT_EQ(result[1], 10); EXPECT_EQ(result[2], 20); } TEST(ECMASet, IterationSkipsTombstones) { TestSet set; set.Add(1); set.Add(2); set.Add(3); set.Add(4); set.Delete(2); set.Delete(3); nsTArray result; for (const auto& val : set) { result.AppendElement(val); } ASSERT_EQ(result.Length(), 2u); EXPECT_EQ(result[0], 1); EXPECT_EQ(result[1], 4); } TEST(ECMASet, ReAddAppearsAtEnd) { TestSet set; set.Add(1); set.Add(2); set.Add(3); set.Delete(2); set.Add(2); nsTArray result; for (const auto& val : set) { result.AppendElement(val); } ASSERT_EQ(result.Length(), 3u); EXPECT_EQ(result[0], 1); EXPECT_EQ(result[1], 3); EXPECT_EQ(result[2], 2); } // ========================================================================== // ECMASet -- Cursor (for WebIDL codegen) // ========================================================================== TEST(ECMASet, CursorSkipsTombstones) { TestSet set; set.Add(10); set.Add(20); set.Add(30); set.Delete(20); auto cursor = set.CreateCursor(); nsTArray result; while (const auto* entry = cursor.Next()) { result.AppendElement(entry->mValue); } ASSERT_EQ(result.Length(), 2u); EXPECT_EQ(result[0], 10); EXPECT_EQ(result[1], 30); } // ========================================================================== // ECMASet -- compaction // ========================================================================== TEST(ECMASet, CompactionHappensWhenTombstonesExceedLive) { TestSet set; for (int i = 0; i < 10; i++) { set.Add(i); } for (int i = 0; i < 8; i++) { set.Delete(i); } EXPECT_EQ(set.Size(), 2u); EXPECT_LT(set.PhysicalLength(), 10u); nsTArray result; for (const auto& val : set) { result.AppendElement(val); } ASSERT_EQ(result.Length(), 2u); EXPECT_EQ(result[0], 8); EXPECT_EQ(result[1], 9); } TEST(ECMASet, ClearCompacts) { TestSet set; set.Add(1); set.Add(2); set.Add(3); set.Clear(); EXPECT_EQ(set.PhysicalLength(), 0u); EXPECT_EQ(set.Size(), 0u); } // ========================================================================== // ECMASet -- compaction under live iterators // // Compaction reclaims tombstoned memory while iterators are alive; the // collection moves their cursors so they resume at the same logical position. // ========================================================================== TEST(ECMASet, CompactionProceedsUnderLiveIterator) { TestSet set; for (int i = 0; i < 10; i++) { set.Add(i); } auto it = set.begin(); EXPECT_EQ(*it, 0); for (int i = 1; i < 10; i++) { set.Delete(i); } // The tombstones are reclaimed even though the iteration is unfinished. EXPECT_EQ(set.PhysicalLength(), 1u); EXPECT_EQ(set.Size(), 1u); // Not even the entry the iterator yielded last is pinned. set.Delete(0); EXPECT_EQ(set.PhysicalLength(), 0u); ++it; EXPECT_TRUE(it == set.end()); } TEST(ECMASet, CompactionUnderLiveIteratorYieldsRemainingEntries) { TestSet set; for (int i = 0; i < 10; i++) { set.Add(i); } auto it = set.begin(); EXPECT_EQ(*it, 0); ++it; EXPECT_EQ(*it, 1); for (int i = 2; i < 8; i++) { set.Delete(i); } EXPECT_EQ(set.PhysicalLength(), 4u); EXPECT_EQ(set.Size(), 4u); nsTArray rest; for (++it; it != set.end(); ++it) { rest.AppendElement(*it); } ASSERT_EQ(rest.Length(), 2u); EXPECT_EQ(rest[0], 8); EXPECT_EQ(rest[1], 9); } TEST(ECMASet, TwoLiveIteratorsResumeAfterCompaction) { TestSet set; for (int i = 0; i < 10; i++) { set.Add(i); } auto first = set.begin(); ++first; auto second = set.begin(); ++second; ++second; ++second; EXPECT_EQ(*first, 1); EXPECT_EQ(*second, 3); for (int i = 4; i < 10; i++) { set.Delete(i); } EXPECT_EQ(set.PhysicalLength(), 4u); nsTArray fromFirst; for (++first; first != set.end(); ++first) { fromFirst.AppendElement(*first); } ASSERT_EQ(fromFirst.Length(), 2u); EXPECT_EQ(fromFirst[0], 2); EXPECT_EQ(fromFirst[1], 3); ++second; EXPECT_TRUE(second == set.end()); } TEST(ECMASet, TwoIteratorsAtSamePositionResumeAfterCompaction) { TestSet set; for (int i = 0; i < 10; i++) { set.Add(i); } auto first = set.begin(); auto second = set.begin(); EXPECT_EQ(*first, 0); EXPECT_EQ(*second, 0); for (int i = 4; i < 10; i++) { set.Delete(i); } EXPECT_EQ(set.PhysicalLength(), 4u); nsTArray fromFirst; for (++first; first != set.end(); ++first) { fromFirst.AppendElement(*first); } nsTArray fromSecond; for (++second; second != set.end(); ++second) { fromSecond.AppendElement(*second); } ASSERT_EQ(fromFirst.Length(), 3u); EXPECT_EQ(fromFirst[0], 1); EXPECT_EQ(fromFirst[2], 3); ASSERT_EQ(fromSecond.Length(), fromFirst.Length()); for (size_t i = 0; i < fromFirst.Length(); i++) { EXPECT_EQ(fromSecond[i], fromFirst[i]); } } TEST(ECMASet, ReverseIteratorResumesAfterCompaction) { TestSet set; for (int i = 0; i < 10; i++) { set.Add(i); } auto it = set.rbegin(); EXPECT_EQ(*it, 9); ++it; EXPECT_EQ(*it, 8); for (int i = 0; i < 6; i++) { set.Delete(i); } EXPECT_EQ(set.PhysicalLength(), 4u); nsTArray rest; for (++it; it != set.rend(); ++it) { rest.AppendElement(*it); } ASSERT_EQ(rest.Length(), 2u); EXPECT_EQ(rest[0], 7); EXPECT_EQ(rest[1], 6); } TEST(ECMASet, ReverseCursorOnFirstTombstoneFinishes) { TestSet set; for (int i = 0; i < 10; i++) { set.Add(i); } auto it = set.rbegin(); while (*it != 1) { ++it; } // The next entry the iterator would examine is the tombstone at index 0. set.Delete(0); for (int i = 2; i < 7; i++) { set.Delete(i); } EXPECT_EQ(set.PhysicalLength(), 4u); ++it; EXPECT_TRUE(it == set.rend()); } TEST(ECMASet, CopiedIteratorResumesAfterCompaction) { TestSet set; for (int i = 0; i < 10; i++) { set.Add(i); } auto it = set.begin(); ++it; auto copy = it; EXPECT_TRUE(copy == it); EXPECT_EQ(*copy, 1); for (int i = 2; i < 8; i++) { set.Delete(i); } EXPECT_EQ(set.PhysicalLength(), 4u); ++it; EXPECT_FALSE(copy == it); ++copy; EXPECT_TRUE(copy == it); EXPECT_EQ(*it, 8); EXPECT_EQ(*copy, 8); } TEST(ECMASet, DeleteCurrentEntryResumesAtNextEntry) { TestSet set; for (int i = 0; i < 10; i++) { set.Add(i); } auto it = set.begin(); EXPECT_EQ(*it, 0); // The entry the iterator sits on is deleted, and the following deletes // compact the survivors underneath it. for (int i = 0; i < 6; i++) { set.Delete(i); } EXPECT_EQ(set.PhysicalLength(), 4u); nsTArray rest; for (++it; it != set.end(); ++it) { rest.AppendElement(*it); } ASSERT_EQ(rest.Length(), 4u); EXPECT_EQ(rest[0], 6); EXPECT_EQ(rest[1], 7); EXPECT_EQ(rest[2], 8); EXPECT_EQ(rest[3], 9); } TEST(ECMASet, DeleteEveryEntryWhileIteratingYieldsAll) { TestSet set; for (int i = 0; i < 10; i++) { set.Add(i); } nsTArray seen; for (auto it = set.begin(); it != set.end(); ++it) { const int value = *it; seen.AppendElement(value); ASSERT_LT(seen.Length(), 20u); set.Delete(value); } ASSERT_EQ(seen.Length(), 10u); for (size_t i = 0; i < seen.Length(); i++) { EXPECT_EQ(seen[i], static_cast(i)); } EXPECT_EQ(set.PhysicalLength(), 0u); EXPECT_TRUE(set.IsEmpty()); } TEST(ECMASet, ReverseDeleteCurrentEntryResumesAtPreviousEntry) { TestSet set; for (int i = 0; i < 10; i++) { set.Add(i); } auto it = set.rbegin(); EXPECT_EQ(*it, 9); set.Delete(9); for (int i = 0; i < 5; i++) { set.Delete(i); } EXPECT_EQ(set.PhysicalLength(), 4u); nsTArray rest; for (++it; it != set.rend(); ++it) { rest.AppendElement(*it); } ASSERT_EQ(rest.Length(), 4u); EXPECT_EQ(rest[0], 8); EXPECT_EQ(rest[1], 7); EXPECT_EQ(rest[2], 6); EXPECT_EQ(rest[3], 5); } TEST(ECMASet, MultipleLiveIteratorsDoNotSuppressCompaction) { TestSet set; for (int i = 0; i < 8; i++) { set.Add(i); } auto first = set.begin(); auto second = set.begin(); ++second; auto third = set.begin(); ++third; ++third; for (int i = 3; i < 8; i++) { set.Delete(i); } EXPECT_EQ(set.PhysicalLength(), 3u); EXPECT_EQ(set.Size(), 3u); ++first; ++second; ++third; EXPECT_EQ(*first, 1); EXPECT_EQ(*second, 2); EXPECT_TRUE(third == set.end()); } TEST(ECMASet, ClearUnderLiveIteratorReclaims) { TestSet set; set.Add(1); set.Add(2); set.Add(3); auto it = set.begin(); EXPECT_EQ(*it, 1); set.Clear(); EXPECT_EQ(set.PhysicalLength(), 0u); EXPECT_EQ(set.Size(), 0u); // Like an ES iterator, it goes on to the entries added after the clear. set.Add(4); EXPECT_EQ(set.PhysicalLength(), 1u); ++it; ASSERT_TRUE(it != set.end()); EXPECT_EQ(*it, 4); ++it; EXPECT_TRUE(it == set.end()); } TEST(ECMASet, UnlinkUnderLiveIterator) { TestSet set; set.Add(1); set.Add(2); set.Add(3); auto it = set.begin(); EXPECT_EQ(*it, 1); set.Unlink(); EXPECT_EQ(set.PhysicalLength(), 0u); EXPECT_EQ(set.Size(), 0u); // The detached cursor terminates instead of reading the discarded entries. ++it; EXPECT_TRUE(it == set.end()); } TEST(ECMASet, LongLivedIteratorDoesNotPinTombstones) { TestSet set; for (int i = 0; i < 4; i++) { set.Add(i); } auto it = set.begin(); for (int i = 0; i < 200; i++) { set.Add(100 + i); EXPECT_LE(set.PhysicalLength(), 2 * set.Size() + 2); set.Delete(100 + i); EXPECT_LE(set.PhysicalLength(), 2 * set.Size() + 2); } EXPECT_EQ(set.Size(), 4u); EXPECT_EQ(set.PhysicalLength(), 4u); nsTArray rest; for (++it; it != set.end(); ++it) { rest.AppendElement(*it); } ASSERT_EQ(rest.Length(), 3u); EXPECT_EQ(rest[0], 1); EXPECT_EQ(rest[2], 3); } TEST(ECMASet, RangeForSurvivesCompaction) { TestSet set; for (int i = 0; i < 10; i++) { set.Add(i); } nsTArray seen; for (const auto& val : set) { const int value = val; seen.AppendElement(value); if (value == 1) { for (int i = 2; i < 8; i++) { set.Delete(i); } EXPECT_EQ(set.PhysicalLength(), 4u); } } ASSERT_EQ(seen.Length(), 4u); EXPECT_EQ(seen[0], 0); EXPECT_EQ(seen[1], 1); EXPECT_EQ(seen[2], 8); EXPECT_EQ(seen[3], 9); } // ========================================================================== // ECMASet -- string keys and move semantics // ========================================================================== TEST(ECMASet, StringKeys) { TestSet set; set.Add(u"hello"_ns); set.Add(u"world"_ns); EXPECT_TRUE(set.Has(u"hello"_ns)); EXPECT_FALSE(set.Has(u"missing"_ns)); EXPECT_EQ(set.Size(), 2u); } TEST(ECMASet, MoveAdd) { TestSet set; nsString str(u"hello"_ns); set.Add(std::move(str)); EXPECT_TRUE(str.IsEmpty()); EXPECT_TRUE(set.Has(u"hello"_ns)); EXPECT_EQ(set.Size(), 1u); } TEST(ECMASet, MoveAddDuplicateDoesNotConsume) { TestSet set; set.Add(u"hello"_ns); nsString str(u"hello"_ns); EXPECT_FALSE(set.Add(std::move(str))); // Value not consumed on duplicate. EXPECT_FALSE(str.IsEmpty()); } // ========================================================================== // ECMAMap -- basic operations // ========================================================================== TEST(ECMAMap, EmptyMap) { TestMap map; EXPECT_EQ(map.Size(), 0u); EXPECT_TRUE(map.IsEmpty()); EXPECT_FALSE(map.Has(1)); EXPECT_EQ(map.Get(1), nullptr); } TEST(ECMAMap, SetAndGet) { TestMap map; EXPECT_TRUE(map.Set(1, 100)); EXPECT_TRUE(map.Set(2, 200)); EXPECT_EQ(map.Size(), 2u); ASSERT_NE(map.Get(1), nullptr); EXPECT_EQ(*map.Get(1), 100); ASSERT_NE(map.Get(2), nullptr); EXPECT_EQ(*map.Get(2), 200); EXPECT_EQ(map.Get(3), nullptr); } TEST(ECMAMap, ConstGet) { TestMap map; map.Set(1, 100); const auto& constMap = map; const int* val = constMap.Get(1); ASSERT_NE(val, nullptr); EXPECT_EQ(*val, 100); EXPECT_EQ(constMap.Get(2), nullptr); } TEST(ECMAMap, SetUpdatesValueInPlace) { TestMap map; map.Set(1, 100); map.Set(2, 200); EXPECT_FALSE(map.Set(1, 999)); EXPECT_EQ(map.Size(), 2u); EXPECT_EQ(*map.Get(1), 999); } TEST(ECMAMap, SetUpdatePreservesInsertionOrder) { TestMap map; map.Set(1, 100); map.Set(2, 200); map.Set(3, 300); map.Set(1, 999); nsTArray keys; for (const auto& entry : map) { keys.AppendElement(entry.mKey); } ASSERT_EQ(keys.Length(), 3u); EXPECT_EQ(keys[0], 1); EXPECT_EQ(keys[1], 2); EXPECT_EQ(keys[2], 3); } TEST(ECMAMap, Delete) { TestMap map; map.Set(1, 100); map.Set(2, 200); map.Set(3, 300); EXPECT_TRUE(map.Delete(2)); EXPECT_EQ(map.Size(), 2u); EXPECT_FALSE(map.Has(2)); EXPECT_EQ(map.Get(2), nullptr); EXPECT_TRUE(map.Has(1)); EXPECT_TRUE(map.Has(3)); } TEST(ECMAMap, DeleteNonExistent) { TestMap map; map.Set(1, 100); EXPECT_FALSE(map.Delete(99)); EXPECT_EQ(map.Size(), 1u); } TEST(ECMAMap, DeleteTwice) { TestMap map; map.Set(1, 100); EXPECT_TRUE(map.Delete(1)); EXPECT_FALSE(map.Delete(1)); EXPECT_EQ(map.Size(), 0u); EXPECT_EQ(map.Get(1), nullptr); } TEST(ECMAMap, Clear) { TestMap map; map.Set(1, 100); map.Set(2, 200); map.Clear(); EXPECT_EQ(map.Size(), 0u); EXPECT_FALSE(map.Has(1)); } TEST(ECMAMap, DoubleClear) { TestMap map; map.Set(1, 100); map.Set(2, 200); map.Clear(); EXPECT_EQ(map.Size(), 0u); map.Clear(); EXPECT_EQ(map.Size(), 0u); } TEST(ECMAMap, GetReturnsNullAfterDelete) { TestMap map; map.Set(1, 100); map.Set(2, 200); map.Delete(1); EXPECT_EQ(map.Get(1), nullptr); ASSERT_NE(map.Get(2), nullptr); EXPECT_EQ(*map.Get(2), 200); } TEST(ECMAMap, ReAddAfterDelete) { TestMap map; map.Set(1, 100); map.Set(2, 200); map.Delete(1); EXPECT_TRUE(map.Set(1, 999)); EXPECT_EQ(map.Size(), 2u); EXPECT_EQ(*map.Get(1), 999); } // ========================================================================== // ECMAMap -- insertion-order iteration // ========================================================================== TEST(ECMAMap, InsertionOrder) { TestMap map; map.Set(30, 300); map.Set(10, 100); map.Set(20, 200); nsTArray keys; nsTArray values; for (auto iter = map.begin(); iter != map.end(); ++iter) { keys.AppendElement(iter->mKey); values.AppendElement(iter->mValue); } ASSERT_EQ(keys.Length(), 3u); EXPECT_EQ(keys[0], 30); EXPECT_EQ(keys[1], 10); EXPECT_EQ(keys[2], 20); EXPECT_EQ(values[0], 300); EXPECT_EQ(values[1], 100); EXPECT_EQ(values[2], 200); } TEST(ECMAMap, IterationSkipsTombstones) { TestMap map; map.Set(1, 100); map.Set(2, 200); map.Set(3, 300); map.Set(4, 400); map.Delete(2); map.Delete(3); nsTArray keys; for (const auto& entry : map) { keys.AppendElement(entry.mKey); } ASSERT_EQ(keys.Length(), 2u); EXPECT_EQ(keys[0], 1); EXPECT_EQ(keys[1], 4); } TEST(ECMAMap, ReAddAppearsAtEnd) { TestMap map; map.Set(1, 100); map.Set(2, 200); map.Set(3, 300); map.Delete(2); map.Set(2, 999); nsTArray keys; for (const auto& entry : map) { keys.AppendElement(entry.mKey); } ASSERT_EQ(keys.Length(), 3u); EXPECT_EQ(keys[0], 1); EXPECT_EQ(keys[1], 3); EXPECT_EQ(keys[2], 2); } // ========================================================================== // ECMAMap -- Cursor // ========================================================================== TEST(ECMAMap, CursorSkipsTombstones) { TestMap map; map.Set(10, 100); map.Set(20, 200); map.Set(30, 300); map.Delete(20); auto cursor = map.CreateCursor(); nsTArray keys; while (const auto* entry = cursor.Next()) { keys.AppendElement(entry->mKey); } ASSERT_EQ(keys.Length(), 2u); EXPECT_EQ(keys[0], 10); EXPECT_EQ(keys[1], 30); } // ========================================================================== // ECMAMap -- compaction // ========================================================================== TEST(ECMAMap, CompactionHappensWhenTombstonesExceedLive) { TestMap map; for (int i = 0; i < 10; i++) { map.Set(i, i * 100); } for (int i = 0; i < 8; i++) { map.Delete(i); } EXPECT_EQ(map.Size(), 2u); EXPECT_LT(map.PhysicalLength(), 10u); EXPECT_EQ(*map.Get(8), 800); EXPECT_EQ(*map.Get(9), 900); } TEST(ECMAMap, ClearCompacts) { TestMap map; map.Set(1, 100); map.Set(2, 200); map.Set(3, 300); map.Clear(); EXPECT_EQ(map.PhysicalLength(), 0u); EXPECT_EQ(map.Size(), 0u); } // ========================================================================== // ECMAMap -- compaction under live iterators // ========================================================================== TEST(ECMAMap, CompactionProceedsUnderLiveIterator) { TestMap map; for (int i = 0; i < 10; i++) { map.Set(i, i * 100); } auto it = map.begin(); EXPECT_EQ(it->mKey, 0); for (int i = 1; i < 10; i++) { map.Delete(i); } EXPECT_EQ(map.PhysicalLength(), 1u); EXPECT_EQ(map.Size(), 1u); map.Delete(0); EXPECT_EQ(map.PhysicalLength(), 0u); ++it; EXPECT_TRUE(it == map.end()); } TEST(ECMAMap, CompactionUnderLiveIteratorYieldsRemainingEntries) { TestMap map; for (int i = 0; i < 10; i++) { map.Set(i, i * 100); } auto it = map.begin(); EXPECT_EQ(it->mKey, 0); ++it; EXPECT_EQ(it->mKey, 1); for (int i = 2; i < 8; i++) { map.Delete(i); } EXPECT_EQ(map.PhysicalLength(), 4u); EXPECT_EQ(map.Size(), 4u); nsTArray keys; nsTArray values; for (++it; it != map.end(); ++it) { keys.AppendElement(it->mKey); values.AppendElement(it->mValue); } ASSERT_EQ(keys.Length(), 2u); EXPECT_EQ(keys[0], 8); EXPECT_EQ(keys[1], 9); EXPECT_EQ(values[0], 800); EXPECT_EQ(values[1], 900); } TEST(ECMAMap, TwoLiveIteratorsResumeAfterCompaction) { TestMap map; for (int i = 0; i < 10; i++) { map.Set(i, i * 100); } auto first = map.begin(); ++first; auto second = map.begin(); ++second; ++second; ++second; EXPECT_EQ(first->mKey, 1); EXPECT_EQ(second->mKey, 3); for (int i = 4; i < 10; i++) { map.Delete(i); } EXPECT_EQ(map.PhysicalLength(), 4u); nsTArray fromFirst; for (++first; first != map.end(); ++first) { fromFirst.AppendElement(first->mKey); } ASSERT_EQ(fromFirst.Length(), 2u); EXPECT_EQ(fromFirst[0], 2); EXPECT_EQ(fromFirst[1], 3); ++second; EXPECT_TRUE(second == map.end()); } TEST(ECMAMap, TwoIteratorsAtSamePositionResumeAfterCompaction) { TestMap map; for (int i = 0; i < 10; i++) { map.Set(i, i * 100); } auto first = map.begin(); auto second = map.begin(); EXPECT_EQ(first->mKey, 0); EXPECT_EQ(second->mKey, 0); for (int i = 4; i < 10; i++) { map.Delete(i); } EXPECT_EQ(map.PhysicalLength(), 4u); nsTArray fromFirst; for (++first; first != map.end(); ++first) { fromFirst.AppendElement(first->mKey); } nsTArray fromSecond; for (++second; second != map.end(); ++second) { fromSecond.AppendElement(second->mKey); } ASSERT_EQ(fromFirst.Length(), 3u); EXPECT_EQ(fromFirst[0], 1); EXPECT_EQ(fromFirst[2], 3); ASSERT_EQ(fromSecond.Length(), fromFirst.Length()); for (size_t i = 0; i < fromFirst.Length(); i++) { EXPECT_EQ(fromSecond[i], fromFirst[i]); } } TEST(ECMAMap, ReverseIteratorResumesAfterCompaction) { TestMap map; for (int i = 0; i < 10; i++) { map.Set(i, i * 100); } auto it = map.rbegin(); EXPECT_EQ(it->mKey, 9); ++it; EXPECT_EQ(it->mKey, 8); for (int i = 0; i < 6; i++) { map.Delete(i); } EXPECT_EQ(map.PhysicalLength(), 4u); nsTArray keys; for (++it; it != map.rend(); ++it) { keys.AppendElement(it->mKey); } ASSERT_EQ(keys.Length(), 2u); EXPECT_EQ(keys[0], 7); EXPECT_EQ(keys[1], 6); } TEST(ECMAMap, DeleteCurrentEntryResumesAtNextEntry) { TestMap map; for (int i = 0; i < 10; i++) { map.Set(i, i * 100); } auto it = map.begin(); EXPECT_EQ(it->mKey, 0); for (int i = 0; i < 6; i++) { map.Delete(i); } EXPECT_EQ(map.PhysicalLength(), 4u); nsTArray keys; nsTArray values; for (++it; it != map.end(); ++it) { keys.AppendElement(it->mKey); values.AppendElement(it->mValue); } ASSERT_EQ(keys.Length(), 4u); EXPECT_EQ(keys[0], 6); EXPECT_EQ(keys[3], 9); EXPECT_EQ(values[0], 600); EXPECT_EQ(values[3], 900); } TEST(ECMAMap, ReverseDeleteCurrentEntryResumesAtPreviousEntry) { TestMap map; for (int i = 0; i < 10; i++) { map.Set(i, i * 100); } auto it = map.rbegin(); EXPECT_EQ(it->mKey, 9); map.Delete(9); for (int i = 0; i < 5; i++) { map.Delete(i); } EXPECT_EQ(map.PhysicalLength(), 4u); nsTArray keys; for (++it; it != map.rend(); ++it) { keys.AppendElement(it->mKey); } ASSERT_EQ(keys.Length(), 4u); EXPECT_EQ(keys[0], 8); EXPECT_EQ(keys[1], 7); EXPECT_EQ(keys[2], 6); EXPECT_EQ(keys[3], 5); } TEST(ECMAMap, ClearUnderLiveIteratorReclaims) { TestMap map; map.Set(1, 100); map.Set(2, 200); map.Set(3, 300); auto it = map.begin(); EXPECT_EQ(it->mKey, 1); map.Clear(); EXPECT_EQ(map.PhysicalLength(), 0u); EXPECT_EQ(map.Size(), 0u); // Like an ES iterator, it goes on to the entries added after the clear. map.Set(4, 400); EXPECT_EQ(map.PhysicalLength(), 1u); ++it; ASSERT_TRUE(it != map.end()); EXPECT_EQ(it->mKey, 4); ++it; EXPECT_TRUE(it == map.end()); } TEST(ECMAMap, LongLivedIteratorDoesNotPinTombstones) { TestMap map; for (int i = 0; i < 4; i++) { map.Set(i, i * 100); } auto it = map.begin(); for (int i = 0; i < 200; i++) { map.Set(100 + i, i); EXPECT_LE(map.PhysicalLength(), 2 * map.Size() + 2); map.Delete(100 + i); EXPECT_LE(map.PhysicalLength(), 2 * map.Size() + 2); } EXPECT_EQ(map.Size(), 4u); EXPECT_EQ(map.PhysicalLength(), 4u); nsTArray rest; for (++it; it != map.end(); ++it) { rest.AppendElement(it->mKey); } ASSERT_EQ(rest.Length(), 3u); EXPECT_EQ(rest[0], 1); EXPECT_EQ(rest[2], 3); } // ========================================================================== // ECMAMap -- string keys and move semantics // ========================================================================== TEST(ECMAMap, StringKeys) { TestMap map; map.Set(u"hello"_ns, 1); map.Set(u"world"_ns, 2); EXPECT_TRUE(map.Has(u"hello"_ns)); EXPECT_FALSE(map.Has(u"missing"_ns)); ASSERT_NE(map.Get(u"hello"_ns), nullptr); EXPECT_EQ(*map.Get(u"hello"_ns), 1); } TEST(ECMAMap, MoveSet) { TestMap map; nsString key(u"key"_ns); nsString val(u"value"_ns); map.Set(std::move(key), std::move(val)); EXPECT_TRUE(key.IsEmpty()); EXPECT_TRUE(val.IsEmpty()); EXPECT_TRUE(map.Has(u"key"_ns)); EXPECT_EQ(*map.Get(u"key"_ns), u"value"_ns); } TEST(ECMAMap, MoveSetUpdateDoesNotConsumeKey) { TestMap map; map.Set(u"key"_ns, u"old"_ns); nsString key(u"key"_ns); nsString val(u"new"_ns); EXPECT_FALSE(map.Set(std::move(key), std::move(val))); // Key not consumed on update (only value is moved). EXPECT_FALSE(key.IsEmpty()); EXPECT_TRUE(val.IsEmpty()); EXPECT_EQ(*map.Get(u"key"_ns), u"new"_ns); } // ========================================================================== // ECMAMap -- RefPtr value unwrapping // ========================================================================== class TestRefCounted final { public: NS_INLINE_DECL_REFCOUNTING(TestRefCounted) explicit TestRefCounted(int aValue) : mValue(aValue) {} int mValue; private: ~TestRefCounted() = default; }; // ========================================================================== // ECMASet -- mutation during iteration (ECMA-262 iterator semantics) // ========================================================================== // ECMA-262 sec-set.prototype.delete: deleting an entry ahead of the cursor // must cause the iterator to skip it. TEST(ECMASet, DeleteAheadDuringIteration) { TestSet set; set.Add(1); set.Add(2); set.Add(3); set.Add(4); auto cursor = set.CreateCursor(); nsTArray seen; while (const auto* entry = cursor.Next()) { seen.AppendElement(entry->mValue); if (seen.LastElement() == 1) { // Delete the two entries the cursor has not reached yet. set.Delete(2); set.Delete(3); } } ASSERT_EQ(seen.Length(), 2u); EXPECT_EQ(seen[0], 1); EXPECT_EQ(seen[1], 4); } // ECMA-262 sec-set.prototype.add: adding an entry during iteration appends // it to the end, and the iterator must visit it. TEST(ECMASet, AddDuringIteration) { TestSet set; set.Add(1); set.Add(2); auto cursor = set.CreateCursor(); nsTArray seen; while (const auto* entry = cursor.Next()) { seen.AppendElement(entry->mValue); if (seen.LastElement() == 2) { set.Add(3); } } ASSERT_EQ(seen.Length(), 3u); EXPECT_EQ(seen[0], 1); EXPECT_EQ(seen[1], 2); EXPECT_EQ(seen[2], 3); } // ECMA-262 sec-set.prototype.clear: "The existing [[SetData]] List is // preserved because there may be existing Set Iterator objects that are // suspended midway through iterating over that List." // The iterator must see no more live entries and must terminate. TEST(ECMASet, ClearDuringIteration) { TestSet set; set.Add(1); set.Add(2); set.Add(3); auto cursor = set.CreateCursor(); nsTArray seen; while (const auto* entry = cursor.Next()) { seen.AppendElement(entry->mValue); if (seen.LastElement() == 1) { set.Clear(); } } ASSERT_EQ(seen.Length(), 1u); EXPECT_EQ(seen[0], 1); EXPECT_EQ(set.PhysicalLength(), 0u); } // Delete-then-re-add during iteration: the re-added entry appears at the end // and is visited exactly once, at its new position. TEST(ECMASet, DeleteAndReAddDuringIteration) { TestSet set; set.Add(1); set.Add(2); set.Add(3); auto cursor = set.CreateCursor(); nsTArray seen; while (const auto* entry = cursor.Next()) { seen.AppendElement(entry->mValue); if (seen.LastElement() == 1) { set.Delete(2); set.Add(2); } } ASSERT_EQ(seen.Length(), 3u); EXPECT_EQ(seen[0], 1); EXPECT_EQ(seen[1], 3); EXPECT_EQ(seen[2], 2); } // Adding and deleting on every step, which also drives compaction: every // entry that is live when the cursor reaches it is yielded exactly once, and // the iteration terminates. TEST(ECMASet, EachEntryYieldedOnceUnderChurn) { TestSet set; for (int i = 0; i < 6; i++) { set.Add(i); } auto cursor = set.CreateCursor(); nsTArray seen; uint32_t mutations = 0; while (const auto* entry = cursor.Next()) { const int value = entry->mValue; seen.AppendElement(value); ASSERT_LT(seen.Length(), 20u); if (mutations++ < 3) { set.Delete(value + 1); set.Add(100 + value); } } ASSERT_EQ(seen.Length(), 6u); EXPECT_EQ(seen[0], 0); EXPECT_EQ(seen[1], 2); EXPECT_EQ(seen[2], 4); EXPECT_EQ(seen[3], 100); EXPECT_EQ(seen[4], 102); EXPECT_EQ(seen[5], 104); } // ========================================================================== // ECMASet -- mutation during C++ range-for (ConstIterator) // ========================================================================== TEST(ECMASet, DeleteDuringRangeFor) { TestSet set; set.Add(1); set.Add(2); set.Add(3); set.Add(4); nsTArray seen; for (const auto& val : set) { seen.AppendElement(val); if (val == 1) { // Delete ahead — 2 should be skipped, 3 and 4 still visited. set.Delete(2); } } ASSERT_EQ(seen.Length(), 3u); EXPECT_EQ(seen[0], 1); EXPECT_EQ(seen[1], 3); EXPECT_EQ(seen[2], 4); } TEST(ECMASet, AddDuringExplicitForLoop) { TestSet set; set.Add(1); set.Add(2); nsTArray seen; for (auto it = set.begin(); it != set.end(); ++it) { seen.AppendElement(*it); if (*it == 2) { set.Add(3); } } ASSERT_EQ(seen.Length(), 3u); EXPECT_EQ(seen[0], 1); EXPECT_EQ(seen[1], 2); EXPECT_EQ(seen[2], 3); } TEST(ECMASet, AddDuringRangeFor) { TestSet set; set.Add(1); set.Add(2); nsTArray seen; for (const auto& val : set) { seen.AppendElement(val); if (val == 2) { set.Add(3); } } ASSERT_EQ(seen.Length(), 3u); EXPECT_EQ(seen[0], 1); EXPECT_EQ(seen[1], 2); EXPECT_EQ(seen[2], 3); } // ========================================================================== // ECMAMap -- mutation during iteration (ECMA-262 iterator semantics) // ========================================================================== TEST(ECMAMap, DeleteAheadDuringIteration) { TestMap map; map.Set(1, 100); map.Set(2, 200); map.Set(3, 300); map.Set(4, 400); auto cursor = map.CreateCursor(); nsTArray seen; while (const auto* entry = cursor.Next()) { seen.AppendElement(entry->mKey); if (seen.LastElement() == 1) { map.Delete(2); map.Delete(3); } } ASSERT_EQ(seen.Length(), 2u); EXPECT_EQ(seen[0], 1); EXPECT_EQ(seen[1], 4); } TEST(ECMAMap, SetNewKeyDuringIteration) { TestMap map; map.Set(1, 100); map.Set(2, 200); auto cursor = map.CreateCursor(); nsTArray keys; while (const auto* entry = cursor.Next()) { keys.AppendElement(entry->mKey); if (keys.LastElement() == 2) { map.Set(3, 300); } } ASSERT_EQ(keys.Length(), 3u); EXPECT_EQ(keys[0], 1); EXPECT_EQ(keys[1], 2); EXPECT_EQ(keys[2], 3); } TEST(ECMAMap, SetExistingKeyDuringIteration) { TestMap map; map.Set(1, 100); map.Set(2, 200); map.Set(3, 300); auto cursor = map.CreateCursor(); nsTArray keys; nsTArray values; while (const auto* entry = cursor.Next()) { keys.AppendElement(entry->mKey); values.AppendElement(entry->mValue); if (keys.LastElement() == 1) { // Updating an existing key must not change iteration order or count. map.Set(2, 999); } } ASSERT_EQ(keys.Length(), 3u); EXPECT_EQ(keys[0], 1); EXPECT_EQ(keys[1], 2); EXPECT_EQ(keys[2], 3); EXPECT_EQ(values[1], 999); } TEST(ECMAMap, ClearDuringIteration) { TestMap map; map.Set(1, 100); map.Set(2, 200); map.Set(3, 300); auto cursor = map.CreateCursor(); nsTArray keys; while (const auto* entry = cursor.Next()) { keys.AppendElement(entry->mKey); if (keys.LastElement() == 1) { map.Clear(); } } ASSERT_EQ(keys.Length(), 1u); EXPECT_EQ(keys[0], 1); EXPECT_EQ(map.PhysicalLength(), 0u); } TEST(ECMAMap, DeleteAndReAddDuringIteration) { TestMap map; map.Set(1, 100); map.Set(2, 200); map.Set(3, 300); auto cursor = map.CreateCursor(); nsTArray keys; nsTArray values; while (const auto* entry = cursor.Next()) { keys.AppendElement(entry->mKey); values.AppendElement(entry->mValue); if (keys.LastElement() == 1) { map.Delete(2); map.Set(2, 999); } } ASSERT_EQ(keys.Length(), 3u); EXPECT_EQ(keys[0], 1); EXPECT_EQ(keys[1], 3); EXPECT_EQ(keys[2], 2); EXPECT_EQ(values[2], 999); } TEST(ECMAMap, EachEntryYieldedOnceUnderChurn) { TestMap map; for (int i = 0; i < 6; i++) { map.Set(i, i * 100); } auto cursor = map.CreateCursor(); nsTArray keys; uint32_t mutations = 0; while (const auto* entry = cursor.Next()) { const int key = entry->mKey; keys.AppendElement(key); ASSERT_LT(keys.Length(), 20u); if (mutations++ < 3) { map.Delete(key + 1); map.Set(100 + key, key); } } ASSERT_EQ(keys.Length(), 6u); EXPECT_EQ(keys[0], 0); EXPECT_EQ(keys[1], 2); EXPECT_EQ(keys[2], 4); EXPECT_EQ(keys[3], 100); EXPECT_EQ(keys[4], 102); EXPECT_EQ(keys[5], 104); } // ========================================================================== // ECMAMap -- mutation during C++ range-for (ConstIterator) // ========================================================================== TEST(ECMAMap, DeleteDuringRangeFor) { TestMap map; map.Set(1, 100); map.Set(2, 200); map.Set(3, 300); map.Set(4, 400); nsTArray seen; for (const auto& entry : map) { const int key = entry.mKey; seen.AppendElement(key); if (key == 1) { map.Delete(2); } } ASSERT_EQ(seen.Length(), 3u); EXPECT_EQ(seen[0], 1); EXPECT_EQ(seen[1], 3); EXPECT_EQ(seen[2], 4); } // Its destructor sets an entry in the map that held the last reference to it. class ReentrantValue final { public: NS_INLINE_DECL_REFCOUNTING(ReentrantValue) explicit ReentrantValue(TestMap>* aMap) : mMap(aMap) {} private: ~ReentrantValue() { if (mMap) { mMap->Set(-1, mozilla::MakeRefPtr(nullptr)); } } TestMap>* mMap; }; TEST(ECMAMap, DeleteReleasesValueAfterMutation) { TestMap> map; map.Set(1, mozilla::MakeRefPtr(&map)); EXPECT_TRUE(map.Delete(1)); EXPECT_FALSE(map.Has(1)); EXPECT_TRUE(map.Has(-1)); EXPECT_EQ(map.Size(), 1u); } TEST(ECMAMap, ClearReleasesValuesAfterMutation) { TestMap> map; map.Set(1, mozilla::MakeRefPtr(&map)); map.Set(2, mozilla::MakeRefPtr(nullptr)); map.Clear(); EXPECT_TRUE(map.Has(-1)); EXPECT_EQ(map.Size(), 1u); } TEST(ECMAMap, SetReleasesReplacedValueAfterMutation) { TestMap> map; map.Set(1, mozilla::MakeRefPtr(&map)); EXPECT_FALSE(map.Set(1, mozilla::MakeRefPtr(nullptr))); EXPECT_TRUE(map.Has(-1)); EXPECT_EQ(map.Size(), 2u); } TEST(ECMAMap, SetNewKeyDuringExplicitForLoop) { TestMap map; map.Set(1, 100); map.Set(2, 200); nsTArray keys; for (auto it = map.begin(); it != map.end(); ++it) { const int key = it->mKey; keys.AppendElement(key); if (key == 2) { map.Set(3, 300); } } ASSERT_EQ(keys.Length(), 3u); EXPECT_EQ(keys[0], 1); EXPECT_EQ(keys[1], 2); EXPECT_EQ(keys[2], 3); } // ========================================================================== // ECMAMap -- RefPtr value unwrapping // ========================================================================== TEST(ECMAMap, GetReturnsRawPointerForRefPtrValues) { TestMap> map; auto obj = mozilla::MakeRefPtr(42); TestRefCounted* raw = obj.get(); map.Set(1, obj); // Get() returns TestRefCounted*, not RefPtr*. TestRefCounted* result = map.Get(1); ASSERT_NE(result, nullptr); EXPECT_EQ(result, raw); EXPECT_EQ(result->mValue, 42); EXPECT_EQ(map.Get(2), nullptr); } TEST(ECMAMap, ConstGetReturnsRawPointerForRefPtrValues) { TestMap> map; map.Set(1, mozilla::MakeRefPtr(99)); const auto& constMap = map; TestRefCounted* result = constMap.Get(1); ASSERT_NE(result, nullptr); EXPECT_EQ(result->mValue, 99); } // ========================================================================== // Lookup types: entries are looked up by the hash key's KeyType, so a stored // nsString is found by any nsAString and a stored RefPtr by a raw T*. // ========================================================================== TEST(ECMASet, StringKeysLookUpBySubstring) { TestSet set; set.Add(u"hello"_ns); set.Add(u"world"_ns); const nsAString& hello = Substring(u"say hello"_ns, 4); const nsAString& missing = nsDependentString(u"missing"); EXPECT_TRUE(set.Has(hello)); EXPECT_FALSE(set.Has(missing)); EXPECT_TRUE(set.Delete(hello)); EXPECT_FALSE(set.Has(hello)); EXPECT_EQ(set.Size(), 1u); } TEST(ECMAMap, StringKeysLookUpBySubstring) { TestMap map; map.Set(u"hello"_ns, 1); const nsAString& hello = Substring(u"say hello"_ns, 4); EXPECT_TRUE(map.Has(hello)); ASSERT_NE(map.Get(hello), nullptr); EXPECT_EQ(*map.Get(hello), 1); EXPECT_TRUE(map.Delete(hello)); EXPECT_EQ(map.Get(hello), nullptr); } TEST(ECMASet, RefPtrKeysLookUpByRawPointer) { TestSet> set; auto first = mozilla::MakeRefPtr(1); auto second = mozilla::MakeRefPtr(2); set.Add(first); set.Add(second); TestRefCounted* raw = first.get(); EXPECT_TRUE(set.Has(raw)); EXPECT_TRUE(set.Has(second)); EXPECT_TRUE(set.Delete(raw)); EXPECT_FALSE(set.Has(raw)); EXPECT_EQ(set.Size(), 1u); } TEST(ECMAMap, RefPtrKeysLookUpByRawPointer) { TestMap, int> map; auto key = mozilla::MakeRefPtr(1); map.Set(key, 100); TestRefCounted* raw = key.get(); EXPECT_TRUE(map.Has(raw)); ASSERT_NE(map.Get(raw), nullptr); EXPECT_EQ(*map.Get(raw), 100); EXPECT_TRUE(map.Delete(raw)); EXPECT_EQ(map.Get(raw), nullptr); } // ========================================================================== // Binding argument types: the collections also take what the generated // bindings pass, a U& for a stored RefPtr and a substring for a stored // nsString. // ========================================================================== TEST(ECMASet, RefPtrKeysTakeReferences) { TestSet> set; auto obj = mozilla::MakeRefPtr(1); TestRefCounted& ref = *obj; EXPECT_TRUE(set.Add(ref)); EXPECT_FALSE(set.Add(ref)); EXPECT_TRUE(set.Has(ref)); EXPECT_TRUE(set.Delete(ref)); EXPECT_FALSE(set.Has(ref)); } TEST(ECMASet, StringKeysAddSubstring) { TestSet set; const nsAString& hello = Substring(u"say hello"_ns, 4); EXPECT_TRUE(set.Add(hello)); EXPECT_FALSE(set.Add(hello)); EXPECT_TRUE(set.Has(u"hello"_ns)); EXPECT_EQ(*set.begin(), u"hello"_ns); } TEST(ECMAMap, RefPtrKeysAndValuesTakeReferences) { TestMap, RefPtr> map; auto key = mozilla::MakeRefPtr(1); auto first = mozilla::MakeRefPtr(2); auto second = mozilla::MakeRefPtr(3); EXPECT_TRUE(map.Set(*key, *first)); EXPECT_FALSE(map.Set(*key, *second)); EXPECT_TRUE(map.Has(*key)); EXPECT_EQ(map.Get(*key), second.get()); EXPECT_TRUE(map.Delete(*key)); EXPECT_EQ(map.Get(*key), nullptr); } TEST(ECMAMap, StringKeysSetSubstring) { TestMap> map; auto value = mozilla::MakeRefPtr(1); const nsAString& hello = Substring(u"say hello"_ns, 4); EXPECT_TRUE(map.Set(hello, *value)); EXPECT_EQ(map.Get(u"hello"_ns), value.get()); } // ========================================================================== // Cursors // ========================================================================== TEST(ECMASet, CopiedCursorContinuesIndependently) { TestSet set; set.Add(1); set.Add(2); set.Add(3); auto cursor = set.CreateCursor(); EXPECT_EQ(cursor.Next()->mValue, 1); auto copy = cursor; EXPECT_EQ(cursor.Next()->mValue, 2); EXPECT_EQ(cursor.Next()->mValue, 3); EXPECT_EQ(cursor.Next(), nullptr); EXPECT_EQ(copy.Next()->mValue, 2); } TEST(ECMASet, ReverseCursor) { TestSet set; set.Add(1); set.Add(2); set.Add(3); set.Delete(2); auto cursor = set.CreateReverseCursor(); EXPECT_EQ(cursor.Next()->mValue, 3); EXPECT_EQ(cursor.Next()->mValue, 1); EXPECT_EQ(cursor.Next(), nullptr); } TEST(ECMAMap, CursorEndsOnUnlink) { TestMap map; map.Set(1, 100); map.Set(2, 200); auto cursor = map.CreateCursor(); EXPECT_EQ(cursor.Next()->mKey, 1); map.Unlink(); EXPECT_EQ(cursor.Next(), nullptr); } // ========================================================================== // Standard container behaviour // ========================================================================== TEST(ECMASet, WorksWithStandardAlgorithms) { TestSet set; for (int i = 0; i < 5; i++) { set.Add(i * 10); } set.Delete(20); auto found = std::find_if(set.begin(), set.end(), [](int aValue) { return aValue > 15; }); ASSERT_TRUE(found != set.end()); EXPECT_EQ(*found, 30); EXPECT_EQ(std::distance(set.begin(), set.end()), 4); EXPECT_EQ(*std::max_element(set.begin(), set.end()), 40); EXPECT_EQ(std::count_if(set.begin(), set.end(), [](int aValue) { return aValue % 20 == 0; }), 2); } TEST(ECMAMap, WorksWithStandardAlgorithms) { TestMap map; map.Set(1, 100); map.Set(2, 200); map.Set(3, 300); auto found = std::find_if(map.begin(), map.end(), [](const auto& aEntry) { return aEntry.mValue == 200; }); ASSERT_TRUE(found != map.end()); EXPECT_EQ(found->mKey, 2); EXPECT_EQ(std::distance(map.rbegin(), map.rend()), 3); } TEST(ECMASet, IteratorCopyAssignmentAndPostIncrement) { TestSet set; set.Add(1); set.Add(2); set.Add(3); auto it = set.begin(); auto old = it++; EXPECT_EQ(*old, 1); EXPECT_EQ(*it, 2); old = it; EXPECT_TRUE(old == it); ++it; EXPECT_FALSE(old == it); EXPECT_EQ(*old, 2); EXPECT_EQ(*it, 3); it = set.end(); EXPECT_TRUE(it == set.end()); EXPECT_EQ(*old, 2); } TEST(ECMASet, IteratorEquality) { TestSet set; EXPECT_TRUE(set.begin() == set.end()); EXPECT_TRUE(set.rbegin() == set.rend()); set.Add(1); set.Add(2); EXPECT_TRUE(set.begin() == set.begin()); EXPECT_TRUE(set.end() == set.end()); EXPECT_TRUE(set.begin() != set.end()); EXPECT_TRUE(++set.begin() != set.begin()); } // ========================================================================== // ECMASet -- reverse iteration // ========================================================================== TEST(ECMASet, ReverseInsertionOrder) { TestSet set; set.Add(1); set.Add(2); set.Add(3); nsTArray result; for (auto it = set.rbegin(); it != set.rend(); ++it) { result.AppendElement(*it); } ASSERT_EQ(result.Length(), 3u); EXPECT_EQ(result[0], 3); EXPECT_EQ(result[1], 2); EXPECT_EQ(result[2], 1); } TEST(ECMASet, ReverseEmptySet) { TestSet set; EXPECT_EQ(set.rbegin(), set.rend()); // Iteration over empty set visits nothing. int count = 0; for (auto it = set.rbegin(); it != set.rend(); ++it) { count++; } EXPECT_EQ(count, 0); } TEST(ECMASet, ReverseSingleElement) { TestSet set; set.Add(42); nsTArray result; for (auto it = set.rbegin(); it != set.rend(); ++it) { result.AppendElement(*it); } ASSERT_EQ(result.Length(), 1u); EXPECT_EQ(result[0], 42); } TEST(ECMASet, ReverseSkipsTombstones) { TestSet set; set.Add(1); set.Add(2); set.Add(3); set.Add(4); set.Delete(1); set.Delete(3); nsTArray result; for (auto it = set.rbegin(); it != set.rend(); ++it) { result.AppendElement(*it); } ASSERT_EQ(result.Length(), 2u); EXPECT_EQ(result[0], 4); EXPECT_EQ(result[1], 2); } TEST(ECMASet, ReverseAllTombstoned) { TestSet set; set.Add(1); set.Add(2); set.Add(3); set.Delete(1); set.Delete(2); set.Delete(3); EXPECT_EQ(set.PhysicalLength(), 0u); int count = 0; for (auto it = set.rbegin(); it != set.rend(); ++it) { count++; } EXPECT_EQ(count, 0); } TEST(ECMASet, ForwardAndReverseYieldSameElements) { TestSet set; for (int i = 0; i < 10; i++) { set.Add(i); } set.Delete(3); set.Delete(7); nsTArray fwd; for (const auto& val : set) { fwd.AppendElement(val); } nsTArray rev; for (auto it = set.rbegin(); it != set.rend(); ++it) { rev.AppendElement(*it); } ASSERT_EQ(fwd.Length(), rev.Length()); for (size_t i = 0; i < fwd.Length(); i++) { EXPECT_EQ(fwd[i], rev[rev.Length() - 1 - i]); } } // ========================================================================== // ECMAMap -- reverse iteration // ========================================================================== TEST(ECMAMap, ReverseInsertionOrder) { TestMap map; map.Set(1, 100); map.Set(2, 200); map.Set(3, 300); nsTArray keys; nsTArray values; for (auto it = map.rbegin(); it != map.rend(); ++it) { keys.AppendElement(it->mKey); values.AppendElement(it->mValue); } ASSERT_EQ(keys.Length(), 3u); EXPECT_EQ(keys[0], 3); EXPECT_EQ(keys[1], 2); EXPECT_EQ(keys[2], 1); EXPECT_EQ(values[0], 300); EXPECT_EQ(values[1], 200); EXPECT_EQ(values[2], 100); } // mozilla::Reversed copies the iterators it is given. TEST(ECMAMap, ReversedRange) { TestMap map; map.Set(1, 100); map.Set(2, 200); map.Set(3, 300); map.Delete(2); nsTArray keys; for (const auto& [key, value] : mozilla::Reversed(map)) { EXPECT_EQ(value, key * 100); keys.AppendElement(key); } ASSERT_EQ(keys.Length(), 2u); EXPECT_EQ(keys[0], 3); EXPECT_EQ(keys[1], 1); } TEST(ECMAMap, ReverseEmptyMap) { TestMap map; EXPECT_EQ(map.rbegin(), map.rend()); int count = 0; for (auto it = map.rbegin(); it != map.rend(); ++it) { count++; } EXPECT_EQ(count, 0); } TEST(ECMAMap, ReverseSkipsTombstones) { TestMap map; map.Set(1, 100); map.Set(2, 200); map.Set(3, 300); map.Set(4, 400); map.Delete(1); map.Delete(3); nsTArray keys; for (auto it = map.rbegin(); it != map.rend(); ++it) { keys.AppendElement(it->mKey); } ASSERT_EQ(keys.Length(), 2u); EXPECT_EQ(keys[0], 4); EXPECT_EQ(keys[1], 2); } TEST(ECMAMap, ForwardAndReverseYieldSameElements) { TestMap map; for (int i = 0; i < 10; i++) { map.Set(i, i * 10); } map.Delete(3); map.Delete(7); nsTArray fwd; for (const auto& entry : map) { fwd.AppendElement(entry.mKey); } nsTArray rev; for (auto it = map.rbegin(); it != map.rend(); ++it) { rev.AppendElement(it->mKey); } ASSERT_EQ(fwd.Length(), rev.Length()); for (size_t i = 0; i < fwd.Length(); i++) { EXPECT_EQ(fwd[i], rev[rev.Length() - 1 - i]); } }