[lib/debugger_utils] Add test fixture for running subprograms

Change-Id: I9069b2f9d177c30dd08cb00b109e020f250d4c70
diff --git a/garnet/lib/debugger_utils/BUILD.gn b/garnet/lib/debugger_utils/BUILD.gn
index f0097f0..a9ea860 100644
--- a/garnet/lib/debugger_utils/BUILD.gn
+++ b/garnet/lib/debugger_utils/BUILD.gn
@@ -86,6 +86,8 @@
     "run_all_unittests.cc",
     "sysinfo.cc",
     "sysinfo.h",
+    "test_helper_fixture.cc",
+    "test_helper_fixture.h",
     "threads.cc",
     "threads.h",
     "threads_unittest.cc",
diff --git a/garnet/lib/debugger_utils/jobs_unittest.cc b/garnet/lib/debugger_utils/jobs_unittest.cc
index 96d020f..2b760ce 100644
--- a/garnet/lib/debugger_utils/jobs_unittest.cc
+++ b/garnet/lib/debugger_utils/jobs_unittest.cc
@@ -2,16 +2,13 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#include <lib/fdio/spawn.h>
-#include <lib/fxl/arraysize.h>
-#include <lib/zx/channel.h>
-#include <zircon/processargs.h>
+#include <gtest/gtest.h>
 
 #include "garnet/lib/debugger_utils/jobs.h"
 #include "garnet/lib/debugger_utils/sysinfo.h"
 #include "garnet/lib/debugger_utils/test_helper.h"
+#include "garnet/lib/debugger_utils/test_helper_fixture.h"
 #include "garnet/lib/debugger_utils/util.h"
