[f2fs] Add unit tests for file operations

It covers allocating and freeing node blocks under various file size.

Test: fx test f2fs-unittest f2fs-fs-tests f2fs-slow-fs-tests
Change-Id: Ibfc89dc08d6d544b30be1f0ca0eb8c1a16828a41
Reviewed-on: https://fuchsia-review.googlesource.com/c/third_party/f2fs/+/559387
Reviewed-by: Brett Wilson <brettw@google.com>
diff --git a/test/BUILD.gn b/test/BUILD.gn
index 5aeb4b5..2812ed6 100644
--- a/test/BUILD.gn
+++ b/test/BUILD.gn
@@ -22,6 +22,7 @@
     "unit/bcache.cc",
     "unit/checkpoint.cc",
     "unit/dir.cc",
+    "unit/file.cc",
     "unit/inline.cc",
     "unit/mkfs.cc",
     "unit/mount.cc",
diff --git a/test/unit/file.cc b/test/unit/file.cc
new file mode 100644
index 0000000..8f473f1
--- /dev/null
+++ b/test/unit/file.cc
@@ -0,0 +1,193 @@
+// Copyright 2021 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 <unordered_set>
+
+#include <block-client/cpp/fake-device.h>
+#include <gtest/gtest.h>
+
+#include "third_party/f2fs/f2fs.h"
+#include "unit_lib.h"
+
+namespace f2fs {
+namespace {
+
+TEST(FileTest, BlkAddrLevel) {
+  srand(time(nullptr));
+
+  uint64_t blockCount = static_cast<uint64_t>(8) * 1024 * 1024 * 1024 / kDefaultSectorSize;
+  std::unique_ptr<Bcache> bc;
+  unittest_lib::MkfsOnFakeDev(&bc, blockCount);
+
+  std::unique_ptr<F2fs> fs;
+  MountOptions options{};
+  ASSERT_EQ(options.SetValue(options.GetNameView(kOptInlineDentry), 0), ZX_OK);
+  unittest_lib::MountWithOptions(options, &bc, &fs);
+
+  fbl::RefPtr<VnodeF2fs> root;
+  unittest_lib::CreateRoot(fs.get(), &root);
+  fbl::RefPtr<Dir> root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
+
+  fbl::RefPtr<fs::Vnode> test_file;
+  ASSERT_EQ(root_dir->Create("test", S_IFREG, &test_file), ZX_OK);
+
+  fbl::RefPtr<VnodeF2fs> test_file_vn = fbl::RefPtr<VnodeF2fs>::Downcast(std::move(test_file));
+  File *test_file_ptr = static_cast<File *>(test_file_vn.get());
+
+  char buf[kPageSize];
+  unsigned int level = 0;
+
+  for (size_t i = 0; i < kPageSize; i++) {
+    buf[i] = rand() % 256;
+  }
+
+  // fill kAddrsPerInode blocks
+  for (int i = 0; i < kAddrsPerInode; i++) {
+    unittest_lib::AppendToFile(test_file_ptr, buf, kPageSize);
+  }
+
+  // check direct node #1 is not available yet
+  unittest_lib::CheckNodeLevel(fs.get(), test_file_ptr, level);
+
+  // fill one more block
+  unittest_lib::AppendToFile(test_file_ptr, buf, kPageSize);
+
+  // check direct node #1 is available
+  unittest_lib::CheckNodeLevel(fs.get(), test_file_ptr, ++level);
+
+  // fill direct node #1
+  for (int i = 1; i < kAddrsPerBlock; i++) {
+    unittest_lib::AppendToFile(test_file_ptr, buf, kPageSize);
+  }
+
+  // check direct node #2 is not available yet
+  unittest_lib::CheckNodeLevel(fs.get(), test_file_ptr, level);
+
+  // fill one more block
+  unittest_lib::AppendToFile(test_file_ptr, buf, kPageSize);
+
+  // check direct node #2 is available
+  unittest_lib::CheckNodeLevel(fs.get(), test_file_ptr, ++level);
+
+  // fill direct node #2
+  for (int i = 1; i < kAddrsPerBlock; i++) {
+    unittest_lib::AppendToFile(test_file_ptr, buf, kPageSize);
+  }
+
+  // check indirect node #1 is not available yet
+  unittest_lib::CheckNodeLevel(fs.get(), test_file_ptr, level);
+
+  // fill one more block
+  unittest_lib::AppendToFile(test_file_ptr, buf, kPageSize);
+
+  // check indirect node #1 is available
+  unittest_lib::CheckNodeLevel(fs.get(), test_file_ptr, ++level);
+
+  ASSERT_EQ(test_file_vn->Close(), ZX_OK);
+  test_file_vn = nullptr;
+  ASSERT_EQ(root_dir->Close(), ZX_OK);
+  root_dir = nullptr;
+
+  unittest_lib::Unmount(std::move(fs), &bc);
+}
+
+TEST(FileTest, NidAndBlkaddrAllocFree) {
+  srand(time(nullptr));
+
+  uint64_t blockCount = static_cast<uint64_t>(8) * 1024 * 1024 * 1024 / kDefaultSectorSize;
+  std::unique_ptr<Bcache> bc;
+  unittest_lib::MkfsOnFakeDev(&bc, blockCount);
+
+  std::unique_ptr<F2fs> fs;
+  MountOptions options{};
+  ASSERT_EQ(options.SetValue(options.GetNameView(kOptInlineDentry), 0), ZX_OK);
+  unittest_lib::MountWithOptions(options, &bc, &fs);
+
+  fbl::RefPtr<VnodeF2fs> root;
+  unittest_lib::CreateRoot(fs.get(), &root);
+  fbl::RefPtr<Dir> root_dir = fbl::RefPtr<Dir>::Downcast(std::move(root));
+
+  fbl::RefPtr<fs::Vnode> test_file;
+  ASSERT_EQ(root_dir->Create("test", S_IFREG, &test_file), ZX_OK);
+
+  fbl::RefPtr<VnodeF2fs> test_file_vn = fbl::RefPtr<VnodeF2fs>::Downcast(std::move(test_file));
+  File *test_file_ptr = static_cast<File *>(test_file_vn.get());
+
+  char buf[kPageSize];
+
+  for (size_t i = 0; i < kPageSize; i++) {
+    buf[i] = rand() % 256;
+  }
+
+  // Fill until direct nodes are full
+  unsigned int level = 2;
+  for (int i = 0; i < kAddrsPerInode + kAddrsPerBlock * 2; i++) {
+    unittest_lib::AppendToFile(test_file_ptr, buf, kPageSize);
+  }
+
+  unittest_lib::CheckNodeLevel(fs.get(), test_file_ptr, level);
+
+  // Build nid and blkaddr set
+  std::unordered_set<nid_t> nid_set;
+  std::unordered_set<block_t> blkaddr_set;
+
+  nid_set.insert(test_file_ptr->Ino());
+  Page *ipage = nullptr;
+  ASSERT_EQ(fs->Nodemgr().GetNodePage(test_file_ptr->Ino(), &ipage), ZX_OK);
+  Inode *inode = &(static_cast<Node *>(PageAddress(ipage))->i);
+
+  for (int i = 0; i < kNidsPerInode; i++) {
+    if (inode->i_nid[i] != 0U)
+      nid_set.insert(inode->i_nid[i]);
+  }
+
+  for (int i = 0; i < kAddrsPerInode; i++) {
+    ASSERT_NE(inode->i_addr[i], kNullAddr);
+    blkaddr_set.insert(inode->i_addr[i]);
+  }
+
+  for (int i = 0; i < 2; i++) {
+    Page *direct_node_page = nullptr;
+    ASSERT_EQ(fs->Nodemgr().GetNodePage(inode->i_nid[i], &direct_node_page), ZX_OK);
+    DirectNode *direct_node = &(static_cast<Node *>(PageAddress(direct_node_page))->dn);
+
+    for (int j = 0; j < kAddrsPerBlock; j++) {
+      ASSERT_NE(direct_node->addr[j], kNullAddr);
+      blkaddr_set.insert(direct_node->addr[j]);
+    }
+
+    F2fsPutPage(direct_node_page, 0);
+  }
+
+  F2fsPutPage(ipage, 0);
+
+  ASSERT_EQ(nid_set.size(), level + 1);
+  ASSERT_EQ(blkaddr_set.size(), static_cast<uint32_t>(kAddrsPerInode + kAddrsPerBlock * 2));
+
+  // After writing checkpoint, check if nids are removed from free nid list
+  // Also, for allocated blkaddr, check if corresponding bit is set in valid bitmap of segment
+  fs->WriteCheckpoint(false, false);
+
+  unittest_lib::CheckNidsInuse(fs.get(), nid_set);
+  unittest_lib::CheckBlkaddrsInuse(fs.get(), blkaddr_set);
+
+  // Remove file, writing checkpoint, then check if nids are added to free nid list
+  // Also, for allocated blkaddr, check if corresponding bit is cleared in valid bitmap of segment
+  ASSERT_EQ(test_file_vn->Close(), ZX_OK);
+  test_file_vn = nullptr;
+
+  root_dir->Unlink("test", false);
+  fs->WriteCheckpoint(false, false);
+
+  unittest_lib::CheckNidsFree(fs.get(), nid_set);
+  unittest_lib::CheckBlkaddrsFree(fs.get(), blkaddr_set);
+
+  ASSERT_EQ(root_dir->Close(), ZX_OK);
+  root_dir = nullptr;
+
+  unittest_lib::Unmount(std::move(fs), &bc);
+}
+
+}  // namespace
+}  // namespace f2fs
diff --git a/test/unit/unit_lib.cc b/test/unit/unit_lib.cc
index d951931..3ea5e3d 100644
--- a/test/unit/unit_lib.cc
+++ b/test/unit/unit_lib.cc
@@ -152,5 +152,84 @@
   return str;
 }
 
