blob: 5ff530dc67a55944f23ef906189092b6fa1d116f [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/platform/disk_data_allocator.h"
#include <cstring>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/rand_util.h"
#include "base/test/task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/platform/disk_data_allocator_test_utils.h"
#include "third_party/blink/renderer/platform/disk_data_metadata.h"
using ThreadPoolExecutionMode =
base::test::TaskEnvironment::ThreadPoolExecutionMode;
namespace blink {
class DiskDataAllocatorTest : public ::testing::Test {
public:
explicit DiskDataAllocatorTest(
ThreadPoolExecutionMode thread_pool_execution_mode =
ThreadPoolExecutionMode::DEFAULT)
: task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME,
thread_pool_execution_mode) {}
static std::vector<std::unique_ptr<DiskDataMetadata>>
Allocate(InMemoryDataAllocator* allocator, size_t size, size_t count) {
std::string random_data = base::RandBytesAsString(size);
std::vector<std::unique_ptr<DiskDataMetadata>> all_metadata;
for (size_t i = 0; i < count; i++) {
auto metadata = allocator->Write(random_data.c_str(), random_data.size());
EXPECT_EQ(metadata->start_offset(), static_cast<int64_t>(i * size));
all_metadata.push_back(std::move(metadata));
}
return all_metadata;
}
protected:
void SetUp() override {
// On some platforms, initialization takes time, though it happens when
// base::ThreadTicks is used. To prevent flakiness depending on test
// execution ordering, force initialization.
if (base::ThreadTicks::IsSupported())
base::ThreadTicks::WaitUntilInitialized();
}
base::test::TaskEnvironment task_environment_;
};
TEST_F(DiskDataAllocatorTest, ReadWrite) {
InMemoryDataAllocator allocator;
constexpr size_t kSize = 1000;
std::string random_data = base::RandBytesAsString(kSize);
auto metadata = allocator.Write(random_data.c_str(), random_data.size());
EXPECT_TRUE(metadata);
EXPECT_EQ(kSize, metadata->size());
auto read_data = std::vector<char>(kSize);
allocator.Read(*metadata, &read_data[0]);
EXPECT_EQ(0, memcmp(&read_data[0], random_data.c_str(), kSize));
}
TEST_F(DiskDataAllocatorTest, ReadWriteDiscardMultiple) {
InMemoryDataAllocator allocator;
std::vector<std::pair<std::unique_ptr<DiskDataMetadata>, std::string>>
data_written;
for (int i = 0; i < 10; i++) {
int size = base::RandInt(100, 1000);
auto data = base::RandBytesAsString(size);
auto metadata = allocator.Write(&data[0], size);
ASSERT_TRUE(metadata);
data_written.emplace_back(std::move(metadata), data);
}
base::RandomShuffle(data_written.begin(), data_written.end());
for (const auto& p : data_written) {
size_t size = p.first->size();
auto read_data = std::vector<char>(size);
allocator.Read(*p.first, &read_data[0]);
EXPECT_EQ(0, memcmp(&read_data[0], &p.second[0], size));
}
base::RandomShuffle(data_written.begin(), data_written.end());
for (auto& p : data_written) {
auto metadata = std::move(p.first);
allocator.Discard(std::move(metadata));
}
}
TEST_F(DiskDataAllocatorTest, WriteEventuallyFail) {
InMemoryDataAllocator allocator;
constexpr size_t kSize = 1 << 18;
std::string random_data = base::RandBytesAsString(kSize);
static_assert(4 * kSize == InMemoryDataAllocator::kMaxSize, "");
for (int i = 0; i < 4; i++) {
auto metadata = allocator.Write(random_data.c_str(), random_data.size());
EXPECT_TRUE(metadata);
}
auto metadata = allocator.Write(random_data.c_str(), random_data.size());
EXPECT_FALSE(metadata);
EXPECT_FALSE(allocator.may_write());
}
TEST_F(DiskDataAllocatorTest, CanReuseFreedChunk) {
InMemoryDataAllocator allocator;
constexpr size_t kSize = 1 << 10;
std::vector<std::unique_ptr<DiskDataMetadata>> all_metadata;
for (int i = 0; i < 10; i++) {
std::string random_data = base::RandBytesAsString(kSize);
auto metadata = allocator.Write(random_data.c_str(), random_data.size());
ASSERT_TRUE(metadata);
all_metadata.push_back(std::move(metadata));
}
auto metadata = std::move(all_metadata[4]);
ASSERT_TRUE(metadata);
int64_t start_offset = metadata->start_offset();
allocator.Discard(std::move(metadata));
std::string random_data = base::RandBytesAsString(kSize);
auto new_metadata = allocator.Write(random_data.c_str(), random_data.size());
ASSERT_TRUE(new_metadata);
EXPECT_EQ(new_metadata->start_offset(), start_offset);
}
TEST_F(DiskDataAllocatorTest, ExactThenWorstFit) {
InMemoryDataAllocator allocator;
int count = 10;
size_t size_increment = 1000;
std::vector<std::unique_ptr<DiskDataMetadata>> all_metadata;
size_t size = 10000;
// Allocate a bunch of random-sized
for (int i = 0; i < count; i++) {
std::string random_data = base::RandBytesAsString(size);
auto metadata = allocator.Write(random_data.c_str(), random_data.size());
ASSERT_TRUE(metadata);
all_metadata.push_back(std::move(metadata));
size += size_increment;
}
auto& hole_metadata = all_metadata[4];
size_t hole_size = hole_metadata->size();
int64_t hole_offset = hole_metadata->start_offset();
allocator.Discard(std::move(hole_metadata));
auto& larger_hole_metadata = all_metadata[9];
int64_t larger_hole_offset = larger_hole_metadata->start_offset();
allocator.Discard(std::move(larger_hole_metadata));
std::string random_data = base::RandBytesAsString(hole_size);
auto metadata = allocator.Write(random_data.c_str(), random_data.size());
// Exact fit.
EXPECT_EQ(metadata->start_offset(), hole_offset);
allocator.Discard(std::move(metadata));
// -1 to check that this is not best fit.
random_data = base::RandBytesAsString(hole_size - 1);
metadata = allocator.Write(random_data.c_str(), random_data.size());
EXPECT_EQ(metadata->start_offset(), larger_hole_offset);
}
TEST_F(DiskDataAllocatorTest, FreeChunksMerging) {
constexpr size_t kSize = 100;
auto allocator = std::make_unique<InMemoryDataAllocator>();
auto chunks = Allocate(allocator.get(), kSize, 4);
EXPECT_EQ(static_cast<int64_t>(4 * kSize), allocator->disk_footprint());
EXPECT_EQ(0u, allocator->free_chunks_size());
// Layout is (indices in |chunks|):
// | 0 | 1 | 2 | 3 |
// Discarding a higher index after a lower one triggers merging on the left.
// Merge left.
allocator->Discard(std::move(chunks[0]));
EXPECT_EQ(1u, allocator->FreeChunks().size());
allocator->Discard(std::move(chunks[1]));
EXPECT_EQ(1u, allocator->FreeChunks().size());
EXPECT_EQ(2 * kSize, allocator->FreeChunks().begin()->second);
allocator->Discard(std::move(chunks[2]));
EXPECT_EQ(1u, allocator->FreeChunks().size());
EXPECT_EQ(3 * kSize, allocator->FreeChunks().begin()->second);
EXPECT_EQ(3 * kSize, allocator->free_chunks_size());
allocator->Discard(std::move(chunks[3]));
EXPECT_EQ(1u, allocator->FreeChunks().size());
EXPECT_EQ(4 * kSize, allocator->FreeChunks().begin()->second);
EXPECT_EQ(static_cast<int64_t>(4 * kSize), allocator->disk_footprint());
allocator = std::make_unique<InMemoryDataAllocator>();
chunks = Allocate(allocator.get(), kSize, 4);
// Merge right.
allocator->Discard(std::move(chunks[3]));
EXPECT_EQ(1u, allocator->FreeChunks().size());
allocator->Discard(std::move(chunks[2]));
EXPECT_EQ(1u, allocator->FreeChunks().size());
EXPECT_EQ(2 * kSize, allocator->FreeChunks().begin()->second);
allocator->Discard(std::move(chunks[0]));
EXPECT_EQ(2u, allocator->FreeChunks().size());
EXPECT_EQ(3 * kSize, allocator->free_chunks_size());
// Multiple merges: left, then right.
allocator->Discard(std::move(chunks[1]));
EXPECT_EQ(1u, allocator->FreeChunks().size());
allocator = std::make_unique<InMemoryDataAllocator>();
chunks = Allocate(allocator.get(), kSize, 4);
// Left then right merging.
allocator->Discard(std::move(chunks[0]));
allocator->Discard(std::move(chunks[2]));
EXPECT_EQ(2u, allocator->FreeChunks().size());
allocator->Discard(std::move(chunks[1]));
EXPECT_EQ(1u, allocator->FreeChunks().size());
}
TEST_F(DiskDataAllocatorTest, ProvideInvalidFile) {
DiskDataAllocator allocator;
EXPECT_FALSE(allocator.may_write());
allocator.ProvideTemporaryFile(base::File());
EXPECT_FALSE(allocator.may_write());
}
TEST_F(DiskDataAllocatorTest, ProvideValidFile) {
base::FilePath path;
if (!base::CreateTemporaryFile(&path))
GTEST_SKIP() << "Cannot create temporary file.";
int flags = base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_READ |
base::File::FLAG_WRITE | base::File::FLAG_DELETE_ON_CLOSE;
auto file = base::File(base::FilePath(path), flags);
if (!file.IsValid())
GTEST_SKIP() << "Cannot create temporary file.";
DiskDataAllocator allocator;
EXPECT_FALSE(allocator.may_write());
allocator.ProvideTemporaryFile(std::move(file));
EXPECT_TRUE(allocator.may_write());
// Test read/write with a real file.
constexpr size_t kSize = 1000;
std::string random_data = base::RandBytesAsString(kSize);
auto metadata = allocator.Write(random_data.c_str(), random_data.size());
if (!metadata)
GTEST_SKIP() << "Disk full?";
EXPECT_EQ(kSize, metadata->size());
auto read_data = std::vector<char>(kSize);
allocator.Read(*metadata, &read_data[0]);
EXPECT_EQ(0, memcmp(&read_data[0], random_data.c_str(), kSize));
}
} // namespace blink