[blobfs] Cleanup in blob test utils

Remove the Merkle tree validation from GenerateBlob. The digest library
is responsible for testing the Merkle tree generation so it doesn't need
to be done here. This removes all of the ASSERT_* calls from
GenerateBlob and GenerateRandomBlob allowing them to return the
generated BlobInfo.

Replace The ASSERT_GT in GenerateRealisticBlob with a ZX_ASSERT_MSG
because it's checking an invariant of the test util and not an invariant
of the code being tested.  This allows GenerateRealisticBlob to return
BlobInfo as well.

Stop returning a status from CreateMerkleTree and instead ZX_ASSERT
inside of the method.  Failing to generate the Merkle tree is a bug in
the test and not a bug in the code under test.

Remove the merkle and size_merkle member variables from BlobInfo and
remove the Generate*Blob overloads taking a BlobLayoutFormat.  The
overloads were added to support the new Merkle tree format in blobfs but
very few tests actually use the merkle tree member variables so the
overloads were making the common case more complicated.  If tests need
the Merkle tree or it's size they can call CreateMerkleTree.

Change the type of BlobInfo::data to use uint8_t instead char to match
the rest of the blobfs code.

Remove several unnecessary std::move calls.

Update some function params to better match how the params are used.

Fixed: 63295
Change-Id: Ia5e0d1051bc8085a23df02e1128ed4a8c1f858c4
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/470532
Commit-Queue: Chris Drouillard <cdrllrd@google.com>
Reviewed-by: James Sullivan <jfsulliv@google.com>
diff --git a/src/storage/blobfs/test/blob_utils.cc b/src/storage/blobfs/test/blob_utils.cc
index 183d442..d2fe61f 100644
--- a/src/storage/blobfs/test/blob_utils.cc
+++ b/src/storage/blobfs/test/blob_utils.cc
@@ -10,9 +10,11 @@
 #include <stdio.h>
 #include <sys/stat.h>
 #include <unistd.h>
+#include <zircon/status.h>
 #include <zircon/syscalls.h>
 
 #include <algorithm>
+#include <limits>
 #include <memory>
 
 #include <digest/digest.h>
@@ -27,9 +29,7 @@
 namespace blobfs {
 namespace {
 
-using digest::Digest;
 using digest::MerkleTreeCreator;
-using digest::MerkleTreeVerifier;
 
 fbl::Array<uint8_t> LoadTemplateData() {
   constexpr char kDataFile[] = "/pkg/data/test_binary";
@@ -51,64 +51,37 @@
 
 }  // namespace
 
-void RandomFill(char* data, size_t length) {
+void RandomFill(uint8_t* data, size_t length) {
   for (size_t i = 0; i < length; i++) {
     // TODO(jfsulliv): Use explicit seed
-    data[i] = (char)rand();
+    data[i] = static_cast<uint8_t>(rand());
   }
 }
 
 // Creates, writes, reads (to verify) and operates on a blob.
-void GenerateBlob(BlobSrcFunction data_generator, const std::string& mount_path, size_t data_size,
-                  BlobLayoutFormat blob_layout_format, std::unique_ptr<BlobInfo>* out) {
+std::unique_ptr<BlobInfo> GenerateBlob(const BlobSrcFunction& data_generator,
+                                       const std::string& mount_path, size_t data_size) {
   std::unique_ptr<BlobInfo> info(new BlobInfo);
-  info->data.reset(new char[data_size]);
+  info->data.reset(new uint8_t[data_size]);
   data_generator(info->data.get(), data_size);
   info->size_data = data_size;
 
-  auto merkle_tree = CreateMerkleTree(reinterpret_cast<const uint8_t*>(info->data.get()), data_size,
-                                      ShouldUseCompactMerkleTreeFormat(blob_layout_format));
-  ASSERT_EQ(merkle_tree.status_value(), ZX_OK);
-
-  info->merkle.reset(reinterpret_cast<char*>(merkle_tree->merkle_tree.release()));
-  info->size_merkle = merkle_tree->merkle_tree_size;
+  auto merkle_tree = CreateMerkleTree(info->data.get(), data_size, /*use_compact_format=*/true);
   snprintf(info->path, sizeof(info->path), "%s/%s", mount_path.c_str(),
            merkle_tree->root.ToString().c_str());
 
-  // Sanity-check the merkle tree.
-  MerkleTreeVerifier mtv;
-  mtv.SetUseCompactFormat(ShouldUseCompactMerkleTreeFormat(blob_layout_format));
-  ASSERT_EQ(mtv.SetDataLength(info->size_data), ZX_OK);
-  ASSERT_EQ(mtv.SetTree(info->merkle.get(), info->size_merkle, merkle_tree->root.get(),
-                        merkle_tree->root.len()),
-            ZX_OK);
-  ASSERT_EQ(mtv.Verify(info->data.get(), info->size_data, /*data_off=*/0), ZX_OK);
-
-  *out = std::move(info);
+  return info;
 }
 
-void GenerateBlob(BlobSrcFunction data_generator, const std::string& mount_path, size_t data_size,
-                  std::unique_ptr<BlobInfo>* out) {
-  GenerateBlob(data_generator, mount_path, data_size, BlobLayoutFormat::kPaddedMerkleTreeAtStart,
-               out);
+std::unique_ptr<BlobInfo> GenerateRandomBlob(const std::string& mount_path, size_t data_size) {
+  return GenerateBlob(RandomFill, mount_path, data_size);
 }
 
-void GenerateRandomBlob(const std::string& mount_path, size_t data_size,
-                        BlobLayoutFormat blob_layout_format, std::unique_ptr<BlobInfo>* out) {
-  GenerateBlob(RandomFill, mount_path, data_size, blob_layout_format, out);
-}
-
-void GenerateRandomBlob(const std::string& mount_path, size_t data_size,
-                        std::unique_ptr<BlobInfo>* out) {
-  GenerateRandomBlob(mount_path, data_size, BlobLayoutFormat::kPaddedMerkleTreeAtStart, out);
-}
-
-void GenerateRealisticBlob(const std::string& mount_path, size_t data_size,
-                           BlobLayoutFormat blob_layout_format, std::unique_ptr<BlobInfo>* out) {
+std::unique_ptr<BlobInfo> GenerateRealisticBlob(const std::string& mount_path, size_t data_size) {
   static fbl::Array<uint8_t> template_data = LoadTemplateData();
-  ASSERT_GT(template_data.size(), 0ul);
-  GenerateBlob(
-      [](char* data, size_t length) {
+  ZX_ASSERT_MSG(template_data.size() > 0ul, "Failed to load realistic data");
+  return GenerateBlob(
+      [](uint8_t* data, size_t length) {
         // TODO(jfsulliv): Use explicit seed
         int nonce = rand();
         size_t nonce_size = std::min(sizeof(nonce), length);
@@ -123,28 +96,33 @@
           length -= to_copy;
         }
       },
-      mount_path, data_size, blob_layout_format, out);
+      mount_path, data_size);
 }
 
-void VerifyContents(int fd, const char* data, size_t data_size) {
+void VerifyContents(int fd, const uint8_t* data, size_t data_size) {
   ASSERT_EQ(0, lseek(fd, 0, SEEK_SET));
 
-  constexpr size_t kBuffersize = 8192;
+  // Cast |data_size| to ssize_t to match the return type of |read| and avoid narrowing conversion
+  // warnings from mixing size_t and ssize_t.
+  ZX_ASSERT(std::numeric_limits<ssize_t>::max() >= data_size);
+  ssize_t data_size_signed = static_cast<ssize_t>(data_size);
+
+  constexpr ssize_t kBuffersize = 8192;
   std::unique_ptr<char[]> buffer(new char[kBuffersize]);
 
-  for (size_t total_read = 0; total_read < data_size; total_read += kBuffersize) {
-    ssize_t read_size = std::min(kBuffersize, data_size - total_read);
+  for (ssize_t total_read = 0; total_read < data_size_signed; total_read += kBuffersize) {
+    ssize_t read_size = std::min(kBuffersize, data_size_signed - total_read);
     ASSERT_EQ(read_size, read(fd, buffer.get(), read_size));
     ASSERT_EQ(memcmp(&data[total_read], buffer.get(), read_size), 0);
   }
 }
 
-void MakeBlob(const BlobInfo* info, fbl::unique_fd* fd) {
-  fd->reset(open(info->path, O_CREAT | O_RDWR));
+void MakeBlob(const BlobInfo& info, fbl::unique_fd* fd) {
+  fd->reset(open(info.path, O_CREAT | O_RDWR));
   ASSERT_TRUE(*fd);
-  ASSERT_EQ(ftruncate(fd->get(), info->size_data), 0);
-  ASSERT_EQ(StreamAll(write, fd->get(), info->data.get(), info->size_data), 0);
-  VerifyContents(fd->get(), info->data.get(), info->size_data);
+  ASSERT_EQ(ftruncate(fd->get(), info.size_data), 0);
+  ASSERT_EQ(StreamAll(write, fd->get(), info.data.get(), info.size_data), 0);
+  VerifyContents(fd->get(), info.data.get(), info.size_data);
 }
 
 std::string GetBlobLayoutFormatNameForTests(BlobLayoutFormat format) {
@@ -156,29 +134,26 @@
   }
 }
 
-zx::status<MerkleTreeInfo> CreateMerkleTree(const uint8_t* data, int64_t data_size,
-                                            bool use_compact_format) {
-  MerkleTreeInfo merkle_tree_info;
+std::unique_ptr<MerkleTreeInfo> CreateMerkleTree(const uint8_t* data, uint64_t data_size,
+                                                 bool use_compact_format) {
+  auto merkle_tree_info = std::make_unique<MerkleTreeInfo>();
   MerkleTreeCreator mtc;
-  zx_status_t status;
   mtc.SetUseCompactFormat(use_compact_format);
-  if ((status = mtc.SetDataLength(data_size)) != ZX_OK) {
-    return zx::error(status);
-  }
-  merkle_tree_info.merkle_tree_size = mtc.GetTreeLength();
-  if (merkle_tree_info.merkle_tree_size > 0) {
-    merkle_tree_info.merkle_tree.reset(new uint8_t[merkle_tree_info.merkle_tree_size]);
+  zx_status_t status = mtc.SetDataLength(data_size);
+  ZX_ASSERT_MSG(status == ZX_OK, "Failed to set data length: %s", zx_status_get_string(status));
+  merkle_tree_info->merkle_tree_size = mtc.GetTreeLength();
+  if (merkle_tree_info->merkle_tree_size > 0) {
+    merkle_tree_info->merkle_tree.reset(new uint8_t[merkle_tree_info->merkle_tree_size]);
   }
   uint8_t merkle_tree_root[digest::kSha256Length];
-  if ((status = mtc.SetTree(merkle_tree_info.merkle_tree.get(), merkle_tree_info.merkle_tree_size,
-                            merkle_tree_root, digest::kSha256Length)) != ZX_OK) {
-    return zx::error(status);
-  }
-  if ((status = mtc.Append(data, data_size)) != ZX_OK) {
-    return zx::error(status);
-  }
-  merkle_tree_info.root = merkle_tree_root;
-  return zx::ok(std::move(merkle_tree_info));
+  status = mtc.SetTree(merkle_tree_info->merkle_tree.get(), merkle_tree_info->merkle_tree_size,
+                       merkle_tree_root, digest::kSha256Length);
+  ZX_ASSERT_MSG(status == ZX_OK, "Failed to set Merkle tree: %s", zx_status_get_string(status));
+  status = mtc.Append(data, data_size);
+  ZX_ASSERT_MSG(status == ZX_OK, "Failed to add data to Merkle tree: %s",
+                zx_status_get_string(status));
+  merkle_tree_info->root = merkle_tree_root;
+  return merkle_tree_info;
 }
 
 }  // namespace blobfs
diff --git a/src/storage/blobfs/test/blob_utils.h b/src/storage/blobfs/test/blob_utils.h
index f2c9017..2208388 100644
--- a/src/storage/blobfs/test/blob_utils.h
+++ b/src/storage/blobfs/test/blob_utils.h
@@ -8,6 +8,7 @@
 #include <lib/fdio/io.h>
 
 #include <cstring>
+#include <functional>
 #include <memory>
 #include <string>
 
@@ -18,14 +19,12 @@
 
 namespace blobfs {
 
-using BlobSrcFunction = std::function<void(char* data, size_t length)>;
+using BlobSrcFunction = std::function<void(uint8_t* data, size_t length)>;
 
 // An in-memory representation of a blob.
 struct BlobInfo {
   char path[PATH_MAX];
-  std::unique_ptr<char[]> merkle;
-  size_t size_merkle;
-  std::unique_ptr<char[]> data;
+  std::unique_ptr<uint8_t[]> data;
   size_t size_data;
 };
 
@@ -44,50 +43,34 @@
 
 // Fills the provided buffer with a single character |C|.
 template <char C>
-void CharFill(char* data, size_t length) {
+void CharFill(uint8_t* data, size_t length) {
   memset(data, C, length);
 }
 
 // Fills the provided buffer with random data. The contents of the buffer are
 // repeatable for any iteration of the test, provided that srand() is seeded with
 // the test framework's seed.
-void RandomFill(char* data, size_t length);
+void RandomFill(uint8_t* data, size_t length);
 
 // Generates a blob with data generated by a user-provided function.
-// TODO(fxbug.dev/63295): Return zx::status<std::unique_ptr<BlobInfo>> and remove the
-// |blob_layout_format| param.
-void GenerateBlob(BlobSrcFunction data_generator, const std::string& mount_path, size_t data_size,
-                  BlobLayoutFormat blob_layout_format, std::unique_ptr<BlobInfo>* out);
-
-// Generates a blob with data generated by a user-provided function.
-void GenerateBlob(BlobSrcFunction data_generator, const std::string& mount_path, size_t data_size,
-                  std::unique_ptr<BlobInfo>* out);
+std::unique_ptr<BlobInfo> GenerateBlob(const BlobSrcFunction& data_generator,
+                                       const std::string& mount_path, size_t data_size);
 
 // Generates a blob with random data.
-// TODO(fxbug.dev/63295): Return zx::status<std::unique_ptr<BlobInfo>> and remove the
-// |blob_layout_format| param.
-void GenerateRandomBlob(const std::string& mount_path, size_t data_size,
-                        BlobLayoutFormat blob_layout_format, std::unique_ptr<BlobInfo>* out);
-
-// Generates a blob with random data.
-void GenerateRandomBlob(const std::string& mount_path, size_t data_size,
-                        std::unique_ptr<BlobInfo>* out);
+std::unique_ptr<BlobInfo> GenerateRandomBlob(const std::string& mount_path, size_t data_size);
 
 // Generates a blob with realistic data (derived from, for example, an ELF binary).
 // This is suitable for test cases which aim to exercise compression, since the blob will compress
 // a realistic amount.
-// TODO(fxbug.dev/63295): Return zx::status<std::unique_ptr<BlobInfo>> and remove the
-// |blob_layout_format| param.
-void GenerateRealisticBlob(const std::string& mount_path, size_t data_size,
-                           BlobLayoutFormat blob_layout_format, std::unique_ptr<BlobInfo>* out);
+std::unique_ptr<BlobInfo> GenerateRealisticBlob(const std::string& mount_path, size_t data_size);
 
 // Verifies that a file contains the data in the provided buffer.
-void VerifyContents(int fd, const char* data, size_t data_size);
+void VerifyContents(int fd, const uint8_t* data, size_t data_size);
 
-// Creates an open blob with the provided Merkle tree + Data, and reads back to verify the data.
+// Creates an open blob with the provided Data, and reads back to verify the data.
 // Asserts (via ASSERT_* in gtest) that the write and read succeeded.
 // TODO(jfsulliv): Return a status, or change the name to indicate that this will assert on failure.
-void MakeBlob(const BlobInfo* info, fbl::unique_fd* fd);
+void MakeBlob(const BlobInfo& info, fbl::unique_fd* fd);
 
 // Returns the name of |format| for use in parameterized tests.
 std::string GetBlobLayoutFormatNameForTests(BlobLayoutFormat format);
@@ -100,8 +83,8 @@
 };
 
 // Constructs a Merkle tree for |data|.
-zx::status<MerkleTreeInfo> CreateMerkleTree(const uint8_t* data, int64_t data_size,
-                                            bool use_compact_format);
+std::unique_ptr<MerkleTreeInfo> CreateMerkleTree(const uint8_t* data, uint64_t data_size,
+                                                 bool use_compact_format);
 
 }  // namespace blobfs
 
diff --git a/src/storage/blobfs/test/integration/blobfs_integration_test.cc b/src/storage/blobfs/test/integration/blobfs_integration_test.cc
index f3b6b7a..d028619 100644
--- a/src/storage/blobfs/test/integration/blobfs_integration_test.cc
+++ b/src/storage/blobfs/test/integration/blobfs_integration_test.cc
@@ -28,6 +28,7 @@
 #include <array>
 #include <atomic>
 #include <memory>
+#include <string_view>
 #include <thread>
 #include <vector>
 
@@ -40,6 +41,7 @@
 #include <fs-management/mount.h>
 #include <gtest/gtest.h>
 
+#include "src/storage/blobfs/test/blob_utils.h"
 #include "src/storage/blobfs/test/integration/blobfs_fixtures.h"
 #include "src/storage/blobfs/test/integration/fdio_test.h"
 #include "src/storage/fvm/format.h"
@@ -50,8 +52,9 @@
 
 namespace fio = ::llcpp::fuchsia::io;
 using BlobfsIntegrationTest = ParameterizedBlobfsTest;
+using ::testing::UnitTest;
 
-void VerifyCorruptedBlob(int fd, const char* data, size_t size_data) {
+void VerifyCorruptedBlob(int fd, const uint8_t* data, size_t size_data) {
   // Verify the contents of the Blob
   fbl::Array<char> buf(new char[size_data], size_data);
 
@@ -111,11 +114,10 @@
 
 TEST_P(BlobfsIntegrationTest, Basics) {
   for (unsigned int i = 10; i < 16; i++) {
-    std::unique_ptr<BlobInfo> info;
-    ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), 1 << i, &info));
+    std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), 1 << i);
 
     fbl::unique_fd fd;
-    ASSERT_NO_FATAL_FAILURE(MakeBlob(info.get(), &fd));
+    ASSERT_NO_FATAL_FAILURE(MakeBlob(*info, &fd));
     ASSERT_EQ(close(fd.release()), 0);
 
     // We can re-open and verify the Blob as read-only
@@ -170,12 +172,11 @@
   ASSERT_EQ(status, ZX_OK);
 
   // Create a blob, corrupt it and then attempt to read it.
-  std::unique_ptr<BlobInfo> info;
-  GenerateRandomBlob(fs().mount_path(), 1 << 5, &info);
+  std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), 1 << 5);
   // Flip a random bit of the data
   size_t rand_index = rand() % info->size_data;
-  char old_val = info->data.get()[rand_index];
-  while ((info->data.get()[rand_index] = static_cast<char>(rand())) == old_val) {
+  uint8_t old_val = info->data.get()[rand_index];
+  while ((info->data.get()[rand_index] = static_cast<uint8_t>(rand())) == old_val) {
   }
 
   ASSERT_NO_FATAL_FAILURE(ReadBlobCorrupted(info.get()));
@@ -186,8 +187,7 @@
 }
 
 TEST_P(BlobfsIntegrationTest, UnallocatedBlob) {
-  std::unique_ptr<BlobInfo> info;
-  ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), 1 << 10, &info));
+  std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), 1 << 10);
 
   // We can create a blob with a name.
   ASSERT_TRUE(fbl::unique_fd(open(info->path, O_CREAT | O_EXCL | O_RDWR)));
@@ -203,8 +203,7 @@
 }
 
 TEST_P(BlobfsIntegrationTest, NullBlobCreateUnlink) {
-  std::unique_ptr<BlobInfo> info;
-  ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), 0, &info));
+  std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), 0);
 
   fbl::unique_fd fd(open(info->path, O_CREAT | O_EXCL | O_RDWR));
   ASSERT_TRUE(fd);
