[memory] Don't fail entirely when a process goes away.

Right now we exit entirely if a process disappears in the middle
of interrogation for vmos. This change instead ignores the process
for that capture.

Test: Added Capture unittests, including ones for these cases.
Change-Id: I8e5a5703a786549ca0a43a2173eb4f4c4d5e69b3
diff --git a/src/developer/memory/metrics/capture.cc b/src/developer/memory/metrics/capture.cc
index d199145..cbe5749 100644
--- a/src/developer/memory/metrics/capture.cc
+++ b/src/developer/memory/metrics/capture.cc
@@ -15,114 +15,87 @@
 #include <zircon/status.h>
 
 namespace memory {
-namespace {
 
-zx_status_t get_root_resource(zx_handle_t* root_resource) {
-  const char* sysinfo = "/dev/misc/sysinfo";
-  int fd = open(sysinfo, O_RDWR);
-  if (fd < 0) {
-    FXL_LOG(ERROR) << "Cannot open sysinfo: " << strerror(errno);
-    return ZX_ERR_NOT_FOUND;
-  }
-
-  zx::channel channel;
-  zx_status_t status =
-      fdio_get_service_handle(fd, channel.reset_and_get_address());
-  if (status != ZX_OK) {
-    FXL_LOG(ERROR) << "Cannot obtain sysinfo channel: "
-                   << zx_status_get_string(status);
-    return status;
-  }
-
-  zx_status_t fidl_status = fuchsia_sysinfo_DeviceGetRootResource(
-      channel.get(), &status, root_resource);
-  if (fidl_status != ZX_OK) {
-    FXL_LOG(ERROR) << "Cannot obtain root resource: "
-                   << zx_status_get_string(fidl_status);
-    return fidl_status;
-  } else if (status != ZX_OK) {
-    FXL_LOG(ERROR) << "Cannot obtain root resource: "
-                   << zx_status_get_string(status);
-    return status;
-  }
-  return ZX_OK;
-}
-}  // namespace
-
-class Capture::ProcessGetter final : public TaskEnumerator {
- public:
-  ProcessGetter(CaptureLevel level, zx_koid_t self_koid, Capture& capture)
-    : level_(level), self_koid_(self_koid), capture_(capture) {}
-
-  zx_status_t OnProcess(
-      int depth,
-      zx_handle_t handle,
-      zx_koid_t koid,
-      zx_koid_t parent_koid) override {
-    Process process = { .koid = koid };
-    zx_status_t s = zx_object_get_property(
-        handle, ZX_PROP_NAME, process.name, ZX_MAX_NAME_LEN);
-    if (s != ZX_OK) {
-      return s;
-    }
-    s = zx_object_get_info(handle, ZX_INFO_TASK_STATS,
-                           &process.stats, sizeof(process.stats),
-                           nullptr, nullptr);
-    if (s != ZX_OK) {
-      return s;
-    }
-
-    if (level_ == PROCESS) {
-      return ZX_OK;
-    }
-
-    if (koid == self_koid_) {
-      return ZX_OK;
-    }
-
-    size_t num_vmos;
-    s = zx_object_get_info(handle, ZX_INFO_PROCESS_VMOS,
-                           nullptr, 0, nullptr, &num_vmos);
-    if (s != ZX_OK) {
-      return s;
-    }
-    zx_info_vmo_t* vmos = new zx_info_vmo_t[num_vmos];
-    s = zx_object_get_info(handle, ZX_INFO_PROCESS_VMOS,
-                           vmos, num_vmos * sizeof(zx_info_vmo_t),
-                           &num_vmos, nullptr);
-    if (s != ZX_OK) {
-      delete[] vmos;
-      return s;
-    }
-    process.vmos.reserve(num_vmos);
-    for (size_t i = 0; i < num_vmos; i++) {
-      zx_koid_t vmo_koid = vmos[i].koid;
-      capture_.koid_to_vmo_.emplace(vmo_koid, vmos[i]);
-      process.vmos.push_back(vmo_koid);
-    }
-    delete[] vmos;
-    capture_.koid_to_process_.emplace(koid, process);
-    return ZX_OK;
-  }
-
+class OSImpl : public OS, public TaskEnumerator {
  private:
-  CaptureLevel level_;
-  zx_koid_t self_koid_;
-  Capture& capture_;
+  zx_status_t GetRootResource(zx_handle_t* root_resource) override {
+    const char* sysinfo = "/dev/misc/sysinfo";
+    int fd = open(sysinfo, O_RDWR);
+    if (fd < 0) {
+      return ZX_ERR_NOT_FOUND;
+    }
+
+    zx::channel channel;
+    zx_status_t status =
+        fdio_get_service_handle(fd, channel.reset_and_get_address());
+    if (status != ZX_OK) {
+      return status;
+    }
+
+    zx_status_t fidl_status = fuchsia_sysinfo_DeviceGetRootResource(
+        channel.get(), &status, root_resource);
+    if (fidl_status != ZX_OK) {
+      return fidl_status;
+    }
+    return status;
+  }
+
+  zx_handle_t ProcessSelf() override { return zx_process_self(); }
+  zx_time_t GetMonotonic() override { return zx_clock_get_monotonic(); }
+
+  zx_status_t GetProcesses(
+      fit::function<zx_status_t(int, zx_handle_t, zx_koid_t, zx_koid_t)> cb)
+      override {
+    cb_ = std::move(cb);
+    return WalkRootJobTree();
+  }
+
+  zx_status_t OnProcess(int depth, zx_handle_t handle, zx_koid_t koid,
+                        zx_koid_t parent_koid) override {
+    return cb_(depth, handle, koid, parent_koid);
+  }
+
+  zx_status_t GetProperty(
+      zx_handle_t handle, uint32_t property, void* value, size_t name_len)
+     override {
+    return zx_object_get_property(handle, property, value, name_len);
+  }
+
+  zx_status_t GetInfo(
+      zx_handle_t handle,
+      uint32_t topic,
+      void* buffer,
+      size_t buffer_size,
+      size_t* actual,
+      size_t* avail) override {
+    return zx_object_get_info(
+        handle, topic, buffer, buffer_size, actual, avail);
+  }
 
   bool has_on_process() const final { return true; }
+
+  fit::function<zx_status_t(
+          int /* depth */,
+          zx_handle_t /* handle */,
+          zx_koid_t /* koid */,
+          zx_koid_t /* parent_koid */)> cb_;
 };
 
 // static.
 zx_status_t Capture::GetCaptureState(CaptureState& state) {
-  zx_status_t err = get_root_resource(&state.root);
+  OSImpl osImpl;
+  return GetCaptureState(state, osImpl);
+}
+
+zx_status_t Capture::GetCaptureState(CaptureState& state, OS& os) {
+  zx_status_t err = os.GetRootResource(&state.root);
   if (err != ZX_OK) {
     return err;
   }
 
   zx_info_handle_basic_t info;
-  err = zx_object_get_info(zx_process_self(), ZX_INFO_HANDLE_BASIC,
-                           &info, sizeof(info), nullptr, nullptr);
+  err = os.GetInfo(os.ProcessSelf(), ZX_INFO_HANDLE_BASIC,
+                   &info, sizeof(info), nullptr, nullptr);
   if (err != ZX_OK) {
     return err;
   }
@@ -132,13 +105,21 @@
 }
 
 // static.
+zx_status_t Capture::GetCapture(Capture& capture, const CaptureState& state,
+                                CaptureLevel level) {
+  OSImpl osImpl;
+  return GetCapture(capture, state, level, osImpl);
+}
+
 zx_status_t Capture::GetCapture(
-    Capture& capture, const CaptureState& state, CaptureLevel level) {
-  capture.time_ = zx_clock_get_monotonic();
-  zx_status_t err = zx_object_get_info(
-      state.root, ZX_INFO_KMEM_STATS,
-      &capture.kmem_, sizeof(capture.kmem_),
-      NULL, NULL);
+    Capture& capture,
+    const CaptureState& state,
+    CaptureLevel level,
+    OS& os) {
+  capture.time_ = os.GetMonotonic();
+  zx_status_t err = os.GetInfo(
+      state.root, ZX_INFO_KMEM_STATS, &capture.kmem_, sizeof(capture.kmem_),
+      nullptr, nullptr);
   if (err != ZX_OK) {
     return err;
   }
@@ -147,12 +128,58 @@
     return ZX_OK;
   }
 
-  ProcessGetter getter(level, state.self_koid, capture);
-  err = getter.WalkRootJobTree();
-  if (err != ZX_OK) {
-    return err;
-  }
-  return ZX_OK;
+  err = os.GetProcesses([state, &capture, level, &os](
+      int depth,
+      zx_handle_t handle,
+      zx_koid_t koid,
+      zx_koid_t parent_koid) {
+    Process process = { .koid = koid };
+    zx_status_t s = os.GetProperty(
+        handle, ZX_PROP_NAME, process.name, ZX_MAX_NAME_LEN);
+    if (s != ZX_OK) {
+      return s == ZX_ERR_BAD_STATE ? ZX_OK : s;
+    }
+    s = os.GetInfo(handle, ZX_INFO_TASK_STATS,
+                   &process.stats, sizeof(process.stats),
+                   nullptr, nullptr);
+    if (s != ZX_OK) {
+      return s == ZX_ERR_BAD_STATE ? ZX_OK : s;
+    }
+
+    if (level == PROCESS) {
+      capture.koid_to_process_.emplace(koid, process);
+      return ZX_OK;
+    }
+
+    if (koid == state.self_koid) {
+      return ZX_OK;
+    }
+
+    size_t num_vmos;
+    s = os.GetInfo(handle, ZX_INFO_PROCESS_VMOS,
+                   nullptr, 0, nullptr, &num_vmos);
+    if (s != ZX_OK) {
+      return s == ZX_ERR_BAD_STATE ? ZX_OK : s;
+    }
+    auto vmos = new zx_info_vmo_t[num_vmos];
+    s = os.GetInfo(handle, ZX_INFO_PROCESS_VMOS,
+                   vmos, num_vmos * sizeof(zx_info_vmo_t),
+                   &num_vmos, nullptr);
+    if (s != ZX_OK) {
+      delete[] vmos;
+      return s == ZX_ERR_BAD_STATE ? ZX_OK : s;
+    }
+    process.vmos.reserve(num_vmos);
+    for (size_t i = 0; i < num_vmos; i++) {
+      zx_koid_t vmo_koid = vmos[i].koid;
+      capture.koid_to_vmo_.emplace(vmo_koid, vmos[i]);
+      process.vmos.push_back(vmo_koid);
+    }
+    delete[] vmos;
+    capture.koid_to_process_.emplace(koid, process);
+    return ZX_OK;
+  });
+  return err;
 }
 
 }  // namespace memory
