[minfs] Convert truncate test to zxtest

The patch converts the tests in utest/fs/test-truncate.cc
from unittest to zxtest.

The tests in fs/test/posix/ will contain tests for posix like
behavior of the filesystems. It will be used by minfs and by
memfs. All other posix related tests in utest/fs/ will
eventually move here.

The patch retains the old tests and file just so that we still track
the reported bug in case the new changes don't reproduce the bug.
There is some loss of information in unittest which necessitates
the conversion to zxtest. Since the intention is to reproduce
the bug, I have tried to retain the code from test-truncate.cc
in truncate_test.cc. There is a lot of room for cleanup,
including removal of dead flags and restructuring code for better
names.

Bug: 41601

Change-Id: Ief9420095affdeb76f8c181e769ad5a6d38d6ba6
diff --git a/zircon/system/ulib/BUILD.gn b/zircon/system/ulib/BUILD.gn
index bbf3166..3ef2bf7 100644
--- a/zircon/system/ulib/BUILD.gn
+++ b/zircon/system/ulib/BUILD.gn
@@ -76,6 +76,7 @@
     "fs-test-utils",
     "fs/journal",
     "fs/metrics",
+    "fs/test/posix:posix_tests",
     "fs/transaction",
     "ftl",
     "fvm",
diff --git a/zircon/system/ulib/fs/BUILD.gn b/zircon/system/ulib/fs/BUILD.gn
index a1ce4f6..0c26c05 100644
--- a/zircon/system/ulib/fs/BUILD.gn
+++ b/zircon/system/ulib/fs/BUILD.gn
@@ -191,3 +191,7 @@
     "transaction:test",
   ]
 }
