54247fb266
Reviewers: mtomic, mferencevic, msantl Reviewed By: mtomic Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D1997
149 lines
5.2 KiB
C++
149 lines
5.2 KiB
C++
#include <cstdint>
|
|
#include <cstring>
|
|
#include <limits>
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
#include "utils/memory.hpp"
|
|
|
|
class TestMemory final : public utils::MemoryResource {
|
|
public:
|
|
size_t new_count_{0};
|
|
size_t delete_count_{0};
|
|
|
|
private:
|
|
void *DoAllocate(size_t bytes, size_t alignment) override {
|
|
new_count_++;
|
|
EXPECT_TRUE(alignment != 0U && (alignment & (alignment - 1U)) == 0U)
|
|
<< "Alignment must be power of 2";
|
|
EXPECT_TRUE(alignment <= alignof(std::max_align_t));
|
|
EXPECT_NE(bytes, 0);
|
|
const size_t pad_size = 32;
|
|
EXPECT_TRUE(bytes + pad_size > bytes) << "TestMemory size overflow";
|
|
EXPECT_TRUE(bytes + pad_size + alignment > bytes + alignment)
|
|
<< "TestMemory size overflow";
|
|
EXPECT_TRUE(2U * alignment > alignment) << "TestMemory alignment overflow";
|
|
// Allocate a block containing extra alignment and pad_size bytes, but
|
|
// aligned to 2 * alignment. Then we can offset the ptr so that it's never
|
|
// aligned to 2 * alignment. This ought to make allocator alignment issues
|
|
// more obvious.
|
|
void *ptr = utils::NewDeleteResource()->Allocate(
|
|
alignment + bytes + pad_size, 2U * alignment);
|
|
// Clear allocated memory to 0xFF, marking the invalid region.
|
|
memset(ptr, 0xFF, alignment + bytes + pad_size);
|
|
// Offset the ptr so it's not aligned to 2 * alignment, but still aligned to
|
|
// alignment.
|
|
ptr = static_cast<char *>(ptr) + alignment;
|
|
// Clear the valid region to 0x00, so that we can more easily test that the
|
|
// allocator is doing the right thing.
|
|
memset(ptr, 0, bytes);
|
|
return ptr;
|
|
}
|
|
|
|
void DoDeallocate(void *ptr, size_t bytes, size_t alignment) override {
|
|
delete_count_++;
|
|
// Dealloate the original ptr, before alignment adjustment.
|
|
return utils::NewDeleteResource()->Deallocate(
|
|
static_cast<char *>(ptr) - alignment, bytes, alignment);
|
|
}
|
|
|
|
bool DoIsEqual(const utils::MemoryResource &other) const noexcept override {
|
|
return this == &other;
|
|
}
|
|
};
|
|
|
|
void *CheckAllocation(utils::MemoryResource *mem, size_t bytes,
|
|
size_t alignment = alignof(std::max_align_t)) {
|
|
void *ptr = mem->Allocate(bytes, alignment);
|
|
if (alignment > alignof(std::max_align_t))
|
|
alignment = alignof(std::max_align_t);
|
|
EXPECT_TRUE(ptr);
|
|
EXPECT_EQ(reinterpret_cast<uintptr_t>(ptr) % alignment, 0)
|
|
<< "Allocated misaligned pointer!";
|
|
// There should be no 0xFF bytes because they are either padded at the end of
|
|
// the allocated block or are found in already checked allocations.
|
|
EXPECT_FALSE(memchr(ptr, 0xFF, bytes)) << "Invalid memory region!";
|
|
// Mark the checked allocation with 0xFF bytes.
|
|
memset(ptr, 0xFF, bytes);
|
|
return ptr;
|
|
}
|
|
|
|
TEST(MonotonicBufferResource, AllocationWithinInitialSize) {
|
|
TestMemory test_mem;
|
|
{
|
|
utils::MonotonicBufferResource mem(1024, &test_mem);
|
|
void *fst_ptr = CheckAllocation(&mem, 24, 1);
|
|
void *snd_ptr = CheckAllocation(&mem, 1000, 1);
|
|
EXPECT_EQ(test_mem.new_count_, 1);
|
|
EXPECT_EQ(test_mem.delete_count_, 0);
|
|
mem.Deallocate(snd_ptr, 1000, 1);
|
|
mem.Deallocate(fst_ptr, 24, 1);
|
|
EXPECT_EQ(test_mem.delete_count_, 0);
|
|
mem.Release();
|
|
EXPECT_EQ(test_mem.delete_count_, 1);
|
|
CheckAllocation(&mem, 1024);
|
|
EXPECT_EQ(test_mem.new_count_, 2);
|
|
EXPECT_EQ(test_mem.delete_count_, 1);
|
|
}
|
|
EXPECT_EQ(test_mem.delete_count_, 2);
|
|
}
|
|
|
|
TEST(MonotonicBufferResource, AllocationOverInitialSize) {
|
|
TestMemory test_mem;
|
|
{
|
|
utils::MonotonicBufferResource mem(1024, &test_mem);
|
|
CheckAllocation(&mem, 1025, 1);
|
|
EXPECT_EQ(test_mem.new_count_, 1);
|
|
}
|
|
EXPECT_EQ(test_mem.delete_count_, 1);
|
|
{
|
|
utils::MonotonicBufferResource mem(1024, &test_mem);
|
|
// Test with large alignment
|
|
CheckAllocation(&mem, 1024, 1024);
|
|
EXPECT_EQ(test_mem.new_count_, 2);
|
|
}
|
|
EXPECT_EQ(test_mem.delete_count_, 2);
|
|
}
|
|
|
|
TEST(MonotonicBufferResource, AllocationOverCapacity) {
|
|
TestMemory test_mem;
|
|
{
|
|
utils::MonotonicBufferResource mem(1024, &test_mem);
|
|
CheckAllocation(&mem, 24, 1);
|
|
EXPECT_EQ(test_mem.new_count_, 1);
|
|
CheckAllocation(&mem, 1000, 64);
|
|
EXPECT_EQ(test_mem.new_count_, 2);
|
|
EXPECT_EQ(test_mem.delete_count_, 0);
|
|
mem.Release();
|
|
EXPECT_EQ(test_mem.new_count_, 2);
|
|
EXPECT_EQ(test_mem.delete_count_, 2);
|
|
CheckAllocation(&mem, 1025, 1);
|
|
EXPECT_EQ(test_mem.new_count_, 3);
|
|
CheckAllocation(&mem, 1023, 1);
|
|
// MonotonicBufferResource state after Release is called may or may not
|
|
// allocate a larger block right from the start (i.e. tracked buffer sizes
|
|
// before Release may be retained).
|
|
EXPECT_TRUE(test_mem.new_count_ >= 3);
|
|
}
|
|
EXPECT_TRUE(test_mem.delete_count_ >= 3);
|
|
}
|
|
|
|
TEST(MonotonicBufferResource, AllocationWithAlignmentNotPowerOf2) {
|
|
utils::MonotonicBufferResource mem(1024);
|
|
EXPECT_THROW(mem.Allocate(24, 3), std::bad_alloc);
|
|
EXPECT_THROW(mem.Allocate(24, 0), std::bad_alloc);
|
|
}
|
|
|
|
TEST(MonotonicBufferResource, AllocationWithSize0) {
|
|
utils::MonotonicBufferResource mem(1024);
|
|
EXPECT_THROW(mem.Allocate(0), std::bad_alloc);
|
|
}
|
|
|
|
TEST(MonotonicBufferResource, AllocationWithSizeOverflow) {
|
|
size_t max_size = std::numeric_limits<size_t>::max();
|
|
utils::MonotonicBufferResource mem(1024);
|
|
// Setup so that the next allocation aligning max_size causes overflow.
|
|
mem.Allocate(1, 1);
|
|
EXPECT_THROW(mem.Allocate(max_size, 4), std::bad_alloc);
|
|
}
|