blob: bc9bd0589a05e326352b8800a89474cb0be8c4bc [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
#include "src/developer/forensics/exceptions/process_limbo_manager.h"
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
#include <lib/inspect/cpp/inspect.h>
#include <lib/syslog/cpp/macros.h>
#include <zircon/status.h>
#include <gtest/gtest.h>
#include "fuchsia/exception/cpp/fidl.h"
#include "src/developer/forensics/exceptions/exception_broker.h"
#include "src/developer/forensics/exceptions/tests/crasher_wrapper.h"
#include "src/lib/fsl/handles/object_info.h"
namespace forensics {
namespace exceptions {
namespace {
using fuchsia::exception::ExceptionInfo;
using fuchsia::exception::ExceptionType;
using fuchsia::exception::ProcessException;
using fuchsia::exception::ProcessExceptionMetadata;
using fuchsia::exception::ProcessLimbo_AppendFilters_Result;
using fuchsia::exception::ProcessLimbo_ListProcessesWaitingOnException_Result;
using fuchsia::exception::ProcessLimbo_ReleaseProcess_Result;
using fuchsia::exception::ProcessLimbo_RemoveFilters_Result;
using fuchsia::exception::ProcessLimbo_RetrieveException_Result;
using fuchsia::exception::ProcessLimbo_WatchProcessesWaitingOnException_Result;
bool RetrieveExceptionContext(ExceptionContext* pe) {
// Create a process that crashes and obtain the relevant handles and exception.
// By the time |SpawnCrasher| has returned, the process has already thrown an exception.
if (!SpawnCrasher(pe)) {
FX_LOGS(ERROR) << "Could not spawn crasher process.";
return false;
}
// We mark the exception to be handled. We need this because we pass on the exception to the
// handler, which will resume it before we get the control back. If we don't mark it as handled,
// the exception will bubble out of our environment.
return MarkExceptionAsHandled(pe);
}
ExceptionInfo ExceptionContextToExceptionInfo(const ExceptionContext& pe) {
// Translate the exception to the fidl format.
ExceptionInfo exception_info;
exception_info.process_koid = pe.exception_info.pid;
exception_info.thread_koid = pe.exception_info.tid;
exception_info.type = static_cast<ExceptionType>(pe.exception_info.type);
return exception_info;
}
void AddExceptionToLimbo(ProcessLimboManager* limbo_manager, zx::exception exception,
ExceptionInfo info) {
ProcessException process_exception = {};
process_exception.set_exception(std::move(exception));
process_exception.set_info(std::move(info));
zx_status_t status;
zx::process process;
status = process_exception.exception().get_process(&process);
if (status != ZX_OK) {
FX_PLOGS(WARNING, status) << "Could not obtain process handle for exception.";
} else {
process_exception.set_process(std::move(process));
}
zx::thread thread;
status = process_exception.exception().get_thread(&thread);
if (status != ZX_OK) {
FX_PLOGS(WARNING, status) << "Could not obtain thread handle for exception.";
} else {
process_exception.set_thread(std::move(thread));
}
limbo_manager->AddToLimbo(std::move(process_exception));
}
bool ValidateException(const ProcessExceptionMetadata&) { return true; }
bool ValidateException(const ProcessException& exception) { return exception.has_exception(); }
template <typename T>
void ValidateException(const ExceptionContext& context, const T& process_exception) {
if (std::is_same<T, ProcessException>::value)
ASSERT_TRUE(ValidateException(process_exception));
ASSERT_TRUE(process_exception.has_info());
ASSERT_TRUE(process_exception.has_process());
ASSERT_TRUE(process_exception.has_thread());
const zx::process& process = process_exception.process();
ASSERT_EQ(context.process_koid, fsl::GetKoid(process.get()));
ASSERT_EQ(context.process_koid, process_exception.info().process_koid);
ASSERT_EQ(context.process_name, fsl::GetObjectName(process.get()));
const zx::thread& thread = process_exception.thread();
ASSERT_EQ(context.thread_koid, fsl::GetKoid(thread.get()));
ASSERT_EQ(context.thread_koid, process_exception.info().thread_koid);
ASSERT_EQ(context.thread_name, fsl::GetObjectName(thread.get()));
ASSERT_EQ(process_exception.info().type, ExceptionType::FATAL_PAGE_FAULT);
}
std::unique_ptr<ProcessLimboHandler> CreateHandler(ProcessLimboManager* limbo_manager) {
auto handler = std::make_unique<ProcessLimboHandler>(limbo_manager->GetWeakPtr());
limbo_manager->AddHandler(handler->GetWeakPtr());
return handler;
}
ProcessException GetFakeException(zx_koid_t process_koid = 1, zx_koid_t thread_koid = 1,
ExceptionType type = ExceptionType::FATAL_PAGE_FAULT) {
ExceptionInfo info;
info.process_koid = process_koid;
info.thread_koid = thread_koid;
info.type = type;
ProcessException exception;
exception.set_info(info);
exception.set_process(zx::process(process_koid)); // Invalid handle. Never rely on it.
exception.set_thread(zx::thread(thread_koid)); // Invalid handle. Never rely on it.
return exception;
}
// Tests -------------------------------------------------------------------------------------------
TEST(ProcessLimboManagerTest, ProcessLimboHandler) {
ProcessLimboManager limbo_manager;
// Use the handler interface.
auto handler = CreateHandler(&limbo_manager);
// A disabled limbo should return an error.
{
bool called = false;
ProcessLimbo_WatchProcessesWaitingOnException_Result result;
handler->WatchProcessesWaitingOnException(
[&called, &result](ProcessLimbo_WatchProcessesWaitingOnException_Result res) {
called = true;
result = std::move(res);
});
ASSERT_TRUE(called);
ASSERT_TRUE(result.is_err());
EXPECT_EQ(result.err(), ZX_ERR_UNAVAILABLE);
}
limbo_manager.SetActive(true);
// We create multiple exceptions.
ExceptionContext excps[3];
ASSERT_TRUE(RetrieveExceptionContext(excps + 0));
ASSERT_TRUE(RetrieveExceptionContext(excps + 1));
ASSERT_TRUE(RetrieveExceptionContext(excps + 2));
// Get the fidl representation of the exception.
ExceptionInfo infos[3];
infos[0] = ExceptionContextToExceptionInfo(excps[0]);
infos[1] = ExceptionContextToExceptionInfo(excps[1]);
infos[2] = ExceptionContextToExceptionInfo(excps[2]);
AddExceptionToLimbo(&limbo_manager, std::move(excps[0].exception), infos[0]);
AddExceptionToLimbo(&limbo_manager, std::move(excps[1].exception), infos[1]);
AddExceptionToLimbo(&limbo_manager, std::move(excps[2].exception), infos[2]);
{
// There should be exceptions listed.
bool called = false;
ProcessLimbo_WatchProcessesWaitingOnException_Result result;
handler->WatchProcessesWaitingOnException(
[&called, &result](ProcessLimbo_WatchProcessesWaitingOnException_Result res) {
called = true;
result = std::move(res);
});
ASSERT_TRUE(called);
ASSERT_TRUE(result.is_response()) << zx_status_get_string(result.err());
ASSERT_EQ(result.response().exception_list.size(), 3u);
ValidateException(excps[0], result.response().exception_list[0]);
ValidateException(excps[1], result.response().exception_list[1]);
ValidateException(excps[2], result.response().exception_list[2]);
}
{
// ListProcessesWaitingOnException should succeed.
bool called = false;
ProcessLimbo_ListProcessesWaitingOnException_Result result;
handler->ListProcessesWaitingOnException([&called, &result](auto res) {
called = true;
result = std::move(res);
});
ASSERT_TRUE(called);
ASSERT_TRUE(result.is_response()) << zx_status_get_string(result.err());
ASSERT_EQ(result.response().exception_list.size(), 3u);
ASSERT_EQ(excps[0].process_name, result.response().exception_list[0].process_name());
ASSERT_EQ(excps[1].process_name, result.response().exception_list[1].process_name());
ASSERT_EQ(excps[2].process_name, result.response().exception_list[2].process_name());
}
{
// Getting a exception for a process that doesn't exist should fail.
bool called = false;
ProcessLimbo_RetrieveException_Result result;
handler->RetrieveException(-1, [&called, &result](ProcessLimbo_RetrieveException_Result res) {
called = true;
result = std::move(res);
});
ASSERT_TRUE(called);
ASSERT_TRUE(result.is_err());
// There should still be 3 exceptions.
ASSERT_EQ(limbo_manager.limbo().size(), 3u);
}
{
// Watching for the exception should not call.
bool watch_called = false;
ProcessLimbo_WatchProcessesWaitingOnException_Result watch_result;
handler->WatchProcessesWaitingOnException(
[&watch_called, &watch_result](ProcessLimbo_WatchProcessesWaitingOnException_Result res) {
watch_called = true;
watch_result = std::move(res);
});
ASSERT_FALSE(watch_called);
// Getting an actual exception should work.
bool called = false;
ProcessLimbo_RetrieveException_Result result = {};
handler->RetrieveException(infos[0].process_koid,
[&called, &result](ProcessLimbo_RetrieveException_Result res) {
called = true;
result = std::move(res);
});
ASSERT_TRUE(called);
ASSERT_TRUE(result.is_response()) << zx_status_get_string(result.err());
ValidateException(excps[0], result.response().process_exception);
// There should be one less exception.
ASSERT_EQ(limbo_manager.limbo().size(), 2u);
// We should've received a notification for the watch.
ASSERT_TRUE(watch_called);
ASSERT_TRUE(watch_result.is_response()) << zx_status_get_string(watch_result.err());
ASSERT_EQ(watch_result.response().exception_list.size(), 2u);
ValidateException(excps[1], watch_result.response().exception_list[0]);
ValidateException(excps[2], watch_result.response().exception_list[1]);
}
{
// That process should have been removed.
bool called = false;
ProcessLimbo_RetrieveException_Result result = {};
handler->RetrieveException(infos[0].process_koid,
[&called, &result](ProcessLimbo_RetrieveException_Result res) {
called = true;
result = std::move(res);
});
ASSERT_TRUE(called);
ASSERT_TRUE(result.is_err());
}
{
// Watching for the exception should not call.
bool watch_called = false;
ProcessLimbo_WatchProcessesWaitingOnException_Result watch_result;
handler->WatchProcessesWaitingOnException(
[&watch_called, &watch_result](ProcessLimbo_WatchProcessesWaitingOnException_Result res) {
watch_called = true;
watch_result = std::move(res);
});
ASSERT_FALSE(watch_called);
// Asking for the other process should work.
bool called = false;
ProcessLimbo_RetrieveException_Result result = {};
handler->RetrieveException(infos[2].process_koid,
[&called, &result](ProcessLimbo_RetrieveException_Result res) {
called = true;
result = std::move(res);
});
ASSERT_TRUE(called);
ASSERT_TRUE(result.is_response()) << zx_status_get_string(result.err());
ValidateException(excps[2], result.response().process_exception);
// There should be one less exception.
ASSERT_EQ(limbo_manager.limbo().size(), 1u);
// We should've received a notification for the watch.
ASSERT_TRUE(watch_called);
ASSERT_TRUE(watch_result.is_response()) << zx_status_get_string(watch_result.err());
ASSERT_EQ(watch_result.response().exception_list.size(), 1u);
ValidateException(excps[1], watch_result.response().exception_list[0]);
}
{
// Watching for the exception should not call.
bool watch_called = false;
ProcessLimbo_WatchProcessesWaitingOnException_Result watch_result;
handler->WatchProcessesWaitingOnException(
[&watch_called, &watch_result](ProcessLimbo_WatchProcessesWaitingOnException_Result res) {
watch_called = true;
watch_result = std::move(res);
});
ASSERT_FALSE(watch_called);
// Getting the last one should work.
bool called = false;
ProcessLimbo_ReleaseProcess_Result release_result = {};
handler->ReleaseProcess(infos[1].process_koid,
[&called, &release_result](ProcessLimbo_ReleaseProcess_Result res) {
called = true;
release_result = std::move(res);
});
ASSERT_TRUE(called);
ASSERT_TRUE(release_result.is_response()) << zx_status_get_string(release_result.err());
// There should be one less exception.
ASSERT_EQ(limbo_manager.limbo().size(), 0u);
// We should've received a notification for the watch.
ASSERT_TRUE(watch_called);
ASSERT_TRUE(watch_result.is_response()) << zx_status_get_string(watch_result.err());
ASSERT_EQ(watch_result.response().exception_list.size(), 0u);
}
// We kill the jobs. This kills the underlying process. We do this so that the crashed process
// doesn't get rescheduled. Otherwise the exception on the crash program would bubble out of our
// environment and create noise on the overall system.
excps[0].job.kill();
excps[1].job.kill();
excps[2].job.kill();
}
TEST(ProcessLimboManagerTest, FromExceptionBroker) {
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
inspect::Inspector inspector;
auto broker =
ExceptionBroker::Create(loop.dispatcher(), &inspector.GetRoot(), /*max_num_handlers=*/1u,
/*exception_ttl=*/zx::hour(1));
ASSERT_TRUE(broker);
ASSERT_TRUE(broker->limbo_manager().SetActive(true));
// We create multiple exceptions.
ExceptionContext excps[3];
ASSERT_TRUE(RetrieveExceptionContext(excps + 0));
ASSERT_TRUE(RetrieveExceptionContext(excps + 1));
ASSERT_TRUE(RetrieveExceptionContext(excps + 2));
// Get the fidl representation of the exception.
ExceptionInfo infos[3];
infos[0] = ExceptionContextToExceptionInfo(excps[0]);
infos[1] = ExceptionContextToExceptionInfo(excps[1]);
infos[2] = ExceptionContextToExceptionInfo(excps[2]);
// It's not easy to pass array references to lambdas.
bool cb_call0 = false;
bool cb_call1 = false;
bool cb_call2 = false;
broker->OnException(std::move(excps[0].exception), infos[0], [&cb_call0]() { cb_call0 = true; });
broker->OnException(std::move(excps[1].exception), infos[1], [&cb_call1]() { cb_call1 = true; });
broker->OnException(std::move(excps[2].exception), infos[2], [&cb_call2]() { cb_call2 = true; });
// There should be 3 exceptions on the limbo.
auto& limbo = broker->limbo_manager().limbo();
ValidateException(excps[0], limbo.find(excps[0].process_koid)->second);
ValidateException(excps[1], limbo.find(excps[1].process_koid)->second);
ValidateException(excps[2], limbo.find(excps[2].process_koid)->second);
// We kill the jobs. This kills the underlying process. We do this so that the crashed process
// doesn't get rescheduled. Otherwise the exception on the crash program would bubble out of our
// environment and create noise on the overall system.
excps[0].job.kill();
excps[1].job.kill();
excps[2].job.kill();
}
// WatchActive -------------------------------------------------------------------------------------
TEST(ProcessLimboManagerTest, WatchActiveCalls) {
ProcessLimboManager limbo_manager;
auto handler = CreateHandler(&limbo_manager);
// As no hanging get has been made there should be no change.
ASSERT_TRUE(limbo_manager.SetActive(true));
// Making a get should return immediatelly.
bool called = false;
std::optional<bool> is_active_result = std::nullopt;
handler->WatchActive([&called, &is_active_result](bool is_active) {
called = true;
is_active_result = is_active;
});
ASSERT_TRUE(called);
ASSERT_TRUE(is_active_result.has_value());
EXPECT_TRUE(is_active_result.value());
// A second change should not trigger an event (hanging get).
called = false;
is_active_result = std::nullopt;
handler->WatchActive([&called, &is_active_result](bool is_active) {
called = true;
is_active_result = is_active;
});
ASSERT_FALSE(called);
// GetActive should always return.
is_active_result = std::nullopt;
handler->GetActive([&is_active_result](bool active) { is_active_result = active; });
ASSERT_TRUE(is_active_result.has_value());
ASSERT_TRUE(is_active_result.value());
// Not making the state should no issue the call.
ASSERT_FALSE(limbo_manager.SetActive(true));
ASSERT_FALSE(called);
// Changing the state should trigger the callback.
ASSERT_TRUE(limbo_manager.SetActive(false));
ASSERT_TRUE(called);
ASSERT_TRUE(is_active_result.has_value());
EXPECT_FALSE(is_active_result.value());
// Making two get calls should only call the second.
bool called1 = false;
handler->WatchActive([&called1](bool) { called1 = true; });
bool called2 = false;
is_active_result = std::nullopt;
handler->WatchActive([&called2, &is_active_result](bool is_active) {
called2 = true;
is_active_result = is_active;
});
ASSERT_FALSE(called1);
ASSERT_FALSE(called2);
// Making the call should only call the second handler.
ASSERT_TRUE(limbo_manager.SetActive(true));
ASSERT_FALSE(called1);
ASSERT_TRUE(called2);
ASSERT_TRUE(is_active_result.has_value());
EXPECT_TRUE(is_active_result.value());
// Having an outstanding watch limbo call should fail when the limbo is disabled.
{
// The first call should return successfully an empty list.
bool called = false;
ProcessLimbo_WatchProcessesWaitingOnException_Result result;
handler->WatchProcessesWaitingOnException(
[&called, &result](ProcessLimbo_WatchProcessesWaitingOnException_Result res) {
called = true;
result = std::move(res);
});
ASSERT_TRUE(called);
ASSERT_TRUE(result.is_response()) << zx_status_get_string(result.err());
ASSERT_EQ(result.response().exception_list.size(), 0u);
}
{
// The second call should be pending.
bool called = false;
ProcessLimbo_WatchProcessesWaitingOnException_Result result;
handler->WatchProcessesWaitingOnException(
[&called, &result](ProcessLimbo_WatchProcessesWaitingOnException_Result res) {
called = true;
result = std::move(res);
});
ASSERT_FALSE(called);
ASSERT_TRUE(result.has_invalid_tag());
// Disabling the limbo should call the callback.
ASSERT_TRUE(limbo_manager.SetActive(false));
ASSERT_TRUE(called);
ASSERT_TRUE(result.is_err());
EXPECT_EQ(result.err(), ZX_ERR_CANCELED);
}
}
TEST(ProcessLimboManagerTest, ManyHandlers) {
ProcessLimboManager limbo_manager;
std::vector<std::unique_ptr<ProcessLimboHandler>> handlers;
handlers.push_back(CreateHandler(&limbo_manager));
handlers.push_back(CreateHandler(&limbo_manager));
handlers.push_back(CreateHandler(&limbo_manager));
// Calling each handler should be call the callback immediatelly.
for (auto& handler : handlers) {
bool called = false;
std::optional<bool> result;
handler->WatchActive([&called, &result](bool active) {
called = true;
result = active;
});
ASSERT_TRUE(called);
ASSERT_TRUE(result.has_value());
ASSERT_FALSE(result.value());
}
// Calling again should not return.
std::vector<bool> active_callbacks;
for (auto& handler : handlers) {
handler->WatchActive([&active_callbacks](bool active) { active_callbacks.push_back(active); });
}
ASSERT_EQ(active_callbacks.size(), 0u);
// Not changing the state should not issue any callbacks.
ASSERT_FALSE(limbo_manager.SetActive(false));
ASSERT_EQ(active_callbacks.size(), 0u);
// Having a handler issue a SetActive command should trigger the callbacks.
{
bool called = false;
handlers[0]->SetActive(true, [&called]() { called = true; });
ASSERT_TRUE(called);
}
ASSERT_EQ(active_callbacks.size(), 3u);
EXPECT_TRUE(active_callbacks[0]);
EXPECT_TRUE(active_callbacks[1]);
EXPECT_TRUE(active_callbacks[2]);
}
TEST(ProcessLimboManagerTest, Filters) {
ProcessLimboManager limbo_manager;
// Override how the manager gets the process name.
std::string name_to_return;
limbo_manager.set_obtain_process_name_fn(
[&name_to_return](zx_handle_t) -> std::string { return name_to_return; });
// No filters should add the exception.
name_to_return = "some-process";
constexpr zx_koid_t kProcessKoid1 = 1;
limbo_manager.AddToLimbo(GetFakeException(kProcessKoid1));
// It should've added the exception.
auto& limbo = limbo_manager.limbo();
ASSERT_EQ(limbo.size(), 1u);
ASSERT_TRUE(limbo.find(kProcessKoid1) != limbo.end());
// Adding a filter should filter out.
limbo_manager.AppendFiltersForTesting({"filter"});
constexpr zx_koid_t kProcessKoid2 = 2;
limbo_manager.AddToLimbo(GetFakeException(kProcessKoid2));
// No match, so the process should've been added.
ASSERT_EQ(limbo.size(), 2u);
EXPECT_TRUE(limbo.find(kProcessKoid1) != limbo.end());
EXPECT_TRUE(limbo.find(kProcessKoid2) != limbo.end());
// Adding a match should not append the process.
name_to_return = "some-filtered-process";
constexpr zx_koid_t kProcessKoid3 = 3;
limbo_manager.AddToLimbo(GetFakeException(kProcessKoid3));
ASSERT_EQ(limbo.size(), 2u);
EXPECT_TRUE(limbo.find(kProcessKoid1) != limbo.end());
EXPECT_TRUE(limbo.find(kProcessKoid2) != limbo.end());
}
TEST(ProcessLimboManagerTest, FiltersGetSet) {
ProcessLimboManager limbo_manager;
auto handler = CreateHandler(&limbo_manager);
// We add some initial filters.
limbo_manager.AppendFiltersForTesting({"filter-1", "filter-2"});
// Get the initial watch.
{
bool called = false;
std::vector<std::string> filters;
handler->GetFilters([&called, &filters](std::vector<std::string> r) {
called = true;
filters = std::move(r);
});
ASSERT_TRUE(called);
ASSERT_EQ(filters.size(), 2u);
EXPECT_EQ(filters[0], "filter-1");
EXPECT_EQ(filters[1], "filter-2");
}
// Make the hanging get.
{
bool append_called = false;
ProcessLimbo_AppendFilters_Result append_result;
handler->AppendFilters({"filter-3", "filter-4"},
[&append_called, &append_result](ProcessLimbo_AppendFilters_Result r) {
append_called = true;
append_result = std::move(r);
});
ASSERT_TRUE(append_called);
ASSERT_FALSE(append_result.is_err()) << zx_status_get_string(append_result.err());
bool called = true;
std::vector<std::string> filters;
handler->GetFilters([&called, &filters](std::vector<std::string> r) {
called = true;
filters = std::move(r);
});
// The get should've been called.
ASSERT_TRUE(called);
ASSERT_EQ(filters.size(), 4u);
EXPECT_EQ(filters[0], "filter-1");
EXPECT_EQ(filters[1], "filter-2");
EXPECT_EQ(filters[2], "filter-3");
EXPECT_EQ(filters[3], "filter-4");
}
// Removing some filters should call the hanging get too.
{
bool remove_called = false;
ProcessLimbo_RemoveFilters_Result remove_result;
handler->RemoveFilters({"filter-1", "filter-3"},
[&remove_called, &remove_result](ProcessLimbo_RemoveFilters_Result r) {
remove_called = true;
remove_result = std::move(r);
});
ASSERT_TRUE(remove_called);
ASSERT_FALSE(remove_result.is_err()) << zx_status_get_string(remove_result.err());
bool called = true;
std::vector<std::string> filters;
handler->GetFilters([&called, &filters](std::vector<std::string> r) {
called = true;
filters = std::move(r);
});
// The get should've been called.
ASSERT_TRUE(called);
ASSERT_EQ(filters.size(), 2u);
EXPECT_EQ(filters[0], "filter-2");
EXPECT_EQ(filters[1], "filter-4");
}
}
TEST(ProcessLimboManagerTest, DisablingFrees) {
ProcessLimboManager limbo_manager;
auto handler = CreateHandler(&limbo_manager);
limbo_manager.SetActive(true);
// We create multiple exceptions.
ExceptionContext excps[3];
ASSERT_TRUE(RetrieveExceptionContext(excps + 0));
ASSERT_TRUE(RetrieveExceptionContext(excps + 1));
ASSERT_TRUE(RetrieveExceptionContext(excps + 2));
// Get the fidl representation of the exception.
ExceptionInfo infos[3];
infos[0] = ExceptionContextToExceptionInfo(excps[0]);
infos[1] = ExceptionContextToExceptionInfo(excps[1]);
infos[2] = ExceptionContextToExceptionInfo(excps[2]);
AddExceptionToLimbo(&limbo_manager, std::move(excps[0].exception), infos[0]);
AddExceptionToLimbo(&limbo_manager, std::move(excps[1].exception), infos[1]);
AddExceptionToLimbo(&limbo_manager, std::move(excps[2].exception), infos[2]);
// The exceptions should be there.
ASSERT_EQ(limbo_manager.limbo().size(), 3u);
{
bool called = false;
handler->SetActive(false, [&called]() { called = true; });
ASSERT_TRUE(called);
ASSERT_EQ(limbo_manager.limbo().size(), 0u);
}
// We kill the jobs. This kills the underlying process. We do this so that the crashed process
// doesn't get rescheduled. Otherwise the exception on the crash program would bubble out of our
// environment and create noise on the overall system.
excps[0].job.kill();
excps[1].job.kill();
excps[2].job.kill();
}
} // namespace
} // namespace exceptions
} // namespace forensics