blob: 8059cc250b6001978941c6ebf26532790469897f [file] [log] [blame]
// Copyright 2020 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/exception_broker.h"
#include <array>
#include <gtest/gtest.h>
#include "src/developer/forensics/exceptions/tests/crasher_wrapper.h"
#include "src/developer/forensics/testing/unit_test_fixture.h"
namespace forensics {
namespace exceptions {
namespace {
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))
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);
}
size_t NumSubprocesses() {
size_t actual{0};
size_t avail{0};
std::array<zx_koid_t, 8> children;
children.fill(ZX_KOID_INVALID);
zx_object_get_info(zx_job_default(), ZX_INFO_JOB_PROCESSES, children.data(),
children.size() * sizeof(zx_koid_t), &actual, &avail);
// Account for the process the test is runnning in.
return actual - 1;
}
using ExceptionBrokerTest = UnitTestFixture;
TEST_F(ExceptionBrokerTest, IsActive) {
auto broker = ExceptionBroker::Create(dispatcher(), &InspectRoot(), 0, zx::sec(0));
bool called{false};
broker->IsActive([&called] { called = true; });
EXPECT_TRUE(called);
}
using PendingExceptionTest = UnitTestFixture;
TEST_F(PendingExceptionTest, ExceptionExpires) {
const zx::duration ttl{zx::sec(1)};
// Create the exception.
ExceptionContext exception;
ASSERT_TRUE(RetrieveExceptionContext(&exception));
ASSERT_TRUE(exception.exception.is_valid());
PendingException pending_exception(dispatcher(), ttl, std::move(exception.exception));
RunLoopFor(ttl);
ASSERT_FALSE(pending_exception.TakeException().is_valid());
// We kill the job. 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.
exception.job.kill();
}
using ProcessHandlerTest = UnitTestFixture;
TEST_F(ProcessHandlerTest, ManagesSubprocessLifetime) {
{
ProcessHandler process_handler(
dispatcher(), [](std::string) {}, [] {});
ASSERT_EQ(NumSubprocesses(), 0u);
process_handler.Handle(zx::exception{}, zx::process{}, zx::thread{});
ASSERT_EQ(NumSubprocesses(), 1u);
}
while (NumSubprocesses() > 0) {
RunLoopUntilIdle();
}
EXPECT_EQ(NumSubprocesses(), 0u);
}
TEST_F(ProcessHandlerTest, OnAvailableCalled) {
bool available = false;
ProcessHandler process_handler(
dispatcher(), [](std::string) {}, [&available] { available = true; });
process_handler.Handle(zx::exception{}, zx::process{}, zx::thread{});
while (!available) {
RunLoopUntilIdle();
}
}
} // namespace
} // namespace exceptions
} // namespace forensics