@@ -236,8 +235,7 @@
 }
 
 TEST_P(BlobfsIntegrationTest, NullBlobCreateRemount) {
-  std::unique_ptr<BlobInfo> info;
-  ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), 0, &info));
+  std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), 0);
 
   // Create the null blob.
   fbl::unique_fd fd(open(info->path, O_CREAT | O_EXCL | O_RDWR));
@@ -253,9 +251,7 @@
 }
 
 TEST_P(BlobfsIntegrationTest, ExclusiveCreate) {
-  std::unique_ptr<BlobInfo> info;
-
-  ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), 1 << 17, &info));
+  std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), 1 << 17);
   fbl::unique_fd fd(open(info->path, O_CREAT | O_EXCL | O_RDWR));
   ASSERT_TRUE(fd);
 
@@ -269,23 +265,21 @@
 
 TEST_P(BlobfsIntegrationTest, CompressibleBlob) {
   for (size_t i = 10; i < 22; i++) {
-    std::unique_ptr<BlobInfo> info;
-
     // Create blobs which are trivially compressible.
-    ASSERT_NO_FATAL_FAILURE(GenerateBlob(
-        [](char* data, size_t length) {
+    std::unique_ptr<BlobInfo> info = GenerateBlob(
+        [](uint8_t* data, size_t length) {
           size_t i = 0;
           while (i < length) {
             size_t j = (rand() % (length - i)) + 1;
-            memset(data, (char)j, j);
+            memset(data, static_cast<uint8_t>(j), j);
             data += j;
             i += j;
           }
         },
-        fs().mount_path(), 1 << i, &info));
+        fs().mount_path(), 1 << i);
 
     fbl::unique_fd fd;
-    ASSERT_NO_FATAL_FAILURE(MakeBlob(info.get(), &fd));
+    ASSERT_NO_FATAL_FAILURE(MakeBlob(*info, &fd));
 
     // We can re-open and verify the Blob as read-only.
     fd.reset(open(info->path, O_RDONLY));
@@ -305,11 +299,10 @@
 
 TEST_P(BlobfsIntegrationTest, Mmap) {
   for (size_t i = 10; i < 16; i++) {
-    std::unique_ptr<BlobInfo> info;
-    ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), 1 << i, &info));
+    std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), 1 << i);
 
     fbl::unique_fd fd;
-    ASSERT_NO_FATAL_FAILURE(MakeBlob(info.get(), &fd));
+    ASSERT_NO_FATAL_FAILURE(MakeBlob(*info, &fd));
     fd.reset(open(info->path, O_RDONLY));
     ASSERT_TRUE(fd) << "Failed to-reopen blob";
 