+
+group("posix") {
+  deps = [ "posix:posix_tests" ]
+}
diff --git a/zircon/system/ulib/fs/test/posix/BUILD.gn b/zircon/system/ulib/fs/test/posix/BUILD.gn
new file mode 100644
index 0000000..fe5d303
--- /dev/null
+++ b/zircon/system/ulib/fs/test/posix/BUILD.gn
@@ -0,0 +1,13 @@
+# Copyright 2020 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.
+
+zx_library("posix_tests") {
+  testonly = true
+  sources = [ "truncate.cc" ]
+  deps = [
+    "$zx/system/ulib/fs/test_support",
+    "$zx/system/ulib/zx",
+    "$zx/system/ulib/zxtest",
+  ]
+}
diff --git a/zircon/system/ulib/fs/test/posix/include/fs/test/posix/tests.h b/zircon/system/ulib/fs/test/posix/include/fs/test/posix/tests.h
new file mode 100644
index 0000000..dc9fa90
--- /dev/null
+++ b/zircon/system/ulib/fs/test/posix/include/fs/test/posix/tests.h
@@ -0,0 +1,50 @@
+// Copyright 2020 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.
+
+#ifndef FS_TEST_POSIX_TESTS_H_
+#define FS_TEST_POSIX_TESTS_H_
+
+#include <fs/test_support/fixtures.h>
+namespace posix_tests {
+
+using fs::FilesystemTest;
+
+enum CloseUnlinkOrder {
+  UnlinkThenClose,  // Unlink the file while file is still open.
+  CloseThenUnlink,  // Close the file before unlinking it.
+};
+
+enum TestType {
+  KeepOpen,  // Truncates while file is still open.
+  Reopen,    // Unused.
+  Remount,   // Remounts filesystem after truncate but before writing to it.
+};
+
+// Test that truncate doesn't have issues dealing with files smaller than a
+// block size.
+void TestTruncateSingleBlockFile(FilesystemTest* ops);
+
+// Test that truncate doesn't have issues dealing with larger files.
+// Repeatedly write to / truncate a file.
+void TestTruncateMultiBlockFile(FilesystemTest* ops, size_t buf_size, size_t iterations,
+                                TestType type);
+
+// This test catches a particular regression in truncation, where,
+// if a block is cut in half for truncation, it is read, filled with
+// zeroes, and written back out to disk.
+//
+// This test tries to probe at a variety of offsets of interest.
+void TestTruncatePartialBlockSparse(FilesystemTest* ops, CloseUnlinkOrder order);
+
+// Creates a sparse file with multiple holes and truncates the file in the hole.
+void TestTruncatePartialBlockSparse(FilesystemTest* ops, CloseUnlinkOrder order);
+
+// Tests truncate error conditions like
+//    - truncate to negative size.
+//    - truncate to extremely large file.
+void TestTruncateErrno(FilesystemTest* ops);
+
+}  // namespace posix_tests
+
+#endif  // FS_TEST_POSIX_TESTS_H_
diff --git a/zircon/system/ulib/fs/test/posix/truncate.cc b/zircon/system/ulib/fs/test/posix/truncate.cc
new file mode 100644
index 0000000..b965fd9
--- /dev/null
+++ b/zircon/system/ulib/fs/test/posix/truncate.cc
@@ -0,0 +1,273 @@
+// Copyright 2020 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.
+
+// TODO: 44323
+
+#include <fs/test/posix/tests.h>
+
+#include <assert.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <zircon/syscalls.h>
+
+#include <memory>
+
+#include <fbl/algorithm.h>
+#include <fbl/alloc_checker.h>
+#include <fbl/unique_fd.h>
+#include <zxtest/zxtest.h>
+
+namespace posix_tests {
+namespace {
+
+#define ASSERT_STREAM_ALL(op, fd, buf, len) ASSERT_EQ(op(fd, (buf), (len)), (ssize_t)(len), "");
+
+void check_file_contains(const char* filename, const void* data, ssize_t len) {
+  char buf[PATH_MAX];
+  struct stat st;
+
+  ASSERT_EQ(stat(filename, &st), 0);
+  ASSERT_EQ(st.st_size, len);
+  fbl::unique_fd fd(open(filename, O_RDWR, 0644));
+  ASSERT_TRUE(fd);
+  ASSERT_STREAM_ALL(read, fd.get(), buf, len);
+  ASSERT_EQ(memcmp(buf, data, len), 0);
+}
+
+void check_file_empty(const char* filename) {
+  struct stat st;
+  ASSERT_EQ(stat(filename, &st), 0);
+  ASSERT_EQ(st.st_size, 0);
+}
+
+void fill_file(int fd, uint8_t* u8, ssize_t new_len, ssize_t old_len) {
+  fbl::AllocChecker ac;
+  std::unique_ptr<uint8_t[]> readbuf(new (&ac) uint8_t[new_len]);
+  ASSERT_TRUE(ac.check());
+  if (new_len > old_len) {  // Expanded the file
+    // Verify that the file is unchanged up to old_len
+    ASSERT_EQ(lseek(fd, 0, SEEK_SET), 0);
+    ASSERT_STREAM_ALL(read, fd, readbuf.get(), old_len);
+    ASSERT_EQ(memcmp(readbuf.get(), u8, old_len), 0);
+    // Verify that the file is filled with zeroes from old_len to new_len
+    ASSERT_EQ(lseek(fd, old_len, SEEK_SET), old_len);
+    ASSERT_STREAM_ALL(read, fd, readbuf.get(), new_len - old_len);
+    for (ssize_t n = 0; n < (new_len - old_len); n++) {
+      ASSERT_EQ(readbuf[n], 0);
+    }
+    // Overwrite those zeroes with the contents of u8
+    ASSERT_EQ(lseek(fd, old_len, SEEK_SET), old_len);
+    ASSERT_STREAM_ALL(write, fd, u8 + old_len, new_len - old_len);
+  } else {  // Shrunk the file (or kept it the same length)
+    // Verify that the file is unchanged up to new_len
+    ASSERT_EQ(lseek(fd, 0, SEEK_SET), 0);
+    ASSERT_STREAM_ALL(read, fd, readbuf.get(), new_len);
+    ASSERT_EQ(memcmp(readbuf.get(), u8, new_len), 0);
+  }
+}
+
+void checked_truncate(FilesystemTest* ops, const char* filename, uint8_t* u8, ssize_t new_len,
+                      TestType type) {
+  // Acquire the old size
+  struct stat st;
+  ASSERT_EQ(stat(filename, &st), 0);
+  ssize_t old_len = st.st_size;
+
+  // Truncate the file, verify the size gets updated
+  fbl::unique_fd fd(open(filename, O_RDWR, 0644));
+  ASSERT_TRUE(fd);
+  ASSERT_EQ(ftruncate(fd.get(), new_len), 0);
+  ASSERT_EQ(stat(filename, &st), 0);
+  ASSERT_EQ(st.st_size, new_len);
+
+  // Close and reopen the file; verify the inode stays updated
+  ASSERT_EQ(close(fd.release()), 0);
+  fd.reset(open(filename, O_RDWR, 0644));
+  ASSERT_TRUE(fd);
+  ASSERT_EQ(stat(filename, &st), 0);
+  ASSERT_EQ(st.st_size, new_len);
+
+  if (type == TestType::Remount) {
+    ASSERT_EQ(close(fd.release()), 0);
+    ops->Remount();
+    ASSERT_EQ(stat(filename, &st), 0);
+    ASSERT_EQ(st.st_size, new_len);
+    fd.reset(open(filename, O_RDWR, 0644));
+  }
+
+  fill_file(fd.get(), u8, new_len, old_len);
+}
+
+void fchecked_truncate(int fd, uint8_t* u8, ssize_t new_len) {
+  // Acquire the old size
+  struct stat st;
+  ASSERT_EQ(fstat(fd, &st), 0);
+  ssize_t old_len = st.st_size;
+
+  // Truncate the file, verify the size gets updated
+  ASSERT_EQ(ftruncate(fd, new_len), 0);
+  ASSERT_EQ(fstat(fd, &st), 0);
+  ASSERT_EQ(st.st_size, new_len);
+
+  fill_file(fd, u8, new_len, old_len);
+}
+
+}  // namespace
+
+// Test that the really simple cases of truncate are operational
+void TestTruncateSingleBlockFile(FilesystemTest* ops) {
+  const char* str = "Hello, World!\n";
+  char filename[PATH_MAX];
+  ASSERT_LT(snprintf(filename, sizeof(filename), "%s/%s", ops->mount_path(), __func__),
+            sizeof(filename));
+
+  // Try writing a string to a file
+  fbl::unique_fd fd(open(filename, O_RDWR | O_CREAT, 0644));
+  ASSERT_TRUE(fd);
+  ASSERT_STREAM_ALL(write, fd.get(), str, strlen(str));
+  check_file_contains(filename, str, strlen(str));
+
+  // Check that opening a file with O_TRUNC makes it empty
+  fbl::unique_fd fd2(open(filename, O_RDWR | O_TRUNC, 0644));
+  ASSERT_TRUE(fd2);
+  check_file_empty(filename);
+
+  // Check that we can still write to a file that has been truncated
+  ASSERT_EQ(lseek(fd.get(), 0, SEEK_SET), 0);
+  ASSERT_STREAM_ALL(write, fd.get(), str, strlen(str));
+  check_file_contains(filename, str, strlen(str));
+
+  // Check that we can truncate the file using the "truncate" function
+  ASSERT_EQ(truncate(filename, 5), 0);
+  check_file_contains(filename, str, 5);
+  ASSERT_EQ(truncate(filename, 0), 0);
+  check_file_empty(filename);
+
+  // Check that truncating an already empty file does not cause problems
+  ASSERT_EQ(truncate(filename, 0), 0);
+  check_file_empty(filename);
+
+  // Check that we can use truncate to extend a file
+  char empty[5] = {0, 0, 0, 0, 0};
+  ASSERT_EQ(truncate(filename, 5), 0);
+  check_file_contains(filename, empty, 5);
+
+  ASSERT_EQ(close(fd.release()), 0);
+  ASSERT_EQ(close(fd2.release()), 0);
+  ASSERT_EQ(unlink(filename), 0);
+}
+
+void TestTruncateMultiBlockFile(FilesystemTest* ops, size_t buf_size, size_t iterations,
+                                TestType type) {
+  if ((type == TestType::Remount) && ops->CanBeRemounted()) {
+    fprintf(stderr, "Filesystem cannot be mounted; cannot test persistence\n");
+  }
+
+  // Fill a test buffer with data
+  fbl::AllocChecker ac;
+  std::unique_ptr<uint8_t[]> buf(new (&ac) uint8_t[buf_size]);
+  ASSERT_TRUE(ac.check());
+
+  unsigned seed = static_cast<unsigned>(zx_ticks_get());
+  printf("Truncate test using seed: %u\n", seed);
+  srand(seed);
+  for (unsigned n = 0; n < buf_size; n++) {
+    buf[n] = static_cast<uint8_t>(rand_r(&seed));
+  }
+
+  char filename[PATH_MAX];
+  ASSERT_LT(snprintf(filename, sizeof(filename), "%s/%s-%lu-%lu", ops->mount_path(), __func__,
+                     buf_size, iterations),
+            sizeof(filename));
+  // Start a file filled with a buffer
+  fbl::unique_fd fd(open(filename, O_RDWR | O_CREAT, 0644));
+  ASSERT_TRUE(fd);
+  ASSERT_STREAM_ALL(write, fd.get(), buf.get(), buf_size);
+
+  if (type != TestType::KeepOpen) {
+    ASSERT_EQ(close(fd.release()), 0);
+  }
+
+  // Repeatedly truncate / write to the file
+  for (size_t i = 0; i < iterations; i++) {
+    size_t len = rand_r(&seed) % buf_size;
+    if (type == KeepOpen) {
+      fchecked_truncate(fd.get(), buf.get(), len);
+    } else {
+      checked_truncate(ops, filename, buf.get(), len, type);
+    }
+  }
+  ASSERT_EQ(unlink(filename), 0);
+  if (type == KeepOpen) {
+    ASSERT_EQ(close(fd.release()), 0);
+  }
+}
+
+void TestTruncatePartialBlockSparse(FilesystemTest* ops, CloseUnlinkOrder order) {
+  // TODO(44323): Acquire these constants directly from MinFS's header
+  constexpr size_t kBlockSize = 8192;
+  constexpr size_t kDirectBlocks = 16;
+  constexpr size_t kIndirectBlocks = 31;
+  constexpr size_t kDirectPerIndirect = kBlockSize / 4;
+
+  uint8_t buf[kBlockSize];
+  memset(buf, 0xAB, sizeof(buf));
+
+  off_t write_offsets[] = {
+      kBlockSize * 5,
+      kBlockSize * kDirectBlocks,
+      kBlockSize * kDirectBlocks + kBlockSize * kDirectPerIndirect * 1,
+      kBlockSize * kDirectBlocks + kBlockSize * kDirectPerIndirect * 2,
+      kBlockSize * kDirectBlocks + kBlockSize * kDirectPerIndirect * kIndirectBlocks -
+          2 * kBlockSize,
+      kBlockSize * kDirectBlocks + kBlockSize * kDirectPerIndirect * kIndirectBlocks - kBlockSize,
+      kBlockSize * kDirectBlocks + kBlockSize * kDirectPerIndirect * kIndirectBlocks,
+      kBlockSize * kDirectBlocks + kBlockSize * kDirectPerIndirect * kIndirectBlocks + kBlockSize,
+  };
+
+  char path[PATH_MAX];
+  ASSERT_LT(snprintf(path, sizeof(path), "%s/%s", ops->mount_path(), __func__), sizeof(path));
+  for (size_t i = 0; i < fbl::count_of(write_offsets); i++) {
+    off_t write_off = write_offsets[i];
+    fbl::unique_fd fd(open(path, O_CREAT | O_RDWR));
+    ASSERT_TRUE(fd);
+    ASSERT_EQ(lseek(fd.get(), write_off, SEEK_SET), write_off);
+    ASSERT_EQ(write(fd.get(), buf, sizeof(buf)), sizeof(buf));
+    ASSERT_EQ(ftruncate(fd.get(), write_off + 2 * kBlockSize), 0);
+    ASSERT_EQ(ftruncate(fd.get(), write_off + kBlockSize + kBlockSize / 2), 0);
+    ASSERT_EQ(ftruncate(fd.get(), write_off + kBlockSize / 2), 0);
+    ASSERT_EQ(ftruncate(fd.get(), write_off - kBlockSize / 2), 0);
+    if (order == UnlinkThenClose) {
+      ASSERT_EQ(unlink(path), 0);
+      ASSERT_EQ(close(fd.release()), 0);
+    } else {
+      ASSERT_EQ(close(fd.release()), 0);
+      ASSERT_EQ(unlink(path), 0);
+    }
+  }
+}
+
+void TestTruncateErrno(FilesystemTest* ops) {
+  char path[PATH_MAX];
+  ASSERT_LT(snprintf(path, sizeof(path), "%s/%s", ops->mount_path(), __func__), sizeof(path));
+  fbl::unique_fd fd(open(path, O_RDWR | O_CREAT | O_EXCL));
+  ASSERT_TRUE(fd);
+
+  ASSERT_EQ(ftruncate(fd.get(), -1), -1);
+  ASSERT_EQ(errno, EINVAL);
+  errno = 0;
+  ASSERT_EQ(ftruncate(fd.get(), 1UL << 60), -1);
+  ASSERT_EQ(errno, EINVAL);
+
+  ASSERT_EQ(unlink(path), 0);
+  ASSERT_EQ(close(fd.release()), 0);
+}
+
+}  // namespace posix_tests
diff --git a/zircon/system/ulib/fs/test_support/include/fs/test_support/fixtures.h b/zircon/system/ulib/fs/test_support/include/fs/test_support/fixtures.h
index ba1dc21..f7c53ac 100644
--- a/zircon/system/ulib/fs/test_support/include/fs/test_support/fixtures.h
+++ b/zircon/system/ulib/fs/test_support/include/fs/test_support/fixtures.h
@@ -50,6 +50,7 @@
   // Queries the filesystem for generic info.
   void GetFsInfo(::llcpp::fuchsia::io::FilesystemInfo* info);
 
