blob: add8ed68782a3385bd33134541b070b2175d9ed3 [file] [log] [blame]
// 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
#ifdef __WINDOWS__
#include <process.h>
#endif // __WINDOWS__
#include <set>
#include <gtest/gtest.h>
#include <stout/os.hpp>
#ifndef __WINDOWS__
#include <stout/os/fork.hpp>
#endif // __WINDOWS__
#include <stout/os/pstree.hpp>
#include <stout/tests/utils.hpp>
class ProcessTest : public TemporaryDirectoryTest {};
#ifndef __WINDOWS__
using os::Exec;
using os::Fork;
#endif // __WINDOWS__
using os::Process;
using os::ProcessTree;
using std::list;
using std::set;
using std::string;
const unsigned int init_pid =
#ifdef __WINDOWS__
// NOTE: This is also known as the System Idle Process.
0;
#else
1;
#endif // __WINDOWS__
#ifdef __WINDOWS__
int getppid()
{
const int pid = getpid();
HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
std::shared_ptr<void> sh(h, CloseHandle);
PROCESSENTRY32 pe = { 0 };
pe.dwSize = sizeof(PROCESSENTRY32);
if (Process32First(h, &pe)) {
do {
if (pe.th32ProcessID == pid) {
return pe.th32ParentProcessID;
}
} while (Process32Next(h, &pe));
}
return -1;
}
#endif // __WINDOWS__
TEST_F(ProcessTest, Process)
{
const Result<Process> process = os::process(getpid());
ASSERT_SOME(process);
EXPECT_EQ(getpid(), process->pid);
EXPECT_EQ(getppid(), process->parent);
ASSERT_SOME(process->session);
#ifndef __WINDOWS__
// NOTE: `getsid` does not have a meaningful interpretation on Windows.
EXPECT_EQ(getsid(0), process->session.get());
#endif // __WINDOWS__
ASSERT_SOME(process->rss);
EXPECT_GT(process->rss.get(), 0);
// NOTE: On Linux /proc is a bit slow to update the CPU times,
// hence we allow 0 in this test.
ASSERT_SOME(process->utime);
EXPECT_GE(process->utime.get(), Nanoseconds(0));
ASSERT_SOME(process->stime);
EXPECT_GE(process->stime.get(), Nanoseconds(0));
EXPECT_FALSE(process->command.empty());
// Assert invalid PID returns `None`.
Result<Process> invalid_process = os::process(-1);
EXPECT_NONE(invalid_process);
// Assert init.
Result<Process> init_process = os::process(init_pid);
#ifdef __WINDOWS__
// NOTE: On Windows, the init process is a pseudo-process, and it is not
// possible to get a handle to it. So we expect it to error out instead of
// succeed, unlike the POSIX version.
EXPECT_ERROR(init_process);
#elif __FreeBSD__
// In a FreeBSD jail, we wont find an init process.
if (!isJailed()) {
EXPECT_SOME(init_process);
} else {
EXPECT_NONE(init_process);
}
#else
EXPECT_SOME(init_process);
#endif // __WINDOWS__
}
TEST_F(ProcessTest, Processes)
{
const Try<list<Process>> processes = os::processes();
ASSERT_SOME(processes);
ASSERT_GT(processes->size(), 2u);
// Look for ourselves in the table.
bool found = false;
foreach (const Process& process, processes.get()) {
if (process.pid == getpid()) {
found = true;
EXPECT_EQ(getpid(), process.pid);
EXPECT_EQ(getppid(), process.parent);
ASSERT_SOME(process.session);
#ifndef __WINDOWS__
// NOTE: `getsid` does not have a meaningful interpretation on Windows.
EXPECT_EQ(getsid(0), process.session.get());
#endif // __WINDOWS__
ASSERT_SOME(process.rss);
EXPECT_GT(process.rss.get(), 0);
// NOTE: On linux /proc is a bit slow to update the cpu times,
// hence we allow 0 in this test.
ASSERT_SOME(process.utime);
EXPECT_GE(process.utime.get(), Nanoseconds(0));
ASSERT_SOME(process.stime);
EXPECT_GE(process.stime.get(), Nanoseconds(0));
EXPECT_FALSE(process.command.empty());
break;
}
}
EXPECT_TRUE(found);
}
TEST_F(ProcessTest, Pids)
{
Try<set<pid_t>> pids = os::pids();
ASSERT_SOME(pids);
EXPECT_FALSE(pids->empty());
EXPECT_EQ(1u, pids->count(getpid()));
// In a FreeBSD jail, pid 1 may not exist.
#ifdef __FreeBSD__
if (!isJailed()) {
#endif
#ifdef __WINDOWS__
// On Windows, we explicitly do not return the PID of the System Idle
// Process, because doing so can cause unexpected bugs.
EXPECT_EQ(0u, pids.get().count(init_pid));
#else
// Elsewhere we always expect exactly 1 init PID.
EXPECT_EQ(1u, pids->count(init_pid));
#endif
#ifdef __FreeBSD__
}
#endif
#ifndef __WINDOWS__
// NOTE: `getpgid` does not have a meaningful interpretation on Windows.
pids = os::pids(getpgid(0), None());
EXPECT_SOME(pids);
EXPECT_GE(pids->size(), 1u);
EXPECT_EQ(1u, pids->count(getpid()));
// NOTE: This test is not meaningful on Windows because process IDs are
// expected to be non-negative.
EXPECT_ERROR(os::pids(-1, None()));
// NOTE: `getsid` does not have a meaningful interpretation on Windows.
pids = os::pids(None(), getsid(0));
EXPECT_SOME(pids);
EXPECT_GE(pids->size(), 1u);
EXPECT_EQ(1u, pids->count(getpid()));
// NOTE: This test is not meaningful on Windows because process IDs are
// expected to be non-negative.
EXPECT_ERROR(os::pids(None(), -1));
#endif // __WINDOWS__
}
#ifdef __WINDOWS__
TEST_F(ProcessTest, Pstree)
{
Try<ProcessTree> tree = os::pstree(getpid());
ASSERT_SOME(tree);
// Windows spawns `conhost.exe` if we're running from VS, so the count of
// children could be 0 or 1.
const size_t total_children = tree.get().children.size();
EXPECT_TRUE(0u == total_children ||
1u == total_children) << stringify(tree.get());
const bool conhost_spawned = total_children == 1;
Try<internal::windows::ProcessData> process_data =
internal::windows::create_process(
"powershell",
{"powershell", "-NoProfile", "-Command", "Start-Sleep", "2"},
None());
ASSERT_SOME(process_data);
Try<ProcessTree> tree_after_spawn = os::pstree(getpid());
ASSERT_SOME(tree_after_spawn);
// Windows spawns conhost.exe if we're running from VS, so the count of
// children could be 0 or 1.
const size_t children_after_span = tree_after_spawn.get().children.size();
EXPECT_TRUE((!conhost_spawned && 1u == children_after_span) ||
(conhost_spawned && 2u == children_after_span)
) << stringify(tree_after_spawn.get());
// Wait for the process synchronously.
::WaitForSingleObject(
process_data.get().process_handle.get_handle(), INFINITE);
}
#else
TEST_F(ProcessTest, Pstree)
{
Try<ProcessTree> tree = os::pstree(getpid());
ASSERT_SOME(tree);
EXPECT_TRUE(tree->children.empty()) << stringify(tree.get());
tree =
Fork(None(), // Child.
Fork(Exec(SLEEP_COMMAND(10))), // Grandchild.
Exec(SLEEP_COMMAND(10)))();
ASSERT_SOME(tree);
// Depending on whether or not the shell has fork/exec'ed,
// we could have 1 or 2 direct children. That is, some shells
// might simply exec the command above (i.e., 'sleep 10') while
// others might fork/exec the command, keeping around a 'sh -c'
// process as well.
ASSERT_LE(1u, tree->children.size());
ASSERT_GE(2u, tree->children.size());
pid_t child = tree->process.pid;
pid_t grandchild = tree->children.front().process.pid;
// Now check pstree again.
tree = os::pstree(child);
ASSERT_SOME(tree);
EXPECT_EQ(child, tree->process.pid);
ASSERT_LE(1u, tree->children.size());
ASSERT_GE(2u, tree->children.size());
// Cleanup by killing the descendant processes.
EXPECT_EQ(0, kill(grandchild, SIGKILL));
EXPECT_EQ(0, kill(child, SIGKILL));
// We have to reap the child for running the tests in repetition.
ASSERT_EQ(child, waitpid(child, nullptr, 0));
}
#endif // __WINDOWS__