blob: 5f8c5d66601e00edb8b7447119acd73a7560a61d [file] [log] [blame]
/*
* Copyright (C) 2019 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 "perfetto/ext/base/subprocess.h"
#if PERFETTO_HAS_SUBPROCESS()
#include <thread>
#include <signal.h>
#include <sys/stat.h>
#include <unistd.h>
#include "perfetto/base/time.h"
#include "perfetto/ext/base/file_utils.h"
#include "perfetto/ext/base/pipe.h"
#include "perfetto/ext/base/temp_file.h"
#include "test/gtest_and_gmock.h"
namespace perfetto {
namespace base {
namespace {
std::string GenLargeString() {
std::string contents;
for (int i = 0; i < 4096; i++) {
contents += "very long text " + std::to_string(i) + "\n";
}
// Make sure that |contents| is > the default pipe buffer on Linux (4 pages).
PERFETTO_DCHECK(contents.size() > 4096 * 4);
return contents;
}
TEST(SubprocessTest, InvalidPath) {
Subprocess p({"/usr/bin/invalid_1337"});
EXPECT_FALSE(p.Call());
EXPECT_EQ(p.status(), Subprocess::kExited);
EXPECT_EQ(p.returncode(), 128);
EXPECT_EQ(p.output(), "execve() failed\n");
}
TEST(SubprocessTest, StdoutOnly) {
Subprocess p({"sh", "-c", "(echo skip_err >&2); echo out_only"});
p.args.stdout_mode = Subprocess::kBuffer;
p.args.stderr_mode = Subprocess::kDevNull;
EXPECT_TRUE(p.Call());
EXPECT_EQ(p.status(), Subprocess::kExited);
EXPECT_EQ(p.output(), "out_only\n");
}
TEST(SubprocessTest, StderrOnly) {
Subprocess p({"sh", "-c", "(echo err_only >&2); echo skip_out"});
p.args.stdout_mode = Subprocess::kDevNull;
p.args.stderr_mode = Subprocess::kBuffer;
EXPECT_TRUE(p.Call());
EXPECT_EQ(p.output(), "err_only\n");
}
TEST(SubprocessTest, BothStdoutAndStderr) {
Subprocess p({"sh", "-c", "echo out; (echo err >&2); echo out2"});
p.args.stdout_mode = Subprocess::kBuffer;
p.args.stderr_mode = Subprocess::kBuffer;
EXPECT_TRUE(p.Call());
EXPECT_EQ(p.output(), "out\nerr\nout2\n");
}
TEST(SubprocessTest, BinTrue) {
Subprocess p({"true"});
EXPECT_TRUE(p.Call());
EXPECT_EQ(p.status(), Subprocess::kExited);
EXPECT_EQ(p.returncode(), 0);
}
TEST(SubprocessTest, BinFalse) {
Subprocess p({"false"});
EXPECT_FALSE(p.Call());
EXPECT_EQ(p.status(), Subprocess::kExited);
EXPECT_EQ(p.returncode(), 1);
}
TEST(SubprocessTest, Echo) {
Subprocess p({"echo", "-n", "foobar"});
p.args.stdout_mode = Subprocess::kBuffer;
EXPECT_TRUE(p.Call());
EXPECT_EQ(p.status(), Subprocess::kExited);
EXPECT_EQ(p.returncode(), 0);
EXPECT_EQ(p.output(), "foobar");
}
TEST(SubprocessTest, FeedbackLongInput) {
std::string contents = GenLargeString();
Subprocess p({"cat", "-"});
p.args.stdout_mode = Subprocess::kBuffer;
p.args.input = contents;
EXPECT_TRUE(p.Call());
EXPECT_EQ(p.status(), Subprocess::kExited);
EXPECT_EQ(p.returncode(), 0);
EXPECT_EQ(p.output(), contents);
}
TEST(SubprocessTest, CatLargeFile) {
std::string contents = GenLargeString();
TempFile tf = TempFile::Create();
WriteAll(tf.fd(), contents.data(), contents.size());
FlushFile(tf.fd());
Subprocess p({"cat", tf.path().c_str()});
p.args.stdout_mode = Subprocess::kBuffer;
EXPECT_TRUE(p.Call());
EXPECT_EQ(p.output(), contents);
}
TEST(SubprocessTest, Timeout) {
Subprocess p({"sleep", "60"});
EXPECT_FALSE(p.Call(/*timeout_ms=*/1));
EXPECT_EQ(p.status(), Subprocess::kKilledBySignal);
}
TEST(SubprocessTest, TimeoutNotHit) {
Subprocess p({"sleep", "0.01"});
EXPECT_TRUE(p.Call(/*timeout_ms=*/100000));
}
TEST(SubprocessTest, TimeoutStopOutput) {
Subprocess p({"sh", "-c", "while true; do echo stuff; done"});
p.args.stdout_mode = Subprocess::kDevNull;
EXPECT_FALSE(p.Call(/*timeout_ms=*/10));
EXPECT_EQ(p.status(), Subprocess::kKilledBySignal);
}
TEST(SubprocessTest, ExitBeforeReadingStdin) {
// 'sh -c' is to avoid closing stdin (sleep closes it before sleeping).
Subprocess p({"sh", "-c", "sleep 0.01"});
p.args.stdout_mode = Subprocess::kDevNull;
p.args.stderr_mode = Subprocess::kDevNull;
p.args.input = GenLargeString();
EXPECT_TRUE(p.Call());
EXPECT_EQ(p.status(), Subprocess::kExited);
EXPECT_EQ(p.returncode(), 0);
}
TEST(SubprocessTest, StdinWriteStall) {
// 'sh -c' is to avoid closing stdin (sleep closes it before sleeping).
// This causes a situation where the write on the stdin will stall because
// nobody reads it and the pipe buffer fills up. In this situation we should
// still handle the timeout properly.
Subprocess p({"sh", "-c", "sleep 10"});
p.args.stdout_mode = Subprocess::kDevNull;
p.args.stderr_mode = Subprocess::kDevNull;
p.args.input = GenLargeString();
EXPECT_FALSE(p.Call(/*timeout_ms=*/10));
EXPECT_EQ(p.status(), Subprocess::kKilledBySignal);
}
TEST(SubprocessTest, StartAndWait) {
Subprocess p({"sleep", "1000"});
p.Start();
EXPECT_EQ(p.Poll(), Subprocess::kRunning);
p.KillAndWaitForTermination();
EXPECT_EQ(p.status(), Subprocess::kKilledBySignal);
EXPECT_EQ(p.Poll(), Subprocess::kKilledBySignal);
EXPECT_EQ(p.returncode(), 128 + SIGKILL);
}
TEST(SubprocessTest, PollBehavesProperly) {
Pipe pipe = Pipe::Create();
Subprocess p({"true"});
p.args.stdout_mode = Subprocess::kFd;
p.args.out_fd = std::move(pipe.wr);
p.Start();
// Wait for EOF (which really means the child process has terminated).
char buf;
while (PERFETTO_EINTR(read(*pipe.rd, &buf, 1)) != 0) {
usleep(1000);
}
// The kernel takes some time to detect the termination of the process. The
// best thing we can do here is check that we detect the termination within
// some reasonable time.
auto start_ms = GetWallTimeMs();
while (p.Poll() != Subprocess::kExited) {
auto elapsed_ms = GetWallTimeMs() - start_ms;
ASSERT_LT(elapsed_ms, TimeMillis(10000));
usleep(1000);
}
// At this point Poll() must detect the termination.
EXPECT_EQ(p.Poll(), Subprocess::kExited);
EXPECT_EQ(p.returncode(), 0);
}
// Test the case of passing a lambda in |entrypoint| but no cmd.c
TEST(SubprocessTest, Entrypoint) {
Subprocess p;
p.args.input = "ping\n";
p.args.stdout_mode = Subprocess::kBuffer;
p.args.entrypoint_for_testing = [] {
char buf[32]{};
PERFETTO_CHECK(fgets(buf, sizeof(buf), stdin));
PERFETTO_CHECK(strcmp(buf, "ping\n") == 0);
printf("pong\n");
fflush(stdout);
_exit(42);
};
EXPECT_FALSE(p.Call());
EXPECT_EQ(p.returncode(), 42);
EXPECT_EQ(p.output(), "pong\n");
}
// Test the case of passing both a lambda entrypoint and a process to exec.
TEST(SubprocessTest, EntrypointAndExec) {
base::Pipe pipe1 = base::Pipe::Create();
base::Pipe pipe2 = base::Pipe::Create();
int pipe1_wr = *pipe1.wr;
int pipe2_wr = *pipe2.wr;
Subprocess p({"echo", "123"});
p.args.stdout_mode = Subprocess::kBuffer;
p.args.preserve_fds.push_back(pipe2_wr);
p.args.entrypoint_for_testing = [pipe1_wr, pipe2_wr] {
base::ignore_result(write(pipe1_wr, "fail", 4));
base::ignore_result(write(pipe2_wr, "pass", 4));
};
p.Start();
pipe1.wr.reset();
pipe2.wr.reset();
char buf[8];
EXPECT_LE(read(*pipe1.rd, buf, sizeof(buf)), 0);
EXPECT_EQ(read(*pipe2.rd, buf, sizeof(buf)), 4);
buf[4] = '\0';
EXPECT_STREQ(buf, "pass");
EXPECT_TRUE(p.Wait());
EXPECT_EQ(p.status(), Subprocess::kExited);
EXPECT_EQ(p.output(), "123\n");
}
TEST(SubprocessTest, Wait) {
Subprocess p({"sh", "-c", "echo exec_done; while true; do true; done"});
p.args.stdout_mode = Subprocess::kBuffer;
p.Start();
// Wait for the fork()+exec() to complete.
while (p.output().find("exec_done") == std::string::npos) {
EXPECT_FALSE(p.Wait(1 /*ms*/));
EXPECT_EQ(p.status(), Subprocess::kRunning);
}
kill(p.pid(), SIGBUS);
EXPECT_TRUE(p.Wait(30000 /*ms*/)); // We shouldn't hit this.
EXPECT_TRUE(p.Wait()); // Should be a no-op.
EXPECT_EQ(p.status(), Subprocess::kKilledBySignal);
EXPECT_EQ(p.returncode(), 128 + SIGBUS);
}
TEST(SubprocessTest, KillOnDtor) {
// Here we use kill(SIGWINCH) as a way to tell if the process is still alive.
// SIGWINCH is one of the few signals that has default ignore disposition.
int pid;
{
Subprocess p({"sleep", "10000"});
p.Start();
pid = p.pid();
EXPECT_EQ(kill(pid, SIGWINCH), 0);
}
EXPECT_EQ(kill(pid, SIGWINCH), -1);
}
// Regression test for b/162505491.
TEST(SubprocessTest, MoveOperators) {
{
Subprocess initial = Subprocess({"sleep", "10000"});
initial.Start();
Subprocess moved(std::move(initial));
EXPECT_EQ(moved.Poll(), Subprocess::kRunning);
EXPECT_EQ(initial.Poll(), Subprocess::kNotStarted);
// Check that reuse works
initial = Subprocess({"echo", "-n", "hello"});
initial.args.stdout_mode = Subprocess::OutputMode::kBuffer;
initial.Start();
initial.Wait(/*timeout=*/5000);
EXPECT_EQ(initial.status(), Subprocess::kExited);
EXPECT_EQ(initial.returncode(), 0);
EXPECT_EQ(initial.output(), "hello");
}
std::vector<Subprocess> v;
for (int i = 0; i < 10; i++) {
v.emplace_back(Subprocess({"sleep", "10"}));
v.back().Start();
}
for (auto& p : v)
EXPECT_EQ(p.Poll(), Subprocess::kRunning);
}
} // namespace
} // namespace base
} // namespace perfetto
#endif // PERFETTO_HAS_SUBPROCESS()