+  bool CanBeRemounted() { return true; }
   void set_read_only(bool read_only) { read_only_ = read_only; }
   const std::string& device_path() const { return device_path_; }
   disk_format_type format_type() const { return environment_->format_type(); }
@@ -57,7 +58,6 @@
 
   DISALLOW_COPY_ASSIGN_AND_MOVE(FilesystemTest);
 
-
  protected:
   // Helper function for launching a filesystem and exposing it to tests. Including:
   // - Parse the filesystem from |device_id|, assuming it is in |disk_format|.
diff --git a/zircon/system/ulib/minfs/test/BUILD.gn b/zircon/system/ulib/minfs/test/BUILD.gn
index f890a31..126869f 100644
--- a/zircon/system/ulib/minfs/test/BUILD.gn
+++ b/zircon/system/ulib/minfs/test/BUILD.gn
@@ -52,6 +52,7 @@
     "integration/integration_main.cc",
     "integration/minfs_fixtures.cc",
     "integration/mount_test.cc",
+    "integration/truncate_test.cc",
     "integration/utils.cc",
   ]
   deps = [
@@ -65,6 +66,7 @@
     "$zx/system/ulib/fdio",
     "$zx/system/ulib/fs-management",
     "$zx/system/ulib/fs-test-utils",
+    "$zx/system/ulib/fs/test/posix:posix_tests",
     "$zx/system/ulib/fs/test_support",
     "$zx/system/ulib/memfs",
     "$zx/system/ulib/minfs",
diff --git a/zircon/system/ulib/minfs/test/integration/integration_main.cc b/zircon/system/ulib/minfs/test/integration/integration_main.cc
index e34faad..7b82991 100644
--- a/zircon/system/ulib/minfs/test/integration/integration_main.cc
+++ b/zircon/system/ulib/minfs/test/integration/integration_main.cc
@@ -30,7 +30,7 @@
   }
 
   config.mount_path = kMountPath;