@@ -323,11 +316,10 @@
 
 TEST_P(BlobfsIntegrationTest, MmapUseAfterClose) {
   for (size_t i = 10; i < 16; i++) {
-    std::unique_ptr<BlobInfo> info;
-    ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), 1 << i, &info));
+    std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), 1 << i);
 
     fbl::unique_fd fd;
-    ASSERT_NO_FATAL_FAILURE(MakeBlob(info.get(), &fd));
+    ASSERT_NO_FATAL_FAILURE(MakeBlob(*info, &fd));
     fd.reset(open(info->path, O_RDONLY));
     ASSERT_TRUE(fd) << "Failed to-reopen blob";
 
@@ -371,9 +363,9 @@
 
   // Fill a directory with entries.
   for (size_t i = 0; i < kMaxEntries; i++) {
-    ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), kBlobSize, &info[i]));
+    info[i] = GenerateRandomBlob(fs().mount_path(), kBlobSize);
     fbl::unique_fd fd;
-    ASSERT_NO_FATAL_FAILURE(MakeBlob(info[i].get(), &fd));
+    ASSERT_NO_FATAL_FAILURE(MakeBlob(*info[i], &fd));
   }
 
   // Check that we see the expected number of entries
@@ -497,12 +489,13 @@
   size_t total_bytes = 0;
   ASSERT_NO_FATAL_FAILURE(QueryInfo(fs(), 0, 0));
   for (size_t i = 10; i < 16; i++) {
-    std::unique_ptr<BlobInfo> info;
-    ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), 1 << i, &info));
+    std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), 1 << i);
+    std::unique_ptr<MerkleTreeInfo> merkle_tree =
+        CreateMerkleTree(info->data.get(), info->size_data, /*use_compact_format=*/true);
 
     fbl::unique_fd fd;
-    ASSERT_NO_FATAL_FAILURE(MakeBlob(info.get(), &fd));
-    total_bytes += fbl::round_up(info->size_merkle + info->size_data, kBlobfsBlockSize);
+    ASSERT_NO_FATAL_FAILURE(MakeBlob(*info, &fd));
+    total_bytes += fbl::round_up(merkle_tree->merkle_tree_size + info->size_data, kBlobfsBlockSize);
   }
 
   ASSERT_NO_FATAL_FAILURE(QueryInfo(fs(), 6, total_bytes));
@@ -539,12 +532,13 @@
   }
 
   for (size_t i = 10; i < 16; i++) {
-    std::unique_ptr<BlobInfo> info;
-    ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), 1 << i, &info));
+    std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), 1 << i);
+    std::unique_ptr<MerkleTreeInfo> merkle_tree =
+        CreateMerkleTree(info->data.get(), info->size_data, /*use_compact_format=*/true);
 
     fbl::unique_fd fd;
-    ASSERT_NO_FATAL_FAILURE(MakeBlob(info.get(), &fd));
-    total_bytes += fbl::round_up(info->size_merkle + info->size_data, kBlobfsBlockSize);
+    ASSERT_NO_FATAL_FAILURE(MakeBlob(*info, &fd));
+    total_bytes += fbl::round_up(merkle_tree->merkle_tree_size + info->size_data, kBlobfsBlockSize);
   }
   ASSERT_NO_FATAL_FAILURE(GetAllocations(fs(), &vmo, &count));
 
@@ -558,11 +552,10 @@
 
 TEST_P(BlobfsIntegrationTest, UseAfterUnlink) {
   for (size_t i = 0; i < 16; i++) {
-    std::unique_ptr<BlobInfo> info;
-    ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), 1 << i, &info));
+    std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), 1 << i);
 
     fbl::unique_fd fd;
-    ASSERT_NO_FATAL_FAILURE(MakeBlob(info.get(), &fd));
+    ASSERT_NO_FATAL_FAILURE(MakeBlob(*info, &fd));
 
     // We should be able to unlink the blob.
     ASSERT_EQ(0, unlink(info->path));
@@ -579,11 +572,10 @@
 TEST_P(BlobfsIntegrationTest, WriteAfterRead) {
   // srand(zxtest::Runner::GetInstance()->random_seed());
   for (size_t i = 0; i < 16; i++) {
-    std::unique_ptr<BlobInfo> info;
-    ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), 1 << i, &info));
+    std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), 1 << i);
 
     fbl::unique_fd fd;
-    ASSERT_NO_FATAL_FAILURE(MakeBlob(info.get(), &fd));
+    ASSERT_NO_FATAL_FAILURE(MakeBlob(*info, &fd));
 
     // After blob generation, writes should be rejected.
     ASSERT_LT(write(fd.get(), info->data.get(), 1), 0)
@@ -601,9 +593,8 @@
 }
 
 TEST_P(BlobfsIntegrationTest, WriteAfterUnlink) {
-  std::unique_ptr<BlobInfo> info;
   size_t size = 1 << 20;
-  ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), size, &info));
+  std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), size);
 
   // Partially write out first blob.
   fbl::unique_fd fd(open(info->path, O_CREAT | O_RDWR));
@@ -619,11 +610,10 @@
 
 TEST_P(BlobfsIntegrationTest, ReadTooLarge) {
   for (size_t i = 0; i < 16; i++) {
-    std::unique_ptr<BlobInfo> info;
-    ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), 1 << i, &info));
+    std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), 1 << i);
 
     fbl::unique_fd fd;
-    ASSERT_NO_FATAL_FAILURE(MakeBlob(info.get(), &fd));
+    ASSERT_NO_FATAL_FAILURE(MakeBlob(*info, &fd));
 
     std::unique_ptr<char[]> buffer(new char[info->size_data]);
 
@@ -657,8 +647,7 @@
   fd.reset(open(name.c_str(), O_CREAT | O_RDWR));
   ASSERT_FALSE(fd) << "Only acceptable pathnames are 32 hex-encoded bytes";
 
-  std::unique_ptr<BlobInfo> info;
-  ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), 1 << 15, &info));
+  std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), 1 << 15);
 
   fd.reset(open(info->path, O_CREAT | O_RDWR));
   ASSERT_TRUE(fd) << "Failed to create blob";
@@ -690,7 +679,7 @@
 }
 
 // Attempts to read the contents of the Blob.
-void VerifyCompromised(int fd, const char* data, size_t size_data) {
+void VerifyCompromised(int fd, const uint8_t* data, size_t size_data) {
   std::unique_ptr<char[]> buf(new char[size_data]);
 
   ASSERT_EQ(0, lseek(fd, 0, SEEK_SET));
@@ -712,9 +701,8 @@
 
 TEST_P(BlobfsIntegrationTest, CorruptBlob) {
   // srand(zxtest::Runner::GetInstance()->random_seed());
-  std::unique_ptr<BlobInfo> info;
   for (size_t i = 1; i < 18; i++) {
-    ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), 1 << i, &info));
+    std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), 1 << i);
     info->size_data -= (rand() % info->size_data) + 1;
     if (info->size_data == 0) {
       info->size_data = 1;
@@ -723,11 +711,11 @@
   }
 
   for (size_t i = 0; i < 18; i++) {
-    ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), 1 << i, &info));
+    std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), 1 << i);
     // Flip a random bit of the data.
     size_t rand_index = rand() % info->size_data;
-    char old_val = info->data.get()[rand_index];
-    while ((info->data.get()[rand_index] = static_cast<char>(rand())) == old_val) {
+    uint8_t old_val = info->data.get()[rand_index];
+    while ((info->data.get()[rand_index] = static_cast<uint8_t>(rand())) == old_val) {
     }
     ASSERT_NO_FATAL_FAILURE(MakeBlobCompromised(info.get()));
   }
@@ -735,9 +723,8 @@
 
 TEST_P(BlobfsIntegrationTest, CorruptDigest) {
   // srand(zxtest::Runner::GetInstance()->random_seed());
-  std::unique_ptr<BlobInfo> info;
   for (size_t i = 1; i < 18; i++) {
-    ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), 1 << i, &info));
+    std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), 1 << i);
 
     char hexdigits[17] = "0123456789abcdef";
     size_t idx = strlen(info->path) - 1 - (rand() % digest::kSha256HexLength);
@@ -750,11 +737,11 @@
   }
 
   for (size_t i = 0; i < 18; i++) {
-    ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), 1 << i, &info));
+    std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), 1 << i);
     // Flip a random bit of the data.
     size_t rand_index = rand() % info->size_data;
-    char old_val = info->data.get()[rand_index];
-    while ((info->data.get()[rand_index] = static_cast<char>(rand())) == old_val) {
+    uint8_t old_val = info->data.get()[rand_index];
+    while ((info->data.get()[rand_index] = static_cast<uint8_t>(rand())) == old_val) {
     }
     ASSERT_NO_FATAL_FAILURE(MakeBlobCompromised(info.get()));
   }
@@ -765,20 +752,18 @@
   for (size_t i = 1; i < 16; i++) {
     // -1, 0, +1 offsets...
     for (size_t j = -1; j < 2; j++) {
-      std::unique_ptr<BlobInfo> info;
-      ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), (1 << i) + j, &info));
+      std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), (1 << i) + j);
       fbl::unique_fd fd;
-      ASSERT_NO_FATAL_FAILURE(MakeBlob(info.get(), &fd));
+      ASSERT_NO_FATAL_FAILURE(MakeBlob(*info, &fd));
       ASSERT_EQ(0, unlink(info->path));
     }
   }
 }
 
 TEST_P(BlobfsIntegrationTest, UmountWithOpenFile) {
-  std::unique_ptr<BlobInfo> info;
-  ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), 1 << 16, &info));
+  std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), 1 << 16);
   fbl::unique_fd fd;
-  ASSERT_NO_FATAL_FAILURE(MakeBlob(info.get(), &fd));
+  ASSERT_NO_FATAL_FAILURE(MakeBlob(*info, &fd));
 
   // Intentionally don't close the file descriptor: Unmount anyway.
   EXPECT_EQ(fs().Unmount().status_value(), ZX_OK);
@@ -798,10 +783,9 @@
 }
 
 TEST_P(BlobfsIntegrationTest, UmountWithMappedFile) {
-  std::unique_ptr<BlobInfo> info;
-  ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), 1 << 16, &info));
+  std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), 1 << 16);
   fbl::unique_fd fd;
-  ASSERT_NO_FATAL_FAILURE(MakeBlob(info.get(), &fd));
+  ASSERT_NO_FATAL_FAILURE(MakeBlob(*info, &fd));
 
   void* addr = mmap(nullptr, info->size_data, PROT_READ, MAP_SHARED, fd.get(), 0);
   ASSERT_NE(addr, nullptr);
@@ -819,10 +803,9 @@
 }
 
 TEST_P(BlobfsIntegrationTest, UmountWithOpenMappedFile) {
-  std::unique_ptr<BlobInfo> info;
-  ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), 1 << 16, &info));
+  std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), 1 << 16);
   fbl::unique_fd fd;
-  ASSERT_NO_FATAL_FAILURE(MakeBlob(info.get(), &fd));
+  ASSERT_NO_FATAL_FAILURE(MakeBlob(*info, &fd));
 
   void* addr = mmap(nullptr, info->size_data, PROT_READ, MAP_SHARED, fd.get(), 0);
   ASSERT_NE(addr, nullptr);
@@ -845,11 +828,10 @@
 
 TEST_P(BlobfsIntegrationTest, CreateUmountRemountSmall) {
   for (size_t i = 10; i < 16; i++) {
-    std::unique_ptr<BlobInfo> info;
-    ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), 1 << i, &info));
+    std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), 1 << i);
 
     fbl::unique_fd fd;
