// Copyright 2024 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_multibuf/simple_allocator.h" #include "gtest/gtest.h" #include "pw_allocator/null_allocator.h" #include "pw_allocator/testing.h" namespace pw::multibuf { namespace { using ::pw::allocator::test::AllocatorForTest; constexpr size_t kArbitraryBufferSize = 1024; constexpr size_t kArbitraryMetaSize = 1024; TEST(SimpleAllocator, AllocateWholeDataAreaSizeSucceeds) { std::array data_area; AllocatorForTest meta_alloc; SimpleAllocator simple_allocator(data_area, meta_alloc); std::optional buf = simple_allocator.Allocate(kArbitraryBufferSize); ASSERT_TRUE(buf.has_value()); EXPECT_EQ(buf->size(), kArbitraryBufferSize); } TEST(SimpleAllocator, AllocateContiguousWholeDataAreaSizeSucceeds) { std::array data_area; AllocatorForTest meta_alloc; SimpleAllocator simple_allocator(data_area, meta_alloc); std::optional buf = simple_allocator.AllocateContiguous(kArbitraryBufferSize); ASSERT_TRUE(buf.has_value()); EXPECT_EQ(buf->Chunks().size(), 1U); EXPECT_EQ(buf->size(), kArbitraryBufferSize); } TEST(SimpleAllocator, AllocateContiguousHalfDataAreaSizeTwiceSucceeds) { std::array data_area; AllocatorForTest meta_alloc; SimpleAllocator simple_allocator(data_area, meta_alloc); std::optional buf = simple_allocator.AllocateContiguous(kArbitraryBufferSize / 2); ASSERT_TRUE(buf.has_value()); EXPECT_EQ(buf->Chunks().size(), 1U); EXPECT_EQ(buf->size(), kArbitraryBufferSize / 2); std::optional buf2 = simple_allocator.AllocateContiguous(kArbitraryBufferSize / 2); ASSERT_TRUE(buf2.has_value()); EXPECT_EQ(buf2->Chunks().size(), 1U); EXPECT_EQ(buf2->size(), kArbitraryBufferSize / 2); } TEST(SimpleAllocator, AllocateTooLargeReturnsNullopt) { std::array data_area; AllocatorForTest meta_alloc; SimpleAllocator simple_allocator(data_area, meta_alloc); std::optional buf = simple_allocator.Allocate(kArbitraryBufferSize + 1); EXPECT_FALSE(buf.has_value()); std::optional contiguous_buf = simple_allocator.Allocate(kArbitraryBufferSize + 1); EXPECT_FALSE(contiguous_buf.has_value()); } TEST(SimpleAllocator, AllocateZeroWithNoMetadataOrDataReturnsEmptyMultiBuf) { std::array data_area; pw::allocator::NullAllocator meta_alloc; SimpleAllocator simple_allocator(data_area, meta_alloc); std::optional buf = simple_allocator.Allocate(0); ASSERT_TRUE(buf.has_value()); EXPECT_EQ(buf->size(), 0U); } TEST(SimpleAllocator, AllocateWithNoMetadataRoomReturnsNullopt) { std::array data_area; pw::allocator::NullAllocator meta_alloc; SimpleAllocator simple_allocator(data_area, meta_alloc); std::optional buf = simple_allocator.Allocate(1); EXPECT_FALSE(buf.has_value()); } TEST(SimpleAllocator, SecondLargeAllocationFailsUntilFirstAllocationReleased) { std::array data_area; AllocatorForTest meta_alloc; SimpleAllocator simple_allocator(data_area, meta_alloc); const size_t alloc_size = kArbitraryBufferSize / 2 + 1; std::optional buf = simple_allocator.Allocate(alloc_size); ASSERT_TRUE(buf.has_value()); EXPECT_EQ(buf->size(), alloc_size); EXPECT_FALSE(simple_allocator.Allocate(alloc_size).has_value()); // Release the first buffer buf = std::nullopt; EXPECT_TRUE(simple_allocator.Allocate(alloc_size).has_value()); } TEST(SimpleAllocator, AllocateSkipsMiddleAllocations) { std::array data_area; AllocatorForTest meta_alloc; SimpleAllocator simple_allocator(data_area, meta_alloc); const size_t alloc_size = kArbitraryBufferSize / 3; std::optional buf1 = simple_allocator.Allocate(alloc_size); std::optional buf2 = simple_allocator.Allocate(alloc_size); std::optional buf3 = simple_allocator.Allocate(alloc_size); EXPECT_TRUE(buf1.has_value()); EXPECT_TRUE(buf2.has_value()); EXPECT_TRUE(buf3.has_value()); buf1 = std::nullopt; buf3 = std::nullopt; // Now `buf2` holds the middle third of data_area std::optional split = simple_allocator.Allocate(alloc_size * 2); ASSERT_TRUE(split.has_value()); EXPECT_EQ(split->size(), alloc_size * 2); EXPECT_EQ(split->Chunks().size(), 2U); } TEST(SimpleAllocator, FailedAllocationDoesNotHoldOntoChunks) { std::array data_area; AllocatorForTest meta_alloc; SimpleAllocator simple_allocator(data_area, meta_alloc); const size_t alloc_size = kArbitraryBufferSize / 2; std::optional buf1 = simple_allocator.Allocate(alloc_size); std::optional buf2 = simple_allocator.Allocate(alloc_size); EXPECT_TRUE(buf1.has_value()); EXPECT_TRUE(buf2.has_value()); buf1 = std::nullopt; // When this allocation is attempted, it will initially create a chunk for // the first empty region prior to failing. EXPECT_FALSE(simple_allocator.Allocate(kArbitraryBufferSize).has_value()); buf2 = std::nullopt; // Ensure that all chunk holds are released by attempting an allocation. EXPECT_TRUE(simple_allocator.Allocate(kArbitraryBufferSize).has_value()); } } // namespace } // namespace pw::multibuf