ART: Clean up test exec code

Add a helper for fork+exec of another program and collection of
that process' output. Use the helper in other code.

Clean up some tests. Move away from global #ifdef ARCH and
disable tests with the usual-style macros so that it's easier
to see refactoring issues immediately.

Test: mmma
Test: m test-art-host
Change-Id: Ic450e8a3bb24fc6fe423c0e1e007eb0bb34e22b4
diff --git a/dex2oat/dex2oat_image_test.cc b/dex2oat/dex2oat_image_test.cc
index ae8e1b7..4247e17 100644
--- a/dex2oat/dex2oat_image_test.cc
+++ b/dex2oat/dex2oat_image_test.cc
@@ -186,48 +186,15 @@
     return RunDex2Oat(argv, error_msg);
   }
 
-  int RunDex2Oat(const std::vector<std::string>& args, std::string* error_msg) {
-    int link[2];
-
-    if (pipe(link) == -1) {
+  bool RunDex2Oat(const std::vector<std::string>& args, std::string* error_msg) {
+    // We only want fatal logging for the error message.
+    auto post_fork_fn = []() { return setenv("ANDROID_LOG_TAGS", "*:f", 1) == 0; };
+    ForkAndExecResult res = ForkAndExec(args, post_fork_fn, error_msg);
+    if (res.stage != ForkAndExecResult::kFinished) {
+      *error_msg = strerror(errno);
       return false;
     }
-
-    pid_t pid = fork();
-    if (pid == -1) {
-      return false;
-    }
-
-    if (pid == 0) {
-      // We need dex2oat to actually log things.
-      setenv("ANDROID_LOG_TAGS", "*:f", 1);
-      dup2(link[1], STDERR_FILENO);
-      close(link[0]);
-      close(link[1]);
-      std::vector<const char*> c_args;
-      for (const std::string& str : args) {
-        c_args.push_back(str.c_str());
-      }
-      c_args.push_back(nullptr);
-      execv(c_args[0], const_cast<char* const*>(c_args.data()));
-      exit(1);
-      UNREACHABLE();
-    } else {
-      close(link[1]);
-      char buffer[128];
-      memset(buffer, 0, 128);
-      ssize_t bytes_read = 0;
-
-      while (TEMP_FAILURE_RETRY(bytes_read = read(link[0], buffer, 128)) > 0) {
-        *error_msg += std::string(buffer, bytes_read);
-      }
-      close(link[0]);
-      int status = -1;
-      if (waitpid(pid, &status, 0) != -1) {
-        return (status == 0);
-      }
-      return false;
-    }
+    return res.StandardSuccess();
   }
 };
 
diff --git a/dex2oat/dex2oat_test.cc b/dex2oat/dex2oat_test.cc
index ad44624..1196d11 100644
--- a/dex2oat/dex2oat_test.cc
+++ b/dex2oat/dex2oat_test.cc
@@ -230,47 +230,15 @@
       LOG(ERROR) << all_args;
     }
 
-    int link[2];
-
-    if (pipe(link) == -1) {
-      return false;
+    // We need dex2oat to actually log things.
+    auto post_fork_fn = []() { return setenv("ANDROID_LOG_TAGS", "*:d", 1) == 0; };
+    ForkAndExecResult res = ForkAndExec(argv, post_fork_fn, &output_);
+    if (res.stage != ForkAndExecResult::kFinished) {
+      *error_msg = strerror(errno);
+      return -1;
     }
-
-    pid_t pid = fork();
-    if (pid == -1) {
-      return false;
-    }
-
-    if (pid == 0) {
-      // We need dex2oat to actually log things.
-      setenv("ANDROID_LOG_TAGS", "*:d", 1);
-      dup2(link[1], STDERR_FILENO);
-      close(link[0]);
-      close(link[1]);
-      std::vector<const char*> c_args;
-      for (const std::string& str : argv) {
-        c_args.push_back(str.c_str());
-      }
-      c_args.push_back(nullptr);
-      execv(c_args[0], const_cast<char* const*>(c_args.data()));
-      exit(1);
-      UNREACHABLE();
-    } else {
-      close(link[1]);
-      char buffer[128];
-      memset(buffer, 0, 128);
-      ssize_t bytes_read = 0;
-
-      while (TEMP_FAILURE_RETRY(bytes_read = read(link[0], buffer, 128)) > 0) {
-        output_ += std::string(buffer, bytes_read);
-      }
-      close(link[0]);
-      int status = -1;
-      if (waitpid(pid, &status, 0) != -1) {
-        success_ = (status == 0);
-      }
-      return status;
-    }
+    success_ = res.StandardSuccess();
+    return res.status_code;
   }
 
   std::string output_ = "";
