Make sure to wait for all subprocesses in the process-wrapper.

This is take 2 of https://github.com/bazelbuild/bazel/commit/7828118ea8f4150aa1eae4ab3133935ffe221eb5, which was rolled back because it broke
downstream Android tests.  This change differs from the original one
in that, on Linux, we make sure to kill any subprocess that has escaped
the process group (as otherwise we can get stuck due to the new use of
the child subreaper feature).

When we kill the process group, make sure to wait until the results
of all subprocesses have been collected to ensure all subprocesses are
stopped before the process-wrapper exits.

I have been unable to come up with a test case to show that this does
anything useful though (in particular because the process-wrapper uses
SIGKILL against the processes instead of SIGTERM so I cannot install
any handlers to mess things up). Conceptually, the problem existed, but
it is a race condition that is very hard to trigger. But I'm adding
some unit tests for the internals.

Fixes: https://github.com/bazelbuild/bazel/issues/10245.

RELNOTES: None.
PiperOrigin-RevId: 286596638
diff --git a/src/BUILD b/src/BUILD
index a85a9ac..86d3f12 100644
--- a/src/BUILD
+++ b/src/BUILD
@@ -462,6 +462,7 @@
         "//src/test/py/bazel:srcs",
         "//src/test/shell:srcs",
         "//src/test/testdata/test_tls_certificate",
+        "//src/test/tools:srcs",
         "//src/tools/android/java/com/google/devtools/build/android:srcs",
         "//src/tools/execlog:srcs",
         "//src/tools/workspacelog:srcs",
