blob: ab9b72ebf63b0c1f1d510ef2ceb20f0f5cd05d56 [file] [log] [blame]
// 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 <stdint.h>
#include <utility>
#include "pw_unit_test/framework.h"
namespace pw {
namespace {
class TestItem : public RefCounted<TestItem> {
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<TestItem> ptr(new TestItem());
EXPECT_EQ(TestItem::instance_counter, 1);
}
EXPECT_EQ(TestItem::instance_counter, 0);
}
TEST_F(IntrusivePtrTest, AssigningToNullptrDeletesTheObject) {
IntrusivePtr<TestItem> ptr(new TestItem());
EXPECT_EQ(TestItem::instance_counter, 1);
ptr = nullptr;
EXPECT_EQ(TestItem::instance_counter, 0);
}
TEST_F(IntrusivePtrTest, AssigningToEmptyPtrDeletesTheObject) {
IntrusivePtr<TestItem> ptr(new TestItem());
IntrusivePtr<TestItem> empty;
EXPECT_EQ(TestItem::instance_counter, 1);
ptr = empty;
EXPECT_EQ(TestItem::instance_counter, 0);
}
TEST_F(IntrusivePtrTest, SwapWithNullptrKeepsTheObject) {
IntrusivePtr<TestItem> ptr(new TestItem());
IntrusivePtr<TestItem> 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<TestItem> ptr(new TestItem());
EXPECT_EQ(TestItem::instance_counter, 1);
{
IntrusivePtr<TestItem> 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<TestItem> ptr(new TestItem());
EXPECT_EQ(TestItem::instance_counter, 1);
{
IntrusivePtr<TestItem> 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<TestItem> 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<TestItem> 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<TestItemDerived> ptr(new TestItemDerived());
EXPECT_EQ(TestItem::instance_counter, 1);
EXPECT_EQ(TestItemDerived::derived_instance_counter, 1);
{
IntrusivePtr<TestItem> 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<TestItemDerived> ptr(new TestItemDerived());
EXPECT_EQ(TestItem::instance_counter, 1);
EXPECT_EQ(TestItemDerived::derived_instance_counter, 1);
{
IntrusivePtr<TestItem> 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<TestItemDerived> ptr(new TestItemDerived());
EXPECT_EQ(TestItem::instance_counter, 1);
EXPECT_EQ(TestItemDerived::derived_instance_counter, 1);
{
IntrusivePtr<TestItem> 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<TestItemDerived> ptr(new TestItemDerived());
EXPECT_EQ(TestItem::instance_counter, 1);
EXPECT_EQ(TestItemDerived::derived_instance_counter, 1);
{
IntrusivePtr<TestItem> 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<TestItem> ptr(new TestItem());
EXPECT_EQ(TestItem::instance_counter, 1);
{
IntrusivePtr<TestItem> 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<TestItem> ptr(new TestItem());
EXPECT_EQ(TestItem::instance_counter, 1);
{
IntrusivePtr<TestItem> 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<TestItem> ptr(new TestItem());
IntrusivePtr<TestItem> 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<TestItem> 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<TestItem> ptr(new TestItem());
IntrusivePtr<TestItem> empty;
EXPECT_FALSE(ptr == empty);
EXPECT_FALSE(empty == ptr);
EXPECT_TRUE(ptr != empty);
EXPECT_TRUE(empty != ptr);
}
TEST_F(IntrusivePtrTest, FilledPtrIsNotEqualToNullptr) {
IntrusivePtr<TestItem> ptr(new TestItem());
EXPECT_FALSE(ptr == nullptr);
EXPECT_FALSE(nullptr == ptr);
EXPECT_TRUE(ptr != nullptr);
EXPECT_TRUE(nullptr != ptr);
}
TEST_F(IntrusivePtrTest, EmptyPtrIsEqualToNullptr) {
IntrusivePtr<TestItem> empty;
EXPECT_TRUE(empty == nullptr);
EXPECT_TRUE(nullptr == empty);
EXPECT_FALSE(empty != nullptr);
EXPECT_FALSE(nullptr != empty);
}
TEST_F(IntrusivePtrTest, PtrsWithDifferentObjectsReturnDifferentPointers) {
IntrusivePtr<TestItem> ptr(new TestItem());
IntrusivePtr<TestItem> ptr_2(new TestItem());
EXPECT_NE(ptr.get(), ptr_2.get());
}
TEST_F(IntrusivePtrTest, PtrsWithSameObjectsReturnSamePointer) {
IntrusivePtr<TestItemDerived> ptr(new TestItemDerived());
auto ptr_2 = ptr;
IntrusivePtr<TestItem> ptr_3 = ptr;
EXPECT_EQ(ptr.get(), ptr_2.get());
EXPECT_EQ(static_cast<TestItem*>(ptr.get()), ptr_3.get());
}
TEST_F(IntrusivePtrTest, EmptyPtrReturnsNullptr) {
IntrusivePtr<TestItem> empty;
EXPECT_EQ(empty.get(), nullptr);
}
TEST_F(IntrusivePtrTest, ConstifyWorks) {
IntrusivePtr<TestItem> ptr(new TestItem());
IntrusivePtr<const TestItem> ptr_2 = ptr;
EXPECT_EQ(TestItem::instance_counter, 1);
EXPECT_EQ(ptr.get(), ptr_2.get());
}
TEST_F(IntrusivePtrTest, NonRefCountedObjectWorks) {
// Compilation test only.
IntrusivePtr<FreeTestItem> empty;
IntrusivePtr<FreeTestItem> free(new FreeTestItem);
IntrusivePtr<FreeTestItem> free_2(free);
IntrusivePtr<FreeTestItem> free_3(std::move(free));
}
TEST_F(IntrusivePtrTest, MakeRefCounted) {
auto ptr_1 = MakeRefCounted<TestItem>();
EXPECT_EQ(TestItem::instance_counter, 1);
EXPECT_EQ(ptr_1->first, 0);
EXPECT_EQ(ptr_1->second, 1);
auto ptr_2 = MakeRefCounted<TestItem>(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<TestItem>(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<TestItem>(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<TestItem> ptr(new TestItem());
EXPECT_EQ(ptr.use_count(), 1);
{
IntrusivePtr<TestItem> ptr_copy = ptr;
EXPECT_EQ(ptr.use_count(), 2);
}
EXPECT_EQ(ptr.use_count(), 1);
}
TEST_F(IntrusivePtrTest, UseCountForNullPtr) {
IntrusivePtr<TestItem> 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