diff --git a/libartbase/base/common_art_test.cc b/libartbase/base/common_art_test.cc
index 67413eb..e24b073 100644
--- a/libartbase/base/common_art_test.cc
+++ b/libartbase/base/common_art_test.cc
@@ -24,6 +24,7 @@
 #include "nativehelper/scoped_local_ref.h"
 
 #include "android-base/stringprintf.h"
+#include "android-base/unique_fd.h"
 #include <unicode/uvernum.h>
 
 #include "art_field-inl.h"
@@ -423,4 +424,83 @@
   return classpath;
 }
 
+CommonArtTestImpl::ForkAndExecResult CommonArtTestImpl::ForkAndExec(
+    const std::vector<std::string>& argv,
+    const PostForkFn& post_fork,
+    const OutputHandlerFn& handler) {
+  ForkAndExecResult result;
+  result.status_code = 0;
+  result.stage = ForkAndExecResult::kLink;
+
+  std::vector<const char*> c_args;
+  for (const std::string& str : argv) {
+    c_args.push_back(str.c_str());
+  }
+  c_args.push_back(nullptr);
+
+  android::base::unique_fd link[2];
+  {
+    int link_fd[2];
+
+    if (pipe(link_fd) == -1) {
+      return result;
+    }
+    link[0].reset(link_fd[0]);
+    link[1].reset(link_fd[1]);
+  }
+
+  result.stage = ForkAndExecResult::kFork;
+
+  pid_t pid = fork();
+  if (pid == -1) {
+    return result;
+  }
+
+  if (pid == 0) {
+    if (!post_fork()) {
+      LOG(ERROR) << "Failed post-fork function";
+      exit(1);
+      UNREACHABLE();
+    }
+
+    // Redirect stdout and stderr.
+    dup2(link[1].get(), STDOUT_FILENO);
+    dup2(link[1].get(), STDERR_FILENO);
+
+    link[0].reset();
+    link[1].reset();
+
+    execv(c_args[0], const_cast<char* const*>(c_args.data()));
+    exit(1);
+    UNREACHABLE();
+  }
+
+  result.stage = ForkAndExecResult::kWaitpid;
+  link[1].reset();
+
+  char buffer[128] = { 0 };
+  ssize_t bytes_read = 0;
+  while (TEMP_FAILURE_RETRY(bytes_read = read(link[0].get(), buffer, 128)) > 0) {
+    handler(buffer, bytes_read);
+  }
+  handler(buffer, 0u);  // End with a virtual write of zero length to simplify clients.
+
+  link[0].reset();
+
+  if (waitpid(pid, &result.status_code, 0) == -1) {
+    return result;
+  }
+
+  result.stage = ForkAndExecResult::kFinished;
+  return result;
+}
+
+CommonArtTestImpl::ForkAndExecResult CommonArtTestImpl::ForkAndExec(
+    const std::vector<std::string>& argv, const PostForkFn& post_fork, std::string* output) {
+  auto string_collect_fn = [output](char* buf, size_t len) {
+    *output += std::string(buf, len);
+  };
+  return ForkAndExec(argv, post_fork, string_collect_fn);
+}
+
 }  // namespace art
diff --git a/libartbase/base/common_art_test.h b/libartbase/base/common_art_test.h
index 0ace09d..62834c7 100644
--- a/libartbase/base/common_art_test.h
+++ b/libartbase/base/common_art_test.h
@@ -19,8 +19,11 @@
 
 #include <gtest/gtest.h>
 
+#include <functional>
 #include <string>
 
+#include <sys/wait.h>
+
 #include <android-base/logging.h>
 
 #include "base/globals.h"
@@ -125,6 +128,29 @@
     return true;
   }
 
