blob: 7ab84ebb54c9dd37248bcbc20901f370a8cc8d93 [file] [log] [blame]
// Copyright 2020 The Fuchsia 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 "src/storage/minfs/lazy_reader.h"
#include <lib/fit/defer.h>
#include <gtest/gtest.h>
#include <storage/buffer/resizeable_vmo_buffer.h>
#include "src/storage/lib/block_client/cpp/fake_block_device.h"
#include "src/storage/minfs/bcache.h"
#include "src/storage/minfs/writeback.h"
namespace minfs {
namespace {
using block_client::FakeBlockDevice;
class Mapper : public MapperInterface {
public:
zx::result<DeviceBlockRange> Map(BlockRange range) {
auto iter = mappings_.find(range.Start());
if (iter != mappings_.end()) {
return zx::ok(DeviceBlockRange(iter->second, 1));
} else {
return zx::ok(DeviceBlockRange(DeviceBlock::Unmapped(), 1));
}
}
zx::result<DeviceBlockRange> MapForWrite(PendingWork* transaction, BlockRange range,
bool* allocated) {
EXPECT_FALSE(transaction == nullptr);
EXPECT_TRUE(*allocated == false);
auto iter = mappings_.find(range.Start());
if (iter == mappings_.end()) {
EXPECT_LT(range.Start(), 10ul);
// Reverses and spaces every logical block out by 2.
DeviceBlockRange device_range = DeviceBlockRange(20 - range.Start() * 2, 1);
mappings_[range.Start()] = device_range.block();
*allocated = true;
return zx::ok(device_range);
} else {
return zx::ok(DeviceBlockRange(iter->second, 1));
}
}
const std::map<size_t, size_t>& mappings() const { return mappings_; }
private:
std::map<size_t, size_t> mappings_;
};
class StubTransaction : public PendingWork {
public:
void EnqueueMetadata(storage::Operation operation, storage::BlockBuffer* buffer) override {}
void EnqueueData(storage::Operation operation, storage::BlockBuffer* buffer) override {}
size_t AllocateBlock() override { return 0; }
void DeallocateBlock(size_t) override {}
};
TEST(LazyReaderTest, ReadSucceeds) {
static const int kBlockCount = 21;
auto device = std::make_unique<FakeBlockDevice>(kBlockCount, kMinfsBlockSize);
auto bcache_or = Bcache::Create(std::move(device), kBlockCount);
ASSERT_TRUE(bcache_or.is_ok());
// Write to logical block 1.
char data[kMinfsBlockSize] = "hello";
StubTransaction transaction;
Mapper mapper;
bool allocated = false;
DeviceBlockRange device_range =
mapper.MapForWrite(&transaction, BlockRange(1, 2), &allocated).value();
ASSERT_TRUE(bcache_or->Writeblk(static_cast<blk_t>(device_range.block()), data).is_ok());
// Now read the data back using the lazy_reader.
storage::ResizeableVmoBuffer buffer(kMinfsBlockSize);
ASSERT_TRUE(buffer.Attach("LazyReaderTest", bcache_or.value().get()).is_ok());
auto detach =
fit::defer([&]() { [[maybe_unused]] auto _ = buffer.Detach(bcache_or.value().get()); });
ASSERT_TRUE(buffer.Grow(kBlockCount).is_ok());
MappedFileReader reader(bcache_or.value().get(), &mapper, &buffer);
LazyReader lazy_reader;
ASSERT_TRUE(lazy_reader.Read(ByteRange(kMinfsBlockSize, kMinfsBlockSize + 6), &reader).is_ok());
// We should see the same data read back.
EXPECT_EQ(memcmp(buffer.Data(1), "hello", 6), 0);
}
TEST(LazyReaderTest, UnmappedBlockIsZeroed) {
static const int kBlockCount = 21;
auto device = std::make_unique<FakeBlockDevice>(kBlockCount, kMinfsBlockSize);
auto bcache_or = Bcache::Create(std::move(device), kBlockCount);
ASSERT_TRUE(bcache_or.is_ok());
storage::ResizeableVmoBuffer buffer(kMinfsBlockSize);
ASSERT_TRUE(buffer.Attach("LazyReaderTest", bcache_or.value().get()).is_ok());
auto detach =
fit::defer([&]() { [[maybe_unused]] auto _ = buffer.Detach(bcache_or.value().get()); });
*static_cast<uint8_t*>(buffer.Data(0)) = 0xab;
Mapper mapper;
MappedFileReader reader(bcache_or.value().get(), &mapper, &buffer);
LazyReader lazy_reader;
// There is no mapping for the first block so it should get zeroed.
EXPECT_TRUE(lazy_reader.Read(ByteRange(0, 1), &reader).is_ok());
EXPECT_EQ(0, *static_cast<uint8_t*>(buffer.Data(0)));
// Reading again should not zero it again.
*static_cast<uint8_t*>(buffer.Data(0)) = 0xab;
EXPECT_TRUE(lazy_reader.Read(ByteRange(0, 1), &reader).is_ok());
EXPECT_EQ(0xab, *static_cast<uint8_t*>(buffer.Data(0)));
}
class MockReader : public LazyReader::ReaderInterface {
public:
zx::result<uint64_t> Enqueue(BlockRange range) override {
if (return_error_for_enqueue_) {
return zx::error(ZX_ERR_NO_MEMORY);
}
enqueued_.push_back(range);
return zx::ok(range.Length());
}
// Issues the queued reads and returns the result.
zx::result<> RunRequests() override {
if (return_error_for_run_requests_) {
return zx::error(ZX_ERR_IO);
}
run_requests_called_ = true;
return zx::ok();
}
uint32_t BlockSize() const override { return 512; }
void Reset() {
enqueued_.clear();
run_requests_called_ = false;
return_error_for_enqueue_ = false;
return_error_for_run_requests_ = false;
}
const std::vector<BlockRange>& enqueued() const { return enqueued_; }
bool run_requests_called() const { return run_requests_called_; }
void set_return_error_for_enqueue(bool return_error) { return_error_for_enqueue_ = return_error; }
void set_return_error_for_run_requests(bool return_error) {
return_error_for_run_requests_ = return_error;
}
private:
std::vector<BlockRange> enqueued_;
bool run_requests_called_ = false;
bool return_error_for_enqueue_ = false;
bool return_error_for_run_requests_ = false;
};
TEST(LazyReaderTest, ZeroLengthReadIsNotEnqeued) {
LazyReader lazy_reader;
MockReader reader;
ASSERT_TRUE(lazy_reader.Read(ByteRange(100, 100), &reader).is_ok());
EXPECT_EQ(reader.enqueued().size(), 0ul);
}
TEST(LazyReaderTest, ReadForMutlipleBlocksAfterOneBlockReadEnqueuedCorrectly) {
LazyReader lazy_reader;
MockReader reader;
// Read one block.
ASSERT_TRUE(lazy_reader.Read(ByteRange(530, 531), &reader).is_ok());
ASSERT_EQ(reader.enqueued().size(), 1ul);
EXPECT_EQ(reader.enqueued()[0].Start(), 1ul);
EXPECT_EQ(reader.enqueued()[0].End(), 2ul);
EXPECT_TRUE(reader.run_requests_called());
reader.Reset();
// Now read through blocks 0 to 4.
ASSERT_TRUE(
lazy_reader.Read(ByteRange(reader.BlockSize() - 1, 4 * reader.BlockSize() + 1), &reader)
.is_ok());
// We read one block earlier which shouldn't read again. This should result in Enqueue(0, 1),
// Enqueue(2, 5).
ASSERT_EQ(reader.enqueued().size(), 2ul);
EXPECT_EQ(reader.enqueued()[0].Start(), 0ul);
EXPECT_EQ(reader.enqueued()[0].End(), 1ul);
EXPECT_EQ(reader.enqueued()[1].Start(), 2ul);
EXPECT_EQ(reader.enqueued()[1].End(), 5ul);
}
TEST(LazyReaderTest, EnqueueError) {
LazyReader lazy_reader;
MockReader reader;
reader.set_return_error_for_enqueue(true);
EXPECT_EQ(lazy_reader.Read(ByteRange(530, 531), &reader).status_value(), ZX_ERR_NO_MEMORY);
EXPECT_FALSE(reader.run_requests_called());
reader.Reset();
// If we try again with no error, it should proceed with the read.
ASSERT_TRUE(lazy_reader.Read(ByteRange(530, 531), &reader).is_ok());
ASSERT_EQ(reader.enqueued().size(), 1ul);
EXPECT_EQ(reader.enqueued()[0].Start(), 1ul);
EXPECT_EQ(reader.enqueued()[0].End(), 2ul);
EXPECT_TRUE(reader.run_requests_called());
}
TEST(LazyReaderTest, RunRequestsError) {
LazyReader lazy_reader;
MockReader reader;
reader.set_return_error_for_run_requests(true);
EXPECT_EQ(lazy_reader.Read(ByteRange(530, 531), &reader).status_value(), ZX_ERR_IO);
EXPECT_FALSE(reader.run_requests_called());
reader.Reset();
// If we try again with no error, it should proceed with the read.
ASSERT_TRUE(lazy_reader.Read(ByteRange(530, 531), &reader).is_ok());
ASSERT_EQ(reader.enqueued().size(), 1ul);
EXPECT_EQ(reader.enqueued()[0].Start(), 1ul);
EXPECT_EQ(reader.enqueued()[0].End(), 2ul);
EXPECT_TRUE(reader.run_requests_called());
}
TEST(LazyReaderTest, SetLoadedMarksBlocksAsLoaded) {
LazyReader lazy_reader;
lazy_reader.SetLoaded(BlockRange(1, 2), true);
MockReader reader;
ASSERT_TRUE(
lazy_reader.Read(ByteRange(reader.BlockSize() - 1, 2 * reader.BlockSize() + 1), &reader)
.is_ok());
ASSERT_EQ(reader.enqueued().size(), 2ul);
EXPECT_EQ(reader.enqueued()[0].Start(), 0ul);
EXPECT_EQ(reader.enqueued()[0].End(), 1ul);
EXPECT_EQ(reader.enqueued()[1].Start(), 2ul);
EXPECT_EQ(reader.enqueued()[1].End(), 3ul);
}
TEST(LazyReaderTest, ClearLoadedMarksBlocksAsNotLoaded) {
LazyReader lazy_reader;
MockReader reader;
ASSERT_TRUE(
lazy_reader.Read(ByteRange(reader.BlockSize() - 1, 2 * reader.BlockSize() + 1), &reader)
.is_ok());
lazy_reader.SetLoaded(BlockRange(1, 2), false);
reader.Reset();
ASSERT_TRUE(
lazy_reader.Read(ByteRange(reader.BlockSize() - 1, 2 * reader.BlockSize() + 1), &reader)
.is_ok());
ASSERT_EQ(reader.enqueued().size(), 1ul);
EXPECT_EQ(reader.enqueued()[0].Start(), 1ul);
EXPECT_EQ(reader.enqueued()[0].End(), 2ul);
}
} // namespace
} // namespace minfs