blob: 14738dab528439c09df73b53d7bec45793259557 [file] [log] [blame]
// Copyright 2022 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 <gtest/gtest.h>
#include "src/lib/storage/block_client/cpp/fake_block_device.h"
#include "src/storage/f2fs/f2fs.h"
#include "unit_lib.h"
namespace f2fs {
namespace {
TEST(MultiThreads, Truncate) {
std::unique_ptr<Bcache> bc;
FileTester::MkfsOnFakeDev(&bc);
std::unique_ptr<F2fs> fs;
MountOptions options{};
// Disable inline data option
ASSERT_EQ(options.SetValue(options.GetNameView(kOptInlineData), 0), ZX_OK);
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
fbl::RefPtr<VnodeF2fs> root;
FileTester::CreateRoot(fs.get(), &root);
fbl::RefPtr<Dir> root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
fbl::RefPtr<fs::Vnode> test_file;
root_dir->Create("test2", S_IFREG, &test_file);
fbl::RefPtr<f2fs::File> vn = fbl::RefPtr<f2fs::File>::Downcast(std::move(test_file));
constexpr int kNTry = 1000;
uint8_t buf[kPageSize * 2] = {1};
FileTester::AppendToFile(vn.get(), buf, sizeof(buf));
std::thread thread1 = std::thread([&]() {
for (int i = 0; i < kNTry; ++i) {
size_t out_actual;
ASSERT_EQ(vn->Write(buf, sizeof(buf), 0, &out_actual), ZX_OK);
}
});
std::thread thread2 = std::thread([&]() {
for (int i = 0; i < kNTry; ++i) {
ASSERT_EQ(vn->Truncate(0), ZX_OK);
}
});
thread1.join();
thread2.join();
vn->Close();
vn = nullptr;
root_dir->Close();
root_dir = nullptr;
FileTester::Unmount(std::move(fs), &bc);
EXPECT_EQ(Fsck(std::move(bc), FsckOptions{.repair = false}, &bc), ZX_OK);
}
TEST(MultiThreads, Write) {
std::unique_ptr<Bcache> bc;
FileTester::MkfsOnFakeDev(&bc, 2097152);
std::unique_ptr<F2fs> fs;
MountOptions options{};
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
fbl::RefPtr<VnodeF2fs> root;
FileTester::CreateRoot(fs.get(), &root);
fbl::RefPtr<Dir> root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
fbl::RefPtr<fs::Vnode> test_file1, test_file2;
root_dir->Create("test1", S_IFREG, &test_file1);
root_dir->Create("test2", S_IFREG, &test_file2);
fbl::RefPtr<f2fs::File> vn1 = fbl::RefPtr<f2fs::File>::Downcast(std::move(test_file1));
fbl::RefPtr<f2fs::File> vn2 = fbl::RefPtr<f2fs::File>::Downcast(std::move(test_file2));
constexpr int kNTry = 51200;
uint8_t buf[kPageSize * 2] = {1};
// 3 iterations are enough to touch every block and trigger gc.
for (int i = 0; i < 3; ++i) {
std::thread thread1 = std::thread([&]() {
for (int i = 0; i < kNTry; ++i) {
size_t out_actual;
ASSERT_EQ(vn1->Write(buf, sizeof(buf), i * 8192, &out_actual), ZX_OK);
ASSERT_EQ(out_actual, sizeof(buf));
}
});
std::thread thread2 = std::thread([&]() {
for (int i = 0; i < kNTry; ++i) {
size_t out_actual;
ASSERT_EQ(vn2->Write(buf, sizeof(buf), i * 8192, &out_actual), ZX_OK);
ASSERT_EQ(out_actual, sizeof(buf));
}
});
thread1.join();
thread2.join();
}
vn1->Close();
vn2->Close();
vn1 = nullptr;
vn2 = nullptr;
root_dir->Close();
root_dir = nullptr;
FileTester::Unmount(std::move(fs), &bc);
EXPECT_EQ(Fsck(std::move(bc), FsckOptions{.repair = false}, &bc), ZX_OK);
}
TEST(MultiThreads, Create) {
std::unique_ptr<Bcache> bc;
FileTester::MkfsOnFakeDev(&bc);
std::unique_ptr<F2fs> fs;
MountOptions options{};
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
fbl::RefPtr<VnodeF2fs> root;
FileTester::CreateRoot(fs.get(), &root);
fbl::RefPtr<Dir> root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
std::string dir_name("dir");
fbl::RefPtr<fs::Vnode> child;
ASSERT_EQ(root_dir->Create(dir_name, S_IFDIR, &child), ZX_OK);
fbl::RefPtr<Dir> child_dir = fbl::RefPtr<Dir>::Downcast(std::move(child));
constexpr int kNThreads = 10;
constexpr int kNEntries = 100;
std::thread threads[kNThreads];
for (auto nThread = 0; nThread < kNThreads; ++nThread) {
threads[nThread] = std::thread([nThread, child_dir]() {
// Create dentries more than MaxInlineDentry() to trigger ConvertInlineDir().
for (uint32_t child_count = 0; child_count < kNEntries; ++child_count) {
uint32_t mode = child_count % 2 == 0 ? S_IFDIR : S_IFREG;
auto child_name = child_count + nThread * kNEntries;
FileTester::CreateChild(child_dir.get(), mode, std::to_string(child_name));
}
});
}
for (auto nThread = 0; nThread < kNThreads; ++nThread) {
threads[nThread].join();
}
// Verify dentries.
for (uint32_t child = 0; child < kNThreads * kNEntries; ++child) {
fbl::RefPtr<fs::Vnode> child_vn;
FileTester::Lookup(child_dir.get(), std::to_string(child), &child_vn);
ASSERT_TRUE(child_vn);
ASSERT_EQ(fbl::RefPtr<Dir>::Downcast(child_vn)->IsDir(), (child % 2) == 0);
ASSERT_EQ(child_vn->Close(), ZX_OK);
}
// It should not have inline entires.
FileTester::CheckNonInlineDir(child_dir.get());
ASSERT_EQ(child_dir->Close(), ZX_OK);
ASSERT_EQ(root_dir->Close(), ZX_OK);
child_dir = nullptr;
root_dir = nullptr;
FileTester::Unmount(std::move(fs), &bc);
EXPECT_EQ(Fsck(std::move(bc), FsckOptions{.repair = false}, &bc), ZX_OK);
}
TEST(MultiThreads, Unlink) {
std::unique_ptr<Bcache> bc;
FileTester::MkfsOnFakeDev(&bc);
std::unique_ptr<F2fs> fs;
MountOptions options{};
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
FileTester::MountWithOptions(loop.dispatcher(), options, &bc, &fs);
fbl::RefPtr<VnodeF2fs> root;
FileTester::CreateRoot(fs.get(), &root);
fbl::RefPtr<Dir> root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
std::string dir_name("dir");
fbl::RefPtr<fs::Vnode> child;
ASSERT_EQ(root_dir->Create(dir_name, S_IFDIR, &child), ZX_OK);
fbl::RefPtr<Dir> child_dir = fbl::RefPtr<Dir>::Downcast(std::move(child));
constexpr int kNThreads = 10;
constexpr int kNEntries = 100;
std::thread threads[kNThreads];
// create child vnodes.
for (uint32_t child = 0; child < kNThreads * kNEntries; ++child) {
uint32_t mode = child % 2 == 0 ? S_IFDIR : S_IFREG;
FileTester::CreateChild(child_dir.get(), mode, std::to_string(child));
}
for (auto nThread = 0; nThread < kNThreads; ++nThread) {
threads[nThread] = std::thread([nThread, child_dir]() {
// Each thread deletes dentries to make |child_dir| empty.
for (uint32_t child_count = 0; child_count < kNEntries; ++child_count) {
auto child_name = child_count + nThread * kNEntries;
bool is_dir = child_name % 2 == 0 ? true : false;
FileTester::DeleteChild(child_dir.get(), std::to_string(child_name), is_dir);
}
});
}
for (auto nThread = 0; nThread < kNThreads; ++nThread) {
threads[nThread].join();
}
// If |child_dir| is empty, it should be successful.
FileTester::DeleteChild(root_dir.get(), dir_name);
ASSERT_EQ(child_dir->Close(), ZX_OK);
ASSERT_EQ(root_dir->Close(), ZX_OK);
child_dir = nullptr;
root_dir = nullptr;
FileTester::Unmount(std::move(fs), &bc);
EXPECT_EQ(Fsck(std::move(bc), FsckOptions{.repair = false}, &bc), ZX_OK);
}
} // namespace
} // namespace f2fs