blob: a184ec1ee1a89e8abb1dc07ac5672d9963771c97 [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/storage/f2fs/f2fs.h"
#include "src/storage/lib/block_client/cpp/fake_block_device.h"
#include "unit_lib.h"
namespace f2fs {
namespace {
class MultiThreads : public F2fsFakeDevTestFixture {
public:
MultiThreads() : F2fsFakeDevTestFixture(TestOptions{.block_count = 2097152}) {}
};
TEST_F(MultiThreads, Truncate) {
zx::result test_file = root_dir_->Create("test2", fs::CreationType::kFile);
ASSERT_TRUE(test_file.is_ok()) << test_file.status_string();
{
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 writer = std::thread([&]() {
bool run = true;
std::thread truncate = std::thread([&]() {
while (run) {
ASSERT_EQ(vn->Truncate(0), ZX_OK);
}
});
for (int i = 0; i < kNTry; ++i) {
size_t out_actual;
ASSERT_EQ(FileTester::Write(vn.get(), buf, sizeof(buf), 0, &out_actual), ZX_OK);
}
run = false;
truncate.join();
});
writer.join();
vn->Close();
}
}
TEST_F(MultiThreads, Write) {
zx::result test_file1 = root_dir_->Create("test1", fs::CreationType::kFile);
ASSERT_TRUE(test_file1.is_ok()) << test_file1.status_string();
zx::result test_file2 = root_dir_->Create("test2", fs::CreationType::kFile);
ASSERT_TRUE(test_file2.is_ok()) << test_file2.status_string();
{
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 writer1 = std::thread([&]() {
for (int i = 0; i < kNTry; ++i) {
size_t out_actual;
ASSERT_EQ(FileTester::Write(vn1.get(), buf, sizeof(buf),
static_cast<size_t>(i * kBlockSize), &out_actual),
ZX_OK);
ASSERT_EQ(out_actual, sizeof(buf));
}
});
std::thread writer2 = std::thread([&]() {
for (int i = 0; i < kNTry; ++i) {
size_t out_actual;
ASSERT_EQ(FileTester::Write(vn2.get(), buf, sizeof(buf),
static_cast<size_t>(i * kBlockSize), &out_actual),
ZX_OK);
ASSERT_EQ(out_actual, sizeof(buf));
}
});
writer1.join();
writer2.join();
}
vn1->Close();
vn2->Close();
}
}
TEST_F(MultiThreads, Create) {
zx::result child = root_dir_->Create("dir", fs::CreationType::kDirectory);
ASSERT_TRUE(child.is_ok()) << child.status_string();
{
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) {
umode_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& thread : threads) {
thread.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);
}
}
TEST_F(MultiThreads, Unlink) {
std::string dir_name("dir");
zx::result child = root_dir_->Create(dir_name, fs::CreationType::kDirectory);
ASSERT_TRUE(child.is_ok()) << child.status_string();
{
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) {
umode_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;
FileTester::DeleteChild(child_dir.get(), std::to_string(child_name), is_dir);
}
});
}
for (auto& thread : threads) {
thread.join();
}
// If |child_dir| is empty, it should be successful.
FileTester::DeleteChild(root_dir_.get(), dir_name);
ASSERT_EQ(child_dir->Close(), ZX_OK);
}
}
} // namespace
} // namespace f2fs