// 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 #include #include #include #include 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 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 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 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 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(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 write_counters_; fbl::Array 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(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)