pw_allocator: Refactor test support and example allocator

This CL does the following:
 * It move the SimpleAllocator previously described in Block's
   class-level comments to an actual source file, and adds a test for
   it. The class is now included directly into the module docs.
 * It refactors the FakeAllocator to use SimpleAllocator and renames it
   to AllocatorForTest. It also moves this from pw_allocator_private/ to
   public/pw_allocator/, as these utilities may be useful for testing
   custom allocators provided by downstream projects.
 * It updates the callers of FakeAllocator to use AllocatorForTest.

Bug: b/306686936

Change-Id: I41953e3b64fa831bd23418496ffa683917944b3f
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/177653
Commit-Queue: Aaron Green <aarongreen@google.com>
Reviewed-by: Taylor Cramer <cramertj@google.com>
Reviewed-by: Keir Mierle <keir@google.com>
diff --git a/pw_allocator/BUILD.bazel b/pw_allocator/BUILD.bazel
index 13c82db..c7c7d9a 100644
--- a/pw_allocator/BUILD.bazel
+++ b/pw_allocator/BUILD.bazel
@@ -121,25 +121,6 @@
 )
 
 pw_cc_library(
-    name = "split_free_list_allocator",
-    srcs = [
-        "split_free_list_allocator.cc",
-    ],
-    hdrs = [
-        "public/pw_allocator/split_free_list_allocator.h",
-    ],
-    includes = ["public"],
-    deps = [
-        ":allocator",
-        ":block",
-        "//pw_assert",
-        "//pw_bytes",
-        "//pw_result",
-        "//pw_status",
-    ],
-)
-
-pw_cc_library(
     name = "libc_allocator",
     srcs = [
         "libc_allocator.cc",
@@ -168,18 +149,53 @@
 )
 
 pw_cc_library(
-    name = "allocator_testing",
+    name = "simple_allocator",
+    hdrs = [
+        "public/pw_allocator/simple_allocator.h",
+    ],
+    includes = ["public"],
+    deps = [
+        ":allocator",
+        ":block",
+        "//pw_bytes",
+    ],
+)
+
+pw_cc_library(
+    name = "split_free_list_allocator",
     srcs = [
-        "allocator_testing.cc",
+        "split_free_list_allocator.cc",
     ],
     hdrs = [
-        "pw_allocator_private/allocator_testing.h",
+        "public/pw_allocator/split_free_list_allocator.h",
     ],
+    includes = ["public"],
     deps = [
         ":allocator",
         ":block",
         "//pw_assert",
         "//pw_bytes",
+        "//pw_result",
+        "//pw_status",
+    ],
+)
+
+pw_cc_library(
+    name = "allocator_testing",
+    srcs = [
+        "allocator_testing.cc",
+    ],
+    hdrs = [
+        "public/pw_allocator/allocator_testing.h",
+    ],
+    includes = ["public"],
+    deps = [
+        ":allocator",
+        ":block",
+        ":simple_allocator",
+        "//pw_assert",
+        "//pw_bytes",
+        "//pw_unit_test",
     ],
 )
 
@@ -279,11 +295,23 @@
 )
 
 pw_cc_test(
+    name = "simple_allocator_test",
+    srcs = [
+        "simple_allocator_test.cc",
+    ],
+    deps = [
+        ":allocator_testing",
+        ":simple_allocator",
+    ],
+)
+
+pw_cc_test(
     name = "split_free_list_allocator_test",
     srcs = [
         "split_free_list_allocator_test.cc",
     ],
     deps = [
+        ":allocator_testing",
         ":split_free_list_allocator",
         "//pw_bytes",
         "//pw_containers:vector",
diff --git a/pw_allocator/BUILD.gn b/pw_allocator/BUILD.gn
index f1f2ac7..e7039f0 100644
--- a/pw_allocator/BUILD.gn
+++ b/pw_allocator/BUILD.gn
@@ -136,6 +136,16 @@
   public_deps = [ ":allocator" ]
 }
 
+pw_source_set("simple_allocator") {
+  public_configs = [ ":default_config" ]
+  public = [ "public/pw_allocator/simple_allocator.h" ]
+  public_deps = [
+    ":allocator",
+    ":block",
+    dir_pw_bytes,
+  ]
+}
+
 pw_source_set("split_free_list_allocator") {
   public_configs = [ ":default_config" ]
   public = [ "public/pw_allocator/split_free_list_allocator.h" ]
@@ -181,19 +191,22 @@
     ":freelist_heap_test",
     ":libc_allocator_test",
     ":null_allocator_test",
+    ":simple_allocator_test",
     ":split_free_list_allocator_test",
     ":unique_ptr_test",
   ]
 }
 
 pw_source_set("allocator_testing") {
-  public = [ "pw_allocator_private/allocator_testing.h" ]
+  public = [ "public/pw_allocator/allocator_testing.h" ]
   public_deps = [
     ":allocator",
     ":block",
-    dir_pw_assert,
+    ":simple_allocator",
     dir_pw_bytes,
+    dir_pw_unit_test,
   ]
+  deps = [ dir_pw_assert ]
   sources = [ "allocator_testing.cc" ]
 }
 
@@ -258,8 +271,19 @@
   sources = [ "null_allocator_test.cc" ]
 }
 
