blob: 7f4b2e68c4536a7a00814fe42edf9394dc15289e [file] [log] [blame]
// Copyright 2025 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_allocator/shared_ptr.h"
// TODO(b/402489948): Remove when portable atomics are provided by `pw_atomic`.
#if PW_ALLOCATOR_HAS_ATOMICS
#include <cstddef>
#include "pw_allocator/allocator.h"
#include "pw_allocator/internal/counter.h"
#include "pw_allocator/testing.h"
#include "pw_unit_test/framework.h"
namespace {
using pw::allocator::test::Counter;
using pw::allocator::test::CounterSink;
using pw::allocator::test::CounterWithBuffer;
class SharedPtrTest : public pw::allocator::test::TestWithCounters {
protected:
pw::allocator::test::AllocatorForTest<256> allocator_;
};
TEST_F(SharedPtrTest, DefaultInitializationIsNullptr) {
pw::SharedPtr<int> empty;
EXPECT_EQ(empty.get(), nullptr);
}
TEST_F(SharedPtrTest, OperatorEqNullptrOnEmptySharedPtrSucceeds) {
pw::SharedPtr<int> empty;
EXPECT_TRUE(empty == nullptr);
EXPECT_FALSE(empty != nullptr);
}
TEST_F(SharedPtrTest, OperatorEqNullptrAfterMakeSharedFails) {
auto ptr = allocator_.MakeShared<Counter>(5u);
EXPECT_NE(ptr.get(), nullptr);
EXPECT_TRUE(ptr != nullptr);
EXPECT_FALSE(ptr == nullptr);
}
TEST_F(SharedPtrTest, OperatorEqNullptrAfterMakeSharedNullptrTypeFails) {
auto ptr = allocator_.MakeShared<std::nullptr_t>(nullptr);
EXPECT_TRUE(ptr != nullptr);
EXPECT_FALSE(ptr == nullptr);
EXPECT_TRUE(*ptr == nullptr);
EXPECT_FALSE(*ptr != nullptr);
}
TEST_F(SharedPtrTest, CopyConstructionIncreasesUseCount) {
auto ptr1 = allocator_.MakeShared<Counter>(42u);
pw::SharedPtr<Counter> ptr2(ptr1);
EXPECT_EQ(ptr1.get(), ptr2.get());
EXPECT_EQ(ptr1->value(), 42u);
EXPECT_EQ(ptr2->value(), 42u);
EXPECT_EQ(ptr1.use_count(), 2);
EXPECT_EQ(ptr2.use_count(), 2);
EXPECT_EQ(Counter::TakeNumCtorCalls(), 1U);
EXPECT_EQ(Counter::TakeNumDtorCalls(), 0U);
}
TEST_F(SharedPtrTest, CopyAssignmentIncreasesUseCount) {
auto ptr1 = allocator_.MakeShared<Counter>(42u);
pw::SharedPtr<Counter> ptr2;
ptr2 = ptr1;
EXPECT_EQ(ptr1.get(), ptr2.get());
EXPECT_EQ(ptr1->value(), 42u);
EXPECT_EQ(ptr2->value(), 42u);
EXPECT_EQ(ptr1.use_count(), 2);
EXPECT_EQ(ptr2.use_count(), 2);
EXPECT_EQ(Counter::TakeNumCtorCalls(), 1U);
EXPECT_EQ(Counter::TakeNumDtorCalls(), 0U);
}
TEST_F(SharedPtrTest, MakeSharedForwardsConstructorArguments) {
Counter counter(6);
auto ptr = allocator_.MakeShared<CounterSink>(std::move(counter));
ASSERT_NE(ptr, nullptr);
EXPECT_EQ(ptr->value(), 6u);
}
TEST_F(SharedPtrTest, MoveConstructsFromSubClassAndFreesTotalSize) {
auto ptr = allocator_.MakeShared<CounterWithBuffer>();
ASSERT_NE(ptr, nullptr);
EXPECT_EQ(Counter::TakeNumCtorCalls(), 1U);
size_t allocated = allocator_.allocate_size();
EXPECT_GE(allocated, sizeof(CounterWithBuffer));
pw::SharedPtr<Counter> base_ptr(std::move(ptr));
EXPECT_EQ(base_ptr.use_count(), 1);
EXPECT_EQ(allocator_.deallocate_size(), 0ul);
// The size that is deallocated here should be the size of the larger
// subclass, not the size of the smaller base class.
EXPECT_EQ(Counter::TakeNumDtorCalls(), 0U);
base_ptr.reset();
EXPECT_EQ(Counter::TakeNumDtorCalls(), 1U);
EXPECT_EQ(allocator_.deallocate_size(), allocated);
}
TEST_F(SharedPtrTest, MoveAssignsFromSubClassAndFreesTotalSize) {
auto ptr = allocator_.MakeShared<CounterWithBuffer>();
ASSERT_NE(ptr, nullptr);
EXPECT_EQ(Counter::TakeNumCtorCalls(), 1U);
size_t allocated = allocator_.allocate_size();
EXPECT_GE(allocated, sizeof(CounterWithBuffer));
pw::SharedPtr<Counter> base_ptr = std::move(ptr);
EXPECT_EQ(base_ptr.use_count(), 1);
EXPECT_EQ(allocator_.deallocate_size(), 0ul);
// The size that is deallocated here should be the size of the larger
// subclass, not the size of the smaller base class.
EXPECT_EQ(Counter::TakeNumDtorCalls(), 0U);
base_ptr.reset();
EXPECT_EQ(Counter::TakeNumDtorCalls(), 1U);
EXPECT_EQ(allocator_.deallocate_size(), allocated);
}
TEST_F(SharedPtrTest, ArrayConstruction) {
auto ptr = allocator_.MakeShared<Counter[]>(5);
EXPECT_NE(ptr.get(), nullptr);
EXPECT_EQ(Counter::TakeNumCtorCalls(), 5u);
for (size_t i = 0; i < 5; ++i) {
EXPECT_EQ(ptr[i].value(), i);
}
EXPECT_EQ(Counter::TakeNumDtorCalls(), 0U);
}
TEST_F(SharedPtrTest, SizeReturnsCorrectSize) {
auto ptr_array = allocator_.MakeShared<int[]>(5);
EXPECT_EQ(ptr_array.size(), 5U);
}
TEST_F(SharedPtrTest, ArrayConstructionWithAlignment) {
auto ptr = allocator_.MakeShared<Counter[]>(5, 32);
EXPECT_NE(ptr.get(), nullptr);
EXPECT_EQ(Counter::TakeNumCtorCalls(), 5u);
for (size_t i = 0; i < 5; ++i) {
EXPECT_EQ(ptr[i].value(), i);
}
EXPECT_EQ(Counter::TakeNumDtorCalls(), 0U);
auto addr = reinterpret_cast<uintptr_t>(ptr.get());
EXPECT_EQ(addr % 32, 0u);
}
TEST_F(SharedPtrTest, SizeReturnsCorrectSizeWhenAligned) {
auto ptr_array = allocator_.MakeShared<int[]>(5, 32);
EXPECT_EQ(ptr_array.size(), 5U);
}
TEST_F(SharedPtrTest, FreedExactlyOnce) {
auto ptr1 = allocator_.MakeShared<Counter>(42u);
EXPECT_EQ(ptr1.use_count(), 1);
EXPECT_EQ(Counter::TakeNumCtorCalls(), 1U);
pw::SharedPtr<Counter> ptr2 = ptr1;
EXPECT_EQ(ptr1.use_count(), 2);
EXPECT_EQ(Counter::TakeNumCtorCalls(), 0U);
EXPECT_EQ(Counter::TakeNumDtorCalls(), 0U);
{
pw::SharedPtr<Counter> ptr3(std::move(ptr1));
EXPECT_EQ(ptr3.use_count(), 2);
EXPECT_EQ(Counter::TakeNumCtorCalls(), 0U);
EXPECT_EQ(Counter::TakeNumDtorCalls(), 0U);
ptr2 = nullptr;
EXPECT_EQ(ptr3.use_count(), 1);
EXPECT_EQ(Counter::TakeNumCtorCalls(), 0U);
EXPECT_EQ(Counter::TakeNumDtorCalls(), 0U);
}
EXPECT_EQ(Counter::TakeNumDtorCalls(), 1U);
}
TEST_F(SharedPtrTest, ArrayFreedExactlyOnce) {
auto ptr1 = allocator_.MakeShared<Counter[]>(5);
EXPECT_EQ(ptr1.use_count(), 1);
EXPECT_EQ(Counter::TakeNumCtorCalls(), 5U);
EXPECT_EQ(Counter::TakeNumDtorCalls(), 0U);
pw::SharedPtr<Counter[]> ptr2 = ptr1;
EXPECT_EQ(ptr1.use_count(), 2);
EXPECT_EQ(Counter::TakeNumCtorCalls(), 0U);
EXPECT_EQ(Counter::TakeNumDtorCalls(), 0U);
{
pw::SharedPtr<Counter[]> ptr3(std::move(ptr1));
EXPECT_EQ(ptr3.use_count(), 2);
EXPECT_EQ(Counter::TakeNumCtorCalls(), 0U);
EXPECT_EQ(Counter::TakeNumDtorCalls(), 0U);
ptr2 = nullptr;
EXPECT_EQ(ptr3.use_count(), 1);
EXPECT_EQ(Counter::TakeNumCtorCalls(), 0U);
EXPECT_EQ(Counter::TakeNumDtorCalls(), 0U);
}
EXPECT_EQ(Counter::TakeNumDtorCalls(), 5U);
}
TEST_F(SharedPtrTest, OwnerBeforeProvidesPartialOrder) {
auto ptr1 = allocator_.MakeShared<int>(111);
auto ptr2 = allocator_.MakeShared<int>(222);
auto ptr3 = ptr2;
auto ptr4 = allocator_.MakeShared<int>(444);
// Remain agnostic to allocation order.
bool ascending = ptr1.owner_before(ptr2);
// Reflexive.
EXPECT_FALSE(ptr1.owner_before(ptr1));
EXPECT_FALSE(ptr2.owner_before(ptr3));
EXPECT_FALSE(ptr3.owner_before(ptr2));
// Symmetric.
EXPECT_NE(ptr2.owner_before(ptr1), ascending);
// Transitive.
EXPECT_EQ(ptr2.owner_before(ptr4), ascending);
EXPECT_EQ(ptr1.owner_before(ptr4), ascending);
}
TEST_F(SharedPtrTest, CanSwapWhenNeitherAreEmpty) {
auto ptr1 = allocator_.MakeShared<Counter>(111u);
auto ptr2 = allocator_.MakeShared<Counter>(222u);
ptr1.swap(ptr2);
EXPECT_EQ(ptr1->value(), 222u);
EXPECT_EQ(ptr2->value(), 111u);
}
TEST_F(SharedPtrTest, CanSwapWhenOneIsEmpty) {
auto ptr1 = allocator_.MakeShared<Counter>(111u);
pw::SharedPtr<Counter> ptr2;
// ptr2 is empty.
ptr1.swap(ptr2);
EXPECT_EQ(ptr2->value(), 111u);
EXPECT_EQ(ptr1, nullptr);
// ptr1 is empty.
ptr1.swap(ptr2);
EXPECT_EQ(ptr1->value(), 111u);
EXPECT_EQ(ptr2, nullptr);
}
TEST_F(SharedPtrTest, CanSwapWhenBothAreEmpty) {
pw::SharedPtr<Counter> ptr1;
pw::SharedPtr<Counter> ptr2;
ptr1.swap(ptr2);
EXPECT_EQ(ptr1, nullptr);
EXPECT_EQ(ptr2, nullptr);
}
TEST_F(SharedPtrTest, Conversions) {
struct Foo {
int foo() const { return 1; }
};
struct Bar : public Foo {
int bar() const { return 2; }
};
struct Baz : public Bar {
int baz() const { return 3; }
};
pw::SharedPtr<Bar> bar = allocator_.MakeShared<Baz>();
// Upcast.
pw::SharedPtr<Foo> foo = bar;
EXPECT_EQ(static_cast<pw::SharedPtr<Bar>>(foo), bar);
// Downcast.
pw::SharedPtr<Baz> baz = static_cast<pw::SharedPtr<Baz>>(foo);
EXPECT_EQ(baz->foo(), 1);
EXPECT_EQ(baz->bar(), 2);
EXPECT_EQ(baz->baz(), 3);
}
TEST_F(SharedPtrTest, SharedFromUniquePtr) {
pw::UniquePtr<Counter> owned = allocator_.MakeUnique<Counter>(5u);
pw::SharedPtr<Counter> shared(owned);
ASSERT_NE(shared, nullptr);
EXPECT_EQ(owned, nullptr);
EXPECT_EQ(shared->value(), 5u);
}
TEST_F(SharedPtrTest, SharedFromUniquePtrFailsOnAllocationFailure) {
pw::UniquePtr<Counter> owned = allocator_.MakeUnique<Counter>(5u);
allocator_.Exhaust();
pw::SharedPtr<Counter> shared(owned);
EXPECT_EQ(shared, nullptr);
ASSERT_NE(owned, nullptr);
EXPECT_EQ(owned->value(), 5u);
}
} // namespace
// TODO(b/402489948): Remove when portable atomics are provided by `pw_atomic`.
#endif // PW_ALLOCATOR_HAS_ATOMICS