Beispiel #1
0
TEST_F(SubprocessTest, SetupStatus)
{
  // Exit 0 && setup 1.
  Try<Subprocess> s = subprocess(
      "exit 0",
      Subprocess::FD(STDIN_FILENO),
      Subprocess::FD(STDOUT_FILENO),
      Subprocess::FD(STDERR_FILENO),
      None(),
      lambda::bind(&setupStatus, 1));

  ASSERT_SOME(s);

  // Advance time until the internal reaper reaps the subprocess.
  Clock::pause();
  while (s.get().status().isPending()) {
    Clock::advance(MAX_REAP_INTERVAL());
    Clock::settle();
  }
  Clock::resume();

  AWAIT_ASSERT_READY(s.get().status());
  ASSERT_SOME(s.get().status().get());

  int status = s.get().status().get().get();

  // Verify we received the setup returned value instead of the
  // command status.
  ASSERT_EQ(1, WEXITSTATUS(status));

  // Exit 1 && setup 0.
  s = subprocess(
      "exit 1",
      Subprocess::FD(STDIN_FILENO),
      Subprocess::FD(STDOUT_FILENO),
      Subprocess::FD(STDERR_FILENO),
      None(),
      lambda::bind(&setupStatus, 0));

  ASSERT_SOME(s);

  // Advance time until the internal reaper reaps the subprocess.
  Clock::pause();
  while (s.get().status().isPending()) {
    Clock::advance(MAX_REAP_INTERVAL());
    Clock::settle();
  }
  Clock::resume();

  AWAIT_ASSERT_READY(s.get().status());
  ASSERT_SOME(s.get().status().get());

  status = s.get().status().get().get();

  // Verify we received the command status.
  ASSERT_EQ(1, WEXITSTATUS(status));
}
Beispiel #2
0
// This test checks that we can reap a non-child process, in terms
// of receiving the termination notification.
TEST(ReapTest, NonChildProcess)
{
  // The child process creates a grandchild and then exits. The
  // grandchild sleeps for 10 seconds. The process tree looks like:
  //  -+- child exit 0
  //   \-+- grandchild sleep 10

  // After the child exits, the grandchild is going to be re-parented
  // by 'init', like this:
  //  -+- child (exit 0)
  //  -+- grandchild sleep 10
  Try<ProcessTree> tree = Fork(None(),
                               Fork(Exec(SLEEP_COMMAND(10))),
                               Exec("exit 0"))();
  ASSERT_SOME(tree);
  ASSERT_EQ(1u, tree.get().children.size());
  pid_t grandchild = tree.get().children.front();

  // Reap the grandchild process.
  Future<Option<int>> status = process::reap(grandchild);

  EXPECT_TRUE(status.isPending());

  // Now kill the grandchild.
  // NOTE: We send a SIGKILL here because sometimes the grandchild
  // process seems to be in a hung state and not responding to
  // SIGTERM/SIGINT.
  EXPECT_EQ(0, kill(grandchild, SIGKILL));

  Clock::pause();

  // Now advance time until the Reaper reaps the grandchild.
  while (status.isPending()) {
    Clock::advance(MAX_REAP_INTERVAL());
    Clock::settle();
  }

  AWAIT_READY(status);

  // The status is None because pid is not an immediate child.
  ASSERT_NONE(status.get()) << status.get().get();

  // Reap the child as well to clean up after ourselves.
  status = process::reap(tree.get().process.pid);

  // Now advance time until the child is reaped.
  while (status.isPending()) {
    Clock::advance(MAX_REAP_INTERVAL());
    Clock::settle();
  }

  // Check if the status is correct.
  AWAIT_EXPECT_WEXITSTATUS_EQ(0, status);

  Clock::resume();
}
Beispiel #3
0
TEST_F(SubprocessTest, PipeOutput)
{
  // Standard out.
  Try<Subprocess> s = subprocess(
      "echo hello",
      Subprocess::FD(STDIN_FILENO),
      Subprocess::PIPE(),
      Subprocess::FD(STDERR_FILENO));

  ASSERT_SOME(s);
  ASSERT_SOME(s.get().out());
  AWAIT_EXPECT_EQ("hello\n", io::read(s.get().out().get()));

  // Advance time until the internal reaper reaps the subprocess.
  Clock::pause();
  while (s.get().status().isPending()) {
    Clock::advance(MAX_REAP_INTERVAL());
    Clock::settle();
  }
  Clock::resume();

  AWAIT_ASSERT_READY(s.get().status());
  ASSERT_SOME(s.get().status().get());

  int status = s.get().status().get().get();
  EXPECT_TRUE(WIFEXITED(status));
  EXPECT_EQ(0, WEXITSTATUS(status));

  // Standard error.
  s = subprocess(
      "echo hello 1>&2",
      Subprocess::FD(STDIN_FILENO),
      Subprocess::FD(STDOUT_FILENO),
      Subprocess::PIPE());

  ASSERT_SOME(s);
  ASSERT_SOME(s.get().err());
  AWAIT_EXPECT_EQ("hello\n", io::read(s.get().err().get()));

  // Advance time until the internal reaper reaps the subprocess.
  Clock::pause();
  while (s.get().status().isPending()) {
    Clock::advance(MAX_REAP_INTERVAL());
    Clock::settle();
  }
  Clock::resume();

  AWAIT_ASSERT_READY(s.get().status());
  ASSERT_SOME(s.get().status().get());

  status = s.get().status().get().get();
  EXPECT_TRUE(WIFEXITED(status));
  EXPECT_EQ(0, WEXITSTATUS(status));
}
Beispiel #4
0
// This test checks that the we can reap a child process and obtain
// the correct exit status.
TEST(ReapTest, ChildProcess)
{
  ASSERT_TRUE(GTEST_IS_THREADSAFE);

  // The child process sleeps and will be killed by the parent.
  Try<ProcessTree> tree = Fork(None(),
                               Exec("sleep 10"))();

  ASSERT_SOME(tree);
  pid_t child = tree.get();

  // Reap the child process.
  Future<Option<int>> status = process::reap(child);

  // Now kill the child.
  EXPECT_EQ(0, kill(child, SIGKILL));

  Clock::pause();

  // Now advance time until the reaper reaps the child.
  while (status.isPending()) {
    Clock::advance(MAX_REAP_INTERVAL());
    Clock::settle();
  }

  // Check if the status is correct.
  AWAIT_EXPECT_WTERMSIG_EQ(SIGKILL, status);

  Clock::resume();
}
Beispiel #5
0
TEST_F(SubprocessTest, Setup)
{
  Try<string> directory = os::mkdtemp();
  ASSERT_SOME(directory);

  // chdir().
  Try<Subprocess> s = subprocess(
      "echo hello world > file",
      Subprocess::FD(STDIN_FILENO),
      Subprocess::FD(STDOUT_FILENO),
      Subprocess::FD(STDERR_FILENO),
      None(),
      lambda::bind(&setupChdir, directory.get()));

  ASSERT_SOME(s);

  // Advance time until the internal reaper reaps the subprocess.
  Clock::pause();
  while (s.get().status().isPending()) {
    Clock::advance(MAX_REAP_INTERVAL());
    Clock::settle();
  }
  Clock::resume();

  AWAIT_ASSERT_READY(s.get().status());
  ASSERT_SOME(s.get().status().get());

  // Make sure 'file' is there and contains 'hello world'.
  const string path = path::join(directory.get(), "file");
  EXPECT_TRUE(os::exists(path));
  EXPECT_SOME_EQ("hello world\n", os::read(path));

  os::rmdir(directory.get());
}
Beispiel #6
0
TEST_F(SubprocessTest, EnvironmentOverride)
{
  // Ensure we override an existing environment variable.
  os::setenv("MESSAGE1", "hello");
  os::setenv("MESSAGE2", "world");

  map<string, string> environment;
  environment["MESSAGE2"] = "goodbye";

  Try<Subprocess> s = subprocess(
      "echo $MESSAGE1 $MESSAGE2",
      Subprocess::FD(STDIN_FILENO),
      Subprocess::PIPE(),
      Subprocess::FD(STDERR_FILENO),
      environment);

  ASSERT_SOME(s);
  ASSERT_SOME(s.get().out());
  AWAIT_EXPECT_EQ("goodbye\n", io::read(s.get().out().get()));

  // Advance time until the internal reaper reaps the subprocess.
  Clock::pause();
  while (s.get().status().isPending()) {
    Clock::advance(MAX_REAP_INTERVAL());
    Clock::settle();
  }
  Clock::resume();

  AWAIT_ASSERT_READY(s.get().status());
  ASSERT_SOME(s.get().status().get());

  int status = s.get().status().get().get();
  EXPECT_TRUE(WIFEXITED(status));
  EXPECT_EQ(0, WEXITSTATUS(status));
}
Beispiel #7
0
TEST_F(SubprocessTest, EnvironmentWithSpacesAndQuotes)
{
  // Spaces and quotes in value.
  map<string, string> environment;
  environment["MESSAGE"] = "\"hello world\"";

  Try<Subprocess> s = subprocess(
      "echo $MESSAGE",
      Subprocess::FD(STDIN_FILENO),
      Subprocess::PIPE(),
      Subprocess::FD(STDERR_FILENO),
      environment);

  ASSERT_SOME(s);
  ASSERT_SOME(s.get().out());
  AWAIT_EXPECT_EQ("\"hello world\"\n", io::read(s.get().out().get()));

  // Advance time until the internal reaper reaps the subprocess.
  Clock::pause();
  while (s.get().status().isPending()) {
    Clock::advance(MAX_REAP_INTERVAL());
    Clock::settle();
  }
  Clock::resume();

  AWAIT_ASSERT_READY(s.get().status());
  ASSERT_SOME(s.get().status().get());

  int status = s.get().status().get().get();
  EXPECT_TRUE(WIFEXITED(status));
  EXPECT_EQ(0, WEXITSTATUS(status));
}
Beispiel #8
0
TEST_F(SubprocessTest, PathInput)
{
  string in = path::join(os::getcwd(), "stdin");

  ASSERT_SOME(os::write(in, "hello\n"));

  Try<Subprocess> s = subprocess(
      "read word ; echo $word",
      Subprocess::PATH(in),
      Subprocess::PIPE(),
      Subprocess::FD(STDERR_FILENO));

  ASSERT_SOME(s);
  ASSERT_SOME(s.get().out());
  AWAIT_EXPECT_EQ("hello\n", io::read(s.get().out().get()));

  // Advance time until the internal reaper reaps the subprocess.
  Clock::pause();
  while (s.get().status().isPending()) {
    Clock::advance(MAX_REAP_INTERVAL());
    Clock::settle();
  }
  Clock::resume();

  AWAIT_ASSERT_READY(s.get().status());
  ASSERT_SOME(s.get().status().get());

  int status = s.get().status().get().get();
  EXPECT_TRUE(WIFEXITED(status));
  EXPECT_EQ(0, WEXITSTATUS(status));
}
Beispiel #9
0
TEST_F(SubprocessTest, PipeRedirect)
{
  Try<Subprocess> s = subprocess(
      "echo 'hello world'",
      Subprocess::FD(STDIN_FILENO),
      Subprocess::PIPE(),
      Subprocess::FD(STDERR_FILENO));

  ASSERT_SOME(s);

  // Create a temporary file for splicing into.
  string path = path::join(os::getcwd(), "stdout");

  Try<int> fd = os::open(
      path,
      O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC,
      S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);

  ASSERT_SOME(fd);
  ASSERT_SOME(os::nonblock(fd.get()));

  ASSERT_SOME(s.get().out());
  ASSERT_SOME(os::nonblock(s.get().out().get()));
  AWAIT_READY(io::redirect(s.get().out().get(), fd.get()));

  // Close our copy of the fd.
  EXPECT_SOME(os::close(fd.get()));

  // Advance time until the internal reaper reaps the subprocess.
  Clock::pause();
  while (s.get().status().isPending()) {
    Clock::advance(MAX_REAP_INTERVAL());
    Clock::settle();
  }
  Clock::resume();

  AWAIT_ASSERT_READY(s.get().status());
  ASSERT_SOME(s.get().status().get());

  int status = s.get().status().get().get();
  EXPECT_TRUE(WIFEXITED(status));
  EXPECT_EQ(0, WEXITSTATUS(status));

  // Now make sure all the data is there!
  Try<string> read = os::read(path);
  ASSERT_SOME(read);
  EXPECT_EQ("hello world\n", read.get());
}
Beispiel #10
0
// Check that we can reap a child process that is already exited.
TEST(ReapTest, TerminatedChildProcess)
{
  ASSERT_TRUE(GTEST_IS_THREADSAFE);

  // The child process immediately exits.
  Try<ProcessTree> tree = Fork(None(),
                               Exec("exit 0"))();

  ASSERT_SOME(tree);
  pid_t child = tree.get();

  ASSERT_SOME(os::process(child));

  // Make sure the process is transitioned into the zombie
  // state before we reap it.
  while (true) {
    const Result<os::Process> process = os::process(child);
    ASSERT_SOME(process) << "Process " << child << " reaped unexpectedly";

    if (process.get().zombie) {
      break;
    }

    os::sleep(Milliseconds(1));
  }

  // Now that it's terminated, attempt to reap it.
  Future<Option<int>> status = process::reap(child);

  // Advance time until the reaper sends the notification.
  Clock::pause();
  while (status.isPending()) {
    Clock::advance(MAX_REAP_INTERVAL());
    Clock::settle();
  }

  // Expect to get the correct status.
  AWAIT_EXPECT_WEXITSTATUS_EQ(0, status);

  Clock::resume();
}
Beispiel #11
0
TEST_F(SubprocessTest, Default)
{
  Try<Subprocess> s = subprocess("echo hello world");

  ASSERT_SOME(s);

  // Advance time until the internal reaper reaps the subprocess.
  Clock::pause();
  while (s.get().status().isPending()) {
    Clock::advance(MAX_REAP_INTERVAL());
    Clock::settle();
  }
  Clock::resume();

  AWAIT_ASSERT_READY(s.get().status());
  ASSERT_SOME(s.get().status().get());

  int status = s.get().status().get().get();
  EXPECT_TRUE(WIFEXITED(status));
  EXPECT_EQ(0, WEXITSTATUS(status));
}
Beispiel #12
0
TEST_F(SubprocessTest, Environment)
{
  // Simple value.
  map<string, string> environment;
  environment["MESSAGE"] = "hello";

  Try<Subprocess> s = subprocess(
      "echo $MESSAGE",
      Subprocess::FD(STDIN_FILENO),
      Subprocess::PIPE(),
      Subprocess::FD(STDERR_FILENO),
      environment);

  ASSERT_SOME(s);
  ASSERT_SOME(s.get().out());
  AWAIT_EXPECT_EQ("hello\n", io::read(s.get().out().get()));

  // Advance time until the internal reaper reaps the subprocess.
  Clock::pause();
  while (s.get().status().isPending()) {
    Clock::advance(MAX_REAP_INTERVAL());
    Clock::settle();
  }
  Clock::resume();

  AWAIT_ASSERT_READY(s.get().status());
  ASSERT_SOME(s.get().status().get());

  int status = s.get().status().get().get();
  EXPECT_TRUE(WIFEXITED(status));
  EXPECT_EQ(0, WEXITSTATUS(status));

  // Multiple key-value pairs.
  environment.clear();
  environment["MESSAGE0"] = "hello";
  environment["MESSAGE1"] = "world";

  s = subprocess(
      "echo $MESSAGE0 $MESSAGE1",
      Subprocess::FD(STDIN_FILENO),
      Subprocess::PIPE(),
      Subprocess::FD(STDERR_FILENO),
      environment);

  ASSERT_SOME(s);
  ASSERT_SOME(s.get().out());
  AWAIT_EXPECT_EQ("hello world\n", io::read(s.get().out().get()));

  // Advance time until the internal reaper reaps the subprocess.
  Clock::pause();
  while (s.get().status().isPending()) {
    Clock::advance(MAX_REAP_INTERVAL());
    Clock::settle();
  }
  Clock::resume();

  AWAIT_ASSERT_READY(s.get().status());
  ASSERT_SOME(s.get().status().get());

  status = s.get().status().get().get();
  EXPECT_TRUE(WIFEXITED(status));
  EXPECT_EQ(0, WEXITSTATUS(status));
}
Beispiel #13
0
TEST_F(SubprocessTest, Flags)
{
  Flags flags;
  flags.b = true;
  flags.i = 42;
  flags.s = "hello";
  flags.s2 = "we're";
  flags.s3 = "\"geek\"";
  flags.d = Seconds(10);
  flags.y = Bytes(100);

  JSON::Object object;
  object.values["strings"] = "string";
  object.values["integer1"] = 1;
  object.values["integer2"] = -1;
  object.values["double1"] = 1;
  object.values["double2"] = -1;
  object.values["double3"] = -1.42;

  JSON::Object nested;
  nested.values["string"] = "string";
  object.values["nested"] = nested;

  JSON::Array array;
  array.values.push_back(nested);
  object.values["array"] = array;

  flags.j = object;

  string out = path::join(os::getcwd(), "stdout");

  Try<Subprocess> s = subprocess(
      "/bin/echo",
      vector<string>(1, "echo"),
      Subprocess::FD(STDIN_FILENO),
      Subprocess::PATH(out),
      Subprocess::FD(STDERR_FILENO),
      flags);

  ASSERT_SOME(s);

  // Advance time until the internal reaper reaps the subprocess.
  Clock::pause();
  while (s.get().status().isPending()) {
    Clock::advance(MAX_REAP_INTERVAL());
    Clock::settle();
  }
  Clock::resume();

  AWAIT_ASSERT_READY(s.get().status());
  ASSERT_SOME(s.get().status().get());

  int status = s.get().status().get().get();
  EXPECT_TRUE(WIFEXITED(status));
  EXPECT_EQ(0, WEXITSTATUS(status));

  // Parse the output and make sure that it matches the flags we
  // specified in the beginning.
  Try<string> read = os::read(out);
  ASSERT_SOME(read);

  // TODO(jieyu): Consider testing the case where escaped spaces exist
  // in the arguments.
  vector<string> split = strings::split(read.get(), " ");
  int argc = split.size() + 1;
  char** argv = new char*[argc];
  argv[0] = (char*) "command";
  for (int i = 1; i < argc; i++) {
    argv[i] = ::strdup(split[i - 1].c_str());
  }

  Flags flags2;
  Try<Nothing> load = flags2.load(None(), argc, argv);
  ASSERT_SOME(load);
  EXPECT_EQ(flags.b, flags2.b);
  EXPECT_EQ(flags.i, flags2.i);
  EXPECT_EQ(flags.s, flags2.s);
  EXPECT_EQ(flags.s2, flags2.s2);
  EXPECT_EQ(flags.s3, flags2.s3);
  EXPECT_EQ(flags.d, flags2.d);
  EXPECT_EQ(flags.y, flags2.y);
  EXPECT_EQ(flags.j, flags2.j);

  for (int i = 1; i < argc; i++) {
    ::free(argv[i]);
  }
  delete[] argv;
}
Beispiel #14
0
TEST_F(SubprocessTest, Status)
{
  // Exit 0.
  Try<Subprocess> s = subprocess("exit 0");

  ASSERT_SOME(s);

  // Advance time until the internal reaper reaps the subprocess.
  Clock::pause();
  while (s.get().status().isPending()) {
    Clock::advance(MAX_REAP_INTERVAL());
    Clock::settle();
  }
  Clock::resume();

  AWAIT_ASSERT_READY(s.get().status());
  ASSERT_SOME(s.get().status().get());

  int status = s.get().status().get().get();
  EXPECT_TRUE(WIFEXITED(status));
  EXPECT_EQ(0, WEXITSTATUS(status));

  // Exit 1.
  s = subprocess("exit 1");

  ASSERT_SOME(s);

  // Advance time until the internal reaper reaps the subprocess.
  Clock::pause();
  while (s.get().status().isPending()) {
    Clock::advance(MAX_REAP_INTERVAL());
    Clock::settle();
  }
  Clock::resume();

  AWAIT_ASSERT_READY(s.get().status());
  ASSERT_SOME(s.get().status().get());

  status = s.get().status().get().get();
  EXPECT_TRUE(WIFEXITED(status));
  EXPECT_EQ(1, WEXITSTATUS(status));

  // SIGTERM.
  s = subprocess("sleep 60");

  ASSERT_SOME(s);

  kill(s.get().pid(), SIGTERM);

  // Advance time until the internal reaper reaps the subprocess.
  Clock::pause();
  while (s.get().status().isPending()) {
    Clock::advance(MAX_REAP_INTERVAL());
    Clock::settle();
  }
  Clock::resume();

  AWAIT_ASSERT_READY(s.get().status());
  ASSERT_SOME(s.get().status().get());

  status = s.get().status().get().get();
  EXPECT_TRUE(WIFSIGNALED(status));
  EXPECT_EQ(SIGTERM, WTERMSIG(status));

  // SIGKILL.
  s = subprocess("sleep 60");

  ASSERT_SOME(s);

  kill(s.get().pid(), SIGKILL);

  // Advance time until the internal reaper reaps the subprocess.
  Clock::pause();
  while (s.get().status().isPending()) {
    Clock::advance(MAX_REAP_INTERVAL());
    Clock::settle();
  }
  Clock::resume();

  AWAIT_ASSERT_READY(s.get().status());
  ASSERT_SOME(s.get().status().get());

  status = s.get().status().get().get();
  EXPECT_TRUE(WIFSIGNALED(status));
  EXPECT_EQ(SIGKILL, WTERMSIG(status));
}
Beispiel #15
0
TEST_F(SubprocessTest, FdOutput)
{
  string out = path::join(os::getcwd(), "stdout");
  string err = path::join(os::getcwd(), "stderr");

  // Standard out.
  Try<int> outFd = os::open(
      out,
      O_WRONLY | O_CREAT | O_APPEND | O_CLOEXEC,
      S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);

  ASSERT_SOME(outFd);

  Try<Subprocess> s = subprocess(
      "echo hello",
      Subprocess::FD(STDIN_FILENO),
      Subprocess::FD(outFd.get()),
      Subprocess::FD(STDERR_FILENO));

  ASSERT_SOME(os::close(outFd.get()));
  ASSERT_SOME(s);

  // Advance time until the internal reaper reaps the subprocess.
  Clock::pause();
  while (s.get().status().isPending()) {
    Clock::advance(MAX_REAP_INTERVAL());
    Clock::settle();
  }
  Clock::resume();

  AWAIT_ASSERT_READY(s.get().status());
  ASSERT_SOME(s.get().status().get());

  int status = s.get().status().get().get();
  EXPECT_TRUE(WIFEXITED(status));
  EXPECT_EQ(0, WEXITSTATUS(status));

  Try<string> read = os::read(out);
  ASSERT_SOME(read);
  EXPECT_EQ("hello\n", read.get());

  // Standard error.
  Try<int> errFd = os::open(
      err,
      O_WRONLY | O_CREAT | O_APPEND | O_CLOEXEC,
      S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);

  ASSERT_SOME(errFd);

  s = subprocess(
      "echo hello 1>&2",
      Subprocess::FD(STDIN_FILENO),
      Subprocess::FD(STDOUT_FILENO),
      Subprocess::FD(errFd.get()));

  ASSERT_SOME(os::close(errFd.get()));
  ASSERT_SOME(s);

  // Advance time until the internal reaper reaps the subprocess.
  Clock::pause();
  while (s.get().status().isPending()) {
    Clock::advance(MAX_REAP_INTERVAL());
    Clock::settle();
  }
  Clock::resume();

  AWAIT_ASSERT_READY(s.get().status());
  ASSERT_SOME(s.get().status().get());

  status = s.get().status().get().get();
  EXPECT_TRUE(WIFEXITED(status));
  EXPECT_EQ(0, WEXITSTATUS(status));

  read = os::read(err);
  ASSERT_SOME(read);
  EXPECT_EQ("hello\n", read.get());
}
Beispiel #16
0
TEST_F(SubprocessTest, PathOutput)
{
  string out = path::join(os::getcwd(), "stdout");
  string err = path::join(os::getcwd(), "stderr");

  // Standard out.
  Try<Subprocess> s = subprocess(
      "echo hello",
      Subprocess::FD(STDIN_FILENO),
      Subprocess::PATH(out),
      Subprocess::FD(STDERR_FILENO));

  ASSERT_SOME(s);

  // Advance time until the internal reaper reaps the subprocess.
  Clock::pause();
  while (s.get().status().isPending()) {
    Clock::advance(MAX_REAP_INTERVAL());
    Clock::settle();
  }
  Clock::resume();

  AWAIT_ASSERT_READY(s.get().status());
  ASSERT_SOME(s.get().status().get());

  int status = s.get().status().get().get();
  EXPECT_TRUE(WIFEXITED(status));
  EXPECT_EQ(0, WEXITSTATUS(status));

  Try<string> read = os::read(out);
  ASSERT_SOME(read);
  EXPECT_EQ("hello\n", read.get());

  // Standard error.
  s = subprocess(
      "echo hello 1>&2",
      Subprocess::FD(STDIN_FILENO),
      Subprocess::FD(STDOUT_FILENO),
      Subprocess::PATH(err));

  ASSERT_SOME(s);

  // Advance time until the internal reaper reaps the subprocess.
  Clock::pause();
  while (s.get().status().isPending()) {
    Clock::advance(MAX_REAP_INTERVAL());
    Clock::settle();
  }
  Clock::resume();

  AWAIT_ASSERT_READY(s.get().status());
  ASSERT_SOME(s.get().status().get());

  status = s.get().status().get().get();
  EXPECT_TRUE(WIFEXITED(status));
  EXPECT_EQ(0, WEXITSTATUS(status));

  read = os::read(err);
  ASSERT_SOME(read);
  EXPECT_EQ("hello\n", read.get());
}