+  struct ForkAndExecResult {
+    enum Stage {
+      kLink,
+      kFork,
+      kWaitpid,
+      kFinished,
+    };
+    Stage stage;
+    int status_code;
+
+    bool StandardSuccess() {
+      return stage == kFinished && WIFEXITED(status_code) && WEXITSTATUS(status_code) == 0;
+    }
+  };
+  using OutputHandlerFn = std::function<void(char*, size_t)>;
+  using PostForkFn = std::function<bool()>;
+  static ForkAndExecResult ForkAndExec(const std::vector<std::string>& argv,
+                                       const PostForkFn& post_fork,
+                                       const OutputHandlerFn& handler);
+  static ForkAndExecResult ForkAndExec(const std::vector<std::string>& argv,
+                                       const PostForkFn& post_fork,
+                                       std::string* output);
+
  protected:
   static bool IsHost() {
     return !kIsTargetBuild;
diff --git a/oatdump/oatdump_app_test.cc b/oatdump/oatdump_app_test.cc
index 34b07d2..a344286 100644
--- a/oatdump/oatdump_app_test.cc
+++ b/oatdump/oatdump_app_test.cc
@@ -19,31 +19,23 @@
 namespace art {
 
 TEST_F(OatDumpTest, TestAppWithBootImage) {
-  std::string error_msg;
-  ASSERT_TRUE(GenerateAppOdexFile(kDynamic, {"--runtime-arg", "-Xmx64M"}, &error_msg)) << error_msg;
-  ASSERT_TRUE(Exec(kDynamic, kModeOatWithBootImage, {}, kListAndCode, &error_msg)) << error_msg;
+  ASSERT_TRUE(GenerateAppOdexFile(kDynamic, {"--runtime-arg", "-Xmx64M"}));
+  ASSERT_TRUE(Exec(kDynamic, kModeOatWithBootImage, {}, kListAndCode));
 }
 TEST_F(OatDumpTest, TestAppWithBootImageStatic) {
   TEST_DISABLED_FOR_NON_STATIC_HOST_BUILDS();
-  std::string error_msg;
-  ASSERT_TRUE(GenerateAppOdexFile(kStatic, {"--runtime-arg", "-Xmx64M"}, &error_msg)) << error_msg;
-  ASSERT_TRUE(Exec(kStatic, kModeOatWithBootImage, {}, kListAndCode, &error_msg)) << error_msg;
+  ASSERT_TRUE(GenerateAppOdexFile(kStatic, {"--runtime-arg", "-Xmx64M"}));
+  ASSERT_TRUE(Exec(kStatic, kModeOatWithBootImage, {}, kListAndCode));
 }
 
 TEST_F(OatDumpTest, TestPicAppWithBootImage) {
-  std::string error_msg;
-  ASSERT_TRUE(
-      GenerateAppOdexFile(kDynamic, {"--runtime-arg", "-Xmx64M", "--compile-pic"}, &error_msg))
-      << error_msg;
-  ASSERT_TRUE(Exec(kDynamic, kModeOatWithBootImage, {}, kListAndCode, &error_msg)) << error_msg;
+  ASSERT_TRUE(GenerateAppOdexFile(kDynamic, {"--runtime-arg", "-Xmx64M", "--compile-pic"}));
+  ASSERT_TRUE(Exec(kDynamic, kModeOatWithBootImage, {}, kListAndCode));
 }
 TEST_F(OatDumpTest, TestPicAppWithBootImageStatic) {
   TEST_DISABLED_FOR_NON_STATIC_HOST_BUILDS();
-  std::string error_msg;
-  ASSERT_TRUE(
-      GenerateAppOdexFile(kStatic, {"--runtime-arg", "-Xmx64M", "--compile-pic"}, &error_msg))
-      << error_msg;
-  ASSERT_TRUE(Exec(kStatic, kModeOatWithBootImage, {}, kListAndCode, &error_msg)) << error_msg;
+  ASSERT_TRUE(GenerateAppOdexFile(kStatic, {"--runtime-arg", "-Xmx64M", "--compile-pic"}));
+  ASSERT_TRUE(Exec(kStatic, kModeOatWithBootImage, {}, kListAndCode));
 }
 
 }  // namespace art
diff --git a/oatdump/oatdump_image_test.cc b/oatdump/oatdump_image_test.cc
index d054ece..de48b04 100644
--- a/oatdump/oatdump_image_test.cc
+++ b/oatdump/oatdump_image_test.cc
@@ -19,25 +19,34 @@
 namespace art {
 
 // Disable tests on arm and mips as they are taking too long to run. b/27824283.
-#if !defined(__arm__) && !defined(__mips__)
+#define TEST_DISABLED_FOR_ARM_AND_MIPS() \
+    TEST_DISABLED_FOR_ARM(); \
+    TEST_DISABLED_FOR_ARM64(); \
+    TEST_DISABLED_FOR_MIPS(); \
+    TEST_DISABLED_FOR_MIPS64(); \
+
 TEST_F(OatDumpTest, TestImage) {
+  TEST_DISABLED_FOR_ARM_AND_MIPS();
   std::string error_msg;
-  ASSERT_TRUE(Exec(kDynamic, kModeArt, {}, kListAndCode, &error_msg)) << error_msg;
+  ASSERT_TRUE(Exec(kDynamic, kModeArt, {}, kListAndCode));
 }
 TEST_F(OatDumpTest, TestImageStatic) {
+  TEST_DISABLED_FOR_ARM_AND_MIPS();
   TEST_DISABLED_FOR_NON_STATIC_HOST_BUILDS();
   std::string error_msg;
-  ASSERT_TRUE(Exec(kStatic, kModeArt, {}, kListAndCode, &error_msg)) << error_msg;
+  ASSERT_TRUE(Exec(kStatic, kModeArt, {}, kListAndCode));
 }
 
 TEST_F(OatDumpTest, TestOatImage) {
+  TEST_DISABLED_FOR_ARM_AND_MIPS();
   std::string error_msg;
-  ASSERT_TRUE(Exec(kDynamic, kModeOat, {}, kListAndCode, &error_msg)) << error_msg;
+  ASSERT_TRUE(Exec(kDynamic, kModeOat, {}, kListAndCode));
 }
 TEST_F(OatDumpTest, TestOatImageStatic) {
+  TEST_DISABLED_FOR_ARM_AND_MIPS();
   TEST_DISABLED_FOR_NON_STATIC_HOST_BUILDS();
   std::string error_msg;
-  ASSERT_TRUE(Exec(kStatic, kModeOat, {}, kListAndCode, &error_msg)) << error_msg;
+  ASSERT_TRUE(Exec(kStatic, kModeOat, {}, kListAndCode));
 }
-#endif
+
 }  // namespace art
diff --git a/oatdump/oatdump_test.cc b/oatdump/oatdump_test.cc
index b4eddb9..bcba182 100644
--- a/oatdump/oatdump_test.cc
+++ b/oatdump/oatdump_test.cc
@@ -19,75 +19,92 @@
 namespace art {
 
 // Disable tests on arm and mips as they are taking too long to run. b/27824283.
-#if !defined(__arm__) && !defined(__mips__)
+#define TEST_DISABLED_FOR_ARM_AND_MIPS() \
+    TEST_DISABLED_FOR_ARM(); \
+    TEST_DISABLED_FOR_ARM64(); \
+    TEST_DISABLED_FOR_MIPS(); \
+    TEST_DISABLED_FOR_MIPS64(); \
+
 TEST_F(OatDumpTest, TestNoDumpVmap) {
+  TEST_DISABLED_FOR_ARM_AND_MIPS();
   std::string error_msg;
-  ASSERT_TRUE(Exec(kDynamic, kModeArt, {"--no-dump:vmap"}, kListAndCode, &error_msg)) << error_msg;
+  ASSERT_TRUE(Exec(kDynamic, kModeArt, {"--no-dump:vmap"}, kListAndCode));
 }
 TEST_F(OatDumpTest, TestNoDumpVmapStatic) {
+  TEST_DISABLED_FOR_ARM_AND_MIPS();
   TEST_DISABLED_FOR_NON_STATIC_HOST_BUILDS();
   std::string error_msg;
-  ASSERT_TRUE(Exec(kStatic, kModeArt, {"--no-dump:vmap"}, kListAndCode, &error_msg)) << error_msg;
+  ASSERT_TRUE(Exec(kStatic, kModeArt, {"--no-dump:vmap"}, kListAndCode));
 }
 
 TEST_F(OatDumpTest, TestNoDisassemble) {
+  TEST_DISABLED_FOR_ARM_AND_MIPS();
   std::string error_msg;
-  ASSERT_TRUE(Exec(kDynamic, kModeArt, {"--no-disassemble"}, kListAndCode, &error_msg))
-      << error_msg;
+  ASSERT_TRUE(Exec(kDynamic, kModeArt, {"--no-disassemble"}, kListAndCode));
 }
 TEST_F(OatDumpTest, TestNoDisassembleStatic) {
+  TEST_DISABLED_FOR_ARM_AND_MIPS();
   TEST_DISABLED_FOR_NON_STATIC_HOST_BUILDS();
   std::string error_msg;
-  ASSERT_TRUE(Exec(kStatic, kModeArt, {"--no-disassemble"}, kListAndCode, &error_msg)) << error_msg;
+  ASSERT_TRUE(Exec(kStatic, kModeArt, {"--no-disassemble"}, kListAndCode));
 }
 
 TEST_F(OatDumpTest, TestListClasses) {
+  TEST_DISABLED_FOR_ARM_AND_MIPS();
   std::string error_msg;
-  ASSERT_TRUE(Exec(kDynamic, kModeArt, {"--list-classes"}, kListOnly, &error_msg)) << error_msg;
+  ASSERT_TRUE(Exec(kDynamic, kModeArt, {"--list-classes"}, kListOnly));
 }
 TEST_F(OatDumpTest, TestListClassesStatic) {
+  TEST_DISABLED_FOR_ARM_AND_MIPS();
   TEST_DISABLED_FOR_NON_STATIC_HOST_BUILDS();
   std::string error_msg;
-  ASSERT_TRUE(Exec(kStatic, kModeArt, {"--list-classes"}, kListOnly, &error_msg)) << error_msg;
+  ASSERT_TRUE(Exec(kStatic, kModeArt, {"--list-classes"}, kListOnly));
 }
 
 TEST_F(OatDumpTest, TestListMethods) {
+  TEST_DISABLED_FOR_ARM_AND_MIPS();
   std::string error_msg;
-  ASSERT_TRUE(Exec(kDynamic, kModeArt, {"--list-methods"}, kListOnly, &error_msg)) << error_msg;
+  ASSERT_TRUE(Exec(kDynamic, kModeArt, {"--list-methods"}, kListOnly));
 }
 TEST_F(OatDumpTest, TestListMethodsStatic) {
+  TEST_DISABLED_FOR_ARM_AND_MIPS();
   TEST_DISABLED_FOR_NON_STATIC_HOST_BUILDS();
   std::string error_msg;
-  ASSERT_TRUE(Exec(kStatic, kModeArt, {"--list-methods"}, kListOnly, &error_msg)) << error_msg;
+  ASSERT_TRUE(Exec(kStatic, kModeArt, {"--list-methods"}, kListOnly));
 }
 
 TEST_F(OatDumpTest, TestSymbolize) {
+  TEST_DISABLED_FOR_ARM_AND_MIPS();
   std::string error_msg;
-  ASSERT_TRUE(Exec(kDynamic, kModeSymbolize, {}, kListOnly, &error_msg)) << error_msg;
+  ASSERT_TRUE(Exec(kDynamic, kModeSymbolize, {}, kListOnly));
 }
 TEST_F(OatDumpTest, TestSymbolizeStatic) {
+  TEST_DISABLED_FOR_ARM_AND_MIPS();
   TEST_DISABLED_FOR_NON_STATIC_HOST_BUILDS();
   std::string error_msg;
-  ASSERT_TRUE(Exec(kStatic, kModeSymbolize, {}, kListOnly, &error_msg)) << error_msg;
+  ASSERT_TRUE(Exec(kStatic, kModeSymbolize, {}, kListOnly));
 }
 
 TEST_F(OatDumpTest, TestExportDex) {
+  TEST_DISABLED_FOR_ARM_AND_MIPS();
   // Test is failing on target, b/77469384.
   TEST_DISABLED_FOR_TARGET();
   std::string error_msg;
-  ASSERT_TRUE(Exec(kDynamic, kModeOat, {"--export-dex-to=" + tmp_dir_}, kListOnly, &error_msg))
-      << error_msg;
+  ASSERT_TRUE(Exec(kDynamic, kModeOat, {"--export-dex-to=" + tmp_dir_}, kListOnly));
   const std::string dex_location = tmp_dir_+ "/core-oj-hostdex.jar_export.dex";
   const std::string dexdump2 = GetExecutableFilePath("dexdump2",
                                                      /*is_debug*/false,
                                                      /*is_static*/false);
-  ASSERT_TRUE(ForkAndExecAndWait({dexdump2, "-d", dex_location}, &error_msg)) << error_msg;
+  std::string output;
+  auto post_fork_fn = []() { return true; };
+  ForkAndExecResult res = ForkAndExec({dexdump2, "-d", dex_location}, post_fork_fn, &output);
+  ASSERT_TRUE(res.StandardSuccess());
 }
 TEST_F(OatDumpTest, TestExportDexStatic) {
+  TEST_DISABLED_FOR_ARM_AND_MIPS();
   TEST_DISABLED_FOR_NON_STATIC_HOST_BUILDS();
   std::string error_msg;
-  ASSERT_TRUE(Exec(kStatic, kModeOat, {"--export-dex-to=" + tmp_dir_}, kListOnly, &error_msg))
-      << error_msg;
+  ASSERT_TRUE(Exec(kStatic, kModeOat, {"--export-dex-to=" + tmp_dir_}, kListOnly));
 }
-#endif
+
 }  // namespace art
diff --git a/oatdump/oatdump_test.h b/oatdump/oatdump_test.h
index 231163b..7f997b3 100644
--- a/oatdump/oatdump_test.h
+++ b/oatdump/oatdump_test.h
@@ -111,9 +111,8 @@
     return tmp_dir_ + "/" + GetAppBaseName() + ".odex";
   }
 
