| // 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; |
| } |
| |
| } // namespace |
| |
| UNITTEST_START_TESTCASE(root_job_observer) |
| UNITTEST("CreateDestroy", TestCreateDestroy) |
| UNITTEST("CallbackFiresOnRootJobDeath", TestCallbackFiresOnRootJobDeath) |
| UNITTEST("ChildrenAlreadyDeadWhenCallbackFires", TestChildrenAlreadyDeadWhenCallbackFires) |
| UNITTEST("CallbackFiresWhenNoChildren", TestCallbackFiresWhenNoChildren) |
| UNITTEST_END_TESTCASE(root_job_observer, "root_job_observer", "RootJobObserver tests") |