blob: 245c9c3ae492bab286ed24eec036b21f55cc0613 [file]
// Copyright 2025 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 <fcntl.h>
#include <iomanip>
#include <sstream>
#include <gtest/gtest.h>
#include "src/storage/f2fs/f2fs.h"
#include "src/storage/f2fs/test/unit/unit_lib.h"
#include "src/storage/f2fs/vnode.h"
namespace f2fs {
namespace {
class DirectoryTest : public F2fsFakeDevTestFixture {
public:
DirectoryTest()
: F2fsFakeDevTestFixture(TestOptions{.block_count = kSectorCount100MiB,
.image_file = "directory_test.img.zst"}) {}
};
TEST_F(DirectoryTest, Directory) {
fbl::RefPtr<fs::Vnode> vnode;
FileTester::Lookup(root_dir_.get(), "depth", &vnode);
auto test_dir = fbl::RefPtr<Dir>::Downcast(std::move(vnode));
constexpr int kDirDepth = 60;
// From "depth", recursively find directories from "0" to "59"
for (int depth = 0; depth < kDirDepth; ++depth) {
FileTester::Lookup(test_dir.get(), std::to_string(depth), &vnode);
test_dir->Close();
test_dir = fbl::RefPtr<Dir>::Downcast(std::move(vnode));
}
// Directory "60" should not be exist
ASSERT_EQ(test_dir->Lookup(std::to_string(kDirDepth), &vnode), ZX_ERR_NOT_FOUND);
test_dir->Close();
// "width" should contain "0", "1", ..., "19", "120", "121", ..., "139"
FileTester::Lookup(root_dir_.get(), "width", &vnode);
test_dir = fbl::RefPtr<Dir>::Downcast(std::move(vnode));
for (int i = 0; i <= 19; ++i) {
fbl::RefPtr<fs::Vnode> child_vnode;
FileTester::Lookup(test_dir.get(), std::to_string(i), &child_vnode);
child_vnode->Close();
}
for (int i = 120; i <= 139; ++i) {
fbl::RefPtr<fs::Vnode> child_vnode;
FileTester::Lookup(test_dir.get(), std::to_string(i), &child_vnode);
child_vnode->Close();
}
// "width" should not contain "20", "21", ..., "59"
for (int i = 20; i <= 59; ++i) {
fbl::RefPtr<fs::Vnode> child_vnode;
ASSERT_EQ(test_dir->Lookup(std::to_string(i), &vnode), ZX_ERR_NOT_FOUND);
}
test_dir->Close();
}
class FileTest : public F2fsFakeDevTestFixture {
public:
FileTest()
: F2fsFakeDevTestFixture(
TestOptions{.block_count = kSectorCount100MiB, .image_file = "file_test.img.zst"}) {}
};
TEST_F(FileTest, File) {
fbl::RefPtr<fs::Vnode> vnode;
// File "file_write" has 64KB of data.
FileTester::Lookup(root_dir_.get(), "file_write", &vnode);
auto test_file = fbl::RefPtr<File>::Downcast(std::move(vnode));
ASSERT_EQ(test_file->GetSize(), static_cast<size_t>(64 * 1024));
auto r_buf = std::make_unique<char[]>(kBlockSize);
auto verify = std::make_unique<char[]>(kBlockSize);
for (uint32_t i = 0; i < 64 * 1024 / kBlockSize; ++i) {
memset(verify.get(), static_cast<char>(i % 256), kBlockSize);
FileTester::ReadFromFile(test_file.get(), r_buf.get(), kBlockSize, kBlockSize * i);
ASSERT_EQ(memcmp(r_buf.get(), verify.get(), kBlockSize), 0);
}
test_file->Close();
// File "file_truncate" has zero-filled 16KB data.
FileTester::Lookup(root_dir_.get(), "file_truncate", &vnode);
test_file = fbl::RefPtr<File>::Downcast(std::move(vnode));
ASSERT_EQ(test_file->GetSize(), static_cast<size_t>(16 * 1024));
memset(verify.get(), 0, kBlockSize);
for (uint32_t i = 0; i < 16 * 1024 / kBlockSize; ++i) {
FileTester::ReadFromFile(test_file.get(), r_buf.get(), kBlockSize, kBlockSize * i);
ASSERT_EQ(memcmp(r_buf.get(), verify.get(), kBlockSize), 0);
}
test_file->Close();
// File "file_truncate_shrink" has 16KB of data.
FileTester::Lookup(root_dir_.get(), "file_truncate_shrink", &vnode);
test_file = fbl::RefPtr<File>::Downcast(std::move(vnode));
ASSERT_EQ(test_file->GetSize(), static_cast<size_t>(16 * 1024));
for (uint32_t i = 0; i < 16 * 1024 / kBlockSize; ++i) {
memset(verify.get(), static_cast<char>(i % 256), kBlockSize);
FileTester::ReadFromFile(test_file.get(), r_buf.get(), kBlockSize, kBlockSize * i);
ASSERT_EQ(memcmp(r_buf.get(), verify.get(), kBlockSize), 0);
}
test_file->Close();
// File "file_exceed" has 7KB of data.
FileTester::Lookup(root_dir_.get(), "file_exceed", &vnode);
test_file = fbl::RefPtr<File>::Downcast(std::move(vnode));
ASSERT_EQ(test_file->GetSize(), static_cast<size_t>(7 * 1024));
memset(verify.get(), 0, kBlockSize);
memset(verify.get(), 1, 2 * 1024);
{
size_t actual;
// Read 4KB from 5KB offset, then only 2KB should be read
ASSERT_EQ(
FileTester::Read(test_file.get(), r_buf.get(), kBlockSize, kBlockSize + 1024, &actual),
ZX_OK);
ASSERT_EQ(actual, static_cast<size_t>(2 * 1024));
}
ASSERT_EQ(memcmp(r_buf.get(), verify.get(), 2 * 1024), 0);
test_file->Close();
// File "file_rename" should not be exist while file "renamed_file" is exist
{
fbl::RefPtr<fs::Vnode> vn = nullptr;
ASSERT_EQ(root_dir_->Lookup("file_rename", &vn), ZX_ERR_NOT_FOUND);
}
FileTester::Lookup(root_dir_.get(), "renamed_file", &vnode);
vnode->Close();
// File "file_fallocate" has zero-filled 64KB data.
FileTester::Lookup(root_dir_.get(), "file_fallocate", &vnode);
test_file = fbl::RefPtr<File>::Downcast(std::move(vnode));
ASSERT_EQ(test_file->GetSize(), static_cast<size_t>(64 * 1024));
memset(verify.get(), 0, kBlockSize);
for (uint32_t i = 0; i < 64 * 1024 / kBlockSize; ++i) {
FileTester::ReadFromFile(test_file.get(), r_buf.get(), kBlockSize, kBlockSize * i);
ASSERT_EQ(memcmp(r_buf.get(), verify.get(), kBlockSize), 0);
}
test_file->Close();
// File "file_fallocate_hole" has 64KB of data, with zero-filled hole from offset 8KB to 16KB.
FileTester::Lookup(root_dir_.get(), "file_fallocate_hole", &vnode);
test_file = fbl::RefPtr<File>::Downcast(std::move(vnode));
ASSERT_EQ(test_file->GetSize(), static_cast<size_t>(64 * 1024));
for (uint32_t i = 0; i < 64 * 1024 / kBlockSize; ++i) {
if (i == 2 || i == 3) { // zero fill for punch hole range
memset(verify.get(), 0, kBlockSize);
} else {
memset(verify.get(), static_cast<char>(i % 256), kBlockSize);
}
FileTester::ReadFromFile(test_file.get(), r_buf.get(), kBlockSize, kBlockSize * i);
ASSERT_EQ(memcmp(r_buf.get(), verify.get(), kBlockSize), 0);
}
test_file->Close();
// Files "filemode_xxx" have filemode from 000 to 777
for (uint32_t i = 0; i <= 0777; ++i) {
std::ostringstream oss;
oss << std::setfill('0') << std::setw(3) << std::oct << i;
std::string filename = "filemode_" + oss.str();
FileTester::Lookup(root_dir_.get(), filename, &vnode);
test_file = fbl::RefPtr<File>::Downcast(std::move(vnode));
zx::result<fs::VnodeAttributes> result = test_file->GetAttributes();
ASSERT_TRUE(result.is_ok());
ASSERT_TRUE(result->mode.has_value());
ASSERT_EQ(*result->mode & 0777, i);
test_file->Close();
}
}
class InlineTest : public F2fsFakeDevTestFixture {
public:
InlineTest()
: F2fsFakeDevTestFixture(
TestOptions{.block_count = kSectorCount100MiB, .image_file = "inline_test.img.zst"}) {}
};
TEST_F(InlineTest, Inline) {
fbl::RefPtr<fs::Vnode> vnode;
// Check both |InlineFile| and |DataExist| flags are set
FileTester::Lookup(root_dir_.get(), "inline_file", &vnode);
auto test_file = fbl::RefPtr<File>::Downcast(std::move(vnode));
FileTester::CheckInlineFile(test_file.get());
FileTester::CheckDataExistFlagSet(test_file.get());
test_file->Close();
// For an empty inline file, check |InlineFile| flag is set and |DataExist| flag is unset
FileTester::Lookup(root_dir_.get(), "inline_file_empty", &vnode);
test_file = fbl::RefPtr<File>::Downcast(std::move(vnode));
FileTester::CheckInlineFile(test_file.get());
FileTester::CheckDataExistFlagUnset(test_file.get());
test_file->Close();
// Check if children of inline directory are available
FileTester::Lookup(root_dir_.get(), "inline_dir", &vnode);
auto test_dir = fbl::RefPtr<Dir>::Downcast(std::move(vnode));
FileTester::CheckInlineDir(test_dir.get());
std::unordered_set<std::string> children_set = {"a", "b", "c"};
FileTester::CheckChildrenFromReaddir(test_dir.get(), children_set);
test_dir->Close();
}
} // namespace
} // namespace f2fs