blob: c6cbab012d88355b9a58401919f2b505a72ff48b [file] [log] [blame]
//
// Copyright (C) 2012 The Android Open Source Project
//
// 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 "update_engine/common/subprocess.h"
#include <fcntl.h>
#include <poll.h>
#include <sys/types.h>
#include <unistd.h>
#include <set>
#include <string>
#include <vector>
#include <base/bind.h>
#include <base/files/scoped_temp_dir.h>
#include <base/location.h>
#include <base/message_loop/message_loop.h>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <base/time/time.h>
#include <brillo/bind_lambda.h>
#include <brillo/message_loops/base_message_loop.h>
#include <brillo/message_loops/message_loop.h>
#include <brillo/message_loops/message_loop_utils.h>
#include <brillo/strings/string_utils.h>
#include <gtest/gtest.h>
#include "update_engine/common/test_utils.h"
#include "update_engine/common/utils.h"
using base::TimeDelta;
using brillo::MessageLoop;
using std::string;
using std::vector;
namespace {
#ifdef __ANDROID__
#define kBinPath "/system/bin"
#define kUsrBinPath "/system/bin"
#else
#define kBinPath "/bin"
#define kUsrBinPath "/usr/bin"
#endif // __ANDROID__
} // namespace
namespace chromeos_update_engine {
class SubprocessTest : public ::testing::Test {
protected:
void SetUp() override {
loop_.SetAsCurrent();
async_signal_handler_.Init();
subprocess_.Init(&async_signal_handler_);
}
base::MessageLoopForIO base_loop_;
brillo::BaseMessageLoop loop_{&base_loop_};
brillo::AsynchronousSignalHandler async_signal_handler_;
Subprocess subprocess_;
};
namespace {
void ExpectedResults(int expected_return_code, const string& expected_output,
int return_code, const string& output) {
EXPECT_EQ(expected_return_code, return_code);
EXPECT_EQ(expected_output, output);
MessageLoop::current()->BreakLoop();
}
void ExpectedEnvVars(int return_code, const string& output) {
EXPECT_EQ(0, return_code);
const std::set<string> allowed_envs = {"LD_LIBRARY_PATH", "PATH"};
for (string key_value : brillo::string_utils::Split(output, "\n")) {
auto key_value_pair = brillo::string_utils::SplitAtFirst(
key_value, "=", true);
EXPECT_NE(allowed_envs.end(), allowed_envs.find(key_value_pair.first));
}
MessageLoop::current()->BreakLoop();
}
} // namespace
TEST_F(SubprocessTest, IsASingleton) {
EXPECT_EQ(&subprocess_, &Subprocess::Get());
}
TEST_F(SubprocessTest, InactiveInstancesDontChangeTheSingleton) {
std::unique_ptr<Subprocess> another_subprocess(new Subprocess());
EXPECT_EQ(&subprocess_, &Subprocess::Get());
another_subprocess.reset();
EXPECT_EQ(&subprocess_, &Subprocess::Get());
}
TEST_F(SubprocessTest, SimpleTest) {
EXPECT_TRUE(subprocess_.Exec({kBinPath "/false"},
base::Bind(&ExpectedResults, 1, "")));
loop_.Run();
}
TEST_F(SubprocessTest, EchoTest) {
EXPECT_TRUE(subprocess_.Exec(
{kBinPath "/sh", "-c", "echo this is stdout; echo this is stderr >&2"},
base::Bind(&ExpectedResults, 0, "this is stdout\nthis is stderr\n")));
loop_.Run();
}
TEST_F(SubprocessTest, StderrNotIncludedInOutputTest) {
EXPECT_TRUE(subprocess_.ExecFlags(
{kBinPath "/sh", "-c", "echo on stdout; echo on stderr >&2"},
0,
base::Bind(&ExpectedResults, 0, "on stdout\n")));
loop_.Run();
}
TEST_F(SubprocessTest, EnvVarsAreFiltered) {
EXPECT_TRUE(
subprocess_.Exec({kUsrBinPath "/env"}, base::Bind(&ExpectedEnvVars)));
loop_.Run();
}
TEST_F(SubprocessTest, SynchronousTrueSearchsOnPath) {
int rc = -1;
EXPECT_TRUE(Subprocess::SynchronousExecFlags(
{"true"}, Subprocess::kSearchPath, &rc, nullptr));
EXPECT_EQ(0, rc);
}
TEST_F(SubprocessTest, SynchronousEchoTest) {
vector<string> cmd = {
kBinPath "/sh",
"-c",
"echo -n stdout-here; echo -n stderr-there >&2"};
int rc = -1;
string stdout;
ASSERT_TRUE(Subprocess::SynchronousExec(cmd, &rc, &stdout));
EXPECT_EQ(0, rc);
EXPECT_EQ("stdout-herestderr-there", stdout);
}
TEST_F(SubprocessTest, SynchronousEchoNoOutputTest) {
int rc = -1;
ASSERT_TRUE(Subprocess::SynchronousExec(
{kBinPath "/sh", "-c", "echo test"}, &rc, nullptr));
EXPECT_EQ(0, rc);
}
namespace {
void CallbackBad(int return_code, const string& output) {
ADD_FAILURE() << "should never be called.";
}
} // namespace
// Test that you can cancel a program that's already running.
TEST_F(SubprocessTest, CancelTest) {
base::ScopedTempDir tempdir;
ASSERT_TRUE(tempdir.CreateUniqueTempDir());
string fifo_path = tempdir.path().Append("fifo").value();
EXPECT_EQ(0, mkfifo(fifo_path.c_str(), 0666));
// Start a process, make sure it is running and try to cancel it. We write
// two bytes to the fifo, the first one marks that the program is running and
// the second one marks that the process waited for a timeout and was not
// killed. We should read the first byte but not the second one.
vector<string> cmd = {
kBinPath "/sh",
"-c",
base::StringPrintf(
"echo -n X >\"%s\"; sleep 60; echo -n Y >\"%s\"; exit 1",
fifo_path.c_str(),
fifo_path.c_str())};
uint32_t tag = Subprocess::Get().Exec(cmd, base::Bind(&CallbackBad));
EXPECT_NE(0U, tag);
int fifo_fd = HANDLE_EINTR(open(fifo_path.c_str(), O_RDONLY));
EXPECT_GE(fifo_fd, 0);
loop_.WatchFileDescriptor(FROM_HERE,
fifo_fd,
MessageLoop::WatchMode::kWatchRead,
false,
base::Bind([fifo_fd, tag] {
char c;
EXPECT_EQ(1, HANDLE_EINTR(read(fifo_fd, &c, 1)));
EXPECT_EQ('X', c);
LOG(INFO) << "Killing tag " << tag;
Subprocess::Get().KillExec(tag);
}));
// This test would leak a callback that runs when the child process exits
// unless we wait for it to run.
brillo::MessageLoopRunUntil(
&loop_,
TimeDelta::FromSeconds(120),
base::Bind([] { return Subprocess::Get().subprocess_records_.empty(); }));
EXPECT_TRUE(Subprocess::Get().subprocess_records_.empty());
// Check that there isn't anything else to read from the pipe.
char c;
EXPECT_EQ(0, HANDLE_EINTR(read(fifo_fd, &c, 1)));
IGNORE_EINTR(close(fifo_fd));
}
} // namespace chromeos_update_engine