diff --git a/src/main/tools/BUILD b/src/main/tools/BUILD
index aa29cd4..c68cfe6 100644
--- a/src/main/tools/BUILD
+++ b/src/main/tools/BUILD
@@ -15,7 +15,10 @@
 
 cc_library(
     name = "process-tools",
-    srcs = ["process-tools.cc"],
+    srcs = ["process-tools.cc"] + select({
+        "//src/conditions:darwin": ["process-tools-darwin.cc"],
+        "//conditions:default": ["process-tools-linux.cc"],
+    }),
     hdrs = ["process-tools.h"],
     deps = [
         ":logging",
diff --git a/src/main/tools/process-tools-darwin.cc b/src/main/tools/process-tools-darwin.cc
new file mode 100644
index 0000000..c46e2fa
--- /dev/null
+++ b/src/main/tools/process-tools-darwin.cc
@@ -0,0 +1,118 @@
+// Copyright 2019 The Bazel Authors. All rights reserved.
+//
+// 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
+//
+//    http://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 "src/main/tools/process-tools.h"
+
+#include <sys/event.h>
+#include <sys/sysctl.h>
+
+#include <errno.h>
+#include <inttypes.h>
+#include <signal.h>
+#include <stdint.h>
+#include <unistd.h>
+
+#include "src/main/tools/logging.h"
+
+int WaitForProcessToTerminate(pid_t pid) {
+  int kq;
+  if ((kq = kqueue()) == -1) {
+    return -1;
+  }
+
+  // According to the kqueue(2) documentation, registering for an event
+  // reports any pending such events, so this is not racy even if the
+  // process happened to exit before we got to installing the kevent.
+  struct kevent kc;
+  EV_SET(&kc, pid, EVFILT_PROC, EV_ADD | EV_ENABLE, NOTE_EXIT, 0, 0);
+
+  int nev;
+  struct kevent ke;
+retry:
+  if ((nev = kevent(kq, &kc, 1, &ke, 1, NULL)) == -1) {
+    if (errno == EINTR) {
+      goto retry;
+    }
+    return -1;
+  }
+  if (nev != 1) {
+    DIE("Expected only one event from the kevent call; got %d", nev);
+  }
+  if (ke.ident != pid) {
+    DIE("Expected PID in the kevent to be %" PRIdMAX" but got %" PRIdMAX,
+        (intmax_t) pid, (intmax_t) ke.ident);
+  }
+  if (!(ke.fflags & NOTE_EXIT)) {
+    DIE("Expected the kevent to be for an exit condition");
+  }
+
+  return close(kq);
+}
+
+int WaitForProcessGroupToTerminate(pid_t pgid) {
+  int name[] = {CTL_KERN, KERN_PROC, KERN_PROC_PGRP, pgid};
+
+  for (;;) {
+    // Query the list of processes in the group by using sysctl(3).
+    // This is "hard" because we don't know how big that list is, so we
+    // have to first query the size of the output data and then account for
+    // the fact that the size might change by the time we actually issue
+    // the query.
+    struct kinfo_proc *procs = NULL;
+    size_t nprocs = 0;
+    do {
+      size_t len;
+      if (sysctl(name, 4, 0, &len, NULL, 0) == -1) {
+        return -1;
+      }
+      procs = (struct kinfo_proc *)malloc(len);
+      if (sysctl(name, 4, procs, &len, NULL, 0) == -1) {
+        if (errno != ENOMEM) {
+          DIE("Unexpected error code %d", errno);
+        }
+        free(procs);
+        procs = NULL;
+      } else {
+        nprocs = len / sizeof(struct kinfo_proc);
+      }
+    } while (procs == NULL);
+    if (nprocs < 1) {
+      DIE("Must have found the group leader at least");
+    }
+
+    if (nprocs == 1) {
+      // Found only one process, which must be the leader because we have
+      // purposely expect it as a zombie with WaitForProcess.
+      if (procs->kp_proc.p_pid != pgid) {
+        DIE("Process group leader must be the only process left");
+      }
+      free(procs);
+      return 0;
+    }
+    free(procs);
+
+    // More than one process left in the process group.  Kill the group
+    // again just in case any extra processes appeared just now, which
+    // would not allow us to complete quickly.
+    kill(-pgid, SIGKILL);
+
+    // And pause a little bit before retrying to avoid burning CPU.
+    struct timespec ts;
+    ts.tv_sec = 0;
+    ts.tv_nsec = 1000000;
+    if (nanosleep(&ts, NULL) == -1) {
+      return -1;
+    }
+  }
+}
diff --git a/src/main/tools/process-tools-linux.cc b/src/main/tools/process-tools-linux.cc
new file mode 100644
index 0000000..ce65ce6
--- /dev/null
+++ b/src/main/tools/process-tools-linux.cc
@@ -0,0 +1,55 @@
+// Copyright 2019 The Bazel Authors. All rights reserved.
+//
+// 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
+//
+//    http://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 "src/main/tools/process-tools.h"
+
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+
+int TerminateAndWaitForAll(pid_t pid) {
+  kill(-pid, SIGKILL);
+
+  int res;
+  while ((res = waitpid(-1, nullptr, WNOHANG)) > 0) {
+    // Got one child; try again.
+  }
+  if (res == -1) {
+    // The fast path got all children, so there is nothing else to do.
+    return 0;
+  }
+
+  // Cope with children that may have escaped the process group or that
+  // did not exit quickly enough.
+  FILE *f = fopen("/proc/thread-self/children", "r");
+  if (f == nullptr) {
+    // Oh oh. This feature may be disabled, in which case there is
+    // nothing we can do. Stop early and let any stale children be
+    // reparented to init.
+    return 0;
+  }
+  setbuf(f, nullptr);
+  int child_pid;
+  while ((waitpid(-1, nullptr, WNOHANG) != -1 || errno != ECHILD) &&
+         (rewind(f), 1 == fscanf(f, "%d", &child_pid))) {
+    kill(child_pid, SIGKILL);
+    usleep(100);
+  }
+  fclose(f);
+
+  return 0;
+}
diff --git a/src/main/tools/process-tools.h b/src/main/tools/process-tools.h
index 6f6f494..eb551f2 100644
--- a/src/main/tools/process-tools.h
+++ b/src/main/tools/process-tools.h
@@ -65,4 +65,34 @@
 // Write execution statistics to a file.
 void WriteStatsToFile(struct rusage *rusage, const std::string &stats_path);
 
+// Waits for a process to terminate but does *not* collect its exit status.
+//
+// Note that the process' zombie status may not be available immediately after
+// this call returns.
+//
+// May not be implemented on all platforms.
+int WaitForProcessToTerminate(pid_t pid);
+
+// Waits for a process group to terminate.  Assumes that the process leader
+// still exists in the process table (though it may be a zombie), and allows
+// it to remain.
+//
+// Assumes that the pgid has been sent a termination signal on entry to
+// terminate quickly (or else this will send its own termination signal to
+// the group).
+//
+// May not be implemented on all platforms.
+int WaitForProcessGroupToTerminate(pid_t pgid);
+
+// Terminates and waits for all descendents of the given process to exit.
+//
+// Assumes that the caller has enabled the child subreaper feature before
+// spawning any subprocesses.
+//
+// Assumes that the caller has already waited for the process to collect its
+// exit code as this discards the exit code of all processes it encounters.
+//
+// May not be implemented on all platforms.
+int TerminateAndWaitForAll(pid_t pid);
+
 #endif  // PROCESS_TOOLS_H__
diff --git a/src/main/tools/process-wrapper-legacy.cc b/src/main/tools/process-wrapper-legacy.cc
index 03fdec5..b723bfb 100644
--- a/src/main/tools/process-wrapper-legacy.cc
+++ b/src/main/tools/process-wrapper-legacy.cc
@@ -14,16 +14,20 @@
 
 #include "src/main/tools/process-wrapper-legacy.h"
 
-#include <signal.h>
-#include <stdio.h>
-#include <stdlib.h>
+#include <sys/types.h>
 #include <sys/resource.h>
 #include <sys/stat.h>
 #include <sys/time.h>
-#include <sys/types.h>
 #include <sys/wait.h>
+#if defined(__linux__)
+#include <sys/prctl.h>
+#endif
+
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
 #include <unistd.h>
-#include <vector>
 
 #include "src/main/tools/logging.h"
 #include "src/main/tools/process-tools.h"
@@ -39,6 +43,12 @@
 }
 
 void LegacyProcessWrapper::SpawnChild() {
+#if defined(__linux__)
+  if (prctl(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0) == -1) {
+    DIE("prctl");
+  }
+#endif
+
   child_pid = fork();
   if (child_pid < 0) {
     DIE("fork");
@@ -70,6 +80,23 @@
     SetTimeout(opt.timeout_secs);
   }
 
+  // On macOS, we have to ensure the whole process group is terminated before
+  // collecting the status of the PID we are interested in. (Otherwise other
+  // processes could race us and grab the PGID.)
+#if defined(__APPLE__)
+  if (WaitForProcessToTerminate(child_pid) == -1) {
+    DIE("WaitForProcessToTerminate");
+  }
+
+  // The child is done for, but may have grandchildren that we still have to
+  // kill.
+  kill(-child_pid, SIGKILL);
+
+  if (WaitForProcessGroupToTerminate(child_pid) == -1) {
+    DIE("WaitForProcessGroupToTerminate");
+  }
+#endif
+
   int status;
   if (!opt.stats_path.empty()) {
     struct rusage child_rusage;
@@ -79,9 +106,19 @@
     status = WaitChild(child_pid);
   }
 
-  // The child is done for, but may have grandchildren that we still have to
-  // kill.
-  kill(-child_pid, SIGKILL);
+  // On Linux, we enabled the child subreaper feature, so now that we have
+  // collected the status of the PID we were interested in, terminate the
+  // rest of the process group and wait until all the children are gone.
+  //
+  // If you are wondering why we don't use a PID namespace instead, it's
+  // because those can have subtle effects on the processes we spawn (like
+  // them assuming that the PIDs that they get are unique). The linux-sandbox
+  // offers this functionality.
+#if defined(__linux__)
+  if (TerminateAndWaitForAll(child_pid) == -1) {
+    DIE("TerminateAndWaitForAll");
+  }
+#endif
 
   if (last_signal > 0) {
     // Don't trust the exit code if we got a timeout or signal.
diff --git a/src/test/tools/BUILD b/src/test/tools/BUILD
new file mode 100644
index 0000000..b747f9f
--- /dev/null
+++ b/src/test/tools/BUILD
@@ -0,0 +1,22 @@
+load("@rules_cc//cc:defs.bzl", "cc_test")
+
+package(
+    default_visibility = ["//src:__subpackages__"],
+)
+
+filegroup(
+    name = "srcs",
+    srcs = glob(["**"]),
+)
+
+cc_test(
+    name = "process-tools_test",
+    srcs = ["process-tools_test.cc"] + select({
+        "//src/conditions:darwin": ["process-tools-darwin_test.cc"],
+        "//conditions:default": ["process-tools-linux_test.cc"],
+    }),
+    deps = [
+        "//src/main/tools:process-tools",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
diff --git a/src/test/tools/process-tools-darwin_test.cc b/src/test/tools/process-tools-darwin_test.cc
new file mode 100644
index 0000000..609c23c
--- /dev/null
+++ b/src/test/tools/process-tools-darwin_test.cc
@@ -0,0 +1,169 @@
+// Copyright 2019 The Bazel Authors. All rights reserved.
+//
+// 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
+//
+//    http://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 "src/main/tools/process-tools.h"
+
+#include <sys/types.h>
+#include <sys/sysctl.h>
+#include <sys/wait.h>
+
+#include <signal.h>
+#include <unistd.h>
+
+#include <memory>
+
+#include "googlemock/include/gmock/gmock.h"
+#include "googletest/include/gtest/gtest.h"
+
+namespace {
+
+// Looks for the given process in the process table. Returns the entry if
+// found and nullptr otherwise. Aborts on error.
+std::unique_ptr<kinfo_proc> FindProcess(pid_t pid) {
+  int name[] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
+  std::unique_ptr<kinfo_proc> proc(new kinfo_proc);
+  size_t len = sizeof(kinfo_proc);
+  if (sysctl(name, 4, proc.get(), &len, nullptr, 0) == -1) {
+    abort();
+  }
+  if (len == 0 || proc->kp_proc.p_pid == 0) {
+    return nullptr;
+  } else {
+    if (proc->kp_proc.p_pid != pid) {
+      // Did not expect to get a process with a PID we did not ask for.
+      abort();
+    }
+    return proc;
+  }
+}
+
+class WaitForProcessToTerminateTest : public testing::Test {};
+
+TEST_F(WaitForProcessToTerminateTest, TestExit) {
+  const pid_t pid = fork();
+  ASSERT_NE(pid, -1);
+
+  if (pid == 0) {
+    _exit(42);
+  }
+
+  ASSERT_NE(WaitForProcessToTerminate(pid), -1);
+  // The WaitForProcessToTerminate call guarantees that the process is done,
+  // so we should not be able to affect its exit status any longer.
+  kill(pid, SIGKILL);
+
+  int status;
+  ASSERT_NE(waitpid(pid, &status, 0), -1);
+  ASSERT_TRUE(WIFEXITED(status));
+  ASSERT_EQ(WEXITSTATUS(status), 42);
+}
+
+TEST_F(WaitForProcessToTerminateTest, TestSignal) {
+  const pid_t pid = fork();
+  ASSERT_NE(pid, -1);
+
+  if (pid == 0) {
+    sleep(30);
+    _exit(0);
+  }
+  kill(pid, SIGTERM);
+
+  ASSERT_NE(WaitForProcessToTerminate(pid), -1);
+  // The WaitForProcessToTerminate call guarantees that the process is done,
+  // so we should not be able to affect its exit status any longer.
+  kill(pid, SIGKILL);
+
+  int status;
+  ASSERT_NE(waitpid(pid, &status, 0), -1);
+  ASSERT_TRUE(WIFSIGNALED(status));
+  ASSERT_EQ(WTERMSIG(status), SIGTERM);
+}
+
+class WaitForProcessGroupToTerminateTest : public testing::Test {};
+
+TEST_F(WaitForProcessGroupToTerminateTest, TestOnlyLeader) {
+  const pid_t pid = fork();
+  ASSERT_NE(pid, -1);
+
+  if (pid == 0) {
+    setpgid(0, getpid());
+    sleep(30);
+    _exit(0);
+  }
+  setpgid(pid, pid);
+
+  ASSERT_NE(WaitForProcessGroupToTerminate(pid), -1);
+  kill(pid, SIGKILL);  // Abort sleep to finish test quickly.
+  ASSERT_NE(waitpid(pid, nullptr, 0), -1);
+}
+
+TEST_F(WaitForProcessGroupToTerminateTest, TestManyProcesses) {
+  int fds[2];
+  ASSERT_NE(pipe(fds), -1);
+
+  const size_t nprocs = 3;
+
+  pid_t pid = fork();
+  ASSERT_NE(pid, -1);
+  if (pid == 0) {
+    setpgid(0, getpid());
+
+    close(fds[0]);
+
+    // Spawn a bunch of subprocesses in the same process group as the leader
+    // and report their PIDs to the test before exiting.
+    for (size_t i = 0; i < nprocs; i++) {
+      const pid_t subpid = fork();
+      if (subpid == -1) {
+        abort();
+      } else if (subpid == 0) {
+        close(fds[1]);
+        // Sleep for a very long amount of time to ensure we actually wait for
+        // and terminate processes in the process group.
+        sleep(10000);
+        _exit(0);
+      }
+      if (write(fds[1], &subpid, sizeof(subpid)) != sizeof(subpid)) {
+        abort();
+      }
+    }
+    close(fds[1]);
+
+    _exit(0);
+  }
+  setpgid(pid, pid);
+
+  // Collect the PIDs of all subprocesses (except for the leader).
+  close(fds[1]);
+  pid_t pids[nprocs];
+  for (size_t i = 0; i < nprocs; i++) {
+    ASSERT_EQ(read(fds[0], &pids[i], sizeof(pids[i])), sizeof(pids[i]));
+  }
+  close(fds[0]);
+
+  ASSERT_NE(WaitForProcessGroupToTerminate(pid), -1);
+  // The process leader must still exist (as a zombie or not, we don't know)
+  // but all other processes in the group must be gone by now.
+  ASSERT_NE(FindProcess(pid), nullptr);
+  for (size_t i = 0; i < nprocs; i++) {
+    // This check is racy: some other process might have reclaimed the PID of
+    // the process we already terminated. But it's very unlikely because the
+    // kernel tries very hard to not reassign PIDs too quickly.
+    ASSERT_EQ(FindProcess(pids[i]), nullptr);
+  }
+
+  ASSERT_NE(waitpid(pid, nullptr, 0), -1);
+}
+
+}  // namespace
diff --git a/src/test/tools/process-tools-linux_test.cc b/src/test/tools/process-tools-linux_test.cc
new file mode 100644
index 0000000..92bae1e
--- /dev/null
+++ b/src/test/tools/process-tools-linux_test.cc
@@ -0,0 +1,114 @@
+// Copyright 2019 The Bazel Authors. All rights reserved.
+//
+// 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
+//
+//    http://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 "src/main/tools/process-tools.h"
+
+#include <sys/types.h>
+#include <sys/prctl.h>
+#include <sys/wait.h>
+
+#include <signal.h>
+#include <unistd.h>
+
+#include "googlemock/include/gmock/gmock.h"
+#include "googletest/include/gtest/gtest.h"
+
+namespace {
+
+class TerminateAndWaitForAllTest : public testing::Test {
+  void SetUp(void) override {
+    // TerminateAndWaitForAll requires the caller to have enabled the child
+    // subreaper feature before spawning any processes.
+    ASSERT_NE(prctl(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0), -1);
+  }
+};
+
+TEST_F(TerminateAndWaitForAllTest, TestOnlyLeader) {
+  const pid_t pid = fork();
+  ASSERT_NE(pid, -1);
+
+  if (pid == 0) {
+    setpgid(0, getpid());
+    sleep(30);
+    _exit(0);
+  }
+  setpgid(pid, pid);
+
+  kill(pid, SIGKILL);  // Abort sleep to finish test quickly.
+  ASSERT_NE(TerminateAndWaitForAll(pid), -1);
+  ASSERT_EQ(waitpid(pid, nullptr, 0), -1);
+}
+
+TEST_F(TerminateAndWaitForAllTest, TestOutsideOfGroup) {
+  int fds[2];
+  ASSERT_NE(pipe(fds), -1);
+
+  const size_t nprocs = 32;
+
+  pid_t pid = fork();
+  ASSERT_NE(pid, -1);
+  if (pid == 0) {
+    setpgid(0, getpid());
+
+    close(fds[0]);
+
+    // Spawn a bunch of subprocesses and report their PIDs to the test before
+    // exiting.
+    for (size_t i = 0; i < nprocs; i++) {
+      const pid_t subpid = fork();
+      if (subpid == -1) {
+        abort();
+      } else if (subpid == 0) {
+        close(fds[1]);
+
+        // Keep some subprocesses in the process group and make others escape.
+        if (i % 2 == 0) {
+          setsid();
+        }
+
+        // Sleep for a very long amount of time to ensure we actually wait for
+        // and terminate processes in the process group.
+        sleep(10000);
+        _exit(0);
+      }
+      if (write(fds[1], &subpid, sizeof(subpid)) != sizeof(subpid)) {
+        abort();
+      }
+    }
+    close(fds[1]);
+
+    _exit(0);
+  }
+  setpgid(pid, pid);
+
+  // Collect the PIDs of all subprocesses (except for the leader).
+  close(fds[1]);
+  pid_t pids[nprocs];
+  for (size_t i = 0; i < nprocs; i++) {
+    ASSERT_EQ(read(fds[0], &pids[i], sizeof(pids[i])), sizeof(pids[i]));
+  }
+  close(fds[0]);
+
+  ASSERT_NE(waitpid(pid, nullptr, 0), -1);
+
+  ASSERT_NE(TerminateAndWaitForAll(pid), -1);
+  for (size_t i = 0; i < nprocs; i++) {
+    // This check is racy: some other process might have reclaimed the PID of
+    // the process we already terminated. But it's very unlikely because the
+    // kernel tries very hard to not reassign PIDs too quickly.
+    ASSERT_EQ(kill(pids[i], 0), -1);
+  }
+}
+
+}  // namespace
diff --git a/src/test/tools/process-tools_test.cc b/src/test/tools/process-tools_test.cc
new file mode 100644
index 0000000..af8283b
--- /dev/null
+++ b/src/test/tools/process-tools_test.cc
@@ -0,0 +1,23 @@
+// Copyright 2019 The Bazel Authors. All rights reserved.
+//
+// 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
+//
+//    http://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 "src/main/tools/process-tools.h"
+
+namespace {
+
+// Intentionally left empty to allow the test program to exist on any
+// platform we build on. Add tests here and remove this comment once we
+// have any non-platform specific tests.
+
+}  // namespace