blob: 86375ae975eb17d6d6c5125f3c4f0cbc80c5cb30 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include <lib/unittest/unittest.h>
#include <object/dispatcher.h>
#include <object/job_dispatcher.h>
#include <object/process_dispatcher.h>
#include <object/root_job_observer.h>
#include <object/vm_address_region_dispatcher.h>
namespace {
// Create a suspended thread inside the given process.
KernelHandle<ThreadDispatcher> CreateThread(fbl::RefPtr<ProcessDispatcher> parent_process) {
fbl::RefPtr<ThreadDispatcher> child_thread;
KernelHandle<ThreadDispatcher> thread_handle;
zx_rights_t thread_rights;
ASSERT(ThreadDispatcher::Create(parent_process, 0, "unittest_thread", &thread_handle,
&thread_rights) == ZX_OK);
child_thread = thread_handle.dispatcher();
ASSERT(child_thread->Initialize() == ZX_OK);
ASSERT(child_thread->Suspend() == ZX_OK);
ASSERT(child_thread->Start(ThreadDispatcher::EntryState{}, /*initial_thread=*/true) == ZX_OK);
return thread_handle;
}
// Create a process inside the given job.
KernelHandle<ProcessDispatcher> CreateProcess(fbl::RefPtr<JobDispatcher> parent_job) {
fbl::RefPtr<ProcessDispatcher> child_process;
KernelHandle<ProcessDispatcher> process_handle;
KernelHandle<VmAddressRegionDispatcher> vmar_handle;
zx_rights_t rights, vmar_rights;
ASSERT(ProcessDispatcher::Create(parent_job, "unittest_process", 0, &process_handle, &rights,
&vmar_handle, &vmar_rights) == ZX_OK);
return process_handle;
}
// Exercise basic creation/destruction of the RootJobObserver.
bool TestCreateDestroy() {
BEGIN_TEST;
// Create and destroy the root job observer.
Event callback_fired;
fbl::RefPtr<JobDispatcher> root_job = JobDispatcher::CreateRootJob();
RootJobObserver observer{root_job, nullptr, [&]() { callback_fired.Signal(); }};
// Ensure the callback fired.
callback_fired.Wait();
END_TEST;
}
// Ensure that the callback fires when the root job is killed.
bool TestCallbackFiresOnRootJobDeath() {
BEGIN_TEST;
Event root_job_killed;
// Create the root job with a child process, and start watching it.
fbl::RefPtr<JobDispatcher> root_job = JobDispatcher::CreateRootJob();
KernelHandle<ProcessDispatcher> child_process = CreateProcess(root_job);
RootJobObserver observer{root_job, nullptr, [&root_job_killed]() { root_job_killed.Signal(); }};
// Shouldn't be signalled yet.
EXPECT_EQ(root_job_killed.Wait(Deadline::after(ZX_MSEC(1))), ZX_ERR_TIMED_OUT);
// Kill the root job.
ASSERT_TRUE(root_job->Kill(1));
// Ensure we are signalled.
EXPECT_EQ(root_job_killed.Wait(), ZX_OK);
END_TEST;
}
// Test that by the time the RootJobObserver callback fires due to the
// root job being killed, all of the root job's children have already
// been terminated.
bool TestChildrenAlreadyDeadWhenCallbackFires() {
BEGIN_TEST;
// Create a new root job, containing a process and a thread.
fbl::RefPtr<JobDispatcher> root_job = JobDispatcher::CreateRootJob();
KernelHandle<ProcessDispatcher> child_process = CreateProcess(root_job);
KernelHandle<ThreadDispatcher> child_thread = CreateThread(child_process.dispatcher());
// Create a root job observer. The callback ensures that the child process and thread
// are both dead when it fires.
Event callback_fired;
RootJobObserver observer{
root_job, nullptr, [&]() {
ASSERT(child_process.dispatcher()->state() == ProcessDispatcher::State::DEAD);
ASSERT(child_thread.dispatcher()->IsDyingOrDead());
callback_fired.Signal();
}};
// Ensure everything is running.
ASSERT_EQ(child_process.dispatcher()->state(), ProcessDispatcher::State::RUNNING);
ASSERT_FALSE(child_thread.dispatcher()->IsDyingOrDead());
// Kill the parent job.
ASSERT_TRUE(root_job->Kill(1));
// Wait for the callback to fire.
callback_fired.Wait();
END_TEST;
}
// Ensure that the RootJobObserver callback fires when the root job has
// no children, even if the root job itself is not killed.
bool TestCallbackFiresWhenNoChildren() {
BEGIN_TEST;
// Create a new root job, containing a process and a thread.
fbl::RefPtr<JobDispatcher> root_job = JobDispatcher::CreateRootJob();
KernelHandle<ProcessDispatcher> child_process = CreateProcess(root_job);
KernelHandle<ThreadDispatcher> child_thread = CreateThread(child_process.dispatcher());
// Create a root job observer. The callback ensures that the child process and thread
// are both dead when it fires.
Event callback_fired;
RootJobObserver observer{root_job, nullptr, [&]() { callback_fired.Signal(); }};
// Ensure everything is running.
ASSERT_EQ(child_process.dispatcher()->state(), ProcessDispatcher::State::RUNNING);
ASSERT_FALSE(child_thread.dispatcher()->IsDyingOrDead());
// Kill the process.
child_process.dispatcher()->Kill(1);
// Ensure the callback fires.
callback_fired.Wait();
END_TEST;
}
// Ensure that the it's safe for multiple observers to observe termination of a root job.
bool TestMultipleObserversOneJob() {
BEGIN_TEST;
// Create a new root job containing one process.
fbl::RefPtr<JobDispatcher> root_job = JobDispatcher::CreateRootJob();
KernelHandle<ProcessDispatcher> child_process = CreateProcess(root_job);
// Create two observers.
int count = 0;
RootJobObserver observer1{root_job, nullptr, [&]() { ++count; }};
RootJobObserver observer2{root_job, nullptr, [&]() { ++count; }};
// Kill the process.
ASSERT_EQ(count, 0);
child_process.dispatcher()->Kill(1);
// Ensure the callback fired twice.
ASSERT_EQ(count, 2);
END_TEST;
}
} // namespace
UNITTEST_START_TESTCASE(root_job_observer)
UNITTEST("CreateDestroy", TestCreateDestroy)
UNITTEST("CallbackFiresOnRootJobDeath", TestCallbackFiresOnRootJobDeath)
UNITTEST("ChildrenAlreadyDeadWhenCallbackFires", TestChildrenAlreadyDeadWhenCallbackFires)
UNITTEST("CallbackFiresWhenNoChildren", TestCallbackFiresWhenNoChildren)
UNITTEST("MultipleObserversOneJob", TestMultipleObserversOneJob)
UNITTEST_END_TESTCASE(root_job_observer, "root_job_observer", "RootJobObserver tests")