blob: 3b202d48fbf546b695b819909e061606b8869a58 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include <lib/memalloc.h>
#include <zircon/assert.h>
#include <zircon/types.h>
#include <array>
#include <cstdlib>
#include <memory>
#include <fbl/span.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
namespace memalloc {
namespace {
// GMock matcher to determine if a given zx::status result was successful or failed.
MATCHER(IsOk, "") { return arg.is_ok(); }
MATCHER_P(HasValue, val, "") { return arg.is_ok() && arg.value() == val; }
MATCHER_P(HasError, val, "") { return arg.is_error() && arg.error_value() == val; }
// As above, but also matches against the value:
//
// EXPECT_THAT(view.operation(), IsOkAndHolds(Eq(3u)));
template <typename X>
auto IsOkAndHolds(X matcher) {
return ::testing::AllOf(
IsOk(), ::testing::ResultOf([](const auto& val) { return val.value(); }, matcher));
}
// Create an Allocator and associated storage.
template <size_t Elements = 100>
struct AllocatorAndStorage {
AllocatorAndStorage() : data(), allocator(fbl::Span(data.data(), data.size())) {}
std::array<RangeStorage, Elements> data;
Allocator allocator;
};
TEST(Range, Equality) {
EXPECT_EQ(Range::FromFirstAndLast(0, 1), Range::FromFirstAndLast(0, 1));
EXPECT_NE(Range::FromFirstAndLast(0, 1), Range::FromFirstAndLast(0, 0));
EXPECT_NE(Range::FromFirstAndLast(0, 1), Range::FromFirstAndLast(1, 1));
EXPECT_NE(Range::FromFirstAndLast(0, 1), Range::FromFirstAndLast(1, 0));
}
TEST(Allocator, EmptyAllocator) {
Allocator allocator{fbl::Span<RangeStorage>{}};
EXPECT_THAT(allocator.Allocate(1), HasError(ZX_ERR_NO_RESOURCES));
}
TEST(Allocator, ZeroSizeRanges) {
AllocatorAndStorage storage;
Allocator& allocator = storage.allocator;
// Add an empty range to the allocator.
EXPECT_THAT(allocator.AddRange(0, 0), IsOk());
// Add a real range to the allocator.
EXPECT_THAT(allocator.AddRange(100, 300), IsOk());
// Allocate some empty ranges.
EXPECT_THAT(allocator.Allocate(0), HasValue(0u));
EXPECT_THAT(allocator.Allocate(0), HasValue(0u));
// Allocate some real ranges again.
EXPECT_THAT(allocator.Allocate(200), HasValue(100u));
}
TEST(Allocator, SingleRange) {
AllocatorAndStorage storage;
Allocator& allocator = storage.allocator;
// Create an allocator with a single range in it.
ASSERT_THAT(allocator.AddRange(100, 300), IsOk());
// Expect to be able to allocate it again.
EXPECT_THAT(allocator.Allocate(200), HasValue(100u));
// Ensure we are empty.
EXPECT_THAT(allocator.Allocate(200), HasError(ZX_ERR_NO_RESOURCES));
}
TEST(Allocator, MultipleAllocations) {
AllocatorAndStorage storage;
Allocator& allocator = storage.allocator;
// Create an allocator with a range of size 100.
ASSERT_THAT(allocator.AddRange(100, 100), IsOk());
// Allocate three subranges.
uint64_t a = allocator.Allocate(10).value_or(0);
ASSERT_NE(a, 0u);
uint64_t b = allocator.Allocate(20).value_or(0);
ASSERT_NE(b, 0u);
uint64_t c = allocator.Allocate(70).value_or(0);
ASSERT_NE(c, 0u);
// Ensure the allocator is empty.
EXPECT_THAT(allocator.Allocate(1), HasError(ZX_ERR_NO_RESOURCES));
// Try adding pages back again in a different order.
EXPECT_THAT(allocator.AddRange(a, 10), IsOk());
EXPECT_THAT(allocator.AddRange(c, 70), IsOk());
EXPECT_THAT(allocator.AddRange(b, 20), IsOk());
// We should be able to allocate the entire original range again.
EXPECT_THAT(allocator.Allocate(100), HasValue(100u));
}
TEST(Allocator, AlignedAllocations) {
AllocatorAndStorage storage;
Allocator& allocator = storage.allocator;
// Create a large range.
ASSERT_THAT(allocator.AddRange(1, 16 * 1024 * 1024), IsOk());
// Allocate ranges, at increasing alignment.
for (size_t alignment = 1; alignment <= 1024 * 1024; alignment *= 2) {
uint64_t result = allocator.Allocate(1, alignment).value_or(0);
ASSERT_NE(result, 0u);
EXPECT_EQ(result % alignment, 0u) << "Allocation was not aligned as requested.";
}
}
TEST(Allocator, DeallocationMerging) {
AllocatorAndStorage storage;
Allocator& allocator = storage.allocator;
// Add a range of size 4 into the allocator.
ASSERT_THAT(allocator.AddRange(1, 4), IsOk());
// Allocate 4 the four units and deallocate them again in every possible order.
//
// We attempt all 4 factorial methods of deallocating them to exercise the merging logic.
std::vector<size_t> permutation = {0, 1, 2, 3};
do {
std::vector<uint64_t> values;
// Allocate 4 values.
for (int i = 0; i < 4; i++) {
zx::status<uint64_t> result = allocator.Allocate(1);
ASSERT_THAT(result, IsOk());
values.push_back(result.value());
}
// Deallocate in a given order.
for (int i = 0; i < 4; i++) {
ASSERT_THAT(allocator.AddRange(values[permutation[i]], 1), IsOk());
}
// Ensure we can allocate the full range again.
zx::status<uint64_t> result = allocator.Allocate(4);
ASSERT_THAT(result, IsOk());
ASSERT_EQ(result.value(), 1u);
ASSERT_THAT(allocator.AddRange(1, 4), IsOk());
} while (std::next_permutation(permutation.begin(), permutation.end()));
}
TEST(Allocator, Overflow) {
AllocatorAndStorage storage;
Allocator& allocator = storage.allocator;
constexpr uint64_t kMaxAlign = uint64_t{1} << 63;
// Add a range of size 1024 to the allocator.
ASSERT_THAT(allocator.AddRange(1, 1024), IsOk());
// Attempt to allocate various amounts likely to cause overflow in internal calculations.
EXPECT_THAT(allocator.Allocate(UINT64_MAX, 1), HasError(ZX_ERR_NO_RESOURCES));
EXPECT_THAT(allocator.Allocate(1, kMaxAlign), HasError(ZX_ERR_NO_RESOURCES));
EXPECT_THAT(allocator.Allocate(UINT64_MAX, kMaxAlign), HasError(ZX_ERR_NO_RESOURCES));
EXPECT_THAT(allocator.Allocate(kMaxAlign, kMaxAlign), HasError(ZX_ERR_NO_RESOURCES));
}
TEST(Allocator, OverlappingAllocations) {
AllocatorAndStorage storage;
Allocator& allocator = storage.allocator;
// Create several overlapping allocations, eventually covering [0, 10).
EXPECT_THAT(allocator.AddRange(1, 1), IsOk()); // [1, 2)
EXPECT_THAT(allocator.AddRange(3, 1), IsOk()); // [3, 4)
EXPECT_THAT(allocator.AddRange(5, 1), IsOk()); // [5, 6)
EXPECT_THAT(allocator.AddRange(7, 1), IsOk()); // [7, 8)
EXPECT_THAT(allocator.AddRange(5, 5), IsOk()); // [5, 10)
EXPECT_THAT(allocator.AddRange(0, 5), IsOk()); // [0, 5)
// We should be able to allocate a range of size 10, but no more.
EXPECT_THAT(allocator.Allocate(10), IsOk());
EXPECT_THAT(allocator.Allocate(1), HasError(ZX_ERR_NO_RESOURCES));
}
TEST(Allocator, FullRange) {
AllocatorAndStorage storage;
Allocator& allocator = storage.allocator;
// Add ranges that will cause the full 2**64 space to be filled.
//
// Because the range has 2**64 elements, but we can only pass in
// a range of length (2**64 - 1), we do this in two calls.
ASSERT_THAT(allocator.AddRange(0, 1), IsOk());
ASSERT_THAT(allocator.AddRange(1, UINT64_MAX), IsOk());
// Ensure we can allocate the full range.
zx::status<uint64_t> a = allocator.Allocate(0x80000000'00000000);
ASSERT_THAT(a, IsOk());
zx::status<uint64_t> b = allocator.Allocate(0x80000000'00000000);
ASSERT_THAT(b, IsOk());
}
// Add the given list of ranges, and then remove the second list of ranges. Return the
// number of elements in the remaining range.
uint64_t AddThenRemove(const std::vector<std::pair<uint64_t, uint64_t>>& add,
const std::vector<std::pair<uint64_t, uint64_t>>& remove) {
AllocatorAndStorage storage;
Allocator& allocator = storage.allocator;
// Add the first list of ranges, then remove the second.
for (const auto& range : add) {
(void)allocator.AddRange(range.first, range.second);
}
for (const auto& range : remove) {
(void)allocator.RemoveRange(range.first, range.second);
}
// Keep allocating items from the allocator, until we can't allocate any more.
uint64_t items_left = 0;
while (allocator.Allocate(1).is_ok()) {
items_left++;
}
return items_left;
}
TEST(Allocator, RemoveRange) {
// Remove range that doesn't exist.
EXPECT_EQ(AddThenRemove({}, {{0, 10}}), 0u);
// Remove full range.
EXPECT_EQ(AddThenRemove({{0, 10}}, {{0, 10}}), 0u);
// Remove area larger than a range.
EXPECT_EQ(AddThenRemove({{1, 8}}, {{0, 10}}), 0u);
// Remove area covering two ranges.
EXPECT_EQ(AddThenRemove({{1, 1}, {8, 1}}, {{0, 10}}), 0u);
// Remove end of a range.
EXPECT_EQ(AddThenRemove({{0, 10}}, {{5, 10}}), 5u);
// Remove beginning of a range.
EXPECT_EQ(AddThenRemove({{5, 10}}, {{0, 10}}), 5u);
// Remove middle of a range.
EXPECT_EQ(AddThenRemove({{0, 10}}, {{5, 2}}), 8u);
// Remove end of one range and the beginning of another.
EXPECT_EQ(AddThenRemove({{0, 2}, {8, 2}}, {{1, 8}}), 2u);
}
TEST(Allocator, IterateEmpty) {
Allocator allocator{fbl::Span<RangeStorage>{}};
// Expect the iterator begin and end to be the same.
EXPECT_EQ(allocator.begin(), allocator.end());
// Ensure the standard library can successfully iterate over us.
std::vector<Range> ranges(allocator.begin(), allocator.end());
EXPECT_TRUE(ranges.empty());
}
TEST(Allocator, ManualIterationSingleRange) {
AllocatorAndStorage storage;
Allocator& allocator = storage.allocator;
// Add a range.
EXPECT_THAT(allocator.AddRange(42, 10), IsOk());
// Ensure we see it during iteration.
auto it = allocator.begin();
EXPECT_EQ(it->first, 42u);
EXPECT_EQ(it->last, 51u);
// Expect end of iteration.
++it;
EXPECT_EQ(it, allocator.end());
}
TEST(Allocator, IterateContiguousRanges) {
AllocatorAndStorage storage;
Allocator& allocator = storage.allocator;
// Add multiple, contiguous ranges.
EXPECT_THAT(allocator.AddRange(1, 1), IsOk());
EXPECT_THAT(allocator.AddRange(2, 1), IsOk());
EXPECT_THAT(allocator.AddRange(3, 1), IsOk());
// We expect just a single range at the end.
std::vector<Range> ranges(allocator.begin(), allocator.end());
EXPECT_THAT(ranges, testing::ElementsAre(Range::FromFirstAndLast(1, 3)));
}
TEST(Allocator, IterateNonContiguousRanges) {
AllocatorAndStorage storage;
Allocator& allocator = storage.allocator;
// Add multiple, contiguous ranges.
EXPECT_THAT(allocator.AddRange(3, 1), IsOk());
EXPECT_THAT(allocator.AddRange(1, 1), IsOk());
EXPECT_THAT(allocator.AddRange(5, 1), IsOk());
// We expect to see all three ranges in order.
std::vector<Range> ranges(allocator.begin(), allocator.end());
EXPECT_THAT(ranges,
testing::ElementsAre(Range::FromFirstAndLast(1, 1), Range::FromFirstAndLast(3, 3),
Range::FromFirstAndLast(5, 5)));
}
} // namespace
} // namespace memalloc