//===--- BlotMapVectorTest.cpp --------------------------------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// #define DEBUG_TYPE "swift-blot-map-vector-test" #include "swift/Basic/BlotMapVector.h" #include "swift/Basic/LLVM.h" #include "swift/Basic/Lazy.h" #include "swift/Basic/NullablePtr.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/Debug.h" #include "llvm/Support/raw_ostream.h" #include "gtest/gtest.h" #include #include #include using namespace swift; //===----------------------------------------------------------------------===// // Test Key/Value Generation //===----------------------------------------------------------------------===// namespace { uint32_t getTestKey(int i, uint32_t *) { return i; } uint32_t getTestValue(int i, uint32_t *) { return 42 + i; } uint32_t *getTestKey(int i, uint32_t **) { static uint32_t dummy_arr1[8192]; assert(i < 8192 && "Only support 8192 dummy keys."); return &dummy_arr1[i]; } uint32_t *getTestValue(int i, uint32_t **) { static uint32_t dummy_arr1[8192]; assert(i < 8192 && "Only support 8192 dummy keys."); return &dummy_arr1[i]; } } // end anonymous namespace //===----------------------------------------------------------------------===// // CtorTester //===----------------------------------------------------------------------===// namespace { class CtorTester; class CtorTesterSet { bool IsClearing = false; std::set Constructed; public: void init() { IsClearing = false; } void dumpLiveTesters() const; void verifyTesters() const; bool hasLiveTesters() const; bool numLiveTesters() const; void clearTesters(); void finalize(); bool isLive(CtorTester *T) const; bool insert(CtorTester *T); unsigned erase(CtorTester *T); bool isClearing() const { return IsClearing; } }; Lazy ConstructedTesters; /// A test class that tries to check that construction and destruction /// occur correctly. class CtorTester { friend class CtorTesterSet; NullablePtr Value; public: bool isLive() const { return ConstructedTesters->isLive(const_cast(this)); } enum class EmptyTester { Kind }; enum class TombstoneTester { Kind }; CtorTester() : Value(new int(-1)) { dump("Constructing ()"); LLVM_DEBUG(llvm::errs() << "\n"); // EXPECT_TRUE(ConstructedTesters->insert(this)); assert(!isLive()); fflush(stdout); } explicit CtorTester(EmptyTester) : Value(new int(-2)) { dump("Constructing Empty"); LLVM_DEBUG(llvm::errs() << "\n"); // EXPECT_TRUE(ConstructedTesters->insert(this)); assert(!isLive()); fflush(stdout); } explicit CtorTester(TombstoneTester) : Value(new int(-3)) { dump("Constructing Tombstone"); LLVM_DEBUG(llvm::errs() << "\n"); // EXPECT_TRUE(ConstructedTesters->insert(this)); assert(!isLive()); fflush(stdout); } explicit CtorTester(int V) : Value(new int(V)) { dump("Constructing Normal"); LLVM_DEBUG(llvm::errs() << "\n"); EXPECT_TRUE(ConstructedTesters->insert(this)); assert(!isIgnorableTester()); assert(isLive()); fflush(stdout); } explicit CtorTester(uint32_t V) : Value(new int(V)) { dump("Constructing Normal"); LLVM_DEBUG(llvm::errs() << "\n"); EXPECT_TRUE(ConstructedTesters->insert(this)); assert(!isIgnorableTester()); assert(isLive()); fflush(stdout); } CtorTester(const CtorTester &Arg) : Value(new int(*Arg.Value.get())) { dump("CopyConstructing"); Arg.dump(" From"); LLVM_DEBUG(llvm::errs() << "\n"); if (!Arg.isIgnorableTester()) { EXPECT_TRUE(ConstructedTesters->insert(this)); fflush(stdout); } } CtorTester(CtorTester &&Arg) : Value(new int(-1)) { dump("Operator Move Constructor"); Arg.dump(" From"); LLVM_DEBUG(llvm::errs() << "\n"); assert(Value); assert(Arg.Value); // If Arg is not ignorable, it will be now and we will not be. if (!Arg.isIgnorableTester()) { EXPECT_TRUE(ConstructedTesters->insert(this)); EXPECT_EQ(1u, ConstructedTesters->erase(&Arg)); } std::swap(Value, Arg.Value); LLVM_DEBUG(fflush(stdout)); } CtorTester &operator=(const CtorTester &Arg) { dump("Operator Copy Assignment"); Arg.dump(" From"); LLVM_DEBUG(llvm::errs() << "\n"); assert(Value); assert(Arg.Value); // If arg is not an ignorable tester, but we are an ignorable tester, we // need to be inserted into the constructed testers set. if (!Arg.isIgnorableTester() && isIgnorableTester()) { EXPECT_TRUE(ConstructedTesters->insert(this)); } *Value.get() = Arg.getValue(); fflush(stdout); return *this; } CtorTester &operator=(CtorTester &&Arg) { dump("Operator Move Assignment"); Arg.dump(" From"); LLVM_DEBUG(llvm::errs() << "\n"); assert(Value); assert(Arg.Value); if (!Arg.isIgnorableTester() && isIgnorableTester()) { EXPECT_EQ(1u, ConstructedTesters->erase(&Arg)); EXPECT_TRUE(ConstructedTesters->insert(this)); } else if (Arg.isIgnorableTester() && !isIgnorableTester()) { EXPECT_EQ(1u, ConstructedTesters->erase(this)); EXPECT_TRUE(ConstructedTesters->insert(&Arg)); } std::swap(Value, Arg.Value); LLVM_DEBUG(fflush(stdout)); return *this; } ~CtorTester() { bool IsIgnorable = isIgnorableTester(); dump("Destroying"); LLVM_DEBUG(llvm::errs() << "\n"); delete Value.get(); Value = nullptr; fflush(stdout); if (ConstructedTesters->isClearing() || IsIgnorable) return; EXPECT_EQ(1u, ConstructedTesters->erase(this)); } operator uint32_t() const { return *Value.get(); } int getValue() const { return *Value.get(); } bool operator==(const CtorTester &RHS) const { return *Value.get() == *RHS.Value.get(); } bool isIgnorableTester() const { return *Value.get() >= -3 && *Value.get() < 0; } private: void dump(StringRef Name) const { std::string Addr = "0x"; Addr += llvm::utohexstr(uintptr_t(this)); std::string ValueAddr = "0x"; ValueAddr += llvm::utohexstr(uintptr_t(Value.get())); LLVM_DEBUG(llvm::errs() << Name << " "); } }; void CtorTesterSet::dumpLiveTesters() const { for (auto *Tester : Constructed) { if (Tester->isIgnorableTester()) continue; llvm::SmallString<64> Hex; std::string Addr = llvm::utohexstr(uintptr_t(Tester)); llvm::errs() << "getValue() << ">\n"; } } void CtorTesterSet::verifyTesters() const { for (auto *Tester : Constructed) EXPECT_TRUE(Tester->Value.isNonNull()); } bool CtorTesterSet::hasLiveTesters() const { return std::any_of(Constructed.begin(), Constructed.end(), [](CtorTester *T) -> bool { assert(T); return !T->isIgnorableTester(); }); } bool CtorTesterSet::numLiveTesters() const { return llvm::count_if(Constructed, [](CtorTester *T) -> bool { assert(T); return !T->isIgnorableTester(); }); } void CtorTesterSet::clearTesters() { IsClearing = true; Constructed.clear(); IsClearing = false; } bool CtorTesterSet::isLive(CtorTester *T) const { // if (IsClearing) // return true; return Constructed.count(T); } bool CtorTesterSet::insert(CtorTester *T) { return Constructed.insert(T).second; } unsigned CtorTesterSet::erase(CtorTester *T) { return Constructed.erase(T); } void CtorTesterSet::finalize() { IsClearing = true; Constructed.clear(); } struct CtorTesterMapInfo { static inline CtorTester getEmptyKey() { return CtorTester(CtorTester::EmptyTester::Kind); } static inline CtorTester getTombstoneKey() { return CtorTester(CtorTester::TombstoneTester::Kind); } static unsigned getHashValue(const CtorTester &Val) { return Val.getValue() * 37u; } static bool isEqual(const CtorTester &LHS, const CtorTester &RHS) { return LHS == RHS; } }; CtorTester getTestKey(int i, CtorTester *) { return CtorTester(i); } CtorTester getTestValue(int i, CtorTester *) { return CtorTester(42 + i); } } // end anonymous namespace //===----------------------------------------------------------------------===// // Tests //===----------------------------------------------------------------------===// namespace { // Test fixture, with helper functions implemented by forwarding to global // function overloads selected by component types of the type parameter. This // allows all of the map implementations to be tested with shared // implementations of helper routines. template class BlotMapVectorTest : public ::testing::Test { protected: T Map; std::optional NumExpectedLiveTesters; static typename T::key_type *const dummy_key_ptr; static typename T::mapped_type *const dummy_value_ptr; public: void SetUp() override { ConstructedTesters->init(); EXPECT_TRUE(!ConstructedTesters->hasLiveTesters()); } ~BlotMapVectorTest() override { ConstructedTesters->verifyTesters(); LLVM_DEBUG(llvm::errs() << "Destroying Fixture\n"); ConstructedTesters->finalize(); } void TearDown() override { if (std::is_same::value) EXPECT_EQ(ConstructedTesters->numLiveTesters(), *NumExpectedLiveTesters); } protected: typename T::key_type getKey(int i = 4) { return getTestKey(i, dummy_key_ptr); } typename T::mapped_type getValue(int i = 5) { return getTestValue(i, dummy_value_ptr); } }; template typename T::key_type *const BlotMapVectorTest::dummy_key_ptr = nullptr; template typename T::mapped_type *const BlotMapVectorTest::dummy_value_ptr = nullptr; // Register these types for testing. typedef ::testing::Types< BlotMapVector, BlotMapVector, BlotMapVector>, SmallBlotMapVector, SmallBlotMapVector, SmallBlotMapVector< CtorTester, CtorTester, 4, llvm::SmallDenseMap>> BlotMapVectorTestTypes; TYPED_TEST_SUITE(BlotMapVectorTest, BlotMapVectorTestTypes, ); // Empty map tests TYPED_TEST(BlotMapVectorTest, EmptyIntMapTest) { // Size tests EXPECT_EQ(0u, this->Map.size()); EXPECT_TRUE(this->Map.empty()); // Iterator tests EXPECT_TRUE(this->Map.begin() == this->Map.end()); // Lookup tests EXPECT_FALSE(this->Map.count(this->getKey())); EXPECT_TRUE(this->Map.find(this->getKey()) == this->Map.end()); EXPECT_EQ(typename TypeParam::mapped_type(), this->Map.lookup(this->getKey())); } // Constant map tests TYPED_TEST(BlotMapVectorTest, ConstEmptyMapTest) { const TypeParam &ConstMap = this->Map; EXPECT_EQ(0u, ConstMap.size()); EXPECT_TRUE(ConstMap.empty()); EXPECT_TRUE(ConstMap.begin() == ConstMap.end()); } // A map with a single entry TYPED_TEST(BlotMapVectorTest, SingleEntryMapTest) { this->Map[this->getKey()] = this->getValue(); ConstructedTesters->verifyTesters(); // Size tests EXPECT_EQ(1u, this->Map.size()); EXPECT_FALSE(this->Map.begin() == this->Map.end()); EXPECT_FALSE(this->Map.empty()); // Iterator tests typename TypeParam::iterator it = this->Map.begin(); EXPECT_EQ(this->getKey(), (*it)->first); EXPECT_EQ(this->getValue(), (*it)->second); ++it; EXPECT_TRUE(it == this->Map.end()); // Lookup tests EXPECT_TRUE(this->Map.count(this->getKey())); EXPECT_TRUE(this->Map.find(this->getKey()) == this->Map.begin()); EXPECT_EQ(this->getValue(), this->Map.lookup(this->getKey())); EXPECT_EQ(this->getValue(), this->Map[this->getKey()]); this->NumExpectedLiveTesters = 1; } // Test clear() method TYPED_TEST(BlotMapVectorTest, ClearTest) { this->Map[this->getKey()] = this->getValue(); this->Map.clear(); EXPECT_EQ(0u, this->Map.size()); EXPECT_TRUE(this->Map.empty()); EXPECT_TRUE(this->Map.begin() == this->Map.end()); this->NumExpectedLiveTesters = 0; } // Test erase(iterator) method TYPED_TEST(BlotMapVectorTest, EraseTest) { this->Map[this->getKey()] = this->getValue(); this->Map.erase(this->Map.begin()); EXPECT_EQ(0u, this->Map.size()); EXPECT_TRUE(this->Map.empty()); EXPECT_TRUE(this->Map.begin() != this->Map.end()); EXPECT_EQ(std::next(this->Map.begin()), this->Map.end()); this->NumExpectedLiveTesters = 0; } // Test erase(value) method TYPED_TEST(BlotMapVectorTest, EraseTest2) { this->Map[this->getKey()] = this->getValue(); EXPECT_TRUE(this->Map.erase(this->getKey())); EXPECT_EQ(0u, this->Map.size()); EXPECT_TRUE(this->Map.empty()); EXPECT_TRUE(this->Map.begin() != this->Map.end()); EXPECT_EQ(std::next(this->Map.begin()), this->Map.end()); this->NumExpectedLiveTesters = 0; } // Test insert() method TYPED_TEST(BlotMapVectorTest, InsertTest) { this->Map.insert(std::make_pair(this->getKey(), this->getValue())); EXPECT_EQ(1u, this->Map.size()); EXPECT_EQ(this->getValue(), this->Map[this->getKey()]); EXPECT_EQ(1u, this->Map.size()); this->NumExpectedLiveTesters = 1; } // Test try_emplace() method TYPED_TEST(BlotMapVectorTest, TryEmplaceTest) { this->Map.try_emplace(this->getKey(), this->getValue()); EXPECT_EQ(1u, this->Map.size()); EXPECT_EQ(this->getValue(), this->Map[this->getKey()]); EXPECT_EQ(1u, this->Map.size()); this->Map.try_emplace(this->getKey(), this->getValue()); EXPECT_EQ(1u, this->Map.size()); EXPECT_EQ(this->getValue(), this->Map[this->getKey()]); EXPECT_EQ(1u, this->Map.size()); this->NumExpectedLiveTesters = 1; } // Test copy constructor method TYPED_TEST(BlotMapVectorTest, CopyConstructorTest) { this->Map[this->getKey()] = this->getValue(); TypeParam copyMap(this->Map); EXPECT_EQ(1u, copyMap.size()); EXPECT_EQ(this->getValue(), copyMap[this->getKey()]); EXPECT_EQ(1u, copyMap.size()); this->NumExpectedLiveTesters = 1; } // Test copy constructor method where SmallBlotMapVector isn't small. TYPED_TEST(BlotMapVectorTest, CopyConstructorNotSmallTest) { for (int Key = 0; Key < 5; ++Key) this->Map[this->getKey(Key)] = this->getValue(Key); TypeParam copyMap(this->Map); EXPECT_EQ(5u, copyMap.size()); for (int Key = 0; Key < 5; ++Key) EXPECT_EQ(this->getValue(Key), copyMap[this->getKey(Key)]); this->NumExpectedLiveTesters = 10; } // Test copying from a default-constructed map. TYPED_TEST(BlotMapVectorTest, CopyConstructorFromDefaultTest) { TypeParam copyMap(this->Map); EXPECT_TRUE(copyMap.empty()); this->NumExpectedLiveTesters = 0; } // Test copying from an empty map where SmallBlotMapVector isn't small. TYPED_TEST(BlotMapVectorTest, CopyConstructorFromEmptyTest) { for (int Key = 0; Key < 5; ++Key) this->Map[this->getKey(Key)] = this->getValue(Key); this->Map.clear(); TypeParam copyMap(this->Map); EXPECT_TRUE(copyMap.empty()); this->NumExpectedLiveTesters = 0; } // Test assignment operator method TYPED_TEST(BlotMapVectorTest, AssignmentTest) { this->Map[this->getKey()] = this->getValue(); TypeParam copyMap = this->Map; EXPECT_EQ(1u, copyMap.size()); EXPECT_EQ(this->getValue(), copyMap[this->getKey()]); // test self-assignment. #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wself-assign-overloaded" copyMap = copyMap; #pragma clang diagnostic pop #else copyMap = copyMap; #endif EXPECT_EQ(1u, copyMap.size()); EXPECT_EQ(this->getValue(), copyMap[this->getKey()]); this->NumExpectedLiveTesters = 2; } // A more complex iteration test TYPED_TEST(BlotMapVectorTest, IterationTest) { bool visited[100]; std::map visitedIndex; // Insert 100 numbers into the map for (int i = 0; i < 100; ++i) { visited[i] = false; visitedIndex[this->getKey(i)] = i; this->Map[this->getKey(i)] = this->getValue(i); } // Iterate over all numbers and mark each one found. for (typename TypeParam::iterator it = this->Map.begin(); it != this->Map.end(); ++it) visited[visitedIndex[(*it)->first]] = true; // Ensure every number was visited. for (int i = 0; i < 100; ++i) ASSERT_TRUE(visited[i]) << "Entry #" << i << " was never visited"; this->NumExpectedLiveTesters = 100; } // const_iterator test TYPED_TEST(BlotMapVectorTest, ConstIteratorTest) { // Check conversion from iterator to const_iterator. typename TypeParam::iterator it = this->Map.begin(); typename TypeParam::const_iterator cit(it); EXPECT_TRUE(it == cit); // Check copying of const_iterators. typename TypeParam::const_iterator cit2(cit); EXPECT_TRUE(cit == cit2); } // Make sure BlotMapVector works with StringRef keys. TEST(BlotMapVectorCustomTest, StringRefTest) { BlotMapVector M; M["a"] = 1; M["b"] = 2; M["c"] = 3; EXPECT_EQ(3u, M.size()); EXPECT_EQ(1, M.lookup("a")); EXPECT_EQ(2, M.lookup("b")); EXPECT_EQ(3, M.lookup("c")); EXPECT_EQ(0, M.lookup("q")); // Test the empty string, spelled various ways. EXPECT_EQ(0, M.lookup("")); EXPECT_EQ(0, M.lookup(StringRef())); EXPECT_EQ(0, M.lookup(StringRef("a", 0))); M[""] = 42; EXPECT_EQ(42, M.lookup("")); EXPECT_EQ(42, M.lookup(StringRef())); EXPECT_EQ(42, M.lookup(StringRef("a", 0))); } // Key traits that allows lookup with either an unsigned or char* key; // In the latter case, "a" == 0, "b" == 1 and so on. struct TestBlotMapVectorInfo { static inline unsigned getEmptyKey() { return ~0; } static inline unsigned getTombstoneKey() { return ~0U - 1; } static unsigned getHashValue(const unsigned &Val) { return Val * 37U; } static unsigned getHashValue(const char *Val) { return (unsigned)(Val[0] - 'a') * 37U; } static bool isEqual(const unsigned &LHS, const unsigned &RHS) { return LHS == RHS; } static bool isEqual(const char *LHS, const unsigned &RHS) { return (unsigned)(LHS[0] - 'a') == RHS; } }; // find_as() tests TEST(BlotMapVectorCustomTest, FindAsTest) { BlotMapVector> map; map[0] = 1; map[1] = 2; map[2] = 3; // Size tests EXPECT_EQ(3u, map.size()); // Normal lookup tests EXPECT_EQ(1u, map.count(1)); EXPECT_EQ(1u, map.find(0)->value().second); EXPECT_EQ(2u, map.find(1)->value().second); EXPECT_EQ(3u, map.find(2)->value().second); EXPECT_TRUE(map.find(3) == map.end()); } struct ContiguousBlotMapVectorInfo { static inline unsigned getEmptyKey() { return ~0; } static inline unsigned getTombstoneKey() { return ~0U - 1; } static unsigned getHashValue(const unsigned &Val) { return Val; } static bool isEqual(const unsigned &LHS, const unsigned &RHS) { return LHS == RHS; } }; // Test that filling a small dense map with exactly the number of elements in // the map grows to have enough space for an empty bucket. TEST(BlotMapVectorCustomTest, SmallBlotMapVectorGrowTest) { SmallBlotMapVector> map; // Add some number of elements, then delete a few to leave us some tombstones. // If we just filled the map with 32 elements we'd grow because of not enough // tombstones which masks the issue here. for (unsigned i = 0; i < 20; ++i) map[i] = i + 1; for (unsigned i = 0; i < 10; ++i) EXPECT_TRUE(map.erase(i)); for (unsigned i = 20; i < 32; ++i) map[i] = i + 1; for (unsigned i = 0; i < 10; ++i) EXPECT_FALSE(map.erase(i)); // Size tests EXPECT_EQ(22u, map.size()); // Try to find an element which doesn't exist. There was a bug in // SmallBlotMapVector which led to a map with num elements == small capacity // not // having an empty bucket any more. Finding an element not in the map would // therefore never terminate. EXPECT_TRUE(map.find(32) == map.end()); } } // end anonymous namespace