-    ASSERT_NO_FATAL_FAILURE(MakeBlob(info.get(), &fd));
+    ASSERT_NO_FATAL_FAILURE(MakeBlob(*info, &fd));
 
     fd.reset();
     EXPECT_EQ(fs().Unmount().status_value(), ZX_OK);
@@ -870,9 +852,7 @@
 
 // Tests that we cannot read from the Blob until it has been fully written.
 TEST_P(BlobfsIntegrationTest, EarlyRead) {
-  std::unique_ptr<BlobInfo> info;
-
-  ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), 1 << 17, &info));
+  std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), 1 << 17);
   fbl::unique_fd fd(open(info->path, O_CREAT | O_EXCL | O_RDWR));
   ASSERT_TRUE(fd);
 
@@ -924,9 +904,7 @@
 
 // Tests that poll() can tell, at some point, when it's ok to read.
 TEST_P(BlobfsIntegrationTest, WaitForRead) {
-  std::unique_ptr<BlobInfo> info;
-
-  ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), 1 << 17, &info));
+  std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), 1 << 17);
   fbl::unique_fd fd(open(info->path, O_CREAT | O_EXCL | O_RDWR));
   ASSERT_TRUE(fd);
 
@@ -935,7 +913,7 @@
     std::atomic<bool> result;
     std::thread waiter_thread(CheckReadable, std::move(fd), &result);
 
-    MakeBlob(info.get(), &fd);
+    MakeBlob(*info, &fd);
 
     waiter_thread.join();
     ASSERT_TRUE(result.load()) << "Background operation failed";
@@ -951,8 +929,7 @@
 // Tests that seeks during writing are ignored.
 TEST_P(BlobfsIntegrationTest, WriteSeekIgnored) {
   // srand(zxtest::Runner::GetInstance()->random_seed());
-  std::unique_ptr<BlobInfo> info;
-  ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), 1 << 17, &info));
+  std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), 1 << 17);
   fbl::unique_fd fd(open(info->path, O_CREAT | O_RDWR));
   ASSERT_TRUE(fd) << "Failed to create blob";
   ASSERT_EQ(0, ftruncate(fd.get(), info->size_data));
@@ -975,8 +952,7 @@
 
 // Try unlinking while creating a blob.
 TEST_P(BlobfsIntegrationTest, RestartCreation) {
-  std::unique_ptr<BlobInfo> info;
-  ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), 1 << 17, &info));
+  std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), 1 << 17);
 
   fbl::unique_fd fd(open(info->path, O_CREAT | O_RDWR));
   ASSERT_TRUE(fd) << "Failed to create blob";
@@ -998,10 +974,9 @@
 // Attempt using invalid operations.
 TEST_P(BlobfsIntegrationTest, InvalidOperations) {
   // First off, make a valid blob.
-  std::unique_ptr<BlobInfo> info;
-  ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), 1 << 12, &info));
+  std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), 1 << 12);
   fbl::unique_fd fd;
-  ASSERT_NO_FATAL_FAILURE(MakeBlob(info.get(), &fd));
+  ASSERT_NO_FATAL_FAILURE(MakeBlob(*info, &fd));
   ASSERT_NO_FATAL_FAILURE(VerifyContents(fd.get(), info->data.get(), info->size_data));
 
   // Try some unsupported operations.
@@ -1031,8 +1006,7 @@
   fbl::unique_fd dirfd(open(name.c_str(), O_RDONLY));
   ASSERT_TRUE(dirfd) << "Cannot open root directory";
 
-  std::unique_ptr<BlobInfo> info;
-  ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), 1 << 12, &info));
+  std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), 1 << 12);
 
   // Test operations which should ONLY operate on Blobs.
   ASSERT_LT(ftruncate(dirfd.get(), info->size_data), 0);
@@ -1046,11 +1020,9 @@
 }
 
 TEST_P(BlobfsIntegrationTest, PartialWrite) {
-  std::unique_ptr<BlobInfo> info_complete;
-  std::unique_ptr<BlobInfo> info_partial;
   size_t size = 1 << 20;
-  ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), size, &info_complete));
-  ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), size, &info_partial));
+  std::unique_ptr<BlobInfo> info_complete = GenerateRandomBlob(fs().mount_path(), size);
+  std::unique_ptr<BlobInfo> info_partial = GenerateRandomBlob(fs().mount_path(), size);
 
   // Partially write out first blob.
   fbl::unique_fd fd_partial(open(info_partial->path, O_CREAT | O_RDWR));
@@ -1061,15 +1033,13 @@
 
   // Completely write out second blob.
   fbl::unique_fd fd_complete;
-  ASSERT_NO_FATAL_FAILURE(MakeBlob(info_complete.get(), &fd_complete));
+  ASSERT_NO_FATAL_FAILURE(MakeBlob(*info_complete, &fd_complete));
 }
 
 TEST_P(BlobfsIntegrationTest, PartialWriteSleepyDisk) {
-  std::unique_ptr<BlobInfo> info_complete;
-  std::unique_ptr<BlobInfo> info_partial;
   size_t size = 1 << 20;
-  ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), size, &info_complete));
-  ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), size, &info_partial));
+  std::unique_ptr<BlobInfo> info_complete = GenerateRandomBlob(fs().mount_path(), size);
+  std::unique_ptr<BlobInfo> info_partial = GenerateRandomBlob(fs().mount_path(), size);
 
   // Partially write out first blob.
   fbl::unique_fd fd_partial(open(info_partial->path, O_CREAT | O_RDWR));
@@ -1080,7 +1050,7 @@
 
   // Completely write out second blob.
   fbl::unique_fd fd_complete;
-  ASSERT_NO_FATAL_FAILURE(MakeBlob(info_complete.get(), &fd_complete));
+  ASSERT_NO_FATAL_FAILURE(MakeBlob(*info_complete, &fd_complete));
 
   ASSERT_EQ(0, syncfs(fd_complete.get()));
   ASSERT_EQ(fs().GetRamDisk()->SleepAfter(0).status_value(), ZX_OK);
@@ -1099,8 +1069,7 @@
 }
 
 TEST_P(BlobfsIntegrationTest, MultipleWrites) {
-  std::unique_ptr<BlobInfo> info;
-  ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), 1 << 16, &info));
+  std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), 1 << 16);
 
   fbl::unique_fd fd(open(info->path, O_CREAT | O_RDWR));
   ASSERT_TRUE(fd);
@@ -1155,10 +1124,9 @@
 
 TEST_P(BlobfsIntegrationTest, ReadOnly) {
   // Mount the filesystem as read-write. We can create new blobs.
-  std::unique_ptr<BlobInfo> info;
-  ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), 1 << 10, &info));
+  std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), 1 << 10);
   fbl::unique_fd blob_fd;
-  ASSERT_NO_FATAL_FAILURE(MakeBlob(info.get(), &blob_fd));
+  ASSERT_NO_FATAL_FAILURE(MakeBlob(*info, &blob_fd));
   ASSERT_NO_FATAL_FAILURE(VerifyContents(blob_fd.get(), info->data.get(), info->size_data));
   blob_fd.reset();
 
@@ -1173,7 +1141,7 @@
   ASSERT_NO_FATAL_FAILURE(VerifyContents(blob_fd.get(), info->data.get(), info->size_data));
 
   // We cannot create new blobs
-  ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), 1 << 10, &info));
+  info = GenerateRandomBlob(fs().mount_path(), 1 << 10);
   blob_fd.reset(open(info->path, O_CREAT | O_RDWR));
   ASSERT_FALSE(blob_fd);
 }
@@ -1205,13 +1173,12 @@
 // a blob whose uncompressed data is larger than available free space.
 // The test is expected to fail when compression is turned off.
 TEST_P(BlobfsIntegrationTest, BlobLargerThanAvailableSpaceTest) {
-  std::unique_ptr<BlobInfo> info;
-  ASSERT_NO_FATAL_FAILURE(GenerateBlob(
-      [](char* data, size_t length) { memset(data, '\0', length); }, fs().mount_path(),
-      fs().options().device_block_count * fs().options().device_block_size + 1, &info));
+  std::unique_ptr<BlobInfo> info = GenerateBlob(
+      [](uint8_t* data, size_t length) { memset(data, '\0', length); }, fs().mount_path(),
+      fs().options().device_block_count * fs().options().device_block_size + 1);
 
   fbl::unique_fd fd;
-  ASSERT_NO_FATAL_FAILURE(MakeBlob(info.get(), &fd))
+  ASSERT_NO_FATAL_FAILURE(MakeBlob(*info, &fd))
       << "Test is expected to fail when compression is turned off";
 
   // We can re-open and verify the Blob as read-only.
@@ -1261,11 +1228,10 @@
 
   size_t required = fs().options().fvm_slice_size / kBlobfsInodeSize + 2;
   for (size_t i = 0; i < required; i++) {
-    std::unique_ptr<BlobInfo> info;
-    ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), kBlobfsInodeSize, &info));
+    std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), kBlobfsInodeSize);
 
     fbl::unique_fd fd;
-    ASSERT_NO_FATAL_FAILURE(MakeBlob(info.get(), &fd));
+    ASSERT_NO_FATAL_FAILURE(MakeBlob(*info, &fd));
   }
 
   // Remount partition.
@@ -1326,8 +1292,7 @@
 TEST_P(BlobfsIntegrationTest, FailedWrite) {
   const uint32_t pages_per_block = kBlobfsBlockSize / fs().options().device_block_size;
 
-  std::unique_ptr<BlobInfo> info;
-  ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), kBlobfsBlockSize, &info));
+  std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), kBlobfsBlockSize);
 
   fbl::unique_fd fd(open(info->path, O_CREAT | O_RDWR));
   ASSERT_TRUE(fd) << "Failed to create blob";
@@ -1361,7 +1326,7 @@
   // syncfs will return a failed response.
   ASSERT_LT(syncfs(fd.get()), 0);
 
-  ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), kBlobfsBlockSize, &info));
+  info = GenerateRandomBlob(fs().mount_path(), kBlobfsBlockSize);
   fd.reset(open(info->path, O_CREAT | O_RDWR));
   ASSERT_TRUE(fd) << "Failed to create blob";
 
