// Copyright 2022 The Pigweed Authors // // Licensed under the Apache License, Version 2.0 (the "License"); you may not // use this file except in compliance with the License. You may obtain a copy of // the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the // License for the specific language governing permissions and limitations under // the License. #include "pw_intrusive_ptr/intrusive_ptr.h" #include #include #include "gtest/gtest.h" namespace pw { namespace { class TestItem : public RefCounted { public: TestItem() { ++instance_counter; } explicit TestItem(int32_t f) : TestItem() { first = f; } explicit TestItem(int64_t s) : TestItem() { second = s; } TestItem(int32_t f, int64_t s) : TestItem() { first = f; second = s; } TestItem(const TestItem&) : TestItem() {} TestItem(TestItem&&) noexcept : TestItem() {} TestItem& operator=(const TestItem& other) { if (&other != this) { ++instance_counter; } return *this; } TestItem& operator=(TestItem&& other) noexcept { if (&other != this) { ++instance_counter; } return *this; } virtual ~TestItem() { --instance_counter; } inline static int32_t instance_counter = 0; int32_t first = 0; int64_t second = 1; }; class TestItemDerived : public TestItem { public: TestItemDerived() { ++derived_instance_counter; } TestItemDerived(const TestItemDerived&) : TestItemDerived() {} TestItemDerived(TestItemDerived&&) noexcept : TestItemDerived() {} TestItemDerived& operator=(const TestItemDerived& other) { if (&other != this) { ++derived_instance_counter; } return *this; } TestItemDerived& operator=(TestItemDerived&& other) noexcept { if (&other != this) { ++derived_instance_counter; } return *this; } ~TestItemDerived() override { --derived_instance_counter; } inline static int32_t derived_instance_counter = 0; }; struct FreeTestItem { void AddRef() const { ++instance_counter; } bool ReleaseRef() const { return --instance_counter < 1; } mutable int32_t instance_counter = 0; }; class IntrusivePtrTest : public ::testing::Test { protected: void SetUp() override { TestItem::instance_counter = 0; TestItemDerived::derived_instance_counter = 0; } }; TEST_F(IntrusivePtrTest, DeletingLastPtrDeletesTheObject) { { IntrusivePtr ptr(new TestItem()); EXPECT_EQ(TestItem::instance_counter, 1); } EXPECT_EQ(TestItem::instance_counter, 0); } TEST_F(IntrusivePtrTest, AssigningToNullptrDeletesTheObject) { IntrusivePtr ptr(new TestItem()); EXPECT_EQ(TestItem::instance_counter, 1); ptr = nullptr; EXPECT_EQ(TestItem::instance_counter, 0); } TEST_F(IntrusivePtrTest, AssigningToEmptyPtrDeletesTheObject) { IntrusivePtr ptr(new TestItem()); IntrusivePtr empty; EXPECT_EQ(TestItem::instance_counter, 1); ptr = empty; EXPECT_EQ(TestItem::instance_counter, 0); } TEST_F(IntrusivePtrTest, SwapWithNullptrKeepsTheObject) { IntrusivePtr ptr(new TestItem()); IntrusivePtr empty; EXPECT_EQ(TestItem::instance_counter, 1); ptr.swap(empty); EXPECT_EQ(TestItem::instance_counter, 1); EXPECT_EQ(ptr, nullptr); empty = nullptr; EXPECT_EQ(TestItem::instance_counter, 0); } TEST_F(IntrusivePtrTest, CopyingPtrDoesntCreateNewObjects) { { IntrusivePtr ptr(new TestItem()); EXPECT_EQ(TestItem::instance_counter, 1); { IntrusivePtr ptr_2(ptr); EXPECT_EQ(TestItem::instance_counter, 1); } // We still have a ptr here. EXPECT_EQ(TestItem::instance_counter, 1); } EXPECT_EQ(TestItem::instance_counter, 0); } TEST_F(IntrusivePtrTest, MovingPtrDoesntCreateNewObjects) { { IntrusivePtr ptr(new TestItem()); EXPECT_EQ(TestItem::instance_counter, 1); { IntrusivePtr ptr_2(std::move(ptr)); EXPECT_EQ(TestItem::instance_counter, 1); } // ptr was moved away, object should be deleted. EXPECT_EQ(TestItem::instance_counter, 0); } EXPECT_EQ(TestItem::instance_counter, 0); } TEST_F(IntrusivePtrTest, CopyAssigningPtrDoesntCreateNewObjects) { { IntrusivePtr ptr(new TestItem()); EXPECT_EQ(TestItem::instance_counter, 1); { auto ptr_2 = ptr; EXPECT_EQ(TestItem::instance_counter, 1); } // We still have a ptr here. EXPECT_EQ(TestItem::instance_counter, 1); } EXPECT_EQ(TestItem::instance_counter, 0); } TEST_F(IntrusivePtrTest, MoveAssigningPtrDoesntCreateNewObjects) { { IntrusivePtr ptr(new TestItem()); EXPECT_EQ(TestItem::instance_counter, 1); { auto ptr_2 = std::move(ptr); EXPECT_EQ(TestItem::instance_counter, 1); } // ptr was moved away, object should be deleted. EXPECT_EQ(TestItem::instance_counter, 0); } EXPECT_EQ(TestItem::instance_counter, 0); } TEST_F(IntrusivePtrTest, CopyingPtrToBaseClassPtrDoesntCreateNewObjects) { { IntrusivePtr ptr(new TestItemDerived()); EXPECT_EQ(TestItem::instance_counter, 1); EXPECT_EQ(TestItemDerived::derived_instance_counter, 1); { IntrusivePtr ptr_2(ptr); EXPECT_EQ(TestItem::instance_counter, 1); EXPECT_EQ(TestItemDerived::derived_instance_counter, 1); } // We still have a ptr here. EXPECT_EQ(TestItem::instance_counter, 1); EXPECT_EQ(TestItemDerived::derived_instance_counter, 1); } EXPECT_EQ(TestItem::instance_counter, 0); EXPECT_EQ(TestItemDerived::derived_instance_counter, 0); } TEST_F(IntrusivePtrTest, MovingPtrToBaseClassPtrDoesntCreateNewObjects) { { IntrusivePtr ptr(new TestItemDerived()); EXPECT_EQ(TestItem::instance_counter, 1); EXPECT_EQ(TestItemDerived::derived_instance_counter, 1); { IntrusivePtr ptr_2(std::move(ptr)); EXPECT_EQ(TestItem::instance_counter, 1); EXPECT_EQ(TestItemDerived::derived_instance_counter, 1); } // ptr was moved away, object should be deleted. EXPECT_EQ(TestItem::instance_counter, 0); EXPECT_EQ(TestItemDerived::derived_instance_counter, 0); } EXPECT_EQ(TestItem::instance_counter, 0); EXPECT_EQ(TestItemDerived::derived_instance_counter, 0); } TEST_F(IntrusivePtrTest, CopyAssigningPtrToBaseClassPtrDoesntCreateNewObjects) { { IntrusivePtr ptr(new TestItemDerived()); EXPECT_EQ(TestItem::instance_counter, 1); EXPECT_EQ(TestItemDerived::derived_instance_counter, 1); { IntrusivePtr ptr_2 = ptr; EXPECT_EQ(TestItem::instance_counter, 1); EXPECT_EQ(TestItemDerived::derived_instance_counter, 1); } // We still have a ptr here. EXPECT_EQ(TestItem::instance_counter, 1); EXPECT_EQ(TestItemDerived::derived_instance_counter, 1); } EXPECT_EQ(TestItem::instance_counter, 0); EXPECT_EQ(TestItemDerived::derived_instance_counter, 0); } TEST_F(IntrusivePtrTest, MoveAssigningPtrToBaseClassPtrDoesntCreateNewObjects) { { IntrusivePtr ptr(new TestItemDerived()); EXPECT_EQ(TestItem::instance_counter, 1); EXPECT_EQ(TestItemDerived::derived_instance_counter, 1); { IntrusivePtr ptr_2 = std::move(ptr); EXPECT_EQ(TestItem::instance_counter, 1); EXPECT_EQ(TestItemDerived::derived_instance_counter, 1); } // ptr was moved away, object should be deleted. EXPECT_EQ(TestItem::instance_counter, 0); EXPECT_EQ(TestItemDerived::derived_instance_counter, 0); } EXPECT_EQ(TestItem::instance_counter, 0); EXPECT_EQ(TestItemDerived::derived_instance_counter, 0); } TEST_F(IntrusivePtrTest, CopyAssigningPtrDeletesOldObjectIfLast) { { IntrusivePtr ptr(new TestItem()); EXPECT_EQ(TestItem::instance_counter, 1); { IntrusivePtr ptr_2(new TestItem()); EXPECT_EQ(TestItem::instance_counter, 2); ptr_2 = ptr; // Old object in ptr_2 should be removed. EXPECT_EQ(TestItem::instance_counter, 1); } // We still have a ptr here. EXPECT_EQ(TestItem::instance_counter, 1); } EXPECT_EQ(TestItem::instance_counter, 0); } TEST_F(IntrusivePtrTest, MoveAssigningPtrDeletesOldObjectIfLast) { { IntrusivePtr ptr(new TestItem()); EXPECT_EQ(TestItem::instance_counter, 1); { IntrusivePtr ptr_2(new TestItem()); EXPECT_EQ(TestItem::instance_counter, 2); ptr_2 = std::move(ptr); // Old object in ptr_2 should be removed. EXPECT_EQ(TestItem::instance_counter, 1); } // ptr was moved away, object should be deleted. EXPECT_EQ(TestItem::instance_counter, 0); } EXPECT_EQ(TestItem::instance_counter, 0); } // Comparison tests use operators directly to cover == and != and both // argument orders. TEST_F(IntrusivePtrTest, PtrsWithDifferentObjectsAreNotEqual) { IntrusivePtr ptr(new TestItem()); IntrusivePtr ptr_2(new TestItem()); EXPECT_FALSE(ptr == ptr_2); EXPECT_FALSE(ptr_2 == ptr); EXPECT_TRUE(ptr != ptr_2); EXPECT_TRUE(ptr_2 != ptr); } TEST_F(IntrusivePtrTest, PtrsWithSameObjectsAreEqual) { IntrusivePtr ptr(new TestItem()); auto ptr_2 = ptr; EXPECT_TRUE(ptr == ptr_2); EXPECT_TRUE(ptr_2 == ptr); EXPECT_FALSE(ptr != ptr_2); EXPECT_FALSE(ptr_2 != ptr); } TEST_F(IntrusivePtrTest, FilledPtrIsNotEqualToEmptyPtr) { IntrusivePtr ptr(new TestItem()); IntrusivePtr empty; EXPECT_FALSE(ptr == empty); EXPECT_FALSE(empty == ptr); EXPECT_TRUE(ptr != empty); EXPECT_TRUE(empty != ptr); } TEST_F(IntrusivePtrTest, FilledPtrIsNotEqualToNullptr) { IntrusivePtr ptr(new TestItem()); EXPECT_FALSE(ptr == nullptr); EXPECT_FALSE(nullptr == ptr); EXPECT_TRUE(ptr != nullptr); EXPECT_TRUE(nullptr != ptr); } TEST_F(IntrusivePtrTest, EmptyPtrIsEqualToNullptr) { IntrusivePtr empty; EXPECT_TRUE(empty == nullptr); EXPECT_TRUE(nullptr == empty); EXPECT_FALSE(empty != nullptr); EXPECT_FALSE(nullptr != empty); } TEST_F(IntrusivePtrTest, PtrsWithDifferentObjectsReturnDifferentPointers) { IntrusivePtr ptr(new TestItem()); IntrusivePtr ptr_2(new TestItem()); EXPECT_NE(ptr.get(), ptr_2.get()); } TEST_F(IntrusivePtrTest, PtrsWithSameObjectsReturnSamePointer) { IntrusivePtr ptr(new TestItemDerived()); auto ptr_2 = ptr; IntrusivePtr ptr_3 = ptr; EXPECT_EQ(ptr.get(), ptr_2.get()); EXPECT_EQ(static_cast(ptr.get()), ptr_3.get()); } TEST_F(IntrusivePtrTest, EmptyPtrReturnsNullptr) { IntrusivePtr empty; EXPECT_EQ(empty.get(), nullptr); } TEST_F(IntrusivePtrTest, ConstifyWorks) { IntrusivePtr ptr(new TestItem()); IntrusivePtr ptr_2 = ptr; EXPECT_EQ(TestItem::instance_counter, 1); EXPECT_EQ(ptr.get(), ptr_2.get()); } TEST_F(IntrusivePtrTest, NonRefCountedObjectWorks) { // Compilation test only. IntrusivePtr empty; IntrusivePtr free(new FreeTestItem); IntrusivePtr free_2(free); IntrusivePtr free_3(std::move(free)); } TEST_F(IntrusivePtrTest, MakeRefCounted) { auto ptr_1 = MakeRefCounted(); EXPECT_EQ(TestItem::instance_counter, 1); EXPECT_EQ(ptr_1->first, 0); EXPECT_EQ(ptr_1->second, 1); auto ptr_2 = MakeRefCounted(int32_t(42)); EXPECT_EQ(TestItem::instance_counter, 2); EXPECT_EQ(ptr_2->first, 42); EXPECT_EQ(ptr_2->second, 1); auto ptr_3 = MakeRefCounted(int64_t(2)); EXPECT_EQ(TestItem::instance_counter, 3); EXPECT_EQ(ptr_3->first, 0); EXPECT_EQ(ptr_3->second, 2); auto ptr_4 = MakeRefCounted(42, 5); EXPECT_EQ(TestItem::instance_counter, 4); EXPECT_EQ(ptr_4->first, 42); EXPECT_EQ(ptr_4->second, 5); } TEST_F(IntrusivePtrTest, UseCount) { IntrusivePtr ptr(new TestItem()); EXPECT_EQ(ptr.use_count(), 1); { IntrusivePtr ptr_copy = ptr; EXPECT_EQ(ptr.use_count(), 2); } EXPECT_EQ(ptr.use_count(), 1); } TEST_F(IntrusivePtrTest, UseCountForNullPtr) { IntrusivePtr ptr; EXPECT_EQ(ptr.use_count(), 0); ptr = IntrusivePtr(new TestItem); EXPECT_EQ(ptr.use_count(), 1); ptr = nullptr; EXPECT_EQ(ptr.use_count(), 0); } } // namespace } // namespace pw