blob: 9ee674b36a4affe53437252a60a670ba19533534 [file] [log] [blame]
// Copyright 2023 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/allocator.h"
#include <cstddef>
#include "pw_allocator/capability.h"
#include "pw_allocator/internal/counter.h"
#include "pw_allocator/testing.h"
#include "pw_unit_test/framework.h"
namespace {
using ::pw::allocator::Capability;
using ::pw::allocator::Layout;
using ::pw::allocator::test::Counter;
class AllocatorTest : public ::pw::allocator::test::TestWithCounters {
protected:
using AllocatorType = ::pw::allocator::test::AllocatorForTest<256>;
using BlockType = typename AllocatorType::BlockType;
static_assert(sizeof(uintptr_t) == BlockType::kAlignment);
AllocatorType allocator_;
};
TEST_F(AllocatorTest, HasFlags) {
EXPECT_TRUE(
allocator_.HasCapability(Capability::kImplementsGetRequestedLayout));
EXPECT_TRUE(allocator_.HasCapability(Capability::kImplementsGetUsableLayout));
}
TEST_F(AllocatorTest, NewAndDelete) {
auto* counter = allocator_.New<Counter>(2u);
ASSERT_NE(counter, nullptr);
EXPECT_EQ(counter->value(), 2u);
EXPECT_EQ(Counter::TakeNumDtorCalls(), 0u);
allocator_.Delete(counter);
EXPECT_EQ(Counter::TakeNumDtorCalls(), 1u);
}
class VirtualCounter : public Counter {
public:
virtual ~VirtualCounter() { EXPECT_TRUE(must_be_set_by_derived_destructor_); }
protected:
explicit VirtualCounter(size_t value) : Counter(value) {}
bool must_be_set_by_derived_destructor_ = false;
};
class DerivedVirtualCounter : public VirtualCounter {
public:
explicit DerivedVirtualCounter(size_t value) : VirtualCounter(value) {}
~DerivedVirtualCounter() override {
must_be_set_by_derived_destructor_ = filler_bools[0];
}
private:
// Add an array of bools to increase the size of the object. Use the first
// value in the destructor so the array is used.
bool filler_bools[12] = {true};
};
static_assert(sizeof(DerivedVirtualCounter) != sizeof(VirtualCounter),
"Test should cover deleting an object with a different layout");
TEST_F(AllocatorTest, NewAndDeleteVirtualDestructor) {
// Assign the DerivedVirtualCounter* to its base VirtualCounter*.
VirtualCounter* counter = allocator_.New<DerivedVirtualCounter>(2u);
ASSERT_NE(counter, nullptr);
EXPECT_EQ(counter->value(), 2u);
EXPECT_EQ(Counter::TakeNumDtorCalls(), 0u);
allocator_.Delete(counter);
EXPECT_EQ(Counter::TakeNumDtorCalls(), 1u);
}
TEST_F(AllocatorTest, NewAndDeleteBoundedArray) {
auto* counters = allocator_.New<Counter[3]>();
ASSERT_NE(counters, nullptr);
allocator_.Delete<Counter[3]>(counters);
EXPECT_EQ(Counter::TakeNumDtorCalls(), 3u);
EXPECT_EQ(allocator_.deallocate_size(), 3 * sizeof(Counter));
}
TEST_F(AllocatorTest, NewAndDeleteUnboundedArray) {
auto* counters = allocator_.New<Counter[]>(5);
ASSERT_NE(counters, nullptr);
allocator_.Delete<Counter[]>(counters, 5);
EXPECT_EQ(Counter::TakeNumDtorCalls(), 5u);
EXPECT_EQ(allocator_.deallocate_size(), 5 * sizeof(Counter));
}
TEST_F(AllocatorTest, NewAndDeleteArray) {
auto* counters = allocator_.New<Counter[3]>();
ASSERT_NE(counters, nullptr);
allocator_.DeleteArray(counters, 3);
EXPECT_EQ(Counter::TakeNumDtorCalls(), 3u);
EXPECT_EQ(allocator_.deallocate_size(), 3 * sizeof(Counter));
}
TEST_F(AllocatorTest, ResizeNull) {
EXPECT_FALSE(allocator_.Resize(nullptr, sizeof(uintptr_t)));
}
TEST_F(AllocatorTest, ResizeZero) {
constexpr Layout layout = Layout::Of<uintptr_t>();
void* ptr = allocator_.Allocate(layout);
ASSERT_NE(ptr, nullptr);
EXPECT_FALSE(allocator_.Resize(ptr, 0));
}
TEST_F(AllocatorTest, ResizeSame) {
constexpr Layout layout = Layout::Of<uintptr_t>();
void* ptr = allocator_.Allocate(layout);
ASSERT_NE(ptr, nullptr);
EXPECT_TRUE(allocator_.Resize(ptr, layout.size()));
EXPECT_EQ(allocator_.resize_ptr(), ptr);
EXPECT_EQ(allocator_.resize_old_size(), layout.size());
EXPECT_EQ(allocator_.resize_new_size(), layout.size());
}
TEST_F(AllocatorTest, ReallocateNull) {
constexpr Layout old_layout = Layout::Of<uintptr_t>();
constexpr Layout new_layout(old_layout.size(), old_layout.alignment());
void* new_ptr = allocator_.Reallocate(nullptr, new_layout);
// Resize should fail and Reallocate should call Allocate.
EXPECT_EQ(allocator_.allocate_size(), new_layout.size());
// Deallocate should not be called.
EXPECT_EQ(allocator_.deallocate_ptr(), nullptr);
EXPECT_EQ(allocator_.deallocate_size(), 0U);
// Overall, Reallocate should succeed.
EXPECT_NE(new_ptr, nullptr);
}
TEST_F(AllocatorTest, ReallocateZeroNewSize) {
constexpr Layout old_layout = Layout::Of<uintptr_t[3]>();
void* ptr = allocator_.Allocate(old_layout);
ASSERT_EQ(allocator_.allocate_size(), old_layout.size());
ASSERT_NE(ptr, nullptr);
allocator_.ResetParameters();
constexpr Layout new_layout(0, old_layout.alignment());
void* new_ptr = allocator_.Reallocate(ptr, new_layout);
// Reallocate does not call Resize, Allocate, or Deallocate.
EXPECT_EQ(allocator_.resize_ptr(), nullptr);
EXPECT_EQ(allocator_.resize_old_size(), 0U);
EXPECT_EQ(allocator_.resize_new_size(), 0U);
EXPECT_EQ(allocator_.allocate_size(), 0U);
EXPECT_EQ(allocator_.deallocate_ptr(), nullptr);
EXPECT_EQ(allocator_.deallocate_size(), 0U);
// Overall, Reallocate should fail.
EXPECT_EQ(new_ptr, nullptr);
}
TEST_F(AllocatorTest, ReallocateSame) {
constexpr Layout layout = Layout::Of<uintptr_t[3]>();
void* ptr = allocator_.Allocate(layout);
ASSERT_EQ(allocator_.allocate_size(), layout.size());
ASSERT_NE(ptr, nullptr);
allocator_.ResetParameters();
void* new_ptr = allocator_.Reallocate(ptr, layout);
// Reallocate should call Resize.
EXPECT_EQ(allocator_.resize_ptr(), ptr);
EXPECT_EQ(allocator_.resize_old_size(), layout.size());
EXPECT_EQ(allocator_.resize_new_size(), layout.size());
// DoAllocate should not be called.
EXPECT_EQ(allocator_.allocate_size(), 0U);
// DoDeallocate should not be called.
EXPECT_EQ(allocator_.deallocate_ptr(), nullptr);
EXPECT_EQ(allocator_.deallocate_size(), 0U);
// Overall, Reallocate should succeed.
EXPECT_EQ(new_ptr, ptr);
}
TEST_F(AllocatorTest, ReallocateSmaller) {
constexpr Layout old_layout = Layout::Of<uintptr_t[3]>();
void* ptr = allocator_.Allocate(old_layout);
ASSERT_EQ(allocator_.allocate_size(), old_layout.size());
ASSERT_NE(ptr, nullptr);
allocator_.ResetParameters();
constexpr Layout new_layout(sizeof(uintptr_t), old_layout.alignment());
void* new_ptr = allocator_.Reallocate(ptr, new_layout);
// Reallocate should call Resize.
EXPECT_EQ(allocator_.resize_ptr(), ptr);
EXPECT_EQ(allocator_.resize_old_size(), old_layout.size());
EXPECT_EQ(allocator_.resize_new_size(), new_layout.size());
// Allocate should not be called.
EXPECT_EQ(allocator_.allocate_size(), 0U);
// Deallocate should not be called.
EXPECT_EQ(allocator_.deallocate_ptr(), nullptr);
EXPECT_EQ(allocator_.deallocate_size(), 0U);
// Overall, Reallocate should succeed.
EXPECT_EQ(new_ptr, ptr);
}
TEST_F(AllocatorTest, ReallocateLarger) {
constexpr Layout old_layout = Layout::Of<uintptr_t>();
void* ptr = allocator_.Allocate(old_layout);
ASSERT_EQ(allocator_.allocate_size(), old_layout.size());
ASSERT_NE(ptr, nullptr);
// The abstraction is a bit leaky here: This tests relies on the details of
// `Resize` in order to get it to fail and fallback to
// allocate/copy/deallocate. Allocate a second block, which should prevent the
// first one from being able to grow.
void* next = allocator_.Allocate(old_layout);
ASSERT_EQ(allocator_.allocate_size(), old_layout.size());
ASSERT_NE(next, nullptr);
allocator_.ResetParameters();
constexpr Layout new_layout(sizeof(uintptr_t[3]), old_layout.alignment());
void* new_ptr = allocator_.Reallocate(ptr, new_layout);
// Reallocate should call Resize.
EXPECT_EQ(allocator_.resize_ptr(), ptr);
EXPECT_EQ(allocator_.resize_old_size(), old_layout.size());
EXPECT_EQ(allocator_.resize_new_size(), new_layout.size());
// Resize should fail and Reallocate should call Allocate.
EXPECT_EQ(allocator_.allocate_size(), new_layout.size());
// Deallocate should also be called.
EXPECT_EQ(allocator_.deallocate_ptr(), ptr);
EXPECT_EQ(allocator_.deallocate_size(), old_layout.size());
// Overall, Reallocate should succeed.
EXPECT_NE(new_ptr, nullptr);
EXPECT_NE(new_ptr, ptr);
}
// Test fixture for IsEqual tests.
class BaseAllocator : public pw::Allocator {
public:
BaseAllocator(void* ptr) : pw::Allocator(Capabilities()), ptr_(ptr) {}
private:
void* DoAllocate(Layout) override {
void* ptr = ptr_;
ptr_ = nullptr;
return ptr;
}
void DoDeallocate(void*) override {}
void DoDeallocate(void*, Layout) override {}
void* ptr_;
};
// Test fixture for IsEqual tests.
class DerivedAllocator : public BaseAllocator {
public:
DerivedAllocator(size_t value, void* ptr)
: BaseAllocator(ptr), value_(value) {}
size_t value() const { return value_; }
private:
size_t value_;
};
TEST_F(AllocatorTest, IsEqualFailsWithDifferentObjects) {
std::array<std::byte, 8> buffer;
DerivedAllocator derived1(1, buffer.data());
DerivedAllocator derived2(2, buffer.data());
EXPECT_FALSE(derived1.IsEqual(derived2));
EXPECT_FALSE(derived2.IsEqual(derived1));
}
TEST_F(AllocatorTest, IsEqualSucceedsWithSameObject) {
std::array<std::byte, 8> buffer;
DerivedAllocator derived(1, buffer.data());
BaseAllocator* base = &derived;
EXPECT_TRUE(derived.IsEqual(*base));
EXPECT_TRUE(base->IsEqual(derived));
}
} // namespace