@@ -1384,7 +1349,7 @@
   while (!args->done) {
     fbl::unique_fd fd(open(args->info->path, O_RDONLY));
     ASSERT_TRUE(fd);
-    void* addr = mmap(NULL, args->info->size_data, PROT_READ, MAP_PRIVATE, fd.get(), 0);
+    void* addr = mmap(nullptr, args->info->size_data, PROT_READ, MAP_PRIVATE, fd.get(), 0);
     ASSERT_NE(addr, MAP_FAILED) << "Could not mmap blob";
     // Explicitly close |fd| before unmapping.
     fd.reset();
@@ -1401,12 +1366,11 @@
 // blob from being discarded while there are active clones).
 // See fxbug.dev/53882 for background on this test case.
 TEST_P(BlobfsIntegrationTest, VmoCloneWatchingTest) {
-  std::unique_ptr<BlobInfo> info;
-  ASSERT_NO_FATAL_FAILURE(GenerateBlob(CharFill<'A'>, fs().mount_path(), 4096, &info));
+  std::unique_ptr<BlobInfo> info = GenerateBlob(CharFill<'A'>, fs().mount_path(), 4096);
 
   {
     fbl::unique_fd fd;
-    ASSERT_NO_FATAL_FAILURE(MakeBlob(info.get(), &fd));
+    ASSERT_NO_FATAL_FAILURE(MakeBlob(*info, &fd));
   }
 
   struct CloneThreadArgs thread_args {
@@ -1418,7 +1382,7 @@
   for (int i = 0; i < kIterations; ++i) {
     fbl::unique_fd fd(open(info->path, O_RDONLY));
     ASSERT_TRUE(fd);
-    void* addr = mmap(NULL, info->size_data, PROT_READ, MAP_PRIVATE, fd.get(), 0);
+    void* addr = mmap(nullptr, info->size_data, PROT_READ, MAP_PRIVATE, fd.get(), 0);
     ASSERT_NE(addr, MAP_FAILED) << "Could not mmap blob";
     fd.reset();
 
@@ -1448,11 +1412,11 @@
     ASSERT_TRUE(hierarchy_or_error.is_ok());
     inspect::Hierarchy hierarchy = std::move(hierarchy_or_error.value());
     *total_read_bytes = 0;
-    for (const std::string algorithm : algorithms) {
-      for (const std::string stat : read_methods) {
+    for (const std::string& algorithm : algorithms) {
+      for (const std::string& stat : read_methods) {
         uint64_t read_bytes;
         ASSERT_NO_FATAL_FAILURE(
-            GetUintMetricFromHierarchy(&hierarchy, {stat, algorithm}, "read_bytes", &read_bytes));
+            GetUintMetricFromHierarchy(hierarchy, {stat, algorithm}, "read_bytes", &read_bytes));
         *total_read_bytes += read_bytes;
       }
     }
@@ -1466,8 +1430,7 @@
 
   // Create a new blob with random contents on the mounted filesystem. This is
   // both random and small enough that it should not get compressed.
-  std::unique_ptr<BlobInfo> info;
-  ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(".", 1 << 10, &info));
+  std::unique_ptr<BlobInfo> info = GenerateRandomBlob(".", 1 << 10);
   {
     fbl::unique_fd fd(openat(root_fd(), info->path, O_CREAT | O_RDWR));
     ASSERT_TRUE(fd.is_valid());
diff --git a/src/storage/blobfs/test/integration/executable_mount_test.cc b/src/storage/blobfs/test/integration/executable_mount_test.cc
index 40e99396..0f7d2ef 100644
--- a/src/storage/blobfs/test/integration/executable_mount_test.cc
+++ b/src/storage/blobfs/test/integration/executable_mount_test.cc
@@ -56,8 +56,7 @@
 // fdio_get_vmo_exec exercises.
 TEST_F(ExecutableMountTest, CanLoadBlobsExecutable) {
   // Create a new blob with random contents on the mounted filesystem.
-  std::unique_ptr<BlobInfo> info;
-  ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(".", 1 << 16, &info));
+  std::unique_ptr<BlobInfo> info = GenerateRandomBlob(".", 1 << 16);
 
   fbl::unique_fd fd(openat(root_fd(), info->path, O_CREAT | O_RDWR));
   ASSERT_TRUE(fd.is_valid());
diff --git a/src/storage/blobfs/test/integration/external-decompressor_test.cc b/src/storage/blobfs/test/integration/external-decompressor_test.cc
index 64e3f17..5c4769d 100644
--- a/src/storage/blobfs/test/integration/external-decompressor_test.cc
+++ b/src/storage/blobfs/test/integration/external-decompressor_test.cc
@@ -64,7 +64,7 @@
             compressed_vmo.duplicate(ZX_DEFAULT_VMO_RIGHTS & (~ZX_RIGHT_WRITE), &decompressed_vmo));
 
   zx::status<std::unique_ptr<ExternalDecompressorClient>> client_or =
-      ExternalDecompressorClient::Create(std::move(decompressed_vmo), std::move(compressed_vmo));
+      ExternalDecompressorClient::Create(decompressed_vmo, compressed_vmo);
   ASSERT_EQ(ZX_ERR_INVALID_ARGS, client_or.status_value());
 }
 
@@ -76,7 +76,7 @@
                                               &compressed_vmo));
 
   zx::status<std::unique_ptr<ExternalDecompressorClient>> client_or =
-      ExternalDecompressorClient::Create(std::move(decompressed_vmo), std::move(compressed_vmo));
+      ExternalDecompressorClient::Create(decompressed_vmo, compressed_vmo);
   ASSERT_EQ(ZX_ERR_ACCESS_DENIED, client_or.status_value());
 }
 
@@ -99,8 +99,7 @@
     ASSERT_EQ(ZX_OK, decompressed_mapper_.Map(std::move(decompressed_vmo), kMapSize));
 
     zx::status<std::unique_ptr<ExternalDecompressorClient>> client_or =
-        ExternalDecompressorClient::Create(std::move(remote_decompressed_vmo),
-                                           std::move(remote_compressed_vmo));
+        ExternalDecompressorClient::Create(remote_decompressed_vmo, remote_compressed_vmo);
     ASSERT_EQ(ZX_OK, client_or.status_value());
     client_ = std::move(client_or.value());
   }
@@ -188,9 +187,7 @@
 
 TEST_F(ExternalDecompressorE2ePagedTest, VerifyRemoteDecompression) {
   // Create a new blob on the mounted filesystem.
-  std::unique_ptr<BlobInfo> info;
-  ASSERT_NO_FATAL_FAILURE(
-      GenerateRealisticBlob(".", kDataSize, BlobLayoutFormat::kPaddedMerkleTreeAtStart, &info));
+  std::unique_ptr<BlobInfo> info = GenerateRealisticBlob(".", kDataSize);
   {
     fbl::unique_fd fd(openat(root_fd(), info->path, O_CREAT | O_RDWR));
     ASSERT_TRUE(fd.is_valid());
@@ -216,9 +213,7 @@
 }
 
 TEST_F(ExternalDecompressorE2ePagedTest, MultiframeDecompression) {
-  std::unique_ptr<BlobInfo> info;
-  ASSERT_NO_FATAL_FAILURE(
-      GenerateRealisticBlob(".", kDataSize, BlobLayoutFormat::kPaddedMerkleTreeAtStart, &info));
+  std::unique_ptr<BlobInfo> info = GenerateRealisticBlob(".", kDataSize);
   {
     fbl::unique_fd fd(openat(root_fd(), info->path, O_CREAT | O_RDWR));
     ASSERT_TRUE(fd.is_valid());
@@ -274,9 +269,7 @@
 
 TEST_F(ExternalDecompressorE2eUnpagedTest, VerifyRemoteDecompression) {
   // Create a new blob on the mounted filesystem.
-  std::unique_ptr<BlobInfo> info;
-  ASSERT_NO_FATAL_FAILURE(
-      GenerateRealisticBlob(".", kDataSize, BlobLayoutFormat::kPaddedMerkleTreeAtStart, &info));
+  std::unique_ptr<BlobInfo> info = GenerateRealisticBlob(".", kDataSize);
   {
     fbl::unique_fd fd(openat(root_fd(), info->path, O_CREAT | O_RDWR));
     ASSERT_TRUE(fd.is_valid());
diff --git a/src/storage/blobfs/test/integration/fdio_test.cc b/src/storage/blobfs/test/integration/fdio_test.cc
index d73f5a8..ef107f8 100644
--- a/src/storage/blobfs/test/integration/fdio_test.cc
+++ b/src/storage/blobfs/test/integration/fdio_test.cc
@@ -95,12 +95,11 @@
   return hierarchy_or_error;
 }
 
-void FdioTest::GetUintMetricFromHierarchy(const inspect::Hierarchy* hierarchy,
-                                          std::vector<std::string> path, std::string property,
-                                          uint64_t* value) {
+void FdioTest::GetUintMetricFromHierarchy(const inspect::Hierarchy& hierarchy,
+                                          const std::vector<std::string>& path,
+                                          const std::string& property, uint64_t* value) {
   ASSERT_NE(value, nullptr);
-  ASSERT_NE(hierarchy, nullptr);
-  const inspect::Hierarchy* direct_parent = hierarchy->GetByPath(path);
+  const inspect::Hierarchy* direct_parent = hierarchy.GetByPath(path);
   ASSERT_NE(direct_parent, nullptr);
 
   const inspect::UintPropertyValue* property_node =
@@ -110,10 +109,11 @@
   *value = property_node->value();
 }
 
-void FdioTest::GetUintMetric(std::vector<std::string> path, std::string property, uint64_t* value) {
+void FdioTest::GetUintMetric(const std::vector<std::string>& path, const std::string& property,
+                             uint64_t* value) {
   fit::result<inspect::Hierarchy> hierarchy_or_error = TakeSnapshot();
   ASSERT_TRUE(hierarchy_or_error.is_ok());
-  GetUintMetricFromHierarchy(&(hierarchy_or_error.value()), path, property, value);
+  GetUintMetricFromHierarchy(hierarchy_or_error.value(), path, property, value);
 }
 
 }  // namespace blobfs
diff --git a/src/storage/blobfs/test/integration/fdio_test.h b/src/storage/blobfs/test/integration/fdio_test.h
index 5507ce9..fb44e86 100644
--- a/src/storage/blobfs/test/integration/fdio_test.h
+++ b/src/storage/blobfs/test/integration/fdio_test.h
@@ -50,13 +50,14 @@
 
   // Takes an inspect snapshot `hierarchy` and navigates through the nodes using
   // the `path` given and fetches the `property` there to be stored in `value`.
-  static void GetUintMetricFromHierarchy(const inspect::Hierarchy* hierarchy,
-                                         std::vector<std::string> path, std::string property,
-                                         uint64_t* value);
+  static void GetUintMetricFromHierarchy(const inspect::Hierarchy& hierarchy,
+                                         const std::vector<std::string>& path,
+                                         const std::string& property, uint64_t* value);
 
   // Strings together `TakeSnapshot` and `GetUintMetricFromHierarchy` to fetch a
   // single value from a fresh snapshot.
-  void GetUintMetric(std::vector<std::string> path, std::string property, uint64_t* value);
+  void GetUintMetric(const std::vector<std::string>& path, const std::string& property,
+                     uint64_t* value);
 
   virtual uint64_t GetOldestRevision() const { return kBlobfsCurrentRevision; }
 
diff --git a/src/storage/blobfs/test/integration/large/fragmentation.cc b/src/storage/blobfs/test/integration/large/fragmentation.cc
index f524cc5..613e3f4 100644
--- a/src/storage/blobfs/test/integration/large/fragmentation.cc
+++ b/src/storage/blobfs/test/integration/large/fragmentation.cc
@@ -44,9 +44,8 @@
   size_t large_blob_storage_space_usage = 0;
   size_t count = 0;
   while (true) {
-    std::unique_ptr<BlobInfo> info;
-    ASSERT_NO_FATAL_FAILURE(
-        GenerateRandomBlob(fs().mount_path(), do_small_blob ? kSmallSize : kLargeSize, &info));
+    std::unique_ptr<BlobInfo> info =
+        GenerateRandomBlob(fs().mount_path(), do_small_blob ? kSmallSize : kLargeSize);
     fbl::unique_fd fd(open(info->path, O_CREAT | O_RDWR));
     ASSERT_TRUE(fd) << "Failed to create blob";
     if (capture_large_blob_storage_space_usage && !do_small_blob) {
@@ -81,8 +80,7 @@
 
   // We have filled up the disk with both small and large blobs.
   // Observe that we cannot add another large blob.
-  std::unique_ptr<BlobInfo> info;
-  ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), kLargeSize, &info));
+  std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), kLargeSize);
 
   // ... and we don't have space (as we try allocating).
   fbl::unique_fd fd(open(info->path, O_CREAT | O_RDWR));
diff --git a/src/storage/blobfs/test/integration/large/max-reservation.cc b/src/storage/blobfs/test/integration/large/max-reservation.cc
index 0676c8e..7885fc7 100644
--- a/src/storage/blobfs/test/integration/large/max-reservation.cc
+++ b/src/storage/blobfs/test/integration/large/max-reservation.cc
@@ -22,8 +22,7 @@
   // Refer to fxbug.dev/54001 for the bug that lead to this test.
   size_t count = 0;
   for (uint64_t i = 0; i < kBlobfsDefaultInodeCount; i++) {
-    std::unique_ptr<BlobInfo> info;
-    ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), 64, &info));
+    std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), 64);
 
     // Write the blob
     {
diff --git a/src/storage/blobfs/test/integration/large/no-space.cc b/src/storage/blobfs/test/integration/large/no-space.cc
index 43dbc98..3c6dd46 100644
--- a/src/storage/blobfs/test/integration/large/no-space.cc
+++ b/src/storage/blobfs/test/integration/large/no-space.cc
@@ -31,8 +31,7 @@
   // Keep generating blobs until we run out of space.
   size_t count = 0;
   while (true) {
-    std::unique_ptr<BlobInfo> info;
-    ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), 1 << 17, &info));
+    std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), 1 << 17);
 
     fbl::unique_fd fd(open(info->path, O_CREAT | O_RDWR));
     ASSERT_TRUE(fd) << "Failed to create blob";