-pw_test("split_free_list_allocator_test") {
+pw_test("simple_allocator_test") {
+  configs = [ ":enable_heap_poison" ]
   deps = [
+    ":allocator_testing",
+    ":simple_allocator",
+  ]
+  sources = [ "simple_allocator_test.cc" ]
+}
+
+pw_test("split_free_list_allocator_test") {
+  configs = [ ":enable_heap_poison" ]
+  deps = [
+    ":allocator_testing",
     ":split_free_list_allocator",
     "$dir_pw_containers:vector",
     dir_pw_bytes,
@@ -273,7 +297,10 @@
 }
 
 pw_doc_group("docs") {
-  inputs = [ "doc_resources/pw_allocator_heap_visualizer_demo.png" ]
+  inputs = [
+    "doc_resources/pw_allocator_heap_visualizer_demo.png",
+    "public/pw_allocator/simple_allocator.h",
+  ]
   sources = [ "docs.rst" ]
   report_deps = [ ":allocator_size_report" ]
 }
diff --git a/pw_allocator/CMakeLists.txt b/pw_allocator/CMakeLists.txt
index 82ed745..6f923ed 100644
--- a/pw_allocator/CMakeLists.txt
+++ b/pw_allocator/CMakeLists.txt
@@ -102,23 +102,6 @@
     freelist_heap.cc
 )
 
-pw_add_library(pw_allocator.split_free_list_allocator STATIC
-  HEADERS
-    public/pw_allocator/split_free_list_allocator.h
-  PUBLIC_INCLUDES
-    public
-  PUBLIC_DEPS
-    pw_allocator.allocator
-    pw_allocator.block
-    pw_bytes
-    pw_result
-    pw_status
-  PRIVATE_DEPS
-    pw_assert
-  SOURCES
-    split_free_list_allocator.cc
-)
-
 pw_add_library(pw_allocator.libc_allocator STATIC
   SOURCES
     libc_allocator.cc
@@ -143,14 +126,45 @@
     pw_allocator.allocator
 )
 
-pw_add_library(pw_allocator.allocator_testing STATIC
+pw_add_library(pw_allocator.simple_allocator INTERFACE
   HEADERS
-    pw_allocator_private/allocator_testing.h
+    public/pw_allocator/simple_allocator.h
   PUBLIC_DEPS
     pw_allocator.allocator
     pw_allocator.block
-    pw_assert
     pw_bytes
+)
+
+pw_add_library(pw_allocator.split_free_list_allocator STATIC
+  HEADERS
+    public/pw_allocator/split_free_list_allocator.h
+  PUBLIC_INCLUDES
+    public
+  PUBLIC_DEPS
+    pw_allocator.allocator
+    pw_allocator.block
+    pw_bytes
+    pw_result
+    pw_status
+  PRIVATE_DEPS
+    pw_assert
+  SOURCES
+    split_free_list_allocator.cc
+)
+
+pw_add_library(pw_allocator.allocator_testing STATIC
+  HEADERS
+    public/pw_allocator/allocator_testing.h
+  PUBLIC_INCLUDES
+    public
+  PUBLIC_DEPS
+    pw_allocator.allocator
+    pw_allocator.block
+    pw_allocator.simple_allocator
+    pw_bytes
+  PRIVATE_DEPS
+    pw_assert
+    pw_unit_test
   SOURCES
     allocator_testing.cc
 )
@@ -245,10 +259,19 @@
     pw_allocator
 )
 
+pw_add_test(pw_allocator.simple_allocator_test
+  SOURCES
+    simple_allocator_test.cc
+  PRIVATE_DEPS
+    pw_allocator.allocator_testing
+    pw_allocator.simple_allocator
+)
+
 pw_add_test(pw_allocator.split_free_list_allocator_test
   SOURCES
     split_free_list_allocator_test.cc
   PRIVATE_DEPS
+    pw_allocator.allocator_testing
     pw_allocator.split_free_list_allocator
     pw_containers.vector
     pw_bytes
diff --git a/pw_allocator/allocator_metric_proxy_test.cc b/pw_allocator/allocator_metric_proxy_test.cc
index e426349..cfc94b2 100644
--- a/pw_allocator/allocator_metric_proxy_test.cc
+++ b/pw_allocator/allocator_metric_proxy_test.cc
@@ -15,27 +15,23 @@
 #include "pw_allocator/allocator_metric_proxy.h"
 
 #include "gtest/gtest.h"
-#include "pw_allocator_private/allocator_testing.h"
+#include "pw_allocator/allocator_testing.h"
 
 namespace pw::allocator {
 namespace {
 
 // Test fixtures.
 
-struct AllocatorMetricProxyTest : ::testing::Test {
- private:
-  std::array<std::byte, 256> buffer = {};
-  test::FakeAllocator wrapped;
-
- public:
-  AllocatorMetricProxy allocator;
-
+class AllocatorMetricProxyTest : public ::testing::Test {
+ protected:
   AllocatorMetricProxyTest() : allocator(0) {}
 
-  void SetUp() override {
-    EXPECT_TRUE(wrapped.Initialize(buffer).ok());
-    allocator.Initialize(wrapped);
-  }
+  void SetUp() override { allocator.Initialize(*wrapped); }
+
+  AllocatorMetricProxy allocator;
+
+ private:
+  test::AllocatorForTestWithBuffer<256> wrapped;
 };
 
 // Unit tests.
diff --git a/pw_allocator/allocator_test.cc b/pw_allocator/allocator_test.cc
index bbd3f6c..f0bd198 100644
--- a/pw_allocator/allocator_test.cc
+++ b/pw_allocator/allocator_test.cc
@@ -17,7 +17,7 @@
 #include <cstddef>
 
 #include "gtest/gtest.h"
-#include "pw_allocator_private/allocator_testing.h"
+#include "pw_allocator/allocator_testing.h"
 #include "pw_bytes/alignment.h"
 
 namespace pw::allocator {
@@ -25,14 +25,15 @@
 
 // Test fixtures.
 
-struct AllocatorTest : ::testing::Test {
+class AllocatorTest : public ::testing::Test {
+ protected:
+  void SetUp() override { EXPECT_EQ(allocator.Init(buffer), OkStatus()); }
+  void TearDown() override { allocator.DeallocateAll(); }
+
+  test::AllocatorForTest allocator;
+
  private:
   std::array<std::byte, 256> buffer = {};
-
- public:
-  test::FakeAllocator allocator;
-
-  void SetUp() override { EXPECT_EQ(allocator.Initialize(buffer), OkStatus()); }
 };
 
 // Unit tests
diff --git a/pw_allocator/allocator_testing.cc b/pw_allocator/allocator_testing.cc
index bc67f33..47d046a 100644
--- a/pw_allocator/allocator_testing.cc
+++ b/pw_allocator/allocator_testing.cc
@@ -12,31 +12,35 @@
 // License for the specific language governing permissions and limitations under
 // the License.
 
-#include "pw_allocator_private/allocator_testing.h"
+#include "pw_allocator/allocator_testing.h"
 
 #include "pw_assert/check.h"
-#include "pw_bytes/alignment.h"
 
 namespace pw::allocator::test {
 
-Status FakeAllocator::Initialize(ByteSpan buffer) {
-  auto result = BlockType::Init(buffer);
-  if (!result.ok()) {
-    return result.status();
+AllocatorForTest::~AllocatorForTest() {
+  for (auto* block : allocator_.blocks()) {
+    PW_DCHECK(
+        !block->Used(),
+        "The block at %p was still in use when its allocator was "
+        "destroyed. All memory allocated by an allocator must be released "
+        "before the allocator goes out of scope.",
+        static_cast<void*>(block));
   }
-  begin_ = *result;
-  end_ = begin_->Next();
-  ResetParameters();
-  return OkStatus();
 }
 
-void FakeAllocator::Exhaust() {
-  for (BlockType* block = begin_; block != end_; block = block->Next()) {
+Status AllocatorForTest::Init(ByteSpan bytes) {
+  ResetParameters();
+  return allocator_.Init(bytes);
+}
+
+void AllocatorForTest::Exhaust() {
+  for (auto* block : allocator_.blocks()) {
     block->MarkUsed();
   }
 }
 
-void FakeAllocator::ResetParameters() {
+void AllocatorForTest::ResetParameters() {
   allocate_size_ = 0;
   deallocate_ptr_ = nullptr;
   deallocate_size_ = 0;
@@ -45,82 +49,38 @@
   resize_new_size_ = 0;
 }
 
-Status FakeAllocator::DoQuery(const void* ptr, size_t size, size_t) const {
-  PW_CHECK(begin_ != nullptr);
-  if (size == 0 || ptr == nullptr) {
-    return Status::OutOfRange();
+void AllocatorForTest::DeallocateAll() {
+  for (auto* block : allocator_.blocks()) {
+    BlockType::Free(block);
   }
-  const auto* bytes = static_cast<const std::byte*>(ptr);
-  BlockType* target = BlockType::FromUsableSpace(const_cast<std::byte*>(bytes));
-  if (!target->IsValid()) {
-    return Status::OutOfRange();
-  }
-  size = AlignUp(size, BlockType::kAlignment);
-  for (BlockType* block = begin_; block != end_; block = block->Next()) {
-    if (block == target && block->InnerSize() == size) {
-      return OkStatus();
-    }
-  }
-  return Status::OutOfRange();
+  ResetParameters();
 }
 
-void* FakeAllocator::DoAllocate(size_t size, size_t) {
-  PW_CHECK(begin_ != nullptr);
+Status AllocatorForTest::DoQuery(const void* ptr,
+                                 size_t size,
+                                 size_t alignment) const {
+  return allocator_.QueryUnchecked(ptr, size, alignment);
+}
+
+void* AllocatorForTest::DoAllocate(size_t size, size_t alignment) {
   allocate_size_ = size;
-  for (BlockType* block = begin_; block != end_; block = block->Next()) {
-    if (block->Used() || block->InnerSize() < size) {
-      continue;
-    }
-    Result<BlockType*> result = BlockType::Split(block, size);
-    if (result.ok()) {
-      BlockType* next = *result;
-      BlockType::MergeNext(next).IgnoreError();
-    }
-    block->MarkUsed();
-    return block->UsableSpace();
-  }
-  return nullptr;
+  return allocator_.AllocateUnchecked(size, alignment);
 }
 
-void FakeAllocator::DoDeallocate(void* ptr, size_t size, size_t alignment) {
-  PW_CHECK(begin_ != nullptr);
+void AllocatorForTest::DoDeallocate(void* ptr, size_t size, size_t alignment) {
   deallocate_ptr_ = ptr;
   deallocate_size_ = size;
-  if (!DoQuery(ptr, size, alignment).ok()) {
-    return;
-  }
-  BlockType* block = BlockType::FromUsableSpace(static_cast<std::byte*>(ptr));
-  block->MarkFree();
-  BlockType::MergeNext(block).IgnoreError();
-  BlockType* prev = block->Prev();
-  BlockType::MergeNext(prev).IgnoreError();
+  return allocator_.DeallocateUnchecked(ptr, size, alignment);
 }
 
-bool FakeAllocator::DoResize(void* ptr,
-                             size_t old_size,
-                             size_t old_alignment,
-                             size_t new_size) {
-  PW_CHECK(begin_ != nullptr);
+bool AllocatorForTest::DoResize(void* ptr,
+                                size_t old_size,
+                                size_t old_alignment,
+                                size_t new_size) {
   resize_ptr_ = ptr;
   resize_old_size_ = old_size;
   resize_new_size_ = new_size;
-  if (!DoQuery(ptr, old_size, old_alignment).ok() || old_size == 0 ||
-      new_size == 0) {
-    return false;
-  }
-  if (old_size == new_size) {
-    return true;
-  }
-  BlockType* block = BlockType::FromUsableSpace(static_cast<std::byte*>(ptr));
-  block->MarkFree();
-  BlockType::MergeNext(block).IgnoreError();
-
-  Result<BlockType*> result = BlockType::Split(block, new_size);
-  if (!result.ok()) {
-    BlockType::Split(block, old_size).IgnoreError();
-  }
-  block->MarkUsed();
-  return block->InnerSize() >= new_size;
+  return allocator_.ResizeUnchecked(ptr, old_size, old_alignment, new_size);
 }
 
 }  // namespace pw::allocator::test
diff --git a/pw_allocator/docs.rst b/pw_allocator/docs.rst
index decaf7e..0d5545c 100644
--- a/pw_allocator/docs.rst
+++ b/pw_allocator/docs.rst
@@ -29,6 +29,19 @@
 .. doxygenclass:: pw::allocator::Allocator
    :members:
 
+Example
+-------
+As an example, the following implements a simple allocator that tracks memory
+using ``Block``.
+
+.. literalinclude:: public/pw_allocator/simple_allocator.h
+   :language: cpp
+   :linenos:
+   :start-after: [pw_allocator_examples_simple_allocator]
+   :end-before: [pw_allocator_examples_simple_allocator]
+
+Other Implemetations
+--------------------
 Provided implementations of the ``Allocator`` interface include:
 
 - ``AllocatorMetricProxy``: Wraps another allocator and records its usage.
diff --git a/pw_allocator/fallback_allocator_test.cc b/pw_allocator/fallback_allocator_test.cc
index 982c6cc..c623fa6 100644
--- a/pw_allocator/fallback_allocator_test.cc
+++ b/pw_allocator/fallback_allocator_test.cc
@@ -15,7 +15,7 @@
 #include "pw_allocator/fallback_allocator.h"
 
 #include "gtest/gtest.h"
-#include "pw_allocator_private/allocator_testing.h"
+#include "pw_allocator/allocator_testing.h"
 #include "pw_status/status.h"
 
 namespace pw::allocator {
@@ -23,75 +23,73 @@
 
 // Test fixtures.
 
-struct FallbackAllocatorTest : ::testing::Test {
- private:
-  std::array<std::byte, 128> buffer1 = {};
-  std::array<std::byte, 128> buffer2 = {};
+class FallbackAllocatorTest : public ::testing::Test {
+ protected:
+  void SetUp() override { allocator.Initialize(*primary, *secondary); }
 
- public:
-  test::FakeAllocator primary;
-  test::FakeAllocator secondary;
-  FallbackAllocator allocator;
-
-  void SetUp() override {
-    EXPECT_EQ(primary.Initialize(buffer1), OkStatus());
-    EXPECT_EQ(secondary.Initialize(buffer2), OkStatus());
-    allocator.Initialize(primary, secondary);
+  void TearDown() override {
+    primary->DeallocateAll();
+    secondary->DeallocateAll();
   }
+
+  test::AllocatorForTestWithBuffer<128> primary;
+  test::AllocatorForTestWithBuffer<128> secondary;
+  FallbackAllocator allocator;
 };
 
 // Unit tests.
 
 TEST_F(FallbackAllocatorTest, QueryValidPrimary) {
   Layout layout = Layout::Of<uint32_t>();
-  void* ptr = primary.Allocate(layout);
-  EXPECT_TRUE(primary.Query(ptr, layout).ok());
-  EXPECT_EQ(secondary.Query(ptr, layout), Status::OutOfRange());
+  void* ptr = primary->Allocate(layout);
+  EXPECT_TRUE(primary->Query(ptr, layout).ok());
+  EXPECT_EQ(secondary->Query(ptr, layout), Status::OutOfRange());
   EXPECT_TRUE(allocator.Query(ptr, layout).ok());
 }
 
 TEST_F(FallbackAllocatorTest, QueryValidSecondary) {
   Layout layout = Layout::Of<uint32_t>();
-  void* ptr = secondary.Allocate(layout);
-  EXPECT_FALSE(primary.Query(ptr, layout).ok());
-  EXPECT_TRUE(secondary.Query(ptr, layout).ok());
+  void* ptr = secondary->Allocate(layout);
+  EXPECT_FALSE(primary->Query(ptr, layout).ok());
+  EXPECT_TRUE(secondary->Query(ptr, layout).ok());
   EXPECT_TRUE(allocator.Query(ptr, layout).ok());
 }
 
 TEST_F(FallbackAllocatorTest, QueryInvalidPtr) {
   std::array<std::byte, 128> buffer = {};
-  test::FakeAllocator other;
-  ASSERT_TRUE(other.Initialize(buffer).ok());
+  test::AllocatorForTest other;
+  ASSERT_EQ(other.Init(buffer), OkStatus());
   Layout layout = Layout::Of<uint32_t>();
   void* ptr = other.Allocate(layout);
-  EXPECT_FALSE(primary.Query(ptr, layout).ok());
-  EXPECT_FALSE(secondary.Query(ptr, layout).ok());
+  EXPECT_FALSE(primary->Query(ptr, layout).ok());
+  EXPECT_FALSE(secondary->Query(ptr, layout).ok());
   EXPECT_FALSE(allocator.Query(ptr, layout).ok());
+  other.DeallocateAll();
 }
 
 TEST_F(FallbackAllocatorTest, AllocateFromPrimary) {
   Layout layout = Layout::Of<uint32_t>();
   void* ptr = allocator.Allocate(layout);
   EXPECT_NE(ptr, nullptr);
-  EXPECT_EQ(primary.allocate_size(), layout.size());
-  EXPECT_EQ(secondary.allocate_size(), 0U);
+  EXPECT_EQ(primary->allocate_size(), layout.size());
+  EXPECT_EQ(secondary->allocate_size(), 0U);
 }
 
 TEST_F(FallbackAllocatorTest, AllocateFromSecondary) {
-  primary.Exhaust();
+  primary->Exhaust();
   Layout layout = Layout::Of<uint32_t>();
   void* ptr = allocator.Allocate(layout);
   EXPECT_NE(ptr, nullptr);
-  EXPECT_EQ(primary.allocate_size(), layout.size());
-  EXPECT_EQ(secondary.allocate_size(), layout.size());
+  EXPECT_EQ(primary->allocate_size(), layout.size());
+  EXPECT_EQ(secondary->allocate_size(), layout.size());
 }
 
 TEST_F(FallbackAllocatorTest, AllocateFailure) {
   Layout layout = Layout::Of<uint32_t[0x10000]>();
   void* ptr = allocator.Allocate(layout);
   EXPECT_EQ(ptr, nullptr);
-  EXPECT_EQ(primary.allocate_size(), layout.size());
-  EXPECT_EQ(secondary.allocate_size(), layout.size());
+  EXPECT_EQ(primary->allocate_size(), layout.size());
+  EXPECT_EQ(secondary->allocate_size(), layout.size());
 }
 
 TEST_F(FallbackAllocatorTest, DeallocateUsingPrimary) {
@@ -99,22 +97,22 @@
   void* ptr = allocator.Allocate(layout);
   ASSERT_NE(ptr, nullptr);
   allocator.Deallocate(ptr, layout);
-  EXPECT_EQ(primary.deallocate_ptr(), ptr);
-  EXPECT_EQ(primary.deallocate_size(), layout.size());
-  EXPECT_EQ(secondary.deallocate_ptr(), nullptr);
-  EXPECT_EQ(secondary.deallocate_size(), 0U);
+  EXPECT_EQ(primary->deallocate_ptr(), ptr);
+  EXPECT_EQ(primary->deallocate_size(), layout.size());
+  EXPECT_EQ(secondary->deallocate_ptr(), nullptr);
+  EXPECT_EQ(secondary->deallocate_size(), 0U);
 }
 
 TEST_F(FallbackAllocatorTest, DeallocateUsingSecondary) {
-  primary.Exhaust();
+  primary->Exhaust();
   Layout layout = Layout::Of<uint32_t>();
   void* ptr = allocator.Allocate(layout);
   ASSERT_NE(ptr, nullptr);
   allocator.Deallocate(ptr, layout);
-  EXPECT_EQ(primary.deallocate_ptr(), nullptr);
-  EXPECT_EQ(primary.deallocate_size(), 0U);
-  EXPECT_EQ(secondary.deallocate_ptr(), ptr);
-  EXPECT_EQ(secondary.deallocate_size(), layout.size());
+  EXPECT_EQ(primary->deallocate_ptr(), nullptr);
+  EXPECT_EQ(primary->deallocate_size(), 0U);
+  EXPECT_EQ(secondary->deallocate_ptr(), ptr);
+  EXPECT_EQ(secondary->deallocate_size(), layout.size());
 }
 
 TEST_F(FallbackAllocatorTest, ResizePrimary) {
@@ -124,69 +122,69 @@
 
   size_t new_size = sizeof(uint32_t[3]);
   EXPECT_TRUE(allocator.Resize(ptr, old_layout, new_size));
-  EXPECT_EQ(primary.resize_ptr(), ptr);
-  EXPECT_EQ(primary.resize_old_size(), old_layout.size());
-  EXPECT_EQ(primary.resize_new_size(), new_size);
+  EXPECT_EQ(primary->resize_ptr(), ptr);
+  EXPECT_EQ(primary->resize_old_size(), old_layout.size());
+  EXPECT_EQ(primary->resize_new_size(), new_size);
 
   // Secondary should not be touched.
-  EXPECT_EQ(secondary.resize_ptr(), nullptr);
-  EXPECT_EQ(secondary.resize_old_size(), 0U);
-  EXPECT_EQ(secondary.resize_new_size(), 0U);
+  EXPECT_EQ(secondary->resize_ptr(), nullptr);
+  EXPECT_EQ(secondary->resize_old_size(), 0U);
+  EXPECT_EQ(secondary->resize_new_size(), 0U);
 }
 
 TEST_F(FallbackAllocatorTest, ResizePrimaryFailure) {
   Layout old_layout = Layout::Of<uint32_t>();
   void* ptr = allocator.Allocate(old_layout);
   ASSERT_NE(ptr, nullptr);
-  primary.Exhaust();
+  primary->Exhaust();
 
   size_t new_size = sizeof(uint32_t[3]);
   EXPECT_FALSE(allocator.Resize(ptr, old_layout, new_size));
-  EXPECT_EQ(primary.resize_ptr(), ptr);
-  EXPECT_EQ(primary.resize_old_size(), old_layout.size());
-  EXPECT_EQ(primary.resize_new_size(), new_size);
+  EXPECT_EQ(primary->resize_ptr(), ptr);
+  EXPECT_EQ(primary->resize_old_size(), old_layout.size());
+  EXPECT_EQ(primary->resize_new_size(), new_size);
 
   // Secondary should not be touched.
-  EXPECT_EQ(secondary.resize_ptr(), nullptr);
-  EXPECT_EQ(secondary.resize_old_size(), 0U);
-  EXPECT_EQ(secondary.resize_new_size(), 0U);
+  EXPECT_EQ(secondary->resize_ptr(), nullptr);
+  EXPECT_EQ(secondary->resize_old_size(), 0U);
+  EXPECT_EQ(secondary->resize_new_size(), 0U);
 }
 
 TEST_F(FallbackAllocatorTest, ResizeSecondary) {
-  primary.Exhaust();
+  primary->Exhaust();
   Layout old_layout = Layout::Of<uint32_t>();
   void* ptr = allocator.Allocate(old_layout);
   ASSERT_NE(ptr, nullptr);
 
   size_t new_size = sizeof(uint32_t[3]);
   EXPECT_TRUE(allocator.Resize(ptr, old_layout, new_size));
-  EXPECT_EQ(secondary.resize_ptr(), ptr);
-  EXPECT_EQ(secondary.resize_old_size(), old_layout.size());
-  EXPECT_EQ(secondary.resize_new_size(), new_size);
+  EXPECT_EQ(secondary->resize_ptr(), ptr);
+  EXPECT_EQ(secondary->resize_old_size(), old_layout.size());
+  EXPECT_EQ(secondary->resize_new_size(), new_size);
 
   // Primary should not be touched.
-  EXPECT_EQ(primary.resize_ptr(), nullptr);
-  EXPECT_EQ(primary.resize_old_size(), 0U);
-  EXPECT_EQ(primary.resize_new_size(), 0U);
+  EXPECT_EQ(primary->resize_ptr(), nullptr);
+  EXPECT_EQ(primary->resize_old_size(), 0U);
+  EXPECT_EQ(primary->resize_new_size(), 0U);
 }
 
 TEST_F(FallbackAllocatorTest, ResizeSecondaryFailure) {
-  primary.Exhaust();
+  primary->Exhaust();
   Layout old_layout = Layout::Of<uint32_t>();
   void* ptr = allocator.Allocate(old_layout);
   ASSERT_NE(ptr, nullptr);
-  secondary.Exhaust();
+  secondary->Exhaust();
 
   size_t new_size = sizeof(uint32_t[3]);
   EXPECT_FALSE(allocator.Resize(ptr, old_layout, new_size));
-  EXPECT_EQ(secondary.resize_ptr(), ptr);
-  EXPECT_EQ(secondary.resize_old_size(), old_layout.size());
-  EXPECT_EQ(secondary.resize_new_size(), new_size);
+  EXPECT_EQ(secondary->resize_ptr(), ptr);
+  EXPECT_EQ(secondary->resize_old_size(), old_layout.size());
+  EXPECT_EQ(secondary->resize_new_size(), new_size);
 
   // Primary should not be touched.
-  EXPECT_EQ(primary.resize_ptr(), nullptr);
-  EXPECT_EQ(primary.resize_old_size(), 0U);
-  EXPECT_EQ(primary.resize_new_size(), 0U);
+  EXPECT_EQ(primary->resize_ptr(), nullptr);
+  EXPECT_EQ(primary->resize_old_size(), 0U);
+  EXPECT_EQ(primary->resize_new_size(), 0U);
 }
 
 TEST_F(FallbackAllocatorTest, ReallocateSameAllocator) {
@@ -201,22 +199,22 @@
   size_t new_size = sizeof(uint32_t[3]);
   void* new_ptr = allocator.Reallocate(ptr1, old_layout, new_size);
   EXPECT_NE(new_ptr, nullptr);
-  EXPECT_EQ(primary.deallocate_ptr(), ptr1);
-  EXPECT_EQ(primary.deallocate_size(), old_layout.size());
-  EXPECT_EQ(primary.allocate_size(), new_size);
+  EXPECT_EQ(primary->deallocate_ptr(), ptr1);
+  EXPECT_EQ(primary->deallocate_size(), old_layout.size());
+  EXPECT_EQ(primary->allocate_size(), new_size);
 }
 
 TEST_F(FallbackAllocatorTest, ReallocateDifferentAllocator) {
   Layout old_layout = Layout::Of<uint32_t>();
   void* ptr = allocator.Allocate(old_layout);
-  primary.Exhaust();
+  primary->Exhaust();
 
   size_t new_size = sizeof(uint32_t[3]);
   void* new_ptr = allocator.Reallocate(ptr, old_layout, new_size);
   EXPECT_NE(new_ptr, nullptr);
-  EXPECT_EQ(primary.deallocate_ptr(), ptr);
-  EXPECT_EQ(primary.deallocate_size(), old_layout.size());
-  EXPECT_EQ(secondary.allocate_size(), new_size);
+  EXPECT_EQ(primary->deallocate_ptr(), ptr);
+  EXPECT_EQ(primary->deallocate_size(), old_layout.size());
+  EXPECT_EQ(secondary->allocate_size(), new_size);
 }
 
 }  // namespace
diff --git a/pw_allocator/libc_allocator_test.cc b/pw_allocator/libc_allocator_test.cc
index d69dcb0..967a106 100644
--- a/pw_allocator/libc_allocator_test.cc
+++ b/pw_allocator/libc_allocator_test.cc
@@ -22,7 +22,8 @@
 
 // Test fixtures.
 
-struct LibCAllocatorTest : ::testing::Test {
+class LibCAllocatorTest : public ::testing::Test {
+ protected:
   LibCAllocator allocator;
 };
 
diff --git a/pw_allocator/public/pw_allocator/allocator_testing.h b/pw_allocator/public/pw_allocator/allocator_testing.h
new file mode 100644
index 0000000..4107752
--- /dev/null
+++ b/pw_allocator/public/pw_allocator/allocator_testing.h
@@ -0,0 +1,134 @@
+// Copyright 2023 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#pragma once
+
+#include <array>
+#include <cstddef>
+
+#include "gtest/gtest.h"
+#include "pw_allocator/allocator.h"
+#include "pw_allocator/block.h"
+#include "pw_allocator/simple_allocator.h"
+#include "pw_bytes/span.h"
+
+namespace pw::allocator::test {
+
+/// Simple memory allocator for testing.
+///
+/// This allocator records the most recent parameters passed to the `Allocator`
+/// interface methods, and returns them via accessors.
+class AllocatorForTest : public Allocator {
+ public:
+  constexpr AllocatorForTest() = default;
+  ~AllocatorForTest() override;
+
+  size_t allocate_size() const { return allocate_size_; }
+  void* deallocate_ptr() const { return deallocate_ptr_; }
+  size_t deallocate_size() const { return deallocate_size_; }
+  void* resize_ptr() const { return resize_ptr_; }
+  size_t resize_old_size() const { return resize_old_size_; }
+  size_t resize_new_size() const { return resize_new_size_; }
+
+  /// Provides memory for the allocator to allocate from.
+  Status Init(ByteSpan bytes);
+
+  /// Allocates all the memory from this object.
+  void Exhaust();
+
+  /// Resets the recorded parameters to an initial state.
+  void ResetParameters();
+
+  /// This frees all memory associated with this allocator.
+  void DeallocateAll();
+
+ private:
+  using BlockType = Block<>;
+
+  /// @copydoc Allocator::Query
+  Status DoQuery(const void* ptr, size_t size, size_t alignment) const override;
+
+  /// @copydoc Allocator::Allocate
+  void* DoAllocate(size_t size, size_t alignment) override;
+
+  /// @copydoc Allocator::Deallocate
+  void DoDeallocate(void* ptr, size_t size, size_t alignment) override;
+
+  /// @copydoc Allocator::Resize
+  bool DoResize(void* ptr,
+                size_t old_size,
+                size_t old_alignment,
+                size_t new_size) override;
+
+  SimpleAllocator allocator_;
+  size_t allocate_size_ = 0;
+  void* deallocate_ptr_ = nullptr;
+  size_t deallocate_size_ = 0;
+  void* resize_ptr_ = nullptr;
+  size_t resize_old_size_ = 0;
+  size_t resize_new_size_ = 0;
+};
+
+/// Wraps a default-constructed type a buffer holding a region of memory.
+///
+/// Although the type is arbitrary, the intended purpose of of this class is to
+/// provide allocators with memory to use when testing.
+///
+/// This class uses composition instead of inheritance in order to allow the
+/// wrapped type's destructor to reference the memory without risk of a
+/// use-after-free. As a result, the specific methods of the wrapped type
+/// are not directly accesible. Instead, they can be accessed using the `*` and
+/// `->` operators, e.g.
+///
+/// @code{.cpp}
+/// WithBuffer<MyAllocator, 256> allocator;
+/// allocator->MethodSpecificToMyAllocator();
+/// @endcode
+///
+/// Note that this class does NOT initialize the allocator, since initialization
+/// is not specified as part of the `Allocator` interface and may vary from
+/// allocator to allocator. As a result, typical usgae includes deriving a class
+/// that initializes the wrapped allocator with the buffer in a constructor. See
+/// `AllocatorForTestWithBuffer` below for an example.
+///
+/// @tparam   T             The wrapped object.
+/// @tparam   kBufferSize   The size of the backing memory, in bytes.
+/// @tparam   AlignType     Buffer memory will be aligned to this type's
+///                         alignment boundary.
+template <typename T, size_t kBufferSize, typename AlignType = uint8_t>
+class WithBuffer {
+ public:
+  static constexpr size_t kCapacity = kBufferSize;
+
+  std::byte* data() { return buffer_.data(); }
+  size_t size() const { return buffer_.size(); }
+
+  T& operator*() { return obj_; }
+  T* operator->() { return &obj_; }
+
+ private:
+  alignas(AlignType) std::array<std::byte, kBufferSize> buffer_;
+  T obj_;
+};
+
+/// An `AllocatorForTest` that is automatically initialized on construction.
+template <size_t kBufferSize>
+class AllocatorForTestWithBuffer
+    : public WithBuffer<AllocatorForTest, kBufferSize> {
+ public:
+  AllocatorForTestWithBuffer() {
+    EXPECT_EQ((*this)->Init(ByteSpan(this->data(), this->size())), OkStatus());
+  }
+};
+
+}  // namespace pw::allocator::test
diff --git a/pw_allocator/public/pw_allocator/block.h b/pw_allocator/public/pw_allocator/block.h
index d8d505f..4b5e7e5 100644
--- a/pw_allocator/public/pw_allocator/block.h
+++ b/pw_allocator/public/pw_allocator/block.h
@@ -151,53 +151,8 @@
 /// contiguous region of memory returned from a call to `Init`. This block can
 /// be split into smaller blocks that refer to their neighbors. Neighboring
 /// blocks can be merged. These behaviors allows ``Allocator``s to track
-/// allocated memory with a small amount of overhead.
-///
-/// For example, the following is a simple but functional ``Allocator`` using
-/// ``Block``:
-///
-/// @code{.cpp}
-/// // TODO(b/306686936): Consider moving this to a standalone example.
-/// class SimpleAllocator {
-/// public:
-///   Status Init(ByteSpan region) {
-///     auto result = Block<>::Init(region);
-///     if (!result.ok()) { return result.status(); }
-///     begin_ = *result;
-///     end_ = begin_->Next();
-///     return OkStatus();
-///   }
-///
-/// private:
-///   void* DoAllocate(Layout layout) override {
-///     for (auto* block = begin_; block != end_; block = block->Next()) {
-///       if (block->InnerSize() >= layout.size()) {
-///         if (auto result=Block<>::Split(block, layout.size()); result.ok()) {
-///           // Try to merge the leftovers with the next block.
-///           Block<>::MergeNext(*result).IgnoreError();
-///         }
-///         block->MarkUsed();
-///         return block->UsableSpace();
-///      }
-///     }
-///     return nullptr;
-///   }
-///
-///   void DoDeallocate(void* ptr, Layout) override {
-///     Block<>* block = Block<>::FromUsableSpace(ptr);
-///     block->MarkFree();
-///     // Try to merge the released block with its neighbors.
-///     Block<>::MergeNext(block).IgnoreError();
-///     block = block->Prev();
-///     Block<>::MergeNext(block).IgnoreError();
-///   }
-///
-///   bool DoResize(void*, Layout, size_t) {
-///     // Always reallocate.
-///     return false;
-///   }
-/// };
-/// @endcode
+/// allocated memory with a small amount of overhead. See
+/// pw_allocator_private/simple_allocator.h for an example.
 ///
 /// Blocks will always be aligned to a `kAlignment boundary. Block sizes will
 /// always be rounded up to a multiple of `kAlignment`.
diff --git a/pw_allocator/public/pw_allocator/simple_allocator.h b/pw_allocator/public/pw_allocator/simple_allocator.h
new file mode 100644
index 0000000..9851342
--- /dev/null
+++ b/pw_allocator/public/pw_allocator/simple_allocator.h
@@ -0,0 +1,87 @@
+// Copyright 2023 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+#pragma once
+
+#include "pw_allocator/allocator.h"
+#include "pw_allocator/block.h"
+
+namespace pw::allocator {
+
+// DOCSTAG: [pw_allocator_examples_simple_allocator]
+/// Simple allocator that uses a list of `Block`s.
+class SimpleAllocator : public Allocator {
+ public:
+  using Block = pw::allocator::Block<>;
+  using Range = typename Block::Range;
+
+  constexpr SimpleAllocator() = default;
+
+  /// Initialize this allocator to allocate memory from `region`.
+  Status Init(ByteSpan region) {
+    auto result = Block::Init(region);
+    if (result.ok()) {
+      blocks_ = *result;
+    }
+    return result.status();
+  }
+
+  /// Return the range of blocks for this allocator.
+  Range blocks() { return Range(blocks_); }
+
+ private:
+  /// @copydoc Allocator::Query
+  Status DoQuery(const void* ptr, size_t, size_t) const override {
+    for (auto* block : Range(blocks_)) {
+      if (block->UsableSpace() == ptr) {
+        return OkStatus();
+      }
+    }
+    return Status::OutOfRange();
+  }
+
+  /// @copydoc Allocator::Allocate
+  void* DoAllocate(size_t size, size_t alignment) override {
+    for (auto* block : Range(blocks_)) {
+      if (Block::AllocFirst(block, size, alignment).ok()) {
+        return block->UsableSpace();
+      }
+    }
+    return nullptr;
+  }
+
+  /// @copydoc Allocator::Deallocate
+  void DoDeallocate(void* ptr, size_t, size_t) override {
+    if (ptr == nullptr) {
+      return;
+    }
+    auto* bytes = static_cast<std::byte*>(ptr);
+    Block* block = Block::FromUsableSpace(bytes);
+    Block::Free(block);
+  }
+
+  /// @copydoc Allocator::Resize
+  bool DoResize(void* ptr, size_t, size_t, size_t new_size) override {
+    if (ptr == nullptr) {
+      return false;
+    }
+    auto* bytes = static_cast<std::byte*>(ptr);
+    Block* block = Block::FromUsableSpace(bytes);
+    return Block::Resize(block, new_size).ok();
+  }
+
+  Block* blocks_ = nullptr;
+};
+// DOCSTAG: [pw_allocator_examples_simple_allocator]
+
+}  // namespace pw::allocator
diff --git a/pw_allocator/pw_allocator_private/allocator_testing.h b/pw_allocator/pw_allocator_private/allocator_testing.h
deleted file mode 100644
index 0304928..0000000
--- a/pw_allocator/pw_allocator_private/allocator_testing.h
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2023 The Pigweed Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License"); you may not
-// use this file except in compliance with the License. You may obtain a copy of
-// the License at
-//
-//     https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-// License for the specific language governing permissions and limitations under
-// the License.
-#pragma once
-
-#include <array>
-#include <cstddef>
-
-#include "pw_allocator/allocator.h"
-#include "pw_allocator/block.h"
-#include "pw_bytes/span.h"
-
-namespace pw::allocator::test {
-
-/// Fake memory allocator for testing.
-///
-/// This allocator can return a fixed number of allocations made using an
-/// internal buffer. It records the most recent parameters passed to the
-/// `Allocator` interface methods, and returns them via accessors.
-class FakeAllocator : public Allocator {
- public:
-  constexpr FakeAllocator() = default;
-
-  size_t allocate_size() const { return allocate_size_; }
-  void* deallocate_ptr() const { return deallocate_ptr_; }
-  size_t deallocate_size() const { return deallocate_size_; }
-  void* resize_ptr() const { return resize_ptr_; }
-  size_t resize_old_size() const { return resize_old_size_; }
-  size_t resize_new_size() const { return resize_new_size_; }
-
-  /// Provides memory for the allocator to allocate from.
-  Status Initialize(ByteSpan buffer);
-
-  /// Allocates all the memory from this object.
-  void Exhaust();
-
-  /// Resets the recorded parameters to an initial state.
-  void ResetParameters();
-
- private:
-  using BlockType = Block<>;
-
-  /// @copydoc Allocator::Query
-  Status DoQuery(const void* ptr, size_t size, size_t alignment) const override;
-
-  /// @copydoc Allocator::Allocate
-  void* DoAllocate(size_t size, size_t alignment) override;
-
-  /// @copydoc Allocator::Deallocate
-  void DoDeallocate(void* ptr, size_t size, size_t alignment) override;
-
-  /// @copydoc Allocator::Resize
-  bool DoResize(void* ptr,
-                size_t old_size,
-                size_t old_alignment,
-                size_t new_size) override;
-
-  BlockType* begin_ = nullptr;
-  BlockType* end_ = nullptr;
-  size_t allocate_size_ = 0;
-  void* deallocate_ptr_ = nullptr;
-  size_t deallocate_size_ = 0;
-  void* resize_ptr_ = nullptr;
-  size_t resize_old_size_ = 0;
-  size_t resize_new_size_ = 0;
-};
-
-}  // namespace pw::allocator::test
diff --git a/pw_allocator/simple_allocator_test.cc b/pw_allocator/simple_allocator_test.cc
new file mode 100644
index 0000000..4904bfe
--- /dev/null
+++ b/pw_allocator/simple_allocator_test.cc
@@ -0,0 +1,66 @@
+// Copyright 2023 The Pigweed Authors
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "pw_allocator/simple_allocator.h"
+
+#include "gtest/gtest.h"
+#include "pw_allocator/allocator_testing.h"
+
+namespace pw::allocator {
+
+// Size of the memory region to use in the tests below.
+constexpr size_t kCapacity = 256;
+
+// An `SimpleAllocator` that is automatically initialized on construction.
+class SimpleAllocatorWithBuffer
+    : public test::WithBuffer<SimpleAllocator, kCapacity> {
+ public:
+  SimpleAllocatorWithBuffer() {
+    EXPECT_EQ((*this)->Init(ByteSpan(this->data(), this->size())), OkStatus());
+  }
+};
+
+// This is not meant to be a rigorous unit test of individual behaviors, as much
+// as simply a way to demonstrate and exercise the simple allocator.
+TEST(SimpleAllocatorTest, AllocateResizeDeallocate) {
+  SimpleAllocatorWithBuffer allocator;
+
+  // Can allocate usable memory.
+  constexpr size_t kSize1 = kCapacity / 4;
+  constexpr Layout layout1 = Layout::Of<std::byte[kSize1]>();
+  auto* ptr = static_cast<std::byte*>(allocator->Allocate(layout1));
+  ASSERT_NE(ptr, nullptr);
+  memset(ptr, 0x5A, kSize1);
+
+  // Can shrink memory. Contents are preserved.
+  constexpr size_t kSize2 = kCapacity / 8;
+  constexpr Layout layout2 = Layout::Of<std::byte[kSize2]>();
+  EXPECT_TRUE(allocator->Resize(ptr, layout1, layout2.size()));
+  for (size_t i = 0; i < kSize2; ++i) {
+    EXPECT_EQ(size_t(ptr[i]), 0x5Au);
+  }
+
+  // Can grow memory. Contents are preserved.
+  constexpr size_t kSize3 = kCapacity / 2;
+  constexpr Layout layout3 = Layout::Of<std::byte[kSize3]>();
+  EXPECT_TRUE(allocator->Resize(ptr, layout2, layout3.size()));
+  for (size_t i = 0; i < kSize2; ++i) {
+    EXPECT_EQ(size_t(ptr[i]), 0x5Au);
+  }
+
+  // Can free memory.
+  allocator->Deallocate(ptr, layout3);
+}
+
+}  // namespace pw::allocator
diff --git a/pw_allocator/split_free_list_allocator_test.cc b/pw_allocator/split_free_list_allocator_test.cc
index a3d7fab..ab4bab4 100644
--- a/pw_allocator/split_free_list_allocator_test.cc
+++ b/pw_allocator/split_free_list_allocator_test.cc
@@ -15,6 +15,7 @@
 #include "pw_allocator/split_free_list_allocator.h"
 
 #include "gtest/gtest.h"
+#include "pw_allocator/allocator_testing.h"
 #include "pw_allocator/block.h"
 #include "pw_bytes/alignment.h"
 #include "pw_bytes/span.h"
@@ -23,24 +24,39 @@
 namespace pw::allocator {
 namespace {
 
-// Test fixture.
+// Test fixtures.
 
+// Size of the memory region to use in the tests below.
+static constexpr size_t kCapacity = 256;
+
+// Minimum size of a "large" allocation; allocation less than this size are
+// considered "small".
+static constexpr size_t kThreshold = 64;
+
+// An `SplitFreeListAllocator` that is automatically initialized on
+// construction.
+using BlockType = Block<uint16_t, kCapacity>;
+class SplitFreeListAllocatorWithBuffer
+    : public test::
+          WithBuffer<SplitFreeListAllocator<BlockType>, kCapacity, BlockType> {
+ public:
+  SplitFreeListAllocatorWithBuffer() {
+    EXPECT_EQ((*this)->Init(ByteSpan(this->data(), this->size()), kThreshold),
+              OkStatus());
+  }
+};
+
+// Test case fixture that allows individual tests to cache allocations and
+// release them automatically on tear-down.
 class SplitFreeListAllocatorTest : public ::testing::Test {
  protected:
-  static constexpr size_t kCapacity = 256;
-
-  using BlockType = Block<uint16_t, kCapacity>;
-
   static constexpr size_t kMaxSize = kCapacity - BlockType::kBlockOverhead;
-  static constexpr size_t kThreshold = 64;
   static constexpr size_t kNumPtrs = 16;
 
   void SetUp() override {
     for (size_t i = 0; i < kNumPtrs; ++i) {
       ptrs_[i] = nullptr;
     }
-    span_ = ByteSpan(buffer_.data(), buffer_.size());
-    ASSERT_EQ(allocator_.Init(span_, kThreshold), OkStatus());
   }
 
   // This method simply ensures the memory is usable by writing to it.
@@ -51,14 +67,12 @@
       if (ptrs_[i] != nullptr) {
         // `SplitFreeListAllocator::Deallocate` doesn't actually use the layout,
         // as the information it needs is encoded in the blocks.
-        allocator_.Deallocate(ptrs_[i], Layout::Of<void*>());
+        allocator_->Deallocate(ptrs_[i], Layout::Of<void*>());
       }
     }
   }
 
-  alignas(BlockType) std::array<std::byte, kCapacity> buffer_;
-  ByteSpan span_;
-  SplitFreeListAllocator<BlockType> allocator_;
+  SplitFreeListAllocatorWithBuffer allocator_;
 
   // Tests can store allocations in this array to have them automatically
   // freed in `TearDown`, including on ASSERT failure. If pointers are manually
@@ -72,15 +86,16 @@
   // The test fixture uses aligned memory to make it easier to reason about
   // allocations, but that isn't strictly required.
   SplitFreeListAllocator<Block<>> unaligned;
-  EXPECT_EQ(unaligned.Init(span_.subspan(1), kThreshold), OkStatus());
+  ByteSpan bytes(allocator_.data(), allocator_.size());
+  EXPECT_EQ(unaligned.Init(bytes.subspan(1), kThreshold), OkStatus());
 }
 
 TEST_F(SplitFreeListAllocatorTest, AllocateLarge) {
   constexpr Layout layout = Layout::Of<std::byte[kThreshold]>();
-  ptrs_[0] = allocator_.Allocate(layout);
+  ptrs_[0] = allocator_->Allocate(layout);
   ASSERT_NE(ptrs_[0], nullptr);
-  EXPECT_GE(ptrs_[0], buffer_.data());
-  EXPECT_LT(ptrs_[0], buffer_.data() + buffer_.size());
+  EXPECT_GE(ptrs_[0], allocator_.data());
+  EXPECT_LT(ptrs_[0], allocator_.data() + allocator_.size());
   UseMemory(ptrs_[0], layout.size());
 }
 
@@ -88,27 +103,27 @@
   // Returned pointer should not be from the beginning, but should still be in
   // range. Exact pointer depends on allocator's minimum allocation size.
   constexpr Layout layout = Layout::Of<uint8_t>();
-  ptrs_[0] = allocator_.Allocate(layout);
+  ptrs_[0] = allocator_->Allocate(layout);
   ASSERT_NE(ptrs_[0], nullptr);
-  EXPECT_GT(ptrs_[0], buffer_.data());
-  EXPECT_LT(ptrs_[0], buffer_.data() + buffer_.size());
+  EXPECT_GT(ptrs_[0], allocator_.data());
+  EXPECT_LT(ptrs_[0], allocator_.data() + allocator_.size());
   UseMemory(ptrs_[0], layout.size());
 }
 
 TEST_F(SplitFreeListAllocatorTest, AllocateTooLarge) {
-  ptrs_[0] = allocator_.Allocate(Layout::Of<std::byte[kCapacity * 2]>());
+  ptrs_[0] = allocator_->Allocate(Layout::Of<std::byte[kCapacity * 2]>());
   EXPECT_EQ(ptrs_[0], nullptr);
 }
 
 TEST_F(SplitFreeListAllocatorTest, AllocateLargeAlignment) {
   constexpr size_t kSize = sizeof(uint32_t);
   constexpr size_t kAlignment = 64;
-  ptrs_[0] = allocator_.AllocateUnchecked(kSize, kAlignment);
+  ptrs_[0] = allocator_->AllocateUnchecked(kSize, kAlignment);
   ASSERT_NE(ptrs_[0], nullptr);
   EXPECT_EQ(reinterpret_cast<uintptr_t>(ptrs_[0]) % kAlignment, 0U);
   UseMemory(ptrs_[0], kSize);
 
-  ptrs_[1] = allocator_.AllocateUnchecked(kSize, kAlignment);
+  ptrs_[1] = allocator_->AllocateUnchecked(kSize, kAlignment);
   ASSERT_NE(ptrs_[1], nullptr);
   EXPECT_EQ(reinterpret_cast<uintptr_t>(ptrs_[1]) % kAlignment, 0U);
   UseMemory(ptrs_[0], kSize);
@@ -116,7 +131,8 @@
 
 TEST_F(SplitFreeListAllocatorTest, AllocateFromUnaligned) {
   SplitFreeListAllocator<Block<>> unaligned;
-  ASSERT_EQ(unaligned.Init(span_.subspan(1), kThreshold), OkStatus());
+  ByteSpan bytes(allocator_.data(), allocator_.size());
+  ASSERT_EQ(unaligned.Init(bytes.subspan(1), kThreshold), OkStatus());
 
   constexpr Layout layout = Layout::Of<std::byte[kThreshold + 8]>();
   void* ptr = unaligned.Allocate(layout);
@@ -127,9 +143,9 @@
 
 TEST_F(SplitFreeListAllocatorTest, AllocateAlignmentFailure) {
   // Determine the total number of available bytes.
-  auto base = reinterpret_cast<uintptr_t>(buffer_.data());
+  auto base = reinterpret_cast<uintptr_t>(allocator_.data());
   uintptr_t addr = AlignUp(base, BlockType::kAlignment);
-  size_t outer_size = buffer_.size() - (addr - base);
+  size_t outer_size = allocator_.size() - (addr - base);
 
   // The first block is large....
   addr += BlockType::kBlockOverhead + kThreshold;
@@ -142,43 +158,43 @@
   }
 
   // And the last block consumes the remaining space.
-  // size_t outer_size = allocator_.begin()->OuterSize();
+  // size_t outer_size = allocator_->begin()->OuterSize();
   size_t inner_size1 = next - addr;
   size_t inner_size2 = kThreshold * 2;
   size_t inner_size3 =
       outer_size - (BlockType::kBlockOverhead * 3 + inner_size1 + inner_size2);
 
   // Allocate all the blocks.
-  ptrs_[0] = allocator_.AllocateUnchecked(inner_size1, 1);
+  ptrs_[0] = allocator_->AllocateUnchecked(inner_size1, 1);
   ASSERT_NE(ptrs_[0], nullptr);
 
-  ptrs_[1] = allocator_.AllocateUnchecked(inner_size2, 1);
+  ptrs_[1] = allocator_->AllocateUnchecked(inner_size2, 1);
   ASSERT_NE(ptrs_[1], nullptr);
 
-  ptrs_[2] = allocator_.AllocateUnchecked(inner_size3, 1);
+  ptrs_[2] = allocator_->AllocateUnchecked(inner_size3, 1);
   ASSERT_NE(ptrs_[2], nullptr);
 
   // If done correctly, the second block's usable space should be unaligned.
   EXPECT_NE(reinterpret_cast<uintptr_t>(ptrs_[1]) % kAlignment, 0U);
 
   // Free the second region. This leaves an unaligned region available.
-  allocator_.DeallocateUnchecked(ptrs_[1], inner_size2, 1);
+  allocator_->DeallocateUnchecked(ptrs_[1], inner_size2, 1);
   ptrs_[1] = nullptr;
 
   // The allocator should be unable to create an aligned region..
-  ptrs_[3] = allocator_.AllocateUnchecked(inner_size2, kAlignment);
+  ptrs_[3] = allocator_->AllocateUnchecked(inner_size2, kAlignment);
   EXPECT_EQ(ptrs_[3], nullptr);
 }
 TEST_F(SplitFreeListAllocatorTest, DeallocateNull) {
   constexpr Layout layout = Layout::Of<uint8_t>();
-  allocator_.Deallocate(nullptr, layout);
+  allocator_->Deallocate(nullptr, layout);
 }
 
 TEST_F(SplitFreeListAllocatorTest, DeallocateShuffled) {
   constexpr Layout layout = Layout::Of<std::byte[32]>();
   // Allocate until the pool is exhausted.
   for (size_t i = 0; i < kNumPtrs; ++i) {
-    ptrs_[i] = allocator_.Allocate(layout);
+    ptrs_[i] = allocator_->Allocate(layout);
     if (ptrs_[i] == nullptr) {
       break;
     }
@@ -194,7 +210,7 @@
   }
   // Deallocate everything.
   for (size_t i = 0; i < kNumPtrs; ++i) {
-    allocator_.Deallocate(ptrs_[i], layout);
+    allocator_->Deallocate(ptrs_[i], layout);
     ptrs_[i] = nullptr;
   }
 }
@@ -206,23 +222,23 @@
   // Allocate eight blocks of alternating sizes. After this, the will also be a
   // ninth, unallocated block of the remaining memory.
   for (size_t i = 0; i < 4; ++i) {
-    ptrs_[i] = allocator_.Allocate(layout1);
+    ptrs_[i] = allocator_->Allocate(layout1);
     ASSERT_NE(ptrs_[i], nullptr);
-    ptrs_[i + 4] = allocator_.Allocate(layout2);
+    ptrs_[i + 4] = allocator_->Allocate(layout2);
     ASSERT_NE(ptrs_[i + 4], nullptr);
   }
 
   // Deallocate every other block. After this there will be four more
   // unallocated blocks, for a total of five.
   for (size_t i = 0; i < 4; ++i) {
-    allocator_.Deallocate(ptrs_[i], layout1);
+    allocator_->Deallocate(ptrs_[i], layout1);
   }
 
   // Count the blocks. The unallocated ones vary in size, but the allocated ones
   // should all be the same.
   size_t free_count = 0;
   size_t used_count = 0;
-  for (auto* block : allocator_.blocks()) {
+  for (auto* block : allocator_->blocks()) {
     if (block->Used()) {
       EXPECT_GE(block->InnerSize(), layout2.size());
       ++used_count;
@@ -236,116 +252,116 @@
 
 TEST_F(SplitFreeListAllocatorTest, QueryLargeValid) {
   constexpr Layout layout = Layout::Of<std::byte[kThreshold * 2]>();
-  ptrs_[0] = allocator_.Allocate(layout);
-  EXPECT_EQ(allocator_.Query(ptrs_[0], layout), OkStatus());
+  ptrs_[0] = allocator_->Allocate(layout);
+  EXPECT_EQ(allocator_->Query(ptrs_[0], layout), OkStatus());
 }
 
 TEST_F(SplitFreeListAllocatorTest, QuerySmallValid) {
   constexpr Layout layout = Layout::Of<uint8_t>();
-  ptrs_[0] = allocator_.Allocate(layout);
-  EXPECT_EQ(allocator_.Query(ptrs_[0], layout), OkStatus());
+  ptrs_[0] = allocator_->Allocate(layout);
+  EXPECT_EQ(allocator_->Query(ptrs_[0], layout), OkStatus());
 }
 
 TEST_F(SplitFreeListAllocatorTest, QueryInvalidPtr) {
   constexpr Layout layout = Layout::Of<SplitFreeListAllocatorTest>();
-  EXPECT_EQ(allocator_.Query(this, layout), Status::OutOfRange());
+  EXPECT_EQ(allocator_->Query(this, layout), Status::OutOfRange());
 }
 
 TEST_F(SplitFreeListAllocatorTest, ResizeNull) {
   constexpr Layout old_layout = Layout::Of<uint8_t>();
   size_t new_size = 1;
-  EXPECT_FALSE(allocator_.Resize(nullptr, old_layout, new_size));
+  EXPECT_FALSE(allocator_->Resize(nullptr, old_layout, new_size));
 }
 
 TEST_F(SplitFreeListAllocatorTest, ResizeSame) {
   constexpr Layout old_layout = Layout::Of<uint32_t>();
-  ptrs_[0] = allocator_.Allocate(old_layout);
+  ptrs_[0] = allocator_->Allocate(old_layout);
   ASSERT_NE(ptrs_[0], nullptr);
 
   constexpr Layout new_layout = Layout::Of<uint32_t>();
-  EXPECT_TRUE(allocator_.Resize(ptrs_[0], old_layout, new_layout.size()));
+  EXPECT_TRUE(allocator_->Resize(ptrs_[0], old_layout, new_layout.size()));
   ASSERT_NE(ptrs_[0], nullptr);
   UseMemory(ptrs_[0], new_layout.size());
 }
 
 TEST_F(SplitFreeListAllocatorTest, ResizeLargeSmaller) {
   constexpr Layout old_layout = Layout::Of<std::byte[kMaxSize]>();
-  ptrs_[0] = allocator_.Allocate(old_layout);
+  ptrs_[0] = allocator_->Allocate(old_layout);
   ASSERT_NE(ptrs_[0], nullptr);
 
   // Shrinking always succeeds.
   constexpr Layout new_layout = Layout::Of<std::byte[kThreshold]>();
-  EXPECT_TRUE(allocator_.Resize(ptrs_[0], old_layout, new_layout.size()));
+  EXPECT_TRUE(allocator_->Resize(ptrs_[0], old_layout, new_layout.size()));
   ASSERT_NE(ptrs_[0], nullptr);
   UseMemory(ptrs_[0], new_layout.size());
 }
 
 TEST_F(SplitFreeListAllocatorTest, ResizeLargeLarger) {
   constexpr Layout old_layout = Layout::Of<std::byte[kThreshold]>();
-  ptrs_[0] = allocator_.Allocate(old_layout);
+  ptrs_[0] = allocator_->Allocate(old_layout);
   ASSERT_NE(ptrs_[0], nullptr);
 
   // Nothing after ptr, so `Resize` should succeed.
   constexpr Layout new_layout = Layout::Of<std::byte[kMaxSize]>();
-  EXPECT_TRUE(allocator_.Resize(ptrs_[0], old_layout, new_layout.size()));
+  EXPECT_TRUE(allocator_->Resize(ptrs_[0], old_layout, new_layout.size()));
   ASSERT_NE(ptrs_[0], nullptr);
   UseMemory(ptrs_[0], new_layout.size());
 }
 
 TEST_F(SplitFreeListAllocatorTest, ResizeLargeLargerFailure) {
   constexpr Layout old_layout = Layout::Of<std::byte[kThreshold]>();
-  ptrs_[0] = allocator_.Allocate(old_layout);
+  ptrs_[0] = allocator_->Allocate(old_layout);
   ASSERT_NE(ptrs_[0], nullptr);
 
-  ptrs_[1] = allocator_.Allocate(old_layout);
+  ptrs_[1] = allocator_->Allocate(old_layout);
   ASSERT_NE(ptrs_[1], nullptr);
 
   // Memory after ptr is already allocated, so `Resize` should fail.
-  EXPECT_FALSE(allocator_.Resize(ptrs_[0], old_layout, kMaxSize));
+  EXPECT_FALSE(allocator_->Resize(ptrs_[0], old_layout, kMaxSize));
 }
 
 TEST_F(SplitFreeListAllocatorTest, ResizeLargeSmallerAcrossThreshold) {
   constexpr Layout old_layout = Layout::Of<std::byte[kThreshold]>();
-  ptrs_[0] = allocator_.Allocate(old_layout);
+  ptrs_[0] = allocator_->Allocate(old_layout);
   ASSERT_NE(ptrs_[0], nullptr);
 
   // Shrinking succeeds, and the pointer is unchanged even though it is now
   // below the threshold.
   constexpr Layout new_layout = Layout::Of<std::byte[kThreshold / 4]>();
-  EXPECT_TRUE(allocator_.Resize(ptrs_[0], old_layout, new_layout.size()));
+  EXPECT_TRUE(allocator_->Resize(ptrs_[0], old_layout, new_layout.size()));
   ASSERT_NE(ptrs_[0], nullptr);
   UseMemory(ptrs_[0], new_layout.size());
 }
 
 TEST_F(SplitFreeListAllocatorTest, ResizeSmallSmaller) {
   constexpr Layout old_layout = Layout::Of<uint32_t>();
-  ptrs_[0] = allocator_.Allocate(old_layout);
+  ptrs_[0] = allocator_->Allocate(old_layout);
   ASSERT_NE(ptrs_[0], nullptr);
 
   // Shrinking always succeeds.
   constexpr Layout new_layout = Layout::Of<uint8_t>();
-  EXPECT_TRUE(allocator_.Resize(ptrs_[0], old_layout, new_layout.size()));
+  EXPECT_TRUE(allocator_->Resize(ptrs_[0], old_layout, new_layout.size()));
 }
 
 TEST_F(SplitFreeListAllocatorTest, ResizeSmallLarger) {
   // First, allocate a trailing block.
   constexpr Layout layout1 = Layout::Of<std::byte[kThreshold / 4]>();
-  ptrs_[0] = allocator_.Allocate(layout1);
+  ptrs_[0] = allocator_->Allocate(layout1);
   ASSERT_NE(ptrs_[0], nullptr);
 
   // Next allocate the memory to be resized.
   constexpr Layout old_layout = Layout::Of<std::byte[kThreshold / 4]>();
-  ptrs_[1] = allocator_.Allocate(old_layout);
+  ptrs_[1] = allocator_->Allocate(old_layout);
   ASSERT_NE(ptrs_[1], nullptr);
 
   // Now free the trailing block.
-  allocator_.Deallocate(ptrs_[0], layout1);
+  allocator_->Deallocate(ptrs_[0], layout1);
   ptrs_[0] = nullptr;
 
   // And finally, resize. Since the memory after the block is available and big
   // enough, `Resize` should succeed.
   constexpr Layout new_layout = Layout::Of<std::byte[kThreshold / 2]>();
-  EXPECT_TRUE(allocator_.Resize(ptrs_[1], old_layout, new_layout.size()));
+  EXPECT_TRUE(allocator_->Resize(ptrs_[1], old_layout, new_layout.size()));
   ASSERT_NE(ptrs_[1], nullptr);
   UseMemory(ptrs_[1], new_layout.size());
 }
@@ -353,48 +369,48 @@
 TEST_F(SplitFreeListAllocatorTest, ResizeSmallLargerFailure) {
   // First, allocate a trailing block.
   constexpr Layout layout1 = Layout::Of<std::byte[kThreshold / 4]>();
-  ptrs_[0] = allocator_.Allocate(layout1);
+  ptrs_[0] = allocator_->Allocate(layout1);
   ASSERT_NE(ptrs_[0], nullptr);
 
   // Next allocate the memory to be resized.
   constexpr Layout old_layout = Layout::Of<std::byte[kThreshold / 4]>();
-  ptrs_[1] = allocator_.Allocate(old_layout);
+  ptrs_[1] = allocator_->Allocate(old_layout);
   ASSERT_NE(ptrs_[1], nullptr);
 
   // Now free the trailing block.
-  allocator_.Deallocate(ptrs_[0], layout1);
+  allocator_->Deallocate(ptrs_[0], layout1);
   ptrs_[0] = nullptr;
 
   // And finally, resize. Since the memory after the block is available but not
   // big enough, `Resize` should fail.
   size_t new_size = 48;
-  EXPECT_FALSE(allocator_.Resize(ptrs_[1], old_layout, new_size));
+  EXPECT_FALSE(allocator_->Resize(ptrs_[1], old_layout, new_size));
 }
 
 TEST_F(SplitFreeListAllocatorTest, ResizeSmallLargerAcrossThreshold) {
   // First, allocate several trailing block.
   constexpr Layout layout1 = Layout::Of<std::byte[kThreshold / 2]>();
-  ptrs_[0] = allocator_.Allocate(layout1);
+  ptrs_[0] = allocator_->Allocate(layout1);
   ASSERT_NE(ptrs_[0], nullptr);
 
-  ptrs_[1] = allocator_.Allocate(layout1);
+  ptrs_[1] = allocator_->Allocate(layout1);
   ASSERT_NE(ptrs_[1], nullptr);
 
   // Next allocate the memory to be resized.
   constexpr Layout old_layout = Layout::Of<std::byte[kThreshold / 4]>();
-  ptrs_[2] = allocator_.Allocate(old_layout);
+  ptrs_[2] = allocator_->Allocate(old_layout);
   EXPECT_NE(ptrs_[2], nullptr);
 
   // Now free the trailing blocks.
-  allocator_.Deallocate(ptrs_[0], layout1);
+  allocator_->Deallocate(ptrs_[0], layout1);
   ptrs_[0] = nullptr;
-  allocator_.Deallocate(ptrs_[1], layout1);
+  allocator_->Deallocate(ptrs_[1], layout1);
   ptrs_[1] = nullptr;
 
   // Growing succeeds, and the pointer is unchanged even though it is now
   // above the threshold.
   constexpr Layout new_layout = Layout::Of<std::byte[kThreshold]>();
-  EXPECT_TRUE(allocator_.Resize(ptrs_[2], old_layout, new_layout.size()));
+  EXPECT_TRUE(allocator_->Resize(ptrs_[2], old_layout, new_layout.size()));
   ASSERT_NE(ptrs_[2], nullptr);
   UseMemory(ptrs_[2], new_layout.size());
 }
diff --git a/pw_allocator/unique_ptr_test.cc b/pw_allocator/unique_ptr_test.cc
index 9382a2b..c0aa57c 100644
--- a/pw_allocator/unique_ptr_test.cc
+++ b/pw_allocator/unique_ptr_test.cc
@@ -16,18 +16,12 @@
 
 #include "gtest/gtest.h"
 #include "pw_allocator/allocator.h"
-#include "pw_allocator_private/allocator_testing.h"
+#include "pw_allocator/allocator_testing.h"
 
 namespace pw::allocator {
 namespace {
 
-class FakeAllocWithBuffer : public test::FakeAllocator {
- public:
-  FakeAllocWithBuffer() { EXPECT_EQ(Initialize(buffer_), OkStatus()); }
-
- private:
-  std::array<std::byte, 256> buffer_ = {};
-};
+using FakeAllocWithBuffer = test::AllocatorForTestWithBuffer<256>;
 
 TEST(UniquePtr, DefaultInitializationIsNullptr) {
   UniquePtr<int> empty;
@@ -42,7 +36,7 @@
 
 TEST(UniquePtr, OperatorEqNullptrAfterMakeUniqueFails) {
   FakeAllocWithBuffer alloc;
-  std::optional<UniquePtr<int>> ptr_opt = alloc.MakeUnique<int>(5);
+  std::optional<UniquePtr<int>> ptr_opt = alloc->MakeUnique<int>(5);
   ASSERT_TRUE(ptr_opt.has_value());
   UniquePtr<int> ptr = std::move(*ptr_opt);
   EXPECT_TRUE(ptr != nullptr);
@@ -52,7 +46,7 @@
 TEST(UniquePtr, OperatorEqNullptrAfterMakeUniqueNullptrTypeFails) {
   FakeAllocWithBuffer alloc;
   std::optional<UniquePtr<std::nullptr_t>> ptr_opt =
-      alloc.MakeUnique<std::nullptr_t>(nullptr);
+      alloc->MakeUnique<std::nullptr_t>(nullptr);
   ASSERT_TRUE(ptr_opt.has_value());
   UniquePtr<std::nullptr_t> ptr = std::move(*ptr_opt);
   EXPECT_TRUE(ptr != nullptr);
@@ -86,7 +80,7 @@
   FakeAllocWithBuffer alloc;
   MoveOnly mo(6);
   std::optional<UniquePtr<BuiltWithMoveOnly>> ptr =
-      alloc.MakeUnique<BuiltWithMoveOnly>(std::move(mo));
+      alloc->MakeUnique<BuiltWithMoveOnly>(std::move(mo));
   ASSERT_TRUE(ptr.has_value());
   EXPECT_EQ((*ptr)->Value(), 6);
 }
@@ -97,17 +91,17 @@
     std::array<std::byte, 128> mem;
   };
   FakeAllocWithBuffer alloc;
-  std::optional<UniquePtr<LargerSub>> ptr_opt = alloc.MakeUnique<LargerSub>();
+  std::optional<UniquePtr<LargerSub>> ptr_opt = alloc->MakeUnique<LargerSub>();
   ASSERT_TRUE(ptr_opt.has_value());
-  EXPECT_EQ(alloc.allocate_size(), 128ul);
+  EXPECT_EQ(alloc->allocate_size(), 128ul);
   UniquePtr<LargerSub> ptr = std::move(*ptr_opt);
   UniquePtr<Base> base_ptr(std::move(ptr));
 
-  EXPECT_EQ(alloc.deallocate_size(), 0ul);
+  EXPECT_EQ(alloc->deallocate_size(), 0ul);
   // The size that is deallocated here should be the size of the larger
   // subclass, not the size of the smaller base class.
   base_ptr.Reset();
-  EXPECT_EQ(alloc.deallocate_size(), 128ul);
+  EXPECT_EQ(alloc->deallocate_size(), 128ul);
 }
 
 TEST(UniquePtr, MoveAssignsFromSubClassAndFreesTotalSize) {
@@ -116,17 +110,17 @@
     std::array<std::byte, 128> mem;
   };
   FakeAllocWithBuffer alloc;
-  std::optional<UniquePtr<LargerSub>> ptr_opt = alloc.MakeUnique<LargerSub>();
+  std::optional<UniquePtr<LargerSub>> ptr_opt = alloc->MakeUnique<LargerSub>();
   ASSERT_TRUE(ptr_opt.has_value());
-  EXPECT_EQ(alloc.allocate_size(), 128ul);
+  EXPECT_EQ(alloc->allocate_size(), 128ul);
   UniquePtr<LargerSub> ptr = std::move(*ptr_opt);
   UniquePtr<Base> base_ptr = std::move(ptr);
 
-  EXPECT_EQ(alloc.deallocate_size(), 0ul);
+  EXPECT_EQ(alloc->deallocate_size(), 0ul);
   // The size that is deallocated here should be the size of the larger
   // subclass, not the size of the smaller base class.
   base_ptr.Reset();
-  EXPECT_EQ(alloc.deallocate_size(), 128ul);
+  EXPECT_EQ(alloc->deallocate_size(), 128ul);
 }
 
 TEST(UniquePtr, DestructorDestroysAndFrees) {
@@ -141,15 +135,15 @@
   };
   FakeAllocWithBuffer alloc;
   std::optional<UniquePtr<DestructorCounter>> ptr_opt =
-      alloc.MakeUnique<DestructorCounter>(count);
+      alloc->MakeUnique<DestructorCounter>(count);
   ASSERT_TRUE(ptr_opt.has_value());
 
   EXPECT_EQ(count, 0);
-  EXPECT_EQ(alloc.deallocate_size(), 0ul);
+  EXPECT_EQ(alloc->deallocate_size(), 0ul);
   ptr_opt.reset();  // clear the optional, destroying the UniquePtr
   EXPECT_EQ(count, 1);
-  EXPECT_EQ(alloc.deallocate_size(), sizeof(DestructorCounter));
+  EXPECT_EQ(alloc->deallocate_size(), sizeof(DestructorCounter));
 }
 
 }  // namespace
-}  // namespace pw::allocator
\ No newline at end of file
+}  // namespace pw::allocator