-#include "gtest/gtest.h"
 
 namespace debugger_utils {
 namespace {
@@ -130,84 +127,48 @@
   EXPECT_EQ(process_depth + 1, thread_depth);
 }
 
-static void BuildTestProcess(const zx::job& parent_job, zx::job* job,
-                             zx::process* process, zx::thread* thread,
-                             zx::channel* channel) {
-  ASSERT_EQ(zx::job::create(parent_job, 0, job), ZX_OK);
+TEST_F(TestWithHelper, JobsTestChildJob) {
+  auto parent_job = GetDefaultJob();
+  zx::job child_job;
+  zx::thread thread;
 
-  const char* argv[] = {
-    kTestHelperPath,
-    nullptr,
-  };
+  ASSERT_EQ(zx::job::create(parent_job, 0, &child_job), ZX_OK);
+  ASSERT_EQ(RunHelperProgram(child_job, kWaitPeerClosedArgv), ZX_OK);
 
   // We need the handle of the main thread of the process for test purposes.
   // Technically, we only need its koid. HOWEVER, we do not obtain the koid
   // via, say, ZX_INFO_PROCESS_THREADS, because that is used by the routine
   // we are testing: |WalkJobTree()|. Try to KISS and just get the thread's
   // handle. Alas it's not that simple.
-  zx::channel their_channel;
-  ASSERT_EQ(zx::channel::create(0, channel, &their_channel), ZX_OK);
-  fdio_spawn_action actions[1];
-  actions[0].action = FDIO_SPAWN_ACTION_ADD_HANDLE;
-  actions[0].h.id = PA_HND(PA_USER0, 0);
-  actions[0].h.handle = their_channel.release();
-  uint32_t flags = FDIO_SPAWN_CLONE_ALL;
-  char err_msg[FDIO_SPAWN_ERR_MSG_MAX_LENGTH];
+  ASSERT_EQ(GetHelperThread(&thread), ZX_OK);
 
-  zx_status_t status = fdio_spawn_etc(job->get(), flags, kTestHelperPath, argv,
-                                      nullptr, arraysize(actions), actions,
-                                      process->reset_and_get_address(),
-                                      err_msg);
-  ASSERT_EQ(status, ZX_OK) << err_msg;
-
-  zx_signals_t pending;
-  ASSERT_EQ(channel->wait_one(ZX_CHANNEL_READABLE, zx::time::infinite(),
-                              &pending), ZX_OK);
-
-  uint32_t actual_bytes, actual_handles;
-  ASSERT_EQ(channel->read(0u, nullptr, 0u, 
-                          &actual_bytes, thread->reset_and_get_address(),
-                          1u, &actual_handles), ZX_OK);
-  EXPECT_EQ(actual_bytes, 0u);
-  EXPECT_EQ(actual_handles, 1u);
-
-  // At this point the inferior is waiting for us to close the channel.
-}
-
-TEST(JobsTest, ChildJob) {
-  auto parent_job = GetDefaultJob();
-  zx::job child_job;
-  zx::process process;
-  zx::thread thread;
-  zx::channel channel;
-
-  BuildTestProcess(parent_job, &child_job, &process, &thread, &channel);
   ASSERT_TRUE(child_job.is_valid());
-  ASSERT_TRUE(process.is_valid());
+  ASSERT_TRUE(process().is_valid());
   ASSERT_TRUE(thread.is_valid());
-  ASSERT_TRUE(channel.is_valid());
-  
-  TestDepth(parent_job, child_job.get(), process.get(), thread.get());
+  ASSERT_TRUE(channel().is_valid());
+
+  TestDepth(parent_job, child_job.get(), process().get(), thread.get());
 }
 
-TEST(JobsTest, RootJob) {
+TEST_F(TestWithHelper, JobsTestRootJob) {
   // Make sure we can find ourselves from the root job.
   // This will likely evolve or be replaced, but it's useful to test
   // current functionality.
   auto search_job = GetRootJob();
   auto parent_job = GetDefaultJob();
   zx::job child_job;
-  zx::process process;
   zx::thread thread;
-  zx::channel channel;
 
-  BuildTestProcess(parent_job, &child_job, &process, &thread, &channel);
+  ASSERT_EQ(zx::job::create(parent_job, 0, &child_job), ZX_OK);
+  ASSERT_EQ(RunHelperProgram(child_job, kWaitPeerClosedArgv), ZX_OK);
+  ASSERT_EQ(GetHelperThread(&thread), ZX_OK);
+
   ASSERT_TRUE(child_job.is_valid());
-  ASSERT_TRUE(process.is_valid());
+  ASSERT_TRUE(process().is_valid());
   ASSERT_TRUE(thread.is_valid());
-  ASSERT_TRUE(channel.is_valid());
+  ASSERT_TRUE(channel().is_valid());
 
-  TestDepth(search_job, child_job.get(), process.get(), thread.get());
+  TestDepth(search_job, child_job.get(), process().get(), thread.get());
 }
 
 TEST(JobsTest, FindProcess) {
diff --git a/garnet/lib/debugger_utils/test_helper_fixture.cc b/garnet/lib/debugger_utils/test_helper_fixture.cc
new file mode 100644
index 0000000..1987d0e
--- /dev/null
+++ b/garnet/lib/debugger_utils/test_helper_fixture.cc
@@ -0,0 +1,91 @@
+// 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 <lib/fdio/spawn.h>
+#include <lib/fxl/arraysize.h>
+#include <lib/fxl/logging.h>
+#include <zircon/processargs.h>
+
+#include "test_helper.h"
+#include "test_helper_fixture.h"
+#include "util.h"
+
+namespace debugger_utils {
+
+const char* const TestWithHelper::kWaitPeerClosedArgv[] = {
+  kTestHelperPath,
+  "wait-peer-closed",
+  nullptr,
+};
+
+void TestWithHelper::SetUp() {
+}
+
+void TestWithHelper::TearDown() {
+  // Closing the channel should cause the helper to terminate, if it
+  // hasn't already.
+  channel_.reset();
+
+  zx_signals_t pending;
+  zx_status_t status =
+    process_.wait_one(ZX_PROCESS_TERMINATED, zx::time::infinite(), &pending);
+  ASSERT_EQ(status, ZX_OK);
+}
+
+zx_status_t TestWithHelper::RunHelperProgram(const zx::job& job,
+                                             const char* const argv[]) {
+  zx::channel our_channel, their_channel;
+  zx_status_t status = zx::channel::create(0, &our_channel, &their_channel);
+  if (status != ZX_OK) {
+    FXL_LOG(ERROR) << "zx::channel::create failed: " << ZxErrorString(status);
+    return status;
+  }
+  fdio_spawn_action actions[1];
+  actions[0].action = FDIO_SPAWN_ACTION_ADD_HANDLE;
+  actions[0].h.id = PA_HND(PA_USER0, 0);
+  actions[0].h.handle = their_channel.release();
+  uint32_t flags = FDIO_SPAWN_CLONE_ALL;
+  char err_msg[FDIO_SPAWN_ERR_MSG_MAX_LENGTH];
+
+  zx::process process;
+  status = fdio_spawn_etc(job.get(), flags, kTestHelperPath, argv, nullptr,
+                          arraysize(actions), actions,
+                          process.reset_and_get_address(), err_msg);
+  if (status != ZX_OK) {
+    FXL_LOG(ERROR) << "fdio_spawn_etc failed: " << ZxErrorString(status)
+                   << ", " << err_msg;
+    return status;
+  }
+
+  process_ = std::move(process);
+  channel_ = std::move(our_channel);
+  return status;
+}
+
+zx_status_t TestWithHelper::GetHelperThread(zx::thread* out_thread) {
+  zx_signals_t pending;
+  zx_status_t status =
+    channel_.wait_one(ZX_CHANNEL_READABLE, zx::time::infinite(), &pending);
+  if (status != ZX_OK) {
+    FXL_LOG(ERROR) << "channel->wait_one failed: " << ZxErrorString(status);
+    return status;
+  }
+
+  uint32_t actual_bytes, actual_handles;
+  status = channel_.read(0u, nullptr, 0u,
+                         &actual_bytes, out_thread->reset_and_get_address(),
+                         1u, &actual_handles);
+  if (status != ZX_OK) {
+    FXL_LOG(ERROR) << "channel->read failed: " << ZxErrorString(status);
+    return status;
+  }
+  EXPECT_EQ(actual_bytes, 0u);
+  EXPECT_EQ(actual_handles, 1u);
+
+  // At this point the inferior is generally waiting for us to close the
+  // channel.
+  return ZX_OK;
+}
+
+}  // namespace debugger_utils
diff --git a/garnet/lib/debugger_utils/test_helper_fixture.h b/garnet/lib/debugger_utils/test_helper_fixture.h
new file mode 100644
index 0000000..25ddd98
--- /dev/null
+++ b/garnet/lib/debugger_utils/test_helper_fixture.h
@@ -0,0 +1,44 @@
+// 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.
+
+#ifndef GARNET_LIB_DEBUGGER_UTILS_TEST_HELPER_FIXTURE_H_
+#define GARNET_LIB_DEBUGGER_UTILS_TEST_HELPER_FIXTURE_H_
+
+#include <gtest/gtest.h>
+#include <lib/zx/channel.h>
+#include <lib/zx/job.h>
+#include <lib/zx/process.h>
+#include <lib/zx/thread.h>
+
+namespace debugger_utils {
+
+class TestWithHelper : public ::testing::Test {
+ public:
+  // Pass this for |argv| to have the inferior send back a handle to its
+  // main thread and then wait for us to close the channel.
+  static const char* const kWaitPeerClosedArgv[];
+
+  // ::testing::Test overrides
+  void SetUp() override;
+  void TearDown() override;
+
+  // Call this to run the helper program with |argv| under |job|.
+  zx_status_t RunHelperProgram(const zx::job& job, const char* const argv[]);
+
+  // Call this after |RunHelperProgram| to obtain the handle of the main
+  // thread in the helper program. This assumes the helper program is
+  // following the necessary protocol to send the handle.
+  zx_status_t GetHelperThread(zx::thread* out_thread);
+
+  const zx::process& process() const { return process_; }
+  const zx::channel& channel() const { return channel_; }
+
+ private:
+  zx::process process_;
+  zx::channel channel_;
+};
+
+}  // namespace debugger_utils
+
+#endif  // GARNET_LIB_DEBUGGER_UTILS_TEST_HELPER_FIXTURE_H_
diff --git a/garnet/lib/debugger_utils/util_zx_unittest.cc b/garnet/lib/debugger_utils/util_zx_unittest.cc
index 627b8c1..b1a1f3c 100644
--- a/garnet/lib/debugger_utils/util_zx_unittest.cc
+++ b/garnet/lib/debugger_utils/util_zx_unittest.cc
@@ -32,7 +32,7 @@
   zx::process process;
   zx::job job{GetDefaultJob()};
 
-  const char* argv[] = {
+  static const char* const argv[] = {
     kTestHelperPath,
     nullptr,
   };