| // Copyright (c) 2012 The Chromium OS 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 "brillo/process/process.h" |
| |
| #include <unistd.h> |
| |
| #include <base/bind.h> |
| #include <base/check.h> |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/files/scoped_temp_dir.h> |
| #include <base/logging.h> |
| #include <gtest/gtest.h> |
| |
| #include "brillo/process/process_mock.h" |
| #include "brillo/test_helpers.h" |
| #include "brillo/unittest_utils.h" |
| |
| using base::FilePath; |
| |
| // This test assumes the following standard binaries are installed. |
| #if defined(__ANDROID__) |
| #define SYSTEM_PREFIX "/system" |
| static const char kBinStat[] = SYSTEM_PREFIX "/bin/stat"; |
| #else |
| #define SYSTEM_PREFIX "" |
| static const char kBinStat[] = "/usr/bin/stat"; |
| #endif |
| |
| static const char kBinSh[] = SYSTEM_PREFIX "/bin/sh"; |
| static const char kBinCat[] = SYSTEM_PREFIX "/bin/cat"; |
| static const char kBinCp[] = SYSTEM_PREFIX "/bin/cp"; |
| static const char kBinEcho[] = SYSTEM_PREFIX "/bin/echo"; |
| static const char kBinFalse[] = SYSTEM_PREFIX "/bin/false"; |
| static const char kBinSleep[] = SYSTEM_PREFIX "/bin/sleep"; |
| static const char kBinTrue[] = SYSTEM_PREFIX "/bin/true"; |
| |
| namespace brillo { |
| |
| // Test that the mock has all the functions of the interface by |
| // instantiating it. This variable is not used elsewhere. |
| struct CompileMocks { |
| ProcessMock process_mock; |
| }; |
| |
| TEST(SimpleProcess, Basic) { |
| // Log must be cleared before running this test, just as ProcessTest::SetUp. |
| ClearLog(); |
| ProcessImpl process; |
| process.AddArg(kBinEcho); |
| EXPECT_EQ(0, process.Run()); |
| EXPECT_EQ("", GetLog()); |
| } |
| |
| TEST(SimpleProcess, NoSearchPath) { |
| ProcessImpl process; |
| process.AddArg("echo"); |
| EXPECT_EQ(127, process.Run()); |
| } |
| |
| TEST(SimpleProcess, SearchPath) { |
| ProcessImpl process; |
| process.AddArg("echo"); |
| process.SetSearchPath(true); |
| EXPECT_EQ(EXIT_SUCCESS, process.Run()); |
| } |
| |
| TEST(SimpleProcess, BindFd) { |
| int fds[2]; |
| char buf[16]; |
| static const char* kMsg = "hello, world!"; |
| ProcessImpl process; |
| EXPECT_EQ(0, pipe(fds)); |
| process.AddArg(kBinEcho); |
| process.AddArg(kMsg); |
| process.BindFd(fds[1], 1); |
| process.Run(); |
| memset(buf, 0, sizeof(buf)); |
| EXPECT_EQ(read(fds[0], buf, sizeof(buf) - 1), strlen(kMsg) + 1); |
| EXPECT_EQ(std::string(kMsg) + "\n", std::string(buf)); |
| } |
| |
| // The test framework uses the device's dash shell as "sh", which doesn't |
| // support redirecting stdout to arbitrary large file descriptor numbers |
| // directly, nor has /proc mounted to open /proc/self/fd/NN. This test would |
| // fail if pipe.writer is big enough. |
| // TODO(deymo): Write a helper program that writes "hello_world" to the passed |
| // file descriptor and re-enabled this test. |
| TEST(DISABLED_SimpleProcess, BindFdToSameFd) { |
| static const char* kMsg = "hello_world"; |
| ScopedPipe pipe; |
| ProcessImpl process; |
| process.AddArg(kBinSh); |
| process.AddArg("-c"); |
| process.AddArg(base::StringPrintf("echo %s >&%d", kMsg, pipe.writer)); |
| process.BindFd(pipe.writer, pipe.writer); |
| process.Run(); |
| close(pipe.writer); |
| pipe.writer = -1; |
| |
| char buf[16]; |
| memset(buf, 0, sizeof(buf)); |
| EXPECT_EQ(read(pipe.reader, buf, sizeof(buf) - 1), strlen(kMsg) + 1); |
| EXPECT_EQ(std::string(kMsg) + "\n", std::string(buf)); |
| } |
| |
| class ProcessTest : public ::testing::Test { |
| public: |
| void SetUp() { |
| CHECK(temp_dir_.CreateUniqueTempDir()); |
| output_file_ = temp_dir_.GetPath().Append("fork_out").value(); |
| process_.RedirectOutput(output_file_); |
| ClearLog(); |
| } |
| |
| static void SetUpTestCase() { |
| base::CommandLine::Init(0, nullptr); |
| ::brillo::InitLog(brillo::kLogToStderr); |
| ::brillo::LogToString(true); |
| } |
| |
| protected: |
| void CheckStderrCaptured(); |
| FilePath GetFdPath(int fd); |
| |
| ProcessImpl process_; |
| std::vector<const char*> args_; |
| std::string output_file_; |
| base::ScopedTempDir temp_dir_; |
| }; |
| |
| TEST_F(ProcessTest, Basic) { |
| process_.AddArg(kBinEcho); |
| process_.AddArg("hello world"); |
| EXPECT_EQ(0, process_.Run()); |
| ExpectFileEquals("hello world\n", output_file_.c_str()); |
| EXPECT_EQ("", GetLog()); |
| } |
| |
| TEST_F(ProcessTest, AddStringOption) { |
| process_.AddArg(kBinEcho); |
| process_.AddStringOption("--hello", "world"); |
| EXPECT_EQ(0, process_.Run()); |
| ExpectFileEquals("--hello world\n", output_file_.c_str()); |
| } |
| |
| TEST_F(ProcessTest, AddIntValue) { |
| process_.AddArg(kBinEcho); |
| process_.AddIntOption("--answer", 42); |
| EXPECT_EQ(0, process_.Run()); |
| ExpectFileEquals("--answer 42\n", output_file_.c_str()); |
| } |
| |
| TEST_F(ProcessTest, NonZeroReturnValue) { |
| process_.AddArg(kBinFalse); |
| EXPECT_EQ(1, process_.Run()); |
| ExpectFileEquals("", output_file_.c_str()); |
| EXPECT_EQ("", GetLog()); |
| } |
| |
| TEST_F(ProcessTest, RedirectInputDevNull) { |
| process_.AddArg(kBinCat); |
| process_.RedirectInput("/dev/null"); |
| EXPECT_EQ(0, process_.Run()); |
| } |
| |
| TEST_F(ProcessTest, BadInputFile) { |
| process_.AddArg(kBinCat); |
| process_.RedirectInput("/bad/path"); |
| EXPECT_EQ(static_cast<pid_t>(Process::kErrorExitStatus), process_.Run()); |
| } |
| |
| TEST_F(ProcessTest, BadOutputFile) { |
| process_.AddArg(kBinEcho); |
| process_.RedirectOutput("/bad/path"); |
| EXPECT_EQ(static_cast<pid_t>(Process::kErrorExitStatus), process_.Run()); |
| } |
| |
| TEST_F(ProcessTest, BadExecutable) { |
| process_.AddArg("false"); |
| EXPECT_EQ(static_cast<pid_t>(Process::kErrorExitStatus), process_.Run()); |
| } |
| |
| void ProcessTest::CheckStderrCaptured() { |
| std::string contents; |
| process_.AddArg(kBinSh); |
| process_.AddArg("-c"); |
| process_.AddArg("echo errormessage 1>&2 && exit 1"); |
| EXPECT_EQ(1, process_.Run()); |
| EXPECT_TRUE(base::ReadFileToString(FilePath(output_file_), &contents)); |
| EXPECT_NE(std::string::npos, contents.find("errormessage")); |
| EXPECT_EQ("", GetLog()); |
| } |
| |
| TEST_F(ProcessTest, StderrCaptured) { |
| CheckStderrCaptured(); |
| } |
| |
| TEST_F(ProcessTest, StderrCaptureMemoryMemfd) { |
| std::string contents; |
| process_.RedirectUsingMemory(STDERR_FILENO); |
| process_.AddArg(kBinSh); |
| process_.AddArg("-c"); |
| process_.AddArg("echo errormessage >&2 && exit 1"); |
| EXPECT_EQ(1, process_.Run()); |
| EXPECT_NE(std::string::npos, |
| process_.GetOutputString(STDERR_FILENO).find("errormessage")); |
| } |
| |
| TEST_F(ProcessTest, StdoutCaptureMemoryMemfd) { |
| std::string contents; |
| process_.RedirectUsingMemory(STDOUT_FILENO); |
| process_.AddArg(kBinSh); |
| process_.AddArg("-c"); |
| process_.AddArg("echo errormessage && exit 1"); |
| EXPECT_EQ(1, process_.Run()); |
| EXPECT_NE(std::string::npos, |
| process_.GetOutputString(STDOUT_FILENO).find("errormessage")); |
| } |
| |
| TEST_F(ProcessTest, StdoutCaptureSeparateMemoryMemfd) { |
| std::string contents; |
| process_.RedirectOutputToMemory(false); |
| process_.AddArg(kBinSh); |
| process_.AddArg("-c"); |
| process_.AddArg("echo errormessage1; echo errormessage2 >&2; exit 1"); |
| EXPECT_EQ(1, process_.Run()); |
| EXPECT_NE(std::string::npos, |
| process_.GetOutputString(STDOUT_FILENO).find("errormessage1")); |
| EXPECT_NE(std::string::npos, |
| process_.GetOutputString(STDERR_FILENO).find("errormessage2")); |
| } |
| |
| TEST_F(ProcessTest, StdoutCaptureCombinedMemoryMemfd) { |
| std::string contents; |
| process_.RedirectOutputToMemory(true); |
| process_.AddArg(kBinSh); |
| process_.AddArg("-c"); |
| process_.AddArg("echo errormessage1 >&2; echo errormessage2; exit 1"); |
| EXPECT_EQ(1, process_.Run()); |
| LOG(ERROR) << process_.GetOutputString(STDOUT_FILENO); |
| EXPECT_NE(std::string::npos, |
| process_.GetOutputString(STDOUT_FILENO).find("errormessage1")); |
| EXPECT_NE(std::string::npos, |
| process_.GetOutputString(STDOUT_FILENO).find("errormessage2")); |
| } |
| |
| TEST_F(ProcessTest, StderrCapturedWhenPreviouslyClosed) { |
| int saved_stderr = dup(STDERR_FILENO); |
| close(STDERR_FILENO); |
| CheckStderrCaptured(); |
| dup2(saved_stderr, STDERR_FILENO); |
| } |
| |
| FilePath ProcessTest::GetFdPath(int fd) { |
| return FilePath(base::StringPrintf("/proc/self/fd/%d", fd)); |
| } |
| |
| TEST_F(ProcessTest, RedirectStderrUsingPipe) { |
| std::string contents; |
| process_.RedirectOutput(""); |
| process_.AddArg(kBinSh); |
| process_.AddArg("-c"); |
| process_.AddArg("echo errormessage >&2 && exit 1"); |
| process_.RedirectUsingPipe(STDERR_FILENO, false); |
| EXPECT_EQ(-1, process_.GetPipe(STDERR_FILENO)); |
| EXPECT_EQ(1, process_.Run()); |
| int pipe_fd = process_.GetPipe(STDERR_FILENO); |
| EXPECT_GE(pipe_fd, 0); |
| EXPECT_EQ(-1, process_.GetPipe(STDOUT_FILENO)); |
| EXPECT_EQ(-1, process_.GetPipe(STDIN_FILENO)); |
| EXPECT_TRUE(base::ReadFileToString(GetFdPath(pipe_fd), &contents)); |
| EXPECT_NE(std::string::npos, contents.find("errormessage")); |
| EXPECT_EQ("", GetLog()); |
| } |
| |
| TEST_F(ProcessTest, RedirectStderrUsingPipeWhenPreviouslyClosed) { |
| int saved_stderr = dup(STDERR_FILENO); |
| close(STDERR_FILENO); |
| process_.RedirectOutput(""); |
| process_.AddArg(kBinCp); |
| process_.RedirectUsingPipe(STDERR_FILENO, false); |
| EXPECT_FALSE(process_.Start()); |
| EXPECT_TRUE(FindLog("Unable to fstat fd 2:")); |
| dup2(saved_stderr, STDERR_FILENO); |
| } |
| |
| TEST_F(ProcessTest, RedirectStdoutUsingPipe) { |
| std::string contents; |
| process_.RedirectOutput(""); |
| process_.AddArg(kBinEcho); |
| process_.AddArg("hello world\n"); |
| process_.RedirectUsingPipe(STDOUT_FILENO, false); |
| EXPECT_EQ(-1, process_.GetPipe(STDOUT_FILENO)); |
| EXPECT_EQ(0, process_.Run()); |
| int pipe_fd = process_.GetPipe(STDOUT_FILENO); |
| EXPECT_GE(pipe_fd, 0); |
| EXPECT_EQ(-1, process_.GetPipe(STDERR_FILENO)); |
| EXPECT_EQ(-1, process_.GetPipe(STDIN_FILENO)); |
| EXPECT_TRUE(base::ReadFileToString(GetFdPath(pipe_fd), &contents)); |
| EXPECT_NE(std::string::npos, contents.find("hello world\n")); |
| EXPECT_EQ("", GetLog()); |
| } |
| |
| TEST_F(ProcessTest, RedirectStdinUsingPipe) { |
| std::string contents; |
| const char kMessage[] = "made it!\n"; |
| process_.AddArg(kBinCat); |
| process_.RedirectUsingPipe(STDIN_FILENO, true); |
| process_.RedirectOutput(output_file_); |
| EXPECT_TRUE(process_.Start()); |
| int write_fd = process_.GetPipe(STDIN_FILENO); |
| EXPECT_EQ(-1, process_.GetPipe(STDERR_FILENO)); |
| EXPECT_TRUE(base::WriteFile(GetFdPath(write_fd), kMessage, strlen(kMessage))); |
| close(write_fd); |
| EXPECT_EQ(0, process_.Wait()); |
| ExpectFileEquals(kMessage, output_file_.c_str()); |
| } |
| |
| TEST_F(ProcessTest, WithSameUid) { |
| gid_t uid = geteuid(); |
| process_.AddArg(kBinEcho); |
| process_.SetUid(uid); |
| EXPECT_EQ(0, process_.Run()); |
| } |
| |
| TEST_F(ProcessTest, WithSameGid) { |
| gid_t gid = getegid(); |
| process_.AddArg(kBinEcho); |
| process_.SetGid(gid); |
| EXPECT_EQ(0, process_.Run()); |
| } |
| |
| TEST_F(ProcessTest, WithIllegalUid) { |
| ASSERT_NE(0, geteuid()); |
| process_.AddArg(kBinEcho); |
| process_.SetUid(0); |
| EXPECT_EQ(static_cast<pid_t>(Process::kErrorExitStatus), process_.Run()); |
| std::string contents; |
| EXPECT_TRUE(base::ReadFileToString(FilePath(output_file_), &contents)); |
| EXPECT_NE(std::string::npos, contents.find("Unable to set UID to 0: ")); |
| } |
| |
| TEST_F(ProcessTest, WithIllegalGid) { |
| ASSERT_NE(0, getegid()); |
| process_.AddArg(kBinEcho); |
| process_.SetGid(0); |
| EXPECT_EQ(static_cast<pid_t>(Process::kErrorExitStatus), process_.Run()); |
| std::string contents; |
| EXPECT_TRUE(base::ReadFileToString(FilePath(output_file_), &contents)); |
| EXPECT_NE(std::string::npos, contents.find("Unable to set GID to 0: ")); |
| } |
| |
| TEST_F(ProcessTest, NoParams) { |
| EXPECT_EQ(-1, process_.Run()); |
| } |
| |
| #if !defined(__BIONIC__) // Bionic intercepts the segfault on Android. |
| TEST_F(ProcessTest, SegFaultHandling) { |
| process_.AddArg(kBinSh); |
| process_.AddArg("-c"); |
| process_.AddArg("kill -SEGV $$"); |
| EXPECT_EQ(-1, process_.Run()); |
| EXPECT_TRUE(FindLog("did not exit normally: 11")); |
| } |
| #endif |
| |
| TEST_F(ProcessTest, KillHandling) { |
| process_.AddArg(kBinSh); |
| process_.AddArg("-c"); |
| process_.AddArg("kill -KILL $$"); |
| EXPECT_EQ(-1, process_.Run()); |
| EXPECT_TRUE(FindLog("did not exit normally: 9")); |
| } |
| |
| TEST_F(ProcessTest, KillNoPid) { |
| process_.Kill(SIGTERM, 0); |
| EXPECT_TRUE(FindLog("Process not running")); |
| } |
| |
| TEST_F(ProcessTest, ProcessExists) { |
| EXPECT_FALSE(Process::ProcessExists(0)); |
| EXPECT_TRUE(Process::ProcessExists(1)); |
| EXPECT_TRUE(Process::ProcessExists(getpid())); |
| } |
| |
| TEST_F(ProcessTest, ResetPidByFile) { |
| FilePath pid_path = temp_dir_.GetPath().Append("pid"); |
| EXPECT_FALSE(process_.ResetPidByFile(pid_path.value())); |
| EXPECT_TRUE(base::WriteFile(pid_path, "456\n", 4)); |
| EXPECT_TRUE(process_.ResetPidByFile(pid_path.value())); |
| EXPECT_EQ(456, process_.pid()); |
| // The purpose of this unit test is to check if Process::ResetPidByFile() can |
| // properly read a pid from a file. We don't really want to kill the process |
| // with pid 456, so update the pid to 0 to prevent the Process destructor from |
| // killing any innocent process. |
| process_.UpdatePid(0); |
| } |
| |
| TEST_F(ProcessTest, KillSleeper) { |
| process_.AddArg(kBinSleep); |
| process_.AddArg("10000"); |
| ASSERT_TRUE(process_.Start()); |
| pid_t pid = process_.pid(); |
| ASSERT_GT(pid, 1); |
| EXPECT_TRUE(process_.Kill(SIGTERM, 1)); |
| EXPECT_EQ(0, process_.pid()); |
| } |
| |
| TEST_F(ProcessTest, Reset) { |
| process_.AddArg(kBinFalse); |
| process_.Reset(0); |
| process_.AddArg(kBinEcho); |
| EXPECT_EQ(0, process_.Run()); |
| } |
| |
| bool ReturnFalse() { |
| return false; |
| } |
| |
| TEST_F(ProcessTest, PreExecCallback) { |
| process_.AddArg(kBinTrue); |
| process_.SetPreExecCallback(base::BindOnce(&ReturnFalse)); |
| ASSERT_NE(0, process_.Run()); |
| } |
| |
| TEST_F(ProcessTest, LeakUnusedFileDescriptors) { |
| ScopedPipe pipe; |
| process_.AddArg(kBinStat); |
| process_.AddArg(GetFdPath(pipe.reader).value()); |
| process_.AddArg(GetFdPath(pipe.writer).value()); |
| process_.SetCloseUnusedFileDescriptors(false); |
| EXPECT_EQ(0, process_.Run()); |
| } |
| |
| TEST_F(ProcessTest, CloseUnusedFileDescriptors) { |
| ScopedPipe pipe; |
| process_.AddArg(kBinStat); |
| process_.AddArg(GetFdPath(pipe.reader).value()); |
| process_.AddArg(GetFdPath(pipe.writer).value()); |
| process_.SetCloseUnusedFileDescriptors(true); |
| // Stat should fail when running on these file descriptor because the files |
| // should not be there. |
| EXPECT_EQ(1, process_.Run()); |
| } |
| |
| } // namespace brillo |