-  bool GenerateAppOdexFile(Flavor flavor,
-                           const std::vector<std::string>& args,
-                           /*out*/ std::string* error_msg) {
+  ::testing::AssertionResult GenerateAppOdexFile(Flavor flavor,
+                                                 const std::vector<std::string>& args) {
     std::string dex2oat_path = GetExecutableFilePath(flavor, "dex2oat");
     std::vector<std::string> exec_argv = {
         dex2oat_path,
@@ -131,18 +130,32 @@
     };
     exec_argv.insert(exec_argv.end(), args.begin(), args.end());
 
-    return ForkAndExecAndWait(exec_argv, error_msg);
+    auto post_fork_fn = []() {
+      setpgid(0, 0);  // Change process groups, so we don't get reaped by ProcessManager.
+                      // Ignore setpgid errors.
+      return setenv("ANDROID_LOG_TAGS", "*:e", 1) == 0;  // We're only interested in errors and
+                                                         // fatal logs.
+    };
+
+    std::string error_msg;
+    ForkAndExecResult res = ForkAndExec(exec_argv, post_fork_fn, &error_msg);
+    if (res.stage != ForkAndExecResult::kFinished) {
+      return ::testing::AssertionFailure() << strerror(errno);
+    }
+    return res.StandardSuccess() ? ::testing::AssertionSuccess()
+                                 : (::testing::AssertionFailure() << error_msg);
   }
 
   // Run the test with custom arguments.
-  bool Exec(Flavor flavor,
-            Mode mode,
-            const std::vector<std::string>& args,
-            Display display,
-            /*out*/ std::string* error_msg) {
+  ::testing::AssertionResult Exec(Flavor flavor,
+                                  Mode mode,
+                                  const std::vector<std::string>& args,
+                                  Display display) {
     std::string file_path = GetExecutableFilePath(flavor, "oatdump");
 
-    EXPECT_TRUE(OS::FileExists(file_path.c_str())) << file_path << " should be a valid file path";
+    if (!OS::FileExists(file_path.c_str())) {
+      return ::testing::AssertionFailure() << file_path << " should be a valid file path";
+    }
 
     // ScratchFile scratch;
     std::vector<std::string> exec_argv = { file_path };
@@ -179,129 +192,118 @@
     }
     exec_argv.insert(exec_argv.end(), args.begin(), args.end());
 
-    pid_t pid;
-    int pipe_fd;
-    bool result = ForkAndExec(exec_argv, &pid, &pipe_fd, error_msg);
-    if (result) {
-      static const size_t kLineMax = 256;
-      char line[kLineMax] = {};
-      size_t line_len = 0;
-      size_t total = 0;
-      std::vector<bool> found(expected_prefixes.size(), false);
-      while (true) {
-        while (true) {
+    std::vector<bool> found(expected_prefixes.size(), false);
+    auto line_handle_fn = [&found, &expected_prefixes](const char* line, size_t line_len) {
+      if (line_len == 0) {
+        return;
+      }
+      // Check contents.
+      for (size_t i = 0; i < expected_prefixes.size(); ++i) {
+        const std::string& expected = expected_prefixes[i];
+        if (!found[i] &&
+            line_len >= expected.length() &&
+            memcmp(line, expected.c_str(), expected.length()) == 0) {
+          found[i] = true;
+        }
+      }
+    };
+
+    static constexpr size_t kLineMax = 256;
+    char line[kLineMax] = {};
+    size_t line_len = 0;
+    size_t total = 0;
+    bool ignore_next_line = false;
+    auto line_buf_fn = [&](char* buf, size_t len) {
+      total += len;
+
+      if (len == 0 && line_len > 0 && !ignore_next_line) {
+        // Everything done, handle leftovers.
+        line_handle_fn(line, line_len);
+      }
+
+      while (len > 0) {
+        // Copy buf into the free tail of the line buffer, and move input buffer along.
+        size_t copy = std::min(kLineMax - line_len, len);
+        memcpy(&line[line_len], buf, copy);
+        buf += copy;
+        len -= copy;
+
+        // Skip spaces. Declare a lambda for reuse (incurs a potential extra memmove).
+        auto trim_space = [&]() {
           size_t spaces = 0;
-          // Trim spaces at the start of the line.
           for (; spaces < line_len && isspace(line[spaces]); ++spaces) {}
           if (spaces > 0) {
             line_len -= spaces;
             memmove(&line[0], &line[spaces], line_len);
           }
-          ssize_t bytes_read =
-              TEMP_FAILURE_RETRY(read(pipe_fd, &line[line_len], kLineMax - line_len));
-          if (bytes_read <= 0) {
-            break;
-          }
-          line_len += bytes_read;
-          total += bytes_read;
-        }
-        if (line_len == 0) {
-          break;
-        }
-        // Check contents.
-        for (size_t i = 0; i < expected_prefixes.size(); ++i) {
-          const std::string& expected = expected_prefixes[i];
-          if (!found[i] &&
-              line_len >= expected.length() &&
-              memcmp(line, expected.c_str(), expected.length()) == 0) {
-            found[i] = true;
+        };
+        trim_space();  // This is really only necessary if there wasn't any content in line before.
+
+        // Scan for newline characters.
+        size_t index = line_len;
+        line_len += copy;
+        while (index < line_len) {
+          if (line[index] == '\n') {
+            // Handle line.
+            if (!ignore_next_line) {
+              line_handle_fn(line, index);
+            }
+            // Move the rest to the front, but trim leading spaces.
+            line_len -= index + 1;
+            memmove(&line[0], &line[index + 1], line_len);
+            trim_space();
+            index = 0;
+            ignore_next_line = false;
+          } else {
+            index++;
           }
         }
-        // Skip to next line.
-        size_t next_line = 0;
-        for (; next_line + 1 < line_len && line[next_line] != '\n'; ++next_line) {}
-        line_len -= next_line + 1;
-        memmove(&line[0], &line[next_line + 1], line_len);
-      }
-      if (mode == kModeSymbolize) {
-        EXPECT_EQ(total, 0u);
-      } else {
-        EXPECT_GT(total, 0u);
-      }
-      LOG(INFO) << "Processed bytes " << total;
-      close(pipe_fd);
-      int status = 0;
-      if (waitpid(pid, &status, 0) != -1) {
-        result = (status == 0);
-      }
 
-      for (size_t i = 0; i < expected_prefixes.size(); ++i) {
-        if (!found[i]) {
-          LOG(ERROR) << "Did not find prefix " << expected_prefixes[i];
-          result = false;
+        // Handle a full line without newline characters. Ignore the "next" line, as it is the
+        // tail end of this.
+        if (line_len == kLineMax) {
+          if (!ignore_next_line) {
+            line_handle_fn(line, kLineMax);
+          }
+          line_len = 0;
+          ignore_next_line = true;
         }
       }
+    };
+
+    auto post_fork_fn = []() {
+      setpgid(0, 0);  // Change process groups, so we don't get reaped by ProcessManager.
+      return true;    // Ignore setpgid failures.
+    };
+
+    ForkAndExecResult res = ForkAndExec(exec_argv, post_fork_fn, line_buf_fn);
+    if (res.stage != ForkAndExecResult::kFinished) {
+      return ::testing::AssertionFailure() << strerror(errno);
+    }
+    if (!res.StandardSuccess()) {
+      return ::testing::AssertionFailure() << "Did not terminate successfully: " << res.status_code;
     }
 
-    return result;
-  }
-
-  bool ForkAndExec(const std::vector<std::string>& exec_argv,
-                   /*out*/ pid_t* pid,
-                   /*out*/ int* pipe_fd,
-                   /*out*/ std::string* error_msg) {
-    int link[2];
-    if (pipe(link) == -1) {
-      *error_msg = strerror(errno);
-      return false;
-    }
-
-    *pid = fork();
-    if (*pid == -1) {
-      *error_msg = strerror(errno);
-      close(link[0]);
-      close(link[1]);
-      return false;
-    }
-
-    if (*pid == 0) {
-      dup2(link[1], STDOUT_FILENO);
-      close(link[0]);
-      close(link[1]);
-      // change process groups, so we don't get reaped by ProcessManager
-      setpgid(0, 0);
-      // Use execv here rather than art::Exec to avoid blocking on waitpid here.
-      std::vector<char*> argv;
-      for (size_t i = 0; i < exec_argv.size(); ++i) {
-        argv.push_back(const_cast<char*>(exec_argv[i].c_str()));
-      }
-      argv.push_back(nullptr);
-      UNUSED(execv(argv[0], &argv[0]));
-      const std::string command_line(android::base::Join(exec_argv, ' '));
-      PLOG(ERROR) << "Failed to execv(" << command_line << ")";
-      // _exit to avoid atexit handlers in child.
-      _exit(1);
-      UNREACHABLE();
+    if (mode == kModeSymbolize) {
+      EXPECT_EQ(total, 0u);
     } else {
-      close(link[1]);
-      *pipe_fd = link[0];
-      return true;
+      EXPECT_GT(total, 0u);
     }
-  }
 
-  bool ForkAndExecAndWait(const std::vector<std::string>& exec_argv,
-                          /*out*/ std::string* error_msg) {
-    pid_t pid;
-    int pipe_fd;
-    bool result = ForkAndExec(exec_argv, &pid, &pipe_fd, error_msg);
-    if (result) {
-      close(pipe_fd);
-      int status = 0;
-      if (waitpid(pid, &status, 0) != -1) {
-        result = (status == 0);
+    bool result = true;
+    std::ostringstream oss;
+    for (size_t i = 0; i < expected_prefixes.size(); ++i) {
+      if (!found[i]) {
+        oss << "Did not find prefix " << expected_prefixes[i] << std::endl;
+        result = false;
       }
     }
-    return result;
+    if (!result) {
+      oss << "Processed bytes " << total;
+    }
+
+    return result ? ::testing::AssertionSuccess()
+                  : (::testing::AssertionFailure() << oss.str());
   }
 
   std::string tmp_dir_;
diff --git a/runtime/common_runtime_test.h b/runtime/common_runtime_test.h
index d21973e..234b66a 100644
--- a/runtime/common_runtime_test.h
+++ b/runtime/common_runtime_test.h
@@ -191,12 +191,30 @@
   DISALLOW_COPY_AND_ASSIGN(CheckJniAbortCatcher);
 };
 
+#define TEST_DISABLED_FOR_ARM() \
+  if (kRuntimeISA == InstructionSet::kArm || kRuntimeISA == InstructionSet::kThumb2) { \
+    printf("WARNING: TEST DISABLED FOR ARM\n"); \
+    return; \
+  }
+
+#define TEST_DISABLED_FOR_ARM64() \
+  if (kRuntimeISA == InstructionSet::kArm64) { \
+    printf("WARNING: TEST DISABLED FOR ARM64\n"); \
+    return; \
+  }
+
 #define TEST_DISABLED_FOR_MIPS() \
   if (kRuntimeISA == InstructionSet::kMips) { \
     printf("WARNING: TEST DISABLED FOR MIPS\n"); \
     return; \
   }
 
+#define TEST_DISABLED_FOR_MIPS64() \
+  if (kRuntimeISA == InstructionSet::kMips64) { \
+    printf("WARNING: TEST DISABLED FOR MIPS64\n"); \
+    return; \
+  }
+
 #define TEST_DISABLED_FOR_X86() \
   if (kRuntimeISA == InstructionSet::kX86) { \
     printf("WARNING: TEST DISABLED FOR X86\n"); \