// Copyright 2017 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/threading/sequence_local_storage_slot.h" #include #include "base/memory/ptr_util.h" #include "base/threading/sequence_local_storage_map.h" #include "testing/gtest/include/gtest/gtest.h" namespace base { namespace { template class SequenceLocalStorageSlotTest : public testing::Test { public: SequenceLocalStorageSlotTest(const SequenceLocalStorageSlotTest&) = delete; SequenceLocalStorageSlotTest& operator=(const SequenceLocalStorageSlotTest&) = delete; protected: SequenceLocalStorageSlotTest() : scoped_sequence_local_storage_(&sequence_local_storage_) {} internal::SequenceLocalStorageMap sequence_local_storage_; internal::ScopedSetSequenceLocalStorageMapForCurrentThread scoped_sequence_local_storage_; }; } // namespace struct GenericSLS { template using Type = GenericSequenceLocalStorageSlot; }; struct SmallSLS { template using Type = GenericSequenceLocalStorageSlot; }; using StorageTypes = testing::Types; TYPED_TEST_SUITE(SequenceLocalStorageSlotTest, StorageTypes); // Verify that a value stored with emplace() can be retrieved with operator*(). TYPED_TEST(SequenceLocalStorageSlotTest, GetEmplace) { using SLSType = typename TypeParam::template Type; SLSType slot; slot.emplace(5); EXPECT_EQ(*slot, 5); } // Verify that inserting an object in a SequenceLocalStorageSlot creates a copy // of that object independent of the original one. TYPED_TEST(SequenceLocalStorageSlotTest, EmplaceObjectIsIndependent) { using SLSType = typename TypeParam::template Type; bool should_be_false = false; SLSType slot; slot.emplace(should_be_false); EXPECT_FALSE(*slot); *slot = true; EXPECT_TRUE(*slot); EXPECT_NE(should_be_false, *slot); } // Verify that multiple slots work and that calling emplace after overwriting // a value in a slot yields the new value. TYPED_TEST(SequenceLocalStorageSlotTest, GetEmplaceMultipleSlots) { using SLSType = typename TypeParam::template Type; SLSType slot1; SLSType slot2; SLSType slot3; EXPECT_FALSE(slot1); EXPECT_FALSE(slot2); EXPECT_FALSE(slot3); slot1.emplace(1); slot2.emplace(2); slot3.emplace(3); EXPECT_TRUE(slot1); EXPECT_TRUE(slot2); EXPECT_TRUE(slot3); EXPECT_EQ(*slot1, 1); EXPECT_EQ(*slot2, 2); EXPECT_EQ(*slot3, 3); slot3.emplace(4); slot2.emplace(5); slot1.emplace(6); EXPECT_EQ(*slot3, 4); EXPECT_EQ(*slot2, 5); EXPECT_EQ(*slot1, 6); } // Verify that changing the value returned by Get() changes the value // in sequence local storage. TYPED_TEST(SequenceLocalStorageSlotTest, GetReferenceModifiable) { using SLSType = typename TypeParam::template Type; SLSType slot; slot.emplace(false); *slot = true; EXPECT_TRUE(*slot); } // Verify that a move-only type can be stored in sequence local storage. TYPED_TEST(SequenceLocalStorageSlotTest, EmplaceGetWithMoveOnlyType) { struct MoveOnly { MoveOnly() = default; MoveOnly(const MoveOnly&) = delete; MoveOnly& operator=(const MoveOnly&) = delete; MoveOnly(MoveOnly&&) = default; MoveOnly& operator=(MoveOnly&&) = default; int x = 0x12345678; }; using SLSType = typename TypeParam::template Type; MoveOnly move_only; SLSType slot; slot.emplace(std::move(move_only)); EXPECT_EQ(slot->x, 0x12345678); } // Verify that a Get() without a previous Set() on a slot returns a // default-constructed value. TYPED_TEST(SequenceLocalStorageSlotTest, GetWithoutSetDefaultConstructs) { struct DefaultConstructable { int x = 0x12345678; }; using SLSType = typename TypeParam::template Type; SLSType slot; EXPECT_EQ(slot.GetOrCreateValue().x, 0x12345678); } // Verify that a GetOrCreateValue() without a previous emplace() on a slot with // a POD-type returns a default-constructed value. // Note: this test could be flaky and give a false pass. If it's flaky, the test // might've "passed" because the memory for the slot happened to be zeroed. TYPED_TEST(SequenceLocalStorageSlotTest, GetWithoutSetDefaultConstructsPOD) { using SLSType = typename TypeParam::template Type; SLSType slot; EXPECT_EQ(slot.GetOrCreateValue(), nullptr); } // Verify that the value of a slot is specific to a SequenceLocalStorageMap TEST(SequenceLocalStorageSlotMultipleMapTest, EmplaceGetMultipleMapsOneSlot) { SequenceLocalStorageSlot slot; internal::SequenceLocalStorageMap sequence_local_storage_maps[5]; // Set the value of the slot to be the index of the current // SequenceLocalStorageMaps in the vector for (unsigned int i = 0; i < std::size(sequence_local_storage_maps); ++i) { internal::ScopedSetSequenceLocalStorageMapForCurrentThread scoped_sequence_local_storage(&sequence_local_storage_maps[i]); slot.emplace(i); } for (unsigned int i = 0; i < std::size(sequence_local_storage_maps); ++i) { internal::ScopedSetSequenceLocalStorageMapForCurrentThread scoped_sequence_local_storage(&sequence_local_storage_maps[i]); EXPECT_EQ(*slot, i); } } TEST(SequenceLocalStorageComPtrTest, TestClassesWithNoAddressOfOperatorCanCompile) { internal::SequenceLocalStorageMap sequence_local_storage_map; internal::ScopedSetSequenceLocalStorageMapForCurrentThread scoped_sequence_local_storage(&sequence_local_storage_map); // Microsoft::WRL::ComPtr overrides & operator to release the underlying // pointer. // https://learn.microsoft.com/en-us/cpp/cppcx/wrl/comptr-class?view=msvc-170#operator-ampersand // Types stored in SequenceLocalStorage may override `operator&` to have // additional side effects, e.g. Microsoft::WRL::ComPtr. Make sure // SequenceLocalStorage does not invoke/use custom `operator&`s to avoid // triggering those side effects. class TestNoAddressOfOperator { public: TestNoAddressOfOperator() = default; ~TestNoAddressOfOperator() { // Define a non-trivial destructor so that SequenceLocalStorageSlot // will use the external value path. } // See note above class definition for the reason this operator is deleted. TestNoAddressOfOperator* operator&() = delete; }; SequenceLocalStorageSlot slot; slot.emplace(TestNoAddressOfOperator()); EXPECT_NE(slot.GetValuePointer(), nullptr); } } // namespace base