diff --git a/src/developer/memory/metrics/capture.h b/src/developer/memory/metrics/capture.h
index 24b890b..4bf8fa4 100644
--- a/src/developer/memory/metrics/capture.h
+++ b/src/developer/memory/metrics/capture.h
@@ -5,8 +5,9 @@
 #ifndef SRC_DEVELOPER_MEMORY_METRICS_CAPTURE_H_
 #define SRC_DEVELOPER_MEMORY_METRICS_CAPTURE_H_
 
-#include <src/lib/fxl/macros.h>
+#include <lib/fit/function.h>
 #include <lib/zx/time.h>
+#include <src/lib/fxl/macros.h>
 #include <unordered_map>
 #include <vector>
 #include <zircon/syscalls.h>
@@ -28,6 +29,28 @@
   zx_koid_t self_koid;
 };
 
+class OS {
+ public:
+  virtual zx_status_t GetRootResource(zx_handle_t* root_resource) = 0;
+  virtual zx_handle_t ProcessSelf() = 0;
+  virtual zx_time_t GetMonotonic() = 0;
+  virtual zx_status_t GetProcesses(
+      fit::function<zx_status_t(
+          int /* depth */,
+          zx_handle_t /* handle */,
+          zx_koid_t /* koid */,
+          zx_koid_t /* parent_koid */)> cb) = 0;
+  virtual zx_status_t GetProperty(
+      zx_handle_t handle, uint32_t property, void* value, size_t name_len) = 0;
+  virtual zx_status_t GetInfo(
+      zx_handle_t handle,
+      uint32_t topic,
+      void* buffer,
+      size_t buffer_size,
+      size_t* actual,
+      size_t* avail) = 0;
+};
+
 class Capture {
  public:
   static zx_status_t GetCaptureState(CaptureState& state);
@@ -55,6 +78,9 @@
   }
 
  private:
