blob: ab99fa24cb9f30aef0658702dc3c1ab965a52c0a [file] [log] [blame]
// Copyright 2018 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 "ftl-shell.h"
#include <stdlib.h>
#include <time.h>
#include <fbl/algorithm.h>
#include <fbl/array.h>
#include <unittest/unittest.h>
namespace {
constexpr uint32_t kPageSize = 4096;
// 300 blocks of 64 pages.
constexpr ftl::VolumeOptions kDefaultOptions = {300, 300 / 20, 64 * kPageSize, kPageSize, 16, 0};
bool TrivialLifetimeTest() {
BEGIN_TEST;
FtlShell ftl;
ASSERT_TRUE(ftl.Init(kDefaultOptions));
END_TEST;
}
// See ReAttachTest for a non-trivial flush test.
bool TrivialFlushTest() {
BEGIN_TEST;
FtlShell ftl;
ASSERT_TRUE(ftl.Init(kDefaultOptions));
ASSERT_EQ(ZX_OK, ftl.volume()->Flush());
END_TEST;
}
bool IsEmptyPage(FtlShell* ftl, uint32_t page_num) {
BEGIN_TEST;
fbl::Array<uint8_t> buffer(new uint8_t[kPageSize], kPageSize);
memset(buffer.get(), 0, buffer.size());
ASSERT_EQ(ZX_OK, ftl->volume()->Read(page_num, 1, buffer.get()));
for (uint32_t i = 0; i < buffer.size(); i++) {
ASSERT_EQ(0xff, buffer[i]);
}
END_TEST;
}
bool UnmountTest() {
BEGIN_TEST;
FtlShell ftl;
ASSERT_TRUE(ftl.Init(kDefaultOptions));
ASSERT_EQ(ZX_OK, ftl.volume()->Unmount());
END_TEST;
}
bool MountTest() {
BEGIN_TEST;
FtlShell ftl;
ASSERT_TRUE(ftl.Init(kDefaultOptions));
ASSERT_EQ(ZX_OK, ftl.volume()->Unmount());
ASSERT_EQ(ZX_OK, ftl.volume()->Mount());
ASSERT_TRUE(IsEmptyPage(&ftl, 10));
END_TEST;
}
bool ReadWriteTest() {
BEGIN_TEST;
FtlShell ftl;
ASSERT_TRUE(ftl.Init(kDefaultOptions));
fbl::Array<uint8_t> buffer(new uint8_t[kPageSize * 2], kPageSize * 2);
memset(buffer.get(), 0x55, buffer.size());
ASSERT_EQ(ZX_OK, ftl.volume()->Write(150, 2, buffer.get()));
memset(buffer.get(), 0, buffer.size());
ASSERT_EQ(ZX_OK, ftl.volume()->Read(150, 2, buffer.get()));
for (uint32_t i = 0; i < buffer.size(); i++) {
ASSERT_EQ(0x55, buffer[i]);
}
END_TEST;
}
bool WritePage(FtlShell* ftl, uint32_t page_num) {
BEGIN_TEST;
fbl::Array<uint8_t> buffer(new uint8_t[kPageSize], kPageSize);
memset(buffer.get(), 0x55, buffer.size());
ASSERT_EQ(ZX_OK, ftl->volume()->Write(page_num, 1, buffer.get()));
END_TEST;
}
bool ReAttachTest() {
BEGIN_TEST;
FtlShell ftl;
ASSERT_TRUE(ftl.Init(kDefaultOptions));
fbl::Array<uint8_t> buffer(new uint8_t[kPageSize * 2], kPageSize * 2);
memset(buffer.get(), 0x55, buffer.size());
ASSERT_EQ(ZX_OK, ftl.volume()->Write(150, 2, buffer.get()));
ASSERT_TRUE(ftl.ReAttach());
ASSERT_TRUE(IsEmptyPage(&ftl, 150));
// Try again, this time flushing before removing the volume.
ASSERT_EQ(ZX_OK, ftl.volume()->Write(150, 2, buffer.get()));
ASSERT_EQ(ZX_OK, ftl.volume()->Flush());
ASSERT_TRUE(ftl.ReAttach());
memset(buffer.get(), 0, buffer.size());
ASSERT_EQ(ZX_OK, ftl.volume()->Read(150, 2, buffer.get()));
for (uint32_t i = 0; i < buffer.size(); i++) {
ASSERT_EQ(0x55, buffer[i]);
}
END_TEST;
}
bool FormatTest() {
BEGIN_TEST;
FtlShell ftl;
ASSERT_TRUE(ftl.Init(kDefaultOptions));
ASSERT_TRUE(WritePage(&ftl, 10));
ASSERT_EQ(ZX_OK, ftl.volume()->Format());
ASSERT_TRUE(IsEmptyPage(&ftl, 10));
END_TEST;
}
bool TrimTest() {
BEGIN_TEST;
FtlShell ftl;
ASSERT_TRUE(ftl.Init(kDefaultOptions));
ASSERT_TRUE(WritePage(&ftl, 10));
ASSERT_EQ(ZX_OK, ftl.volume()->Trim(10, 1));
ASSERT_TRUE(IsEmptyPage(&ftl, 10));
END_TEST;
}
bool GarbageCollectTest() {
BEGIN_TEST;
FtlShell ftl;
constexpr int kBlocks = 10;
ASSERT_TRUE(ftl.Init({kBlocks, 1, 32 * kPageSize, kPageSize, 16, 0}));
// Even though the device is empty, the FTL erases the blocks before use,
// and for this API that counts as garbage collection.
// Two reserved blocks + one that may become bad.
for (int i = 0; i < kBlocks - 3; i++) {
ASSERT_EQ(ZX_OK, ftl.volume()->GarbageCollect());
}
ASSERT_EQ(ZX_ERR_STOP, ftl.volume()->GarbageCollect());
END_TEST;
}
bool StatsTest() {
BEGIN_TEST;
FtlShell ftl;
ASSERT_TRUE(ftl.Init(kDefaultOptions));
ftl::Volume::Stats stats;
ASSERT_EQ(ZX_OK, ftl.volume()->GetStats(&stats));
ASSERT_EQ(0, stats.garbage_level);
ASSERT_EQ(0, stats.wear_count);
ASSERT_LT(0, stats.ram_used);
END_TEST;
}
// Test fixture.
class FtlTest {
public:
FtlTest() : rand_seed_(static_cast<uint32_t>(time(nullptr))) { srand(rand_seed_); }
~FtlTest() {
if (!AllOk()) {
unittest_printf_critical("rand seed: %u", rand_seed_);
}
}
bool Init();
// Goes over a single iteration of the "main" ftl test. |num_pages| is the
// number of pages to write at the same time.
bool SingleLoop(uint32_t num_pages);
private:
// Returns the value to use for when writing |page_num|.
uint32_t GetKey(uint32_t page_num) {
return (write_counters_[page_num] << 24) | page_num;
}
// Fills the page buffer with a known pattern for each page.
void PrepareBuffer(uint32_t page_num, uint32_t num_pages);
bool CheckVolume(uint32_t num_pages);
// This is ugly, but at least doesn't use current_test_info->all_ok directly.
// This is the moral equivalent of HasFatalFailure().
bool AllOk() const { END_TEST; }
FtlShell ftl_;
ftl::Volume* volume_ = nullptr;
fbl::Array<uint8_t> write_counters_;
fbl::Array<uint32_t> page_buffer_;
uint32_t rand_seed_; // TODO(rvargas): replace with framework provided seed when available.
};
bool FtlTest::Init() {
BEGIN_TEST;
ASSERT_TRUE(ftl_.Init(kDefaultOptions));
volume_ = ftl_.volume();
ASSERT_EQ(ZX_OK, volume_->Unmount());
write_counters_.reset(new uint8_t[ftl_.num_pages()], ftl_.num_pages());
memset(write_counters_.get(), 0, write_counters_.size());
END_TEST;
}
bool FtlTest::SingleLoop(uint32_t num_pages) {
BEGIN_TEST;
ASSERT_EQ(ZX_OK, volume_->Mount());
size_t buffer_size = num_pages * ftl_.page_size() / sizeof(uint32_t);
page_buffer_.reset(new uint32_t[buffer_size], buffer_size);
memset(page_buffer_.get(), 0, page_buffer_.size() * sizeof(page_buffer_[0]));
// Write pages 5 - 10.
for (uint32_t page = 5; page < 10; page++) {
ASSERT_EQ(ZX_OK, volume_->Write(page, 1, page_buffer_.get()));
}
// Mark pages 5 - 10 as unused.
ASSERT_EQ(ZX_OK, volume_->Trim(5, 5));
// Write every page in the volume once.
for (uint32_t page = 0; page < ftl_.num_pages();) {
uint32_t count = fbl::min(ftl_.num_pages() - page, num_pages);
PrepareBuffer(page, count);
ASSERT_EQ(ZX_OK, volume_->Write(page, count, page_buffer_.get()));
page += count;
}
ASSERT_EQ(ZX_OK, volume_->Flush());
ASSERT_TRUE(CheckVolume(num_pages));
// Randomly rewrite half the pages in the volume.
for (uint32_t i = 0; i < ftl_.num_pages() / 2; i++) {
uint32_t page = static_cast<uint32_t>(rand() % ftl_.num_pages());
PrepareBuffer(page, 1);
ASSERT_EQ(ZX_OK, volume_->Write(page, 1, page_buffer_.get()));
}
ASSERT_TRUE(CheckVolume(num_pages));
// Detach and re-add test volume without erasing the media.
ASSERT_EQ(ZX_OK, volume_->Unmount());
ASSERT_TRUE(ftl_.ReAttach());
ASSERT_TRUE(CheckVolume(num_pages));
ASSERT_EQ(ZX_OK, volume_->Unmount());
END_TEST;
}
void FtlTest::PrepareBuffer(uint32_t page_num, uint32_t num_pages) {
uint32_t* key_buffer = page_buffer_.get();
for (; num_pages; num_pages--, page_num++) {
write_counters_[page_num]++;
uint32_t value = GetKey(page_num);
// Fill page buffer with repetitions of its unique write value.
for (uint32_t i = 0; i < ftl_.page_size() / sizeof(value); i++) {
*key_buffer++ = value;
}
}
}
bool FtlTest::CheckVolume(uint32_t num_pages) {
BEGIN_TEST;
for (uint32_t page = 0; page < ftl_.num_pages();) {
uint32_t count = fbl::min(ftl_.num_pages() - page, num_pages);
ASSERT_EQ(ZX_OK, volume_->Read(page, count, page_buffer_.get()));
// Verify each page independently.
uint32_t* key_buffer = page_buffer_.get();
uint32_t* end = key_buffer + ftl_.page_size() / sizeof(uint32_t) * count;
for (; key_buffer < end; page++) {
// Get 32-bit data unique to most recent page write.
uint32_t value = GetKey(page);
for (size_t i = 0; i < ftl_.page_size(); i += sizeof(value), key_buffer++) {
if (*key_buffer != value) {
unittest_printf_critical("Page #%u corrupted at offset %zu. Expected 0x%08X, "
"found 0x%08X\n", page, i, value, *key_buffer);
ASSERT_TRUE(false);
}
}
}
}
END_TEST;
}
bool SinglePassTest() {
BEGIN_TEST;
FtlTest test;
ASSERT_TRUE(test.Init());
ASSERT_TRUE(test.SingleLoop(5));
END_TEST;
}
bool MultiplePassTest() {
BEGIN_TEST;
FtlTest test;
ASSERT_TRUE(test.Init());
for (int i = 1; i < 7; i++) {
ASSERT_TRUE(test.SingleLoop(i * 3));
}
END_TEST;
}
} // namespace
BEGIN_TEST_CASE(FtlTests)
RUN_TEST_SMALL(TrivialLifetimeTest)
RUN_TEST_SMALL(TrivialFlushTest)
RUN_TEST_SMALL(UnmountTest)
RUN_TEST_SMALL(MountTest)
RUN_TEST_SMALL(ReadWriteTest)
RUN_TEST_SMALL(ReAttachTest)
RUN_TEST_SMALL(FormatTest)
RUN_TEST_SMALL(TrimTest)
RUN_TEST_SMALL(GarbageCollectTest)
RUN_TEST_SMALL(StatsTest)
RUN_TEST_SMALL(SinglePassTest)
RUN_TEST_MEDIUM(MultiplePassTest)
END_TEST_CASE(FtlTests)