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)); }
// 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(); }
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)); }
// 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(); }
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()); }
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)); }
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)); }
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)); }
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()); }
// 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(); }
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)); }
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)); }
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; }
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)); }
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()); }
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()); }