+  static zx_status_t GetCaptureState(CaptureState& state, OS& os);
+  static zx_status_t GetCapture(
+      Capture& capture, const CaptureState& state, CaptureLevel level, OS& os);
   zx_time_t time_;
   zx_info_kmem_stats_t kmem_;
   std::unordered_map<zx_koid_t, const Process> koid_to_process_;
diff --git a/src/developer/memory/metrics/tests/BUILD.gn b/src/developer/memory/metrics/tests/BUILD.gn
index 55106f2..a0a29bf3 100644
--- a/src/developer/memory/metrics/tests/BUILD.gn
+++ b/src/developer/memory/metrics/tests/BUILD.gn
@@ -14,6 +14,7 @@
   testonly = true
   output_name = "memory_metrics_unittests"
   sources = [
+    "capture_unittest.cc",
     "printer_unittest.cc",
     "summary_unittest.cc",
     "test_utils.cc",
diff --git a/src/developer/memory/metrics/tests/capture_unittest.cc b/src/developer/memory/metrics/tests/capture_unittest.cc
new file mode 100644
index 0000000..067f064
--- /dev/null
+++ b/src/developer/memory/metrics/tests/capture_unittest.cc
@@ -0,0 +1,270 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <gtest/gtest.h>
+#include <lib/gtest/test_loop_fixture.h>
+#include <zircon/types.h>
+
+#include "src/developer/memory/metrics/capture.h"
+#include "src/developer/memory/metrics/tests/test_utils.h"
+
+namespace memory {
+namespace test {
+
+using CaptureUnitTest = gtest::TestLoopFixture;
+
+const static zx_info_kmem_stats _kmem = {
+  .total_bytes = 300,
+  .free_bytes = 100,
+  .wired_bytes = 10,
+  .total_heap_bytes = 20,
+  .free_heap_bytes = 30,
+  .vmo_bytes = 40,
+  .mmu_overhead_bytes = 50,
+  .ipc_bytes = 60,
+  .other_bytes = 70
+};
+const static GetInfoResponse kmem_info = {
+  TestUtils::kRootHandle, ZX_INFO_KMEM_STATS, &_kmem, sizeof(_kmem), 1, ZX_OK
+};
+
+const static zx_info_handle_basic_t _self = {.koid = TestUtils::kSelfKoid };
+const static GetInfoResponse self_info = {
+  TestUtils::kSelfHandle, ZX_INFO_HANDLE_BASIC, &_self, sizeof(_self), 1, ZX_OK
+};
+
+const zx_koid_t proc_koid = 10;
+const zx_handle_t proc_handle = 100;
+const char proc_name[] = "P1";
+const static zx_info_task_stats _proc = {};
+const static GetInfoResponse proc_info =
+    {proc_handle, ZX_INFO_TASK_STATS, &_proc, sizeof(_proc), 1, ZX_OK};
+const static GetPropertyResponse proc_prop =
+    {proc_handle, ZX_PROP_NAME, proc_name, sizeof(proc_name), ZX_OK};
+const static GetProcessesCallback proc_cb =
+    {1, proc_handle, proc_koid, 0};
+
+const zx_koid_t proc2_koid = 20;
+const zx_handle_t proc2_handle = 200;
+const char proc2_name[] = "P2";
+const static zx_info_task_stats _proc2 = {};
+const static GetInfoResponse proc2_info =
+    {proc2_handle, ZX_INFO_TASK_STATS, &_proc2, sizeof(_proc2), 1, ZX_OK};
+const static GetPropertyResponse proc2_prop =
+    {proc2_handle, ZX_PROP_NAME, proc2_name, sizeof(proc2_name), ZX_OK};
+const static GetProcessesCallback proc2_cb =
+    {1, proc2_handle, proc2_koid, 0};
+
+const zx_koid_t vmo_koid = 1000;
+const uint64_t vmo_size = 10000;
+const char vmo_name[] = "V1";
+const static zx_info_vmo_t _vmo = {
+  .koid = vmo_koid,
+  .name = "V1",
+  .size_bytes = vmo_size,
+};
+const static GetInfoResponse vmos_info =
+    {proc_handle, ZX_INFO_PROCESS_VMOS, &_vmo, sizeof(_vmo), 1, ZX_OK};
+
+const zx_koid_t vmo2_koid = 2000;
+const uint64_t vmo2_size = 20000;
+const char vmo2_name[] = "V2";
+const static zx_info_vmo_t _vmo2 = {
+  .koid = vmo2_koid,
+  .name = "V2",
+  .size_bytes = vmo2_size,
+};
+const static GetInfoResponse vmos2_info =
+    {proc2_handle, ZX_INFO_PROCESS_VMOS, &_vmo2, sizeof(_vmo2), 1, ZX_OK};
+
+TEST_F(CaptureUnitTest, KMEM) {
+  Capture c;
+  auto ret = TestUtils::GetCapture(c, KMEM, {
+    .get_info = {self_info, kmem_info},
+  });
+  EXPECT_EQ(ZX_OK, ret);
+  auto const& got_kmem = c.kmem();
+  EXPECT_EQ(_kmem.total_bytes, got_kmem.total_bytes);
+}
+
+TEST_F(CaptureUnitTest, PROCESS) {
+  Capture c;
+  auto ret = TestUtils::GetCapture(c, PROCESS, {
+    .get_info = {self_info, kmem_info, proc_info},
+    .get_processes = {{ZX_OK, {proc_cb}}},
+    .get_property = {proc_prop}
+  });
+  EXPECT_EQ(ZX_OK, ret);
+  EXPECT_EQ(1U, c.koid_to_process().size());
+  auto const& process = c.process_for_koid(proc_koid);
+  EXPECT_EQ(proc_koid, process.koid);
+  EXPECT_STREQ(proc_name, process.name);
+}
+
+TEST_F(CaptureUnitTest, VMO) {
+  Capture c;
+  auto ret = TestUtils::GetCapture(c, VMO, {
+    .get_info = {self_info, kmem_info, proc_info, vmos_info, vmos_info},
+    .get_processes = {{ZX_OK, {proc_cb}}},
+    .get_property = {proc_prop}
+  });
+  EXPECT_EQ(ZX_OK, ret);
+  EXPECT_EQ(1U, c.koid_to_process().size());
+  auto const& process = c.process_for_koid(proc_koid);
+  EXPECT_EQ(proc_koid, process.koid);
+  EXPECT_STREQ(proc_name, process.name);
+  EXPECT_EQ(1U, process.vmos.size());
+  EXPECT_EQ(1U, c.koid_to_vmo().size());
+  EXPECT_EQ(vmo_koid, process.vmos[0]);
+  auto const& vmo = c.vmo_for_koid(vmo_koid);
+  EXPECT_EQ(vmo_koid, vmo.koid);
+  EXPECT_EQ(vmo_size, vmo.size_bytes);
+  EXPECT_STREQ(vmo_name, vmo.name);
+}
+
+TEST_F(CaptureUnitTest, VMODouble) {
+  Capture c;
+  auto ret = TestUtils::GetCapture(c, VMO, {
+    .get_info = {
+      self_info,
+      kmem_info,
+      proc_info,
+      vmos_info,
+      vmos_info,
+      proc2_info,
+      vmos2_info,
+      vmos2_info,
+    },
+    .get_processes = {{ZX_OK, {proc_cb, proc2_cb}}},
+    .get_property = {proc_prop, proc2_prop}
+  });
+  EXPECT_EQ(ZX_OK, ret);
+  EXPECT_EQ(2U, c.koid_to_process().size());
+  EXPECT_EQ(2U, c.koid_to_vmo().size());
+
+  auto const& process = c.process_for_koid(proc_koid);
+  EXPECT_EQ(proc_koid, process.koid);
+  EXPECT_STREQ(proc_name, process.name);
+  EXPECT_EQ(1U, process.vmos.size());
+  EXPECT_EQ(vmo_koid, process.vmos[0]);
+  auto const& vmo = c.vmo_for_koid(vmo_koid);
+  EXPECT_EQ(vmo_koid, vmo.koid);
+  EXPECT_EQ(vmo_size, vmo.size_bytes);
+  EXPECT_STREQ(vmo_name, vmo.name);
+
+  auto const& process2 = c.process_for_koid(proc2_koid);
+  EXPECT_EQ(proc2_koid, process2.koid);
+  EXPECT_STREQ(proc2_name, process2.name);
+  EXPECT_EQ(1U, process2.vmos.size());
+  EXPECT_EQ(vmo2_koid, process2.vmos[0]);
+  auto const& vmo2 = c.vmo_for_koid(vmo2_koid);
+  EXPECT_EQ(vmo2_koid, vmo2.koid);
+  EXPECT_EQ(vmo2_size, vmo2.size_bytes);
+  EXPECT_STREQ(vmo2_name, vmo2.name);
+}
+
+TEST_F(CaptureUnitTest, ProcessPropBadState) {
+  // If the process disappears we should ignore it and continue.
+  Capture c;
+  auto ret = TestUtils::GetCapture(c, PROCESS, {
+    .get_info = {self_info, kmem_info, proc2_info},
+    .get_processes = {{ZX_OK, {proc_cb, proc2_cb}}},
+    .get_property = {
+      {proc_handle, ZX_PROP_NAME, nullptr, 0, ZX_ERR_BAD_STATE},
+      proc2_prop
+    }
+  });
+  EXPECT_EQ(ZX_OK, ret);
+  EXPECT_EQ(1U, c.koid_to_process().size());
+  auto const& process = c.process_for_koid(proc2_koid);
+  EXPECT_EQ(proc2_koid, process.koid);
+  EXPECT_STREQ(proc2_name, process.name);
+}
+
+TEST_F(CaptureUnitTest, ProcessInfoBadState) {
+  // If the process disappears we should ignore it and continue.
+  Capture c;
+  auto ret = TestUtils::GetCapture(c, PROCESS, {
+    .get_info = {
+      self_info,
+      kmem_info,
+      {proc_handle, ZX_INFO_TASK_STATS,
+       &_proc, sizeof(_proc), 1, ZX_ERR_BAD_STATE},
+      proc2_info
+    },
+    .get_processes = {{ZX_OK, {proc_cb, proc2_cb}}},
+    .get_property = {proc_prop, proc2_prop},
+  });
+  EXPECT_EQ(ZX_OK, ret);
+  EXPECT_EQ(1U, c.koid_to_process().size());
+  auto const& process = c.process_for_koid(proc2_koid);
+  EXPECT_EQ(proc2_koid, process.koid);
+  EXPECT_STREQ(proc2_name, process.name);
+}
+
+TEST_F(CaptureUnitTest, VMOCountBadState) {
+  // If the process disappears we should ignore it and continue.
+  Capture c;
+  auto ret = TestUtils::GetCapture(c, VMO, {
+    .get_info = {
+      self_info,
+      kmem_info,
+      proc_info,
+      {proc_handle, ZX_INFO_PROCESS_VMOS,
+       &_vmo, sizeof(_vmo), 1, ZX_ERR_BAD_STATE},
+      proc2_info,
+      vmos2_info,
+      vmos2_info
+    },
+    .get_processes = {{ZX_OK, {proc_cb, proc2_cb}}},
+    .get_property = {proc_prop, proc2_prop}
+  });
+  EXPECT_EQ(ZX_OK, ret);
+  EXPECT_EQ(1U, c.koid_to_process().size());
+  auto const& process = c.process_for_koid(proc2_koid);
+  EXPECT_EQ(proc2_koid, process.koid);
+  EXPECT_STREQ(proc2_name, process.name);
+  EXPECT_EQ(1U, process.vmos.size());
+  EXPECT_EQ(1U, c.koid_to_vmo().size());
+  EXPECT_EQ(vmo2_koid, process.vmos[0]);
+  auto const& vmo = c.vmo_for_koid(vmo2_koid);
+  EXPECT_EQ(vmo2_koid, vmo.koid);
+  EXPECT_EQ(vmo2_size, vmo.size_bytes);
+  EXPECT_STREQ(vmo2_name, vmo.name);
+}
+
+TEST_F(CaptureUnitTest, VMOGetBadState) {
+  // If the process disappears we should ignore it and continue.
+  Capture c;
+  auto ret = TestUtils::GetCapture(c, VMO, {
+    .get_info = {
+      self_info,
+      kmem_info,
+      proc_info,
+      vmos_info,
+      {proc_handle, ZX_INFO_PROCESS_VMOS,
+       &_vmo, sizeof(_vmo), 1, ZX_ERR_BAD_STATE},
+      proc2_info,
+      vmos2_info,
+      vmos2_info
+    },
+    .get_processes = {{ZX_OK, {proc_cb, proc2_cb}}},
+    .get_property = {proc_prop, proc2_prop}
+  });
+  EXPECT_EQ(ZX_OK, ret);
+  EXPECT_EQ(1U, c.koid_to_process().size());
+  auto const& process = c.process_for_koid(proc2_koid);
+  EXPECT_EQ(proc2_koid, process.koid);
+  EXPECT_STREQ(proc2_name, process.name);
+  EXPECT_EQ(1U, process.vmos.size());
+  EXPECT_EQ(1U, c.koid_to_vmo().size());
+  EXPECT_EQ(vmo2_koid, process.vmos[0]);
+  auto const& vmo = c.vmo_for_koid(vmo2_koid);
+  EXPECT_EQ(vmo2_koid, vmo.koid);
+  EXPECT_EQ(vmo2_size, vmo.size_bytes);
+  EXPECT_STREQ(vmo2_name, vmo.name);
+}
+
+}  // namespace test
+}  // namespace memory
diff --git a/src/developer/memory/metrics/tests/test_utils.cc b/src/developer/memory/metrics/tests/test_utils.cc
index d614c07..dc8ba1a 100644
--- a/src/developer/memory/metrics/tests/test_utils.cc
+++ b/src/developer/memory/metrics/tests/test_utils.cc
@@ -4,8 +4,94 @@
 
 #include "src/developer/memory/metrics/tests/test_utils.h"
 
+#include <gtest/gtest.h>
+
 namespace memory {
 
+const zx_handle_t TestUtils::kRootHandle = 1;
+const zx_handle_t TestUtils::kSelfHandle = 2;
+const zx_koid_t TestUtils::kSelfKoid = 3;
+
+class MockOS : public OS {
+ public:
+  MockOS(OsResponses responses)
+      : responses_(responses),
+        i_get_processes_(0),
+        i_get_property_(0),
+        i_get_info_(0),
+        clock_(0) {}
+
+ private:
+  zx_status_t GetRootResource(zx_handle_t* root_resource) override {
+    *root_resource = TestUtils::kRootHandle;
+    return ZX_OK;
+  }
+
+  zx_handle_t ProcessSelf() override {
+    return TestUtils::kSelfHandle;
+  }
+
+  zx_time_t GetMonotonic() override {
+    return clock_++;
+  }
+
+  zx_status_t GetProcesses(
+      fit::function<zx_status_t(int, zx_handle_t, zx_koid_t, zx_koid_t)> cb)
+      override {
+    auto const& r = responses_.get_processes.at(i_get_processes_++);
+    for (auto const& c : r.callbacks) {
+      auto ret = cb(c.depth, c.handle, c.koid, c.parent_koid);
+      if (ret != ZX_OK) {
+        return ret;
+      }
+    }
+    return r.ret;
+  }
+
+  zx_status_t GetProperty(
+      zx_handle_t handle, uint32_t property, void* value, size_t name_len)
+     override {
+    auto const& r = responses_.get_property.at(i_get_property_++);
+    EXPECT_EQ(r.handle, handle);
+    EXPECT_EQ(r.property, property);
+    auto len = std::min(name_len, r.value_len);
+    memcpy(value, r.value, len);
+    return r.ret;
+  }
+
+  zx_status_t GetInfo(
+      zx_handle_t handle,
+      uint32_t topic,
+      void* buffer,
+      size_t buffer_size,
+      size_t* actual,
+      size_t* avail) override {
+    auto const& r = responses_.get_info.at(i_get_info_++);
+    EXPECT_EQ(r.handle, handle);
+    EXPECT_EQ(r.topic, topic);
+    size_t num_copied = 0;
+    if (buffer != nullptr) {
+      num_copied = std::min(r.value_count, buffer_size / r.value_size);
+      memcpy(buffer, r.values, num_copied * r.value_size);
+    }
+    if (actual != nullptr) {
+      *actual = num_copied;
+    }
+    if (avail != nullptr) {
+      if (num_copied < r.value_count) {
+        *avail = r.value_count - num_copied;
+      } else {
+        *avail = 0;
+      }
+    }
+    return r.ret;
+  }
+
+  OsResponses responses_;
+  uint32_t i_get_processes_, i_get_property_, i_get_info_;
+  zx_time_t clock_;
+};
+
 // static.
 void TestUtils::CreateCapture(memory::Capture& capture,
                               const CaptureTemplate& t) {
@@ -27,4 +113,14 @@
        [](ProcessSummary a, ProcessSummary b) { return a.koid() < b.koid(); } );
   return summaries;
 }
+
+zx_status_t TestUtils::GetCapture(
+      Capture& capture, CaptureLevel level, const OsResponses& r) {
+  MockOS os(r);
+  CaptureState state;
+  zx_status_t ret = Capture::GetCaptureState(state, os);
+  EXPECT_EQ(ZX_OK, ret);
+  return Capture::GetCapture(capture, state, level, os);
+}
+
 }  // namespace memory