+void AppendToFile(File *file, const void *data, size_t len) {
+  size_t end = 0;
+  size_t ret = 0;
+
+  ASSERT_EQ(file->Append(data, len, &end, &ret), ZX_OK);
+  ASSERT_EQ(ret, kPageSize);
+}
+
+void CheckNodeLevel(F2fs *fs, VnodeF2fs *vn, int level) {
+  Page *ipage = nullptr;
+  ASSERT_EQ(fs->Nodemgr().GetNodePage(vn->Ino(), &ipage), ZX_OK);
+  Inode *inode = &(static_cast<Node *>(PageAddress(ipage))->i);
+
+  int i;
+  for (i = 0; i < level; i++)
+    ASSERT_NE(inode->i_nid[i], 0U);
+
+  for (; i < kNidsPerInode; i++)
+    ASSERT_EQ(inode->i_nid[i], 0U);
+
+  F2fsPutPage(ipage, 0);
+}
+
+void CheckNidsFree(F2fs *fs, std::unordered_set<nid_t> &nids) {
+  SbInfo &sbi = fs->GetSbInfo();
+  NmInfo *nm_i = GetNmInfo(&sbi);
+
+  fbl::AutoLock lock(&nm_i->free_nid_list_lock);
+  for (auto nid : nids) {
+    bool found = false;
+    list_node_t *iter;
+    list_for_every(&nm_i->free_nid_list, iter) {
+      FreeNid *fnid = containerof(iter, FreeNid, list);
+      if (fnid->nid == nid) {
+        found = true;
+        break;
+      }
+    }
+    ASSERT_TRUE(found);
+  }
+}
+
+void CheckNidsInuse(F2fs *fs, std::unordered_set<nid_t> &nids) {
+  SbInfo &sbi = fs->GetSbInfo();
+  NmInfo *nm_i = GetNmInfo(&sbi);
+
+  fbl::AutoLock lock(&nm_i->free_nid_list_lock);
+  for (auto nid : nids) {
+    bool found = false;
+    list_node_t *iter;
+    list_for_every(&nm_i->free_nid_list, iter) {
+      FreeNid *fnid = containerof(iter, FreeNid, list);
+      if (fnid->nid == nid) {
+        found = true;
+        break;
+      }
+    }
+    ASSERT_FALSE(found);
+  }
+}
+
+void CheckBlkaddrsFree(F2fs *fs, std::unordered_set<block_t> &blkaddrs) {
+  SbInfo &sbi = fs->GetSbInfo();
+  for (auto blkaddr : blkaddrs) {
+    SegEntry *se = fs->Segmgr().GetSegEntry(GetSegNo(&sbi, blkaddr));
+    uint32_t offset = GetSegOffFromSeg0(&sbi, blkaddr) & (sbi.blocks_per_seg - 1);
+    ASSERT_EQ(TestValidBitmap(offset, reinterpret_cast<char *>(se->ckpt_valid_map)), 0);
+  }
+}
+
+void CheckBlkaddrsInuse(F2fs *fs, std::unordered_set<block_t> &blkaddrs) {
+  SbInfo &sbi = fs->GetSbInfo();
+  for (auto blkaddr : blkaddrs) {
+    SegEntry *se = fs->Segmgr().GetSegEntry(GetSegNo(&sbi, blkaddr));
+    uint32_t offset = GetSegOffFromSeg0(&sbi, blkaddr) & (sbi.blocks_per_seg - 1);
+    ASSERT_NE(TestValidBitmap(offset, reinterpret_cast<char *>(se->ckpt_valid_map)), 0);
+  }
+}
+
 }  // namespace unittest_lib
 }  // namespace f2fs
diff --git a/test/unit/unit_lib.h b/test/unit/unit_lib.h
index 8038885..4f47dd5 100644
--- a/test/unit/unit_lib.h
+++ b/test/unit/unit_lib.h
@@ -32,6 +32,15 @@
 
 std::string GetRandomName(unsigned int len);
 
+void AppendToFile(File *file, const void *data, size_t len);
+
+void CheckNodeLevel(F2fs *fs, VnodeF2fs *vn, int level);
+
+void CheckNidsFree(F2fs *fs, std::unordered_set<nid_t> &nids);
+void CheckNidsInuse(F2fs *fs, std::unordered_set<nid_t> &nids);
+void CheckBlkaddrsFree(F2fs *fs, std::unordered_set<block_t> &blkaddrs);
+void CheckBlkaddrsInuse(F2fs *fs, std::unordered_set<block_t> &blkaddrs);
+
 }  // namespace unittest_lib
 }  // namespace f2fs