diff --git a/src/storage/blobfs/test/integration/large/use-second-bitmap.cc b/src/storage/blobfs/test/integration/large/use-second-bitmap.cc
index 1ca39bf..c390799 100644
--- a/src/storage/blobfs/test/integration/large/use-second-bitmap.cc
+++ b/src/storage/blobfs/test/integration/large/use-second-bitmap.cc
@@ -23,8 +23,6 @@
 namespace blobfs {
 namespace {
 
-namespace fio = ::llcpp::fuchsia::io;
-
 class LargeBlobTest : public BlobfsFixedDiskSizeTest {
  public:
   LargeBlobTest() : BlobfsFixedDiskSizeTest(GetDiskSize()) {}
@@ -47,12 +45,11 @@
 
 TEST_F(LargeBlobTest, UseSecondBitmap) {
   // Create (and delete) a blob large enough to overflow into the second bitmap block.
-  std::unique_ptr<BlobInfo> info;
   size_t blob_size = ((GetDataBlockCount() / 2) + 1) * kBlobfsBlockSize;
-  ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), blob_size, &info));
+  std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), blob_size);
 
   fbl::unique_fd fd;
-  ASSERT_NO_FATAL_FAILURE(MakeBlob(info.get(), &fd));
+  ASSERT_NO_FATAL_FAILURE(MakeBlob(*info, &fd));
   ASSERT_EQ(syncfs(fd.get()), 0);
   ASSERT_EQ(close(fd.release()), 0);
   ASSERT_EQ(unlink(info->path), 0);
diff --git a/src/storage/blobfs/test/integration/load_generator.cc b/src/storage/blobfs/test/integration/load_generator.cc
index f608880..8d1fec1 100644
--- a/src/storage/blobfs/test/integration/load_generator.cc
+++ b/src/storage/blobfs/test/integration/load_generator.cc
@@ -13,7 +13,7 @@
 namespace blobfs {
 
 // Make sure we do not exceed maximum fd count.
-static_assert(FDIO_MAX_FD >= 256, "");
+static_assert(FDIO_MAX_FD >= 256);
 constexpr uint32_t kMaxBlobs = FDIO_MAX_FD - 32;
 
 enum class BlobList::QueueId : uint32_t { kCreated = 0, kTruncated, kWritten };
@@ -93,9 +93,8 @@
 }
 
 void BlobList::CreateBlob(unsigned int* rand_state, size_t num_writes) {
-  std::unique_ptr<BlobInfo> info;
-  ASSERT_NO_FATAL_FAILURE(
-      GenerateRandomBlob(mount_path_, 1 + (rand_r(rand_state) % (1 << 16)), &info));
+  std::unique_ptr<BlobInfo> info =
+      GenerateRandomBlob(mount_path_, 1 + (rand_r(rand_state) % (1 << 16)));
 
   BlobFile file(std::move(info), num_writes);
 
diff --git a/src/storage/blobfs/test/integration/load_generator.h b/src/storage/blobfs/test/integration/load_generator.h
index 9cef517..34ae5d8 100644
--- a/src/storage/blobfs/test/integration/load_generator.h
+++ b/src/storage/blobfs/test/integration/load_generator.h
@@ -23,7 +23,7 @@
       : info(std::move(i)), writes_remaining(writes_remaining) {
     bytes_remaining = info->size_data;
   }
-  BlobFile() {}
+  BlobFile() = default;
 
   std::unique_ptr<BlobInfo> info;
   fbl::unique_fd fd;
@@ -39,7 +39,7 @@
  public:
   explicit BlobList(const char* mount_path) : mount_path_(mount_path) {}
 
-  // Cycles through |num_opertaions| filesystem operations. |rand_state| should
+  // Cycles through |num_operations| filesystem operations. |rand_state| should
   // be initialized to the desired seed for the random operations (and data).
   void GenerateLoad(uint32_t num_operations, unsigned int* rand_state);
 
diff --git a/src/storage/blobfs/test/integration/mount_test.cc b/src/storage/blobfs/test/integration/mount_test.cc
index ae373f1..e8a0e6d 100644
--- a/src/storage/blobfs/test/integration/mount_test.cc
+++ b/src/storage/blobfs/test/integration/mount_test.cc
@@ -84,8 +84,7 @@
 // creation then it does not support VMO_FLAG_EXEC to obtain executable VMOs from blobs.
 TEST_F(DataMountTest, CannotLoadBlobsExecutable) {
   // Create a new blob with random contents on the mounted filesystem.
-  std::unique_ptr<BlobInfo> info;
-  ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(".", 1 << 16, &info));
+  std::unique_ptr<BlobInfo> info = GenerateRandomBlob(".", 1 << 16);
 
   fbl::unique_fd fd(openat(root_fd(), info->path, O_CREAT | O_RDWR));
   ASSERT_TRUE(fd.is_valid());
diff --git a/src/storage/blobfs/test/integration/query_service_test.cc b/src/storage/blobfs/test/integration/query_service_test.cc
index 8c43b10..02d26d4 100644
--- a/src/storage/blobfs/test/integration/query_service_test.cc
+++ b/src/storage/blobfs/test/integration/query_service_test.cc
@@ -90,12 +90,13 @@
   size_t total_bytes = 0;
   ASSERT_NO_FATAL_FAILURE(QueryInfo(0, 0));
   for (size_t i = 10; i < 16; i++) {
-    std::unique_ptr<BlobInfo> info;
-    ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob(fs().mount_path(), 1 << i, &info));
+    std::unique_ptr<BlobInfo> info = GenerateRandomBlob(fs().mount_path(), 1 << i);
+    std::unique_ptr<MerkleTreeInfo> merkle_tree =
+        CreateMerkleTree(info->data.get(), info->size_data, /*use_compact_format=*/true);
 
     fbl::unique_fd fd;
-    ASSERT_NO_FATAL_FAILURE(MakeBlob(info.get(), &fd));
-    total_bytes += fbl::round_up(info->size_merkle + info->size_data, kBlobfsBlockSize);
+    ASSERT_NO_FATAL_FAILURE(MakeBlob(*info, &fd));
+    total_bytes += fbl::round_up(merkle_tree->merkle_tree_size + info->size_data, kBlobfsBlockSize);
   }
 
   ASSERT_NO_FATAL_FAILURE(QueryInfo(6, total_bytes));
diff --git a/src/storage/blobfs/test/integration/sync_test.cc b/src/storage/blobfs/test/integration/sync_test.cc
index 8090243..7bdde1b 100644
--- a/src/storage/blobfs/test/integration/sync_test.cc
+++ b/src/storage/blobfs/test/integration/sync_test.cc
@@ -34,8 +34,7 @@
 // Verifies that fdio "fsync" calls actually sync blobfs files to the block device and verifies
 // behavior for different lifecycles of creating a file.
 TEST_F(SyncFdioTest, Sync) {
-  std::unique_ptr<BlobInfo> info;
-  ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob("", 64, &info));
+  std::unique_ptr<BlobInfo> info = GenerateRandomBlob("", 64);
 
   memmove(info->path, info->path + 1, strlen(info->path));  // Remove leading slash.
   int file = openat(root_fd(), info->path, O_RDWR | O_CREAT);
@@ -83,8 +82,7 @@
   options.device_block_count = 0;  // Uses VMO size.
   options.device_block_size = 8192;
 
-  std::unique_ptr<BlobInfo> info;
-  ASSERT_NO_FATAL_FAILURE(GenerateRandomBlob("", 64, &info));
+  std::unique_ptr<BlobInfo> info = GenerateRandomBlob("", 64);
 
   memmove(info->path, info->path + 1, strlen(info->path));  // Remove leading slash.
   auto snapshot = std::make_unique<uint8_t[]>(kVmoSize);
diff --git a/src/storage/blobfs/test/unit/allocator-test.cc b/src/storage/blobfs/test/unit/allocator-test.cc
index 70cf6cc..1191d2a 100644
--- a/src/storage/blobfs/test/unit/allocator-test.cc
+++ b/src/storage/blobfs/test/unit/allocator-test.cc
@@ -574,8 +574,7 @@
   // Create a blob that takes up more than half of the volume.
   fbl::RefPtr<fs::Vnode> root;
   ASSERT_EQ(fs->OpenRootNode(&root), ZX_OK);
-  std::unique_ptr<BlobInfo> info;
-  GenerateRandomBlob("", kBlobSize, GetBlobLayoutFormat(fs->Info()), &info);
+  std::unique_ptr<BlobInfo> info = GenerateRandomBlob(/*mount_path=*/"", kBlobSize);
   fbl::RefPtr<fs::Vnode> file;
   ASSERT_EQ(root->Create(info->path + 1, 0, &file), ZX_OK);
   size_t actual;
@@ -584,8 +583,7 @@
   EXPECT_EQ(file->Close(), ZX_OK);
 
   // Attempting to create another blob should result in a no-space condition.
-  std::unique_ptr<BlobInfo> info2;
-  GenerateRandomBlob("", kBlobSize, GetBlobLayoutFormat(fs->Info()), &info2);
+  std::unique_ptr<BlobInfo> info2 = GenerateRandomBlob("", kBlobSize);
   ASSERT_EQ(root->Create(info2->path + 1, 0, &file), ZX_OK);
   EXPECT_EQ(file->Truncate(info2->size_data), ZX_OK);
   EXPECT_EQ(file->Write(info2->data.get(), info2->size_data, 0, &actual), ZX_ERR_NO_SPACE);