diff --git a/src/developer/memory/metrics/tests/test_utils.h b/src/developer/memory/metrics/tests/test_utils.h
index f73401f..c573791 100644
--- a/src/developer/memory/metrics/tests/test_utils.h
+++ b/src/developer/memory/metrics/tests/test_utils.h
@@ -19,9 +19,50 @@
   std::vector<Process> processes;
 };
 
+struct GetProcessesCallback {
+  int depth;
+  zx_handle_t handle;
+  zx_koid_t koid;
+  zx_koid_t parent_koid;
+};
+
+struct GetProcessesResponse {
+  zx_status_t ret;
+  std::vector<GetProcessesCallback> callbacks;
+};
+
+struct GetPropertyResponse {
+  zx_handle_t handle;
+  uint32_t property;
+  const void* value;
+  size_t value_len;
+  zx_status_t ret;
+};
+
+struct GetInfoResponse {
+  zx_handle_t handle;
+  uint32_t topic;
+  const void* values;
+  size_t value_size;
+  size_t value_count;
+  zx_status_t ret;
+};
+
+struct OsResponses {
+  const std::vector<GetProcessesResponse> get_processes;
+  const std::vector<GetPropertyResponse> get_property;
+  const std::vector<GetInfoResponse> get_info;
+};
+
 class TestUtils {
  public:
+  const static zx_handle_t kRootHandle;
+  const static zx_handle_t kSelfHandle;
+  const static zx_koid_t kSelfKoid;
+
   static void CreateCapture(memory::Capture& capture, const CaptureTemplate& t);
+  static zx_status_t GetCapture(
+      Capture& capture, CaptureLevel level, const OsResponses& r);
 
   // Sorted by koid.
   static std::vector<ProcessSummary> GetProcessSummaries(