-  config.ramdisk_block_count = 1 << 15;  // 16 MB.
+  config.ramdisk_block_count = 1 << 20;  // 1M blocks.
   config.format_type = DISK_FORMAT_MINFS;
 
   auto parent = std::make_unique<fs::Environment>(config);
diff --git a/zircon/system/ulib/minfs/test/integration/truncate_test.cc b/zircon/system/ulib/minfs/test/integration/truncate_test.cc
new file mode 100644
index 0000000..e6abfaa
--- /dev/null
+++ b/zircon/system/ulib/minfs/test/integration/truncate_test.cc
@@ -0,0 +1,160 @@
+// Copyright 2020 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 <fbl/unique_fd.h>
+#include <fs/test/posix/tests.h>
+#include <zxtest/zxtest.h>
+
+#include "minfs_fixtures.h"
+#include "utils.h"
+
+namespace {
+
+using fs::FilesystemTest;
+
+using TruncateTest = MinfsTest;
+using TruncateTestWithFvm = MinfsTestWithFvm;
+
+constexpr size_t kTinyFileSize = 1 << 10;
+constexpr size_t kSmallFileSize = 1 << 15;
+constexpr size_t kMediumFileSize = 1 << 20;
+constexpr size_t kLargeFileSize = 1 << 25;
+
+// With kFewIterations or with kManyIterations, large tests are timing out.
+// The tests need to be modified to spend less time doing IO or comparing
+// results. We need to fix this and delete kTenIterations.
+// TODO: 44323
+constexpr size_t kTenIterations = 10;
+constexpr size_t kFewIterations = 50;
+constexpr size_t kManyIterations = 100;
+
+TEST_F(TruncateTest, TruncateSingleBlockFile) { posix_tests::TestTruncateSingleBlockFile(this); }
+
+TEST_F(TruncateTest, TruncateTinyFile) {
+  posix_tests::TestTruncateMultiBlockFile(this, kTinyFileSize, kManyIterations,
+                                          posix_tests::TestType::KeepOpen);
+}
+
+TEST_F(TruncateTest, TruncateTinyFileWithReopen) {
+  posix_tests::TestTruncateMultiBlockFile(this, kTinyFileSize, kManyIterations,
+                                          posix_tests::TestType::Reopen);
+}
+
+TEST_F(TruncateTest, TruncateSmallFile) {
+  posix_tests::TestTruncateMultiBlockFile(this, kSmallFileSize, kFewIterations,
+                                          posix_tests::TestType::KeepOpen);
+}
+
+TEST_F(TruncateTest, TruncateSmallFileWithReopen) {
+  posix_tests::TestTruncateMultiBlockFile(this, kSmallFileSize, kFewIterations,
+                                          posix_tests::TestType::Reopen);
+}
+
+TEST_F(TruncateTest, TruncateMediumFile) {
+  posix_tests::TestTruncateMultiBlockFile(this, kMediumFileSize, kFewIterations,
+                                          posix_tests::TestType::KeepOpen);
+}
+
+TEST_F(TruncateTest, TruncateMediumFileWithReopen) {
+  posix_tests::TestTruncateMultiBlockFile(this, kMediumFileSize, kFewIterations,
+                                          posix_tests::TestType::Reopen);
+}
+
+TEST_F(TruncateTest, TruncateMediumFileWithRemount) {
+  posix_tests::TestTruncateMultiBlockFile(this, kMediumFileSize, kFewIterations,
+                                          posix_tests::TestType::Remount);
+}
+
+TEST_F(TruncateTest, TruncateLargeFile) {
+  posix_tests::TestTruncateMultiBlockFile(this, kLargeFileSize, kFewIterations,
+                                          posix_tests::TestType::KeepOpen);
+}
+
+TEST_F(TruncateTest, TruncateLargeFileWithReopen) {
+  posix_tests::TestTruncateMultiBlockFile(this, kLargeFileSize, kFewIterations,
+                                          posix_tests::TestType::Reopen);
+}
+
+TEST_F(TruncateTest, TruncateLargeFileWithRemount) {
+  posix_tests::TestTruncateMultiBlockFile(this, kLargeFileSize, kFewIterations,
+                                          posix_tests::TestType::Remount);
+}
+
+TEST_F(TruncateTest, ePartialBlockSparseUnlinkThenClose) {
+  posix_tests::TestTruncatePartialBlockSparse(this, posix_tests::CloseUnlinkOrder::UnlinkThenClose);
+}
+
+TEST_F(TruncateTest, PartialBlockSparseCloseThenUnlink) {
+  posix_tests::TestTruncatePartialBlockSparse(this, posix_tests::CloseUnlinkOrder::CloseThenUnlink);
+}
+
+TEST_F(TruncateTest, InvalidArguments) { posix_tests::TestTruncateErrno(this); }
+
+TEST_F(TruncateTestWithFvm, TruncateSingleBlockFile) {
+  posix_tests::TestTruncateSingleBlockFile(this);
+}
+
+TEST_F(TruncateTestWithFvm, TruncateTinyFile) {
+  posix_tests::TestTruncateMultiBlockFile(this, kTinyFileSize, kManyIterations,
+                                          posix_tests::TestType::KeepOpen);
+}
+
+TEST_F(TruncateTestWithFvm, TruncateTinyFileWithReopen) {
+  posix_tests::TestTruncateMultiBlockFile(this, kTinyFileSize, kManyIterations,
+                                          posix_tests::TestType::Reopen);
+}
+
+TEST_F(TruncateTestWithFvm, TruncateSmallFile) {
+  posix_tests::TestTruncateMultiBlockFile(this, kSmallFileSize, kFewIterations,
+                                          posix_tests::TestType::KeepOpen);
+}
+
+TEST_F(TruncateTestWithFvm, TruncateSmallFileWithReopen) {
+  posix_tests::TestTruncateMultiBlockFile(this, kSmallFileSize, kFewIterations,
+                                          posix_tests::TestType::Reopen);
+}
+
+TEST_F(TruncateTestWithFvm, TruncateMediumFile) {
+  posix_tests::TestTruncateMultiBlockFile(this, kMediumFileSize, kFewIterations,
+                                          posix_tests::TestType::KeepOpen);
+}
+
+TEST_F(TruncateTestWithFvm, TruncateMediumFileWithReopen) {
+  posix_tests::TestTruncateMultiBlockFile(this, kMediumFileSize, kFewIterations,
+                                          posix_tests::TestType::Reopen);
+}
+
+TEST_F(TruncateTestWithFvm, TruncateMediumFileWithRemount) {
+  posix_tests::TestTruncateMultiBlockFile(this, kMediumFileSize, kFewIterations,
+                                          posix_tests::TestType::Remount);
+}
+
+TEST_F(TruncateTestWithFvm, TruncateLargeFile) {
+  posix_tests::TestTruncateMultiBlockFile(this, kLargeFileSize, kTenIterations,
+                                          posix_tests::TestType::KeepOpen);
+}
+
+TEST_F(TruncateTestWithFvm, TruncateLargeFileWithReopen) {
+  posix_tests::TestTruncateMultiBlockFile(this, kLargeFileSize, kTenIterations,
+                                          posix_tests::TestType::Reopen);
+}
+
+TEST_F(TruncateTestWithFvm, TruncateLargeFileWithRemount) {
+  posix_tests::TestTruncateMultiBlockFile(this, kLargeFileSize, kTenIterations,
+                                          posix_tests::TestType::Remount);
+}
+
+TEST_F(TruncateTestWithFvm, PartialBlockSparseUnlinkThenClose) {
+  posix_tests::TestTruncatePartialBlockSparse(this, posix_tests::CloseUnlinkOrder::UnlinkThenClose);
+}
+
+TEST_F(TruncateTestWithFvm, PartialBlockSparseCloseThenUnlink) {
+  posix_tests::TestTruncatePartialBlockSparse(this, posix_tests::CloseUnlinkOrder::CloseThenUnlink);
+}
+
+TEST_F(TruncateTestWithFvm, InvalidArguments) { posix_tests::TestTruncateErrno(this); }
+
+}  // namespace