diff --git a/src/storage/blobfs/test/unit/blob-loader-test.cc b/src/storage/blobfs/test/unit/blob-loader-test.cc
index 3afca67..49979f7 100644
--- a/src/storage/blobfs/test/unit/blob-loader-test.cc
+++ b/src/storage/blobfs/test/unit/blob-loader-test.cc
@@ -99,8 +99,7 @@
     EXPECT_EQ(fs_->OpenRootNode(&root), ZX_OK);
     fs::Vnode* root_node = root.get();
 
-    std::unique_ptr<BlobInfo> info;
-    GenerateRealisticBlob("", sz, blob_layout_format_, &info);
+    std::unique_ptr<BlobInfo> info = GenerateRealisticBlob("", sz);
     memmove(info->path, info->path + 1, strlen(info->path));  // Remove leading slash.
 
     fbl::RefPtr<fs::Vnode> file;
@@ -144,19 +143,23 @@
   }
 
   void CheckMerkleTreeContents(const fzl::OwnedVmoMapper& merkle, const BlobInfo& info) {
+    std::unique_ptr<MerkleTreeInfo> merkle_tree = CreateMerkleTree(
+        info.data.get(), info.size_data, ShouldUseCompactMerkleTreeFormat(blob_layout_format_));
     ASSERT_TRUE(merkle.vmo().is_valid());
-    ASSERT_GE(merkle.size(), info.size_merkle);
+    ASSERT_GE(merkle.size(), merkle_tree->merkle_tree_size);
     switch (blob_layout_format_) {
       case BlobLayoutFormat::kPaddedMerkleTreeAtStart:
         // In the padded layout the Merkle starts at the start of the vmo.
-        EXPECT_EQ(memcmp(merkle.start(), info.merkle.get(), info.size_merkle), 0);
+        EXPECT_EQ(
+            memcmp(merkle.start(), merkle_tree->merkle_tree.get(), merkle_tree->merkle_tree_size),
+            0);
         break;
       case BlobLayoutFormat::kCompactMerkleTreeAtEnd:
         // In the compact layout the Merkle tree is aligned to end at the end of the vmo.
-        EXPECT_EQ(
-            memcmp(static_cast<const uint8_t*>(merkle.start()) + (merkle.size() - info.size_merkle),
-                   info.merkle.get(), info.size_merkle),
-            0);
+        EXPECT_EQ(memcmp(static_cast<const uint8_t*>(merkle.start()) +
+                             (merkle.size() - merkle_tree->merkle_tree_size),
+                         merkle_tree->merkle_tree.get(), merkle_tree->merkle_tree_size),
+                  0);
         break;
     }
   }
@@ -168,7 +171,7 @@
   BlobLayoutFormat blob_layout_format_;
 };
 
-// A seperate parameterized test fixture that will only be run with compression algorithms that
+// A separate parameterized test fixture that will only be run with compression algorithms that
 // support paging.
 using BlobLoaderPagedTest = BlobLoaderTest;
 
@@ -184,7 +187,7 @@
   EXPECT_EQ(data.size(), 0ul);
 
   EXPECT_FALSE(merkle.vmo().is_valid());
-  EXPECT_EQ(info->size_merkle, 0ul);
+  EXPECT_EQ(merkle.size(), 0ul);
 }
 
 TEST_P(BlobLoaderTest, SmallBlob) {
@@ -202,7 +205,7 @@
   EXPECT_EQ(memcmp(data.start(), info->data.get(), info->size_data), 0);
 
   EXPECT_FALSE(merkle.vmo().is_valid());
-  EXPECT_EQ(info->size_merkle, 0ul);
+  EXPECT_EQ(merkle.size(), 0ul);
 }
 
 TEST_P(BlobLoaderPagedTest, SmallBlob) {
@@ -225,7 +228,7 @@
   EXPECT_EQ(memcmp(buf.get(), info->data.get(), info->size_data), 0);
 
   EXPECT_FALSE(merkle.vmo().is_valid());
-  EXPECT_EQ(info->size_merkle, 0ul);
+  EXPECT_EQ(merkle.size(), 0ul);
 }
 
 TEST_P(BlobLoaderTest, LargeBlob) {
diff --git a/src/storage/blobfs/test/unit/blob-test.cc b/src/storage/blobfs/test/unit/blob-test.cc
index 191d608..ba678a9 100644
--- a/src/storage/blobfs/test/unit/blob-test.cc
+++ b/src/storage/blobfs/test/unit/blob-test.cc
@@ -7,6 +7,7 @@
 #include <zircon/assert.h>
 
 #include <condition_variable>
+#include <memory>
 
 #include <block-client/cpp/fake-device.h>
 #include <digest/digest.h>
@@ -91,8 +92,7 @@
 TEST_P(BlobTest, SyncBehavior) {
   auto root = OpenRoot();
 
-  std::unique_ptr<BlobInfo> info;
-  GenerateRandomBlob("", 64, GetBlobLayoutFormat(fs_->Info()), &info);
+  std::unique_ptr<BlobInfo> info = GenerateRandomBlob("", 64);
   memmove(info->path, info->path + 1, strlen(info->path));  // Remove leading slash.
 
   fbl::RefPtr<fs::Vnode> file;
@@ -135,11 +135,10 @@
                            zx::resource(), &fs_),
             ZX_OK);
 
-  std::unique_ptr<BlobInfo> info;
+  std::unique_ptr<BlobInfo> info = GenerateRandomBlob("", 64);
   uint64_t block;
   {
     auto root = OpenRoot();
-    GenerateRandomBlob("", 64, GetBlobLayoutFormat(fs_->Info()), &info);
     fbl::RefPtr<fs::Vnode> file;
     ASSERT_EQ(root->Create(info->path + 1, 0, &file), ZX_OK);
     size_t out_actual = 0;
@@ -205,11 +204,10 @@
 
 TEST_P(BlobTestWithOldRevision, ReadWriteAllCompressionFormats) {
   auto root = OpenRoot();
-  std::unique_ptr<BlobInfo> info;
+  std::unique_ptr<BlobInfo> info = GenerateRealisticBlob("", 1 << 16);
 
   // Write the blob
   {
-    GenerateRealisticBlob("", 1 << 16, GetBlobLayoutFormat(fs_->Info()), &info);
     fbl::RefPtr<fs::Vnode> file;
     ASSERT_EQ(root->Create(info->path + 1, 0, &file), ZX_OK);
     size_t out_actual = 0;
@@ -254,15 +252,16 @@
                            zx::resource(), &fs_),
             ZX_OK);
 
-  std::unique_ptr<BlobInfo> info;
+  // Create a blob where the Merkle tree in the compact layout fits perfectly into the space
+  // remaining at the end of the blob.
+  ASSERT_EQ(fs_->Info().block_size, digest::kDefaultNodeSize);
+  std::unique_ptr<BlobInfo> info =
+      GenerateRealisticBlob("", (digest::kDefaultNodeSize - digest::kSha256Length) * 3);
   {
-    // Create a blob where the Merkle tree in the compact layout fits perfectly into the space
-    // remaining at the end of the blob.
-    ASSERT_EQ(fs_->Info().block_size, digest::kDefaultNodeSize);
-    GenerateRealisticBlob("", (digest::kDefaultNodeSize - digest::kSha256Length) * 3,
-                          GetBlobLayoutFormat(fs_->Info()), &info);
     if (GetBlobLayoutFormat(fs_->Info()) == BlobLayoutFormat::kCompactMerkleTreeAtEnd) {
-      EXPECT_EQ(info->size_data + info->size_merkle, digest::kDefaultNodeSize * 3);
+      std::unique_ptr<MerkleTreeInfo> merkle_tree =
+          CreateMerkleTree(info->data.get(), info->size_data, /*use_compact_format=*/true);
+      EXPECT_EQ(info->size_data + merkle_tree->merkle_tree_size, digest::kDefaultNodeSize * 3);
     }
     fbl::RefPtr<fs::Vnode> file;
     auto root = OpenRoot();
@@ -292,8 +291,7 @@
 }
 
 TEST_P(BlobTest, WriteErrorsAreFused) {
-  std::unique_ptr<BlobInfo> info;
-  GenerateRandomBlob("", kBlockSize * kNumBlocks, GetBlobLayoutFormat(fs_->Info()), &info);
+  std::unique_ptr<BlobInfo> info = GenerateRandomBlob("", kBlockSize * kNumBlocks);
   auto root = OpenRoot();
   fbl::RefPtr<fs::Vnode> file;
   ASSERT_EQ(root->Create(info->path + 1, 0, &file), ZX_OK);
@@ -308,11 +306,10 @@
 
 TEST_P(BlobMigrationTest, MigrateLargeBlobSucceeds) {
   auto root = OpenRoot();
-  std::unique_ptr<BlobInfo> info;
+  std::unique_ptr<BlobInfo> info = GenerateRandomBlob("", 300 * 1024);
 
   // Write the blob
   {
-    GenerateRandomBlob("", 300 * 1024, GetBlobLayoutFormat(fs_->Info()), &info);
     fbl::RefPtr<fs::Vnode> file;
     ASSERT_EQ(root->Create(info->path + 1, 0, &file), ZX_OK);
     auto blob = fbl::RefPtr<Blob>::Downcast(file);
@@ -346,11 +343,10 @@
 
 TEST_P(BlobMigrationTest, MigrateWhenNoSpaceSkipped) {
   auto root = OpenRoot();
-  std::unique_ptr<BlobInfo> info;
+  // Create a blob that takes up half the disk.
+  std::unique_ptr<BlobInfo> info = GenerateRandomBlob("", kNumBlocks * kBlockSize / 2);
 
-  // Write a blob that takes up half the disk.
   {
-    GenerateRandomBlob("", kNumBlocks * kBlockSize / 2, GetBlobLayoutFormat(fs_->Info()), &info);
     fbl::RefPtr<fs::Vnode> file;
     ASSERT_EQ(root->Create(info->path + 1, 0, &file), ZX_OK);
     auto blob = fbl::RefPtr<Blob>::Downcast(file);
diff --git a/src/storage/blobfs/test/unit/blob-verifier-test.cc b/src/storage/blobfs/test/unit/blob-verifier-test.cc
index cce79b0..27ab2cf 100644
--- a/src/storage/blobfs/test/unit/blob-verifier-test.cc
+++ b/src/storage/blobfs/test/unit/blob-verifier-test.cc
@@ -4,6 +4,7 @@
 
 #include "src/storage/blobfs/blob-verifier.h"
 
+#include <memory>
 #include <random>
 
 #include <digest/merkle-tree.h>
@@ -22,7 +23,7 @@
 
   void SetUp() override { srand(testing::UnitTest::GetInstance()->random_seed()); }
 
-  static zx::status<MerkleTreeInfo> GenerateTree(const uint8_t* data, size_t len) {
+  static std::unique_ptr<MerkleTreeInfo> GenerateTree(const uint8_t* data, size_t len) {
     return CreateMerkleTree(data, len, ShouldUseCompactMerkleTreeFormat(GetParam()));
   }
 
@@ -32,13 +33,12 @@
 
 void FillWithRandom(uint8_t* buf, size_t len) {
   for (unsigned i = 0; i < len; ++i) {
-    buf[i] = (uint8_t)rand();
+    buf[i] = static_cast<uint8_t>(rand());
   }
 }
 
 TEST_P(BlobVerifierTest, CreateAndVerify_NullBlob) {
   auto merkle_tree = GenerateTree(nullptr, 0);
-  ASSERT_EQ(merkle_tree.status_value(), ZX_OK);
 
   std::unique_ptr<BlobVerifier> verifier;
   ASSERT_EQ(BlobVerifier::CreateWithoutTree(std::move(merkle_tree->root), Metrics(), 0ul, nullptr,
@@ -53,7 +53,6 @@
   FillWithRandom(buf, sizeof(buf));
 
   auto merkle_tree = GenerateTree(buf, sizeof(buf));
-  ASSERT_EQ(merkle_tree.status_value(), ZX_OK);
 
   std::unique_ptr<BlobVerifier> verifier;
   ASSERT_EQ(BlobVerifier::CreateWithoutTree(std::move(merkle_tree->root), Metrics(), sizeof(buf),
@@ -76,7 +75,6 @@
   FillWithRandom(buf, sizeof(buf));
 
   auto merkle_tree = GenerateTree(buf, sizeof(buf));
-  ASSERT_EQ(merkle_tree.status_value(), ZX_OK);
 
   // Invert one character
   buf[42] = ~(buf[42]);
@@ -96,7 +94,6 @@
   FillWithRandom(buf.get(), sz);
 
   auto merkle_tree = GenerateTree(buf.get(), sz);
-  ASSERT_EQ(merkle_tree.status_value(), ZX_OK);
 
   std::unique_ptr<BlobVerifier> verifier;
   ASSERT_EQ(
@@ -127,7 +124,6 @@
   FillWithRandom(buf.get(), sz);
 
   auto merkle_tree = GenerateTree(buf.get(), sz);
-  ASSERT_EQ(merkle_tree.status_value(), ZX_OK);
 
   // Invert a char in the first block. All other blocks are still valid.
   buf.get()[42] = ~(buf.get()[42]);
@@ -155,7 +151,6 @@
   FillWithRandom(buf.get(), sz);
 
   auto merkle_tree = GenerateTree(buf.get(), sz);
-  ASSERT_EQ(merkle_tree.status_value(), ZX_OK);
 
   // Invert a char in the tree.
   merkle_tree->merkle_tree.get()[0] = ~(merkle_tree->merkle_tree.get()[0]);
@@ -184,7 +179,6 @@
   memset(&buf[kBlobSize], 0, kBlobfsBlockSize - kBlobSize);
 
   auto merkle_tree = GenerateTree(buf, kBlobSize);
-  ASSERT_EQ(merkle_tree.status_value(), ZX_OK);
 
   std::unique_ptr<BlobVerifier> verifier;
   EXPECT_EQ(BlobVerifier::CreateWithoutTree(std::move(merkle_tree->root), Metrics(), kBlobSize,
@@ -203,7 +197,6 @@
   FillWithRandom(buf.data(), kBlobSize);
 
   auto merkle_tree = GenerateTree(buf.data(), kBlobSize);
-  ASSERT_EQ(merkle_tree.status_value(), ZX_OK);
 
   std::unique_ptr<BlobVerifier> verifier;
   ASSERT_EQ(BlobVerifier::Create(std::move(merkle_tree->root), Metrics(),
diff --git a/src/storage/blobfs/test/unit/blobfs-checker-test.cc b/src/storage/blobfs/test/unit/blobfs-checker-test.cc
index 4b30645..6f7d46e 100644
--- a/src/storage/blobfs/test/unit/blobfs-checker-test.cc
+++ b/src/storage/blobfs/test/unit/blobfs-checker-test.cc
@@ -65,8 +65,7 @@
   // of the provided Vnode. Optionally returns the block the blob starts at if block_out is
   // provided, and the size of the blob if size_out is provided.
   void AddRandomBlob(fs::Vnode* node, uint64_t* block_out = nullptr, uint64_t* size_out = nullptr) {
-    std::unique_ptr<BlobInfo> info;
-    GenerateRandomBlob("", 1024, GetBlobLayoutFormat(fs_->Info()), &info);
+    std::unique_ptr<BlobInfo> info = GenerateRandomBlob("", 1024);
     memmove(info->path, info->path + 1, strlen(info->path));  // Remove leading slash.
 
     fbl::RefPtr<fs::Vnode> file;
@@ -138,7 +137,7 @@
 
 class BlobfsCheckerPagedTest : public BlobfsCheckerTest {
  public:
-  void SetUp() {
+  void SetUp() override {
     enable_paging = true;
     BlobfsCheckerTest::SetUp();
   }
diff --git a/src/storage/blobfs/test/unit/blobfs-test.cc b/src/storage/blobfs/test/unit/blobfs-test.cc
index e6829d9..f895b17 100644
--- a/src/storage/blobfs/test/unit/blobfs-test.cc
+++ b/src/storage/blobfs/test/unit/blobfs-test.cc
@@ -214,8 +214,7 @@
   ASSERT_EQ(fs_->OpenRootNode(&root), ZX_OK);
   fs::Vnode* root_node = root.get();
 
-  std::unique_ptr<BlobInfo> info;
-  GenerateRandomBlob("", 1024, GetBlobLayoutFormat(fs_->Info()), &info);
+  std::unique_ptr<BlobInfo> info = GenerateRandomBlob("", 1024);
   memmove(info->path, info->path + 1, strlen(info->path));  // Remove leading slash.
 
   fbl::RefPtr<fs::Vnode> file;
@@ -295,9 +294,8 @@
   ASSERT_EQ(fs_->OpenRootNode(&root), ZX_OK);
   fs::Vnode* root_node = root.get();
 
-  std::unique_ptr<BlobInfo> info;
-  GenerateRealisticBlob("", (fs_->WriteBufferBlockCount() + 1) * kBlobfsBlockSize,
-                        GetBlobLayoutFormat(fs_->Info()), &info);
+  std::unique_ptr<BlobInfo> info =
+      GenerateRealisticBlob("", (fs_->WriteBufferBlockCount() + 1) * kBlobfsBlockSize);
   fbl::RefPtr<fs::Vnode> file;
   ASSERT_EQ(root_node->Create(info->path + 1, 0, &file), ZX_OK);
   auto blob = fbl::RefPtr<Blob>::Downcast(std::move(file));
@@ -339,8 +337,7 @@
   ASSERT_EQ(fs_->OpenRootNode(&root), ZX_OK);
   fs::Vnode* root_node = root.get();
 
-  std::unique_ptr<BlobInfo> info;
-  GenerateRealisticBlob("", 500123, GetBlobLayoutFormat(fs_->Info()), &info);
+  std::unique_ptr<BlobInfo> info = GenerateRealisticBlob("", 500123);
   {
     fbl::RefPtr<fs::Vnode> file;
     ASSERT_EQ(root_node->Create(info->path + 1, 0, &file), ZX_OK);
@@ -369,11 +366,9 @@
   }
 }
 
-std::unique_ptr<BlobInfo> CreateBlob(const fbl::RefPtr<fs::Vnode>& root, size_t size,
-                                     BlobLayoutFormat layout) {
+std::unique_ptr<BlobInfo> CreateBlob(const fbl::RefPtr<fs::Vnode>& root, size_t size) {
   // Create fragmentation by creating blobs and deleting a few.
-  std::unique_ptr<BlobInfo> info;
-  GenerateRandomBlob("", size, layout, &info);
+  std::unique_ptr<BlobInfo> info = GenerateRandomBlob("", size);
   memmove(info->path, info->path + 1, strlen(info->path));  // Remove leading slash.
 
   fbl::RefPtr<fs::Vnode> file;
@@ -576,7 +571,7 @@
   // look like (first 10 bits set and all other bits unset.)
   // 111111111100000000....
   for (int i = 0; i < kSmallBlobCount; i++) {
-    infos.push_back(CreateBlob(root, 8192, GetBlobLayoutFormat(fs->Info())));
+    infos.push_back(CreateBlob(root, 8192));
   }
 
   expected.blobs_in_use = kSmallBlobCount;
@@ -617,7 +612,7 @@
 
   // Create a huge(10 blocks) blob that potentially fills atleast three free fragments that we
   // created above.
-  auto info = CreateBlob(root, 20 * 8192, GetBlobLayoutFormat(fs->Info()));
+  auto info = CreateBlob(root, 20 * 8192);
   fbl::RefPtr<fs::Vnode> file;
   ASSERT_EQ(root->Lookup(info->path, &file), ZX_OK);
   fs::VnodeAttributes attributes;
diff --git a/src/storage/blobfs/test/unit/blobfs_inspector_test.cc b/src/storage/blobfs/test/unit/blobfs_inspector_test.cc
index 9ba0c09..d9c731d 100644
--- a/src/storage/blobfs/test/unit/blobfs_inspector_test.cc
+++ b/src/storage/blobfs/test/unit/blobfs_inspector_test.cc
@@ -97,7 +97,7 @@
   uint32_t block_size_;
 };
 
-// Initialize a FakeTrnasactionHandler backed by a buffer representing a
+// Initialize a FakeTransactionHandler backed by a buffer representing a
 // a fresh Blobfs partition and journal entries.
 void CreateFakeBlobfsHandler(std::unique_ptr<FakeTransactionHandler>* handler) {
   auto device = std::make_unique<storage::ArrayBuffer>(kBlockCount, kBlobfsBlockSize);
@@ -246,7 +246,7 @@
 
   auto result = inspector->InspectJournalSuperblock();
   ASSERT_TRUE(result.is_ok());
-  fs::JournalInfo journal_info = std::move(result.value());
+  fs::JournalInfo journal_info = result.value();
 
   EXPECT_EQ(fs::kJournalMagic, journal_info.magic);
   EXPECT_EQ(0u, journal_info.start_block);
@@ -466,7 +466,7 @@
   // Make sure superblock original values are correct.
   auto inspect_result = inspector->InspectJournalSuperblock();
   ASSERT_TRUE(inspect_result.is_ok());
-  fs::JournalInfo journal_info = std::move(inspect_result.value());
+  fs::JournalInfo journal_info = inspect_result.value();
   ASSERT_EQ(fs::kJournalMagic, journal_info.magic);
   ASSERT_EQ(0ul, journal_info.start_block);
 
@@ -479,7 +479,7 @@
   // Re-inspect that values have changed.
   inspect_result = inspector->InspectJournalSuperblock();
   ASSERT_TRUE(inspect_result.is_ok());
-  journal_info = std::move(inspect_result.value());
+  journal_info = inspect_result.value();
   ASSERT_EQ(magic, journal_info.magic);
   ASSERT_EQ(start_block, journal_info.start_block);
 }
diff --git a/src/storage/blobfs/test/unit/compressor-test.cc b/src/storage/blobfs/test/unit/compressor-test.cc
index 414da7e..eabca58 100644
--- a/src/storage/blobfs/test/unit/compressor-test.cc
+++ b/src/storage/blobfs/test/unit/compressor-test.cc
@@ -420,13 +420,12 @@
   }
 
   fbl::RefPtr<fs::Vnode> AddBlobToBlobfs(size_t data_size, DataType type) {
-    std::unique_ptr<BlobInfo> blob_info;
-    GenerateBlob(
-        [type](char* data, size_t length) {
+    std::unique_ptr<BlobInfo> blob_info = GenerateBlob(
+        [type](uint8_t* data, size_t length) {
           auto generated_data = GenerateInput(type, 0, length);
           memcpy(data, generated_data.get(), length);
         },
-        "", data_size, GetBlobLayoutFormat(blobfs_->Info()), &blob_info);
+        "", data_size);
 
     fbl::RefPtr<fs::Vnode> file;
     zx_status_t status = root_->Create(blob_info->path + 1, 0, &file);