blob: 761a599cc4c5cad4449723695bfb7677af2058ec [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/last_reboot/reporter.h"
#include <lib/fpromise/result.h>
#include <lib/inspect/cpp/vmo/types.h>
#include <lib/zx/time.h>
#include <zircon/errors.h>
#include <cstdint>
#include <memory>
#include <optional>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "src/developer/forensics/feedback/reboot_log/reboot_log.h"
#include "src/developer/forensics/testing/gpretty_printers.h"
#include "src/developer/forensics/testing/stubs/cobalt_logger.h"
#include "src/developer/forensics/testing/stubs/cobalt_logger_factory.h"
#include "src/developer/forensics/testing/stubs/crash_reporter.h"
#include "src/developer/forensics/testing/unit_test_fixture.h"
#include "src/developer/forensics/utils/cobalt/event.h"
#include "src/developer/forensics/utils/cobalt/logger.h"
#include "src/developer/forensics/utils/cobalt/metrics.h"
#include "src/lib/files/file.h"
#include "src/lib/files/path.h"
#include "src/lib/files/scoped_temp_dir.h"
#include "src/lib/fxl/strings/string_printf.h"
#include "src/lib/timekeeper/test_clock.h"
namespace forensics {
namespace last_reboot {
namespace {
using testing::IsEmpty;
using testing::UnorderedElementsAreArray;
constexpr char kHasReportedOnPath[] = "/tmp/has_reported_on_reboot_log.txt";
constexpr char kNoGracefulReason[] = "GRACEFUL REBOOT REASON (NONE)";
struct UngracefulRebootTestParam {
std::string test_name;
std::string zircon_reboot_log;
std::string reboot_reason;
std::string output_crash_signature;
std::optional<zx::duration> output_uptime;
cobalt::LastRebootReason output_last_reboot_reason;
};
struct GracefulRebootTestParam {
std::string test_name;
std::optional<std::string> graceful_reboot_log;
cobalt::LastRebootReason output_last_reboot_reason;
};
struct GracefulRebootWithCrashTestParam {
std::string test_name;
std::string graceful_reboot_log;
std::string reboot_reason;
std::string output_crash_signature;
zx::duration output_uptime;
cobalt::LastRebootReason output_last_reboot_reason;
bool output_is_fatal;
};
template <typename TestParam>
class ReporterTest : public UnitTestFixture, public testing::WithParamInterface<TestParam> {
public:
ReporterTest()
: cobalt_(dispatcher(), services(), &clock_),
redactor_(new IdentityRedactor(inspect::BoolProperty())) {}
void TearDown() override { files::DeletePath(kHasReportedOnPath, /*recursive=*/false); }
protected:
void SetUpCrashReporterServer(std::unique_ptr<stubs::CrashReporterBase> server) {
crash_reporter_server_ = std::move(server);
}
void SetUpRedactor(std::unique_ptr<RedactorBase> redactor) { redactor_ = std::move(redactor); }
void WriteZirconRebootLogContents(const std::string& contents) {
FX_CHECK(tmp_dir_.NewTempFileWithData(contents, &zircon_reboot_log_path_));
}
void WriteGracefulRebootLogContents(const std::string& contents) {
FX_CHECK(tmp_dir_.NewTempFileWithData(contents, &graceful_reboot_log_path_));
}
void SetAsFdr() { not_a_fdr_ = false; }
void ReportOnRebootLog() {
const auto reboot_log = feedback::RebootLog::ParseRebootLog(
zircon_reboot_log_path_, graceful_reboot_log_path_, not_a_fdr_);
ReportOn(reboot_log);
}
void ReportOn(const feedback::RebootLog& reboot_log) {
Reporter reporter(dispatcher(), services(), &cobalt_, redactor_.get(),
crash_reporter_server_.get());
reporter.ReportOn(reboot_log, /*delay=*/zx::sec(0));
RunLoopUntilIdle();
}
std::string zircon_reboot_log_path_;
std::string graceful_reboot_log_path_;
std::unique_ptr<stubs::CrashReporterBase> crash_reporter_server_;
private:
timekeeper::TestClock clock_;
cobalt::Logger cobalt_;
std::unique_ptr<RedactorBase> redactor_;
files::ScopedTempDir tmp_dir_;
bool not_a_fdr_{true};
};
using GenericReporterTest = ReporterTest<UngracefulRebootTestParam /*does not matter*/>;
TEST_F(GenericReporterTest, Succeed_WellFormedRebootLog) {
const zx::duration uptime = zx::msec(74715002);
const feedback::RebootLog reboot_log(
feedback::RebootReason::kKernelPanic,
"ZIRCON REBOOT REASON (KERNEL PANIC)\n\nUPTIME (ms)\n74715002", uptime, std::nullopt);
SetUpCrashReporterServer(
std::make_unique<stubs::CrashReporter>(stubs::CrashReporter::Expectations{
.crash_signature = ToCrashSignature(reboot_log.RebootReason()),
.reboot_log = reboot_log.RebootLogStr(),
.uptime = reboot_log.Uptime(),
.is_fatal = true,
}));
SetUpCobaltServer(std::make_unique<stubs::CobaltLoggerFactory>());
ReportOn(reboot_log);
EXPECT_THAT(ReceivedCobaltEvents(),
UnorderedElementsAreArray({
cobalt::Event(cobalt::LastRebootReason::kKernelPanic, uptime.to_usecs()),
}));
EXPECT_TRUE(files::IsFile(kHasReportedOnPath));
}
TEST_F(GenericReporterTest, Succeed_RootJobTerminationRebootLog) {
const zx::duration uptime = zx::msec(74715002);
const feedback::RebootLog reboot_log(
feedback::RebootReason::kRootJobTermination,
"ZIRCON REBOOT REASON (USERSPACE ROOT JOB TERMINATION)\n\nUPTIME (ms)\n74715002\n"
"ROOT JOB TERMINATED BY CRITICAL PROCESS DEATH: foo (1)",
uptime, "foo");
SetUpCrashReporterServer(
std::make_unique<stubs::CrashReporter>(stubs::CrashReporter::Expectations{
.crash_signature =
ToCrashSignature(reboot_log.RebootReason(), reboot_log.CriticalProcess()),
.reboot_log = reboot_log.RebootLogStr(),
.uptime = reboot_log.Uptime(),
.is_fatal = true,
}));
SetUpCobaltServer(std::make_unique<stubs::CobaltLoggerFactory>());
ReportOn(reboot_log);
EXPECT_THAT(ReceivedCobaltEvents(),
UnorderedElementsAreArray({
cobalt::Event(cobalt::LastRebootReason::kRootJobTermination, uptime.to_usecs()),
}));
EXPECT_TRUE(files::IsFile(kHasReportedOnPath));
}
TEST_F(GenericReporterTest, Succeed_NoUptime) {
const feedback::RebootLog reboot_log(feedback::RebootReason::kKernelPanic,
"ZIRCON REBOOT REASON (KERNEL PANIC)\n", std::nullopt,
std::nullopt);
SetUpCrashReporterServer(
std::make_unique<stubs::CrashReporter>(stubs::CrashReporter::Expectations{
.crash_signature = ToCrashSignature(reboot_log.RebootReason()),
.reboot_log = reboot_log.RebootLogStr(),
.uptime = std::nullopt,
.is_fatal = true,
}));
SetUpCobaltServer(std::make_unique<stubs::CobaltLoggerFactory>());
ReportOn(reboot_log);
EXPECT_THAT(ReceivedCobaltEvents(),
UnorderedElementsAreArray({
cobalt::Event(cobalt::LastRebootReason::kKernelPanic, /*duration=*/0u),
}));
}
class SimpleRedactor : public RedactorBase {
public:
SimpleRedactor() : RedactorBase(inspect::BoolProperty()) {}
std::string& Redact(std::string& text) override {
text = "<REDACTED>";
return text;
}
std::string UnredactedCanary() const override { return ""; }
std::string RedactedCanary() const override { return ""; }
};
TEST_F(GenericReporterTest, Succeed_RedactsData) {
const feedback::RebootLog reboot_log(feedback::RebootReason::kKernelPanic,
"ZIRCON REBOOT REASON (KERNEL PANIC)\n", std::nullopt,
std::nullopt);
SetUpCrashReporterServer(
std::make_unique<stubs::CrashReporter>(stubs::CrashReporter::Expectations{
.crash_signature = ToCrashSignature(reboot_log.RebootReason()),
.reboot_log = "<REDACTED>",
.uptime = std::nullopt,
.is_fatal = true,
}));
SetUpCobaltServer(std::make_unique<stubs::CobaltLoggerFactory>());
SetUpRedactor(std::make_unique<SimpleRedactor>());
ReportOn(reboot_log);
EXPECT_THAT(ReceivedCobaltEvents(),
UnorderedElementsAreArray({
cobalt::Event(cobalt::LastRebootReason::kKernelPanic, /*duration=*/0u),
}));
}
TEST_F(GenericReporterTest, Succeed_NoCrashReportFiledCleanReboot) {
const zx::duration uptime = zx::msec(74715002);
const feedback::RebootLog reboot_log(feedback::RebootReason::kGenericGraceful,
"ZIRCON REBOOT REASON (NO CRASH)\n\nUPTIME (ms)\n74715002",
uptime, std::nullopt);
SetUpCrashReporterServer(
std::make_unique<stubs::CrashReporter>(stubs::CrashReporter::Expectations{
.crash_signature = ToCrashSignature(reboot_log.RebootReason()),
.reboot_log = reboot_log.RebootLogStr(),
.uptime = uptime,
.is_fatal = true,
}));
SetUpCobaltServer(std::make_unique<stubs::CobaltLoggerFactory>());
ReportOn(reboot_log);
EXPECT_THAT(ReceivedCobaltEvents(),
UnorderedElementsAreArray({
cobalt::Event(cobalt::LastRebootReason::kGenericGraceful, uptime.to_usecs()),
}));
}
TEST_F(GenericReporterTest, Succeed_NoCrashReportFiledColdReboot) {
const feedback::RebootLog reboot_log(feedback::RebootReason::kCold, "", std::nullopt,
std::nullopt);
SetUpCrashReporterServer(std::make_unique<stubs::CrashReporterNoFileExpected>());
SetUpCobaltServer(std::make_unique<stubs::CobaltLoggerFactory>());
ReportOn(reboot_log);
EXPECT_THAT(ReceivedCobaltEvents(),
UnorderedElementsAreArray({
cobalt::Event(cobalt::LastRebootReason::kCold, /*duration=*/0u),
}));
}
TEST_F(GenericReporterTest, Fail_CrashReporterFailsToFile) {
const zx::duration uptime = zx::msec(74715002);
const feedback::RebootLog reboot_log(
feedback::RebootReason::kKernelPanic,
"ZIRCON REBOOT REASON (KERNEL PANIC)\n\nUPTIME (ms)\n74715002", uptime, std::nullopt);
SetUpCrashReporterServer(std::make_unique<stubs::CrashReporterAlwaysReturnsError>());
SetUpCobaltServer(std::make_unique<stubs::CobaltLoggerFactory>());
ReportOn(reboot_log);
EXPECT_THAT(ReceivedCobaltEvents(),
UnorderedElementsAreArray({
cobalt::Event(cobalt::LastRebootReason::kKernelPanic, uptime.to_usecs()),
}));
}
TEST_F(GenericReporterTest, Succeed_DoesNothingIfAlreadyReportedOn) {
ASSERT_TRUE(files::WriteFile(kHasReportedOnPath, /*data=*/"", /*size=*/0));
const feedback::RebootLog reboot_log(
feedback::RebootReason::kKernelPanic,
"ZIRCON REBOOT REASON (KERNEL PANIC)\n\nUPTIME (ms)\n74715002", zx::msec(74715002),
std::nullopt);
SetUpCrashReporterServer(std::make_unique<stubs::CrashReporterNoFileExpected>());
SetUpCobaltServer(std::make_unique<stubs::CobaltLoggerFactory>());
ReportOn(reboot_log);
EXPECT_THAT(ReceivedCobaltEvents(), IsEmpty());
}
using UngracefulReporterTest = ReporterTest<UngracefulRebootTestParam>;
INSTANTIATE_TEST_SUITE_P(WithVariousRebootLogs, UngracefulReporterTest,
::testing::ValuesIn(std::vector<UngracefulRebootTestParam>({
{
"KernelPanic",
"ZIRCON REBOOT REASON (KERNEL PANIC)\n\nUPTIME (ms)\n65487494",
"FINAL REBOOT REASON (KERNEL PANIC)",
"fuchsia-kernel-panic",
zx::msec(65487494),
cobalt::LastRebootReason::kKernelPanic,
},
{
"OOM",
"ZIRCON REBOOT REASON (OOM)\n\nUPTIME (ms)\n65487494",
"FINAL REBOOT REASON (OOM)",
"fuchsia-oom",
zx::msec(65487494),
cobalt::LastRebootReason::kSystemOutOfMemory,
},
{
"Spontaneous",
"ZIRCON REBOOT REASON (UNKNOWN)\n\nUPTIME (ms)\n65487494",
"FINAL REBOOT REASON (SPONTANEOUS)",
"fuchsia-brief-power-loss",
zx::msec(65487494),
cobalt::LastRebootReason::kBriefPowerLoss,
},
{
"SoftwareWatchdogTimeout",
"ZIRCON REBOOT REASON (SW WATCHDOG)\n\nUPTIME (ms)\n65487494",
"FINAL REBOOT REASON (SOFTWARE WATCHDOG TIMEOUT)",
"fuchsia-sw-watchdog-timeout",
zx::msec(65487494),
cobalt::LastRebootReason::kSoftwareWatchdogTimeout,
},
{
"HardwareWatchdogTimeout",
"ZIRCON REBOOT REASON (HW WATCHDOG)\n\nUPTIME (ms)\n65487494",
"FINAL REBOOT REASON (HARDWARE WATCHDOG TIMEOUT)",
"fuchsia-hw-watchdog-timeout",
zx::msec(65487494),
cobalt::LastRebootReason::kHardwareWatchdogTimeout,
},
{
"BrownoutPower",
"ZIRCON REBOOT REASON (BROWNOUT)\n\nUPTIME (ms)\n65487494",
"FINAL REBOOT REASON (BROWNOUT)",
"fuchsia-brownout",
zx::msec(65487494),
cobalt::LastRebootReason::kBrownout,
},
{
"NotParseable",
"NOT PARSEABLE",
"FINAL REBOOT REASON (NOT PARSEABLE)",
"fuchsia-reboot-log-not-parseable",
std::nullopt,
cobalt::LastRebootReason::kUnknown,
},
})),
[](const testing::TestParamInfo<UngracefulRebootTestParam>& info) {
return info.param.test_name;
});
TEST_P(UngracefulReporterTest, Succeed) {
const auto param = GetParam();
WriteZirconRebootLogContents(param.zircon_reboot_log);
SetUpCrashReporterServer(
std::make_unique<stubs::CrashReporter>(stubs::CrashReporter::Expectations{
.crash_signature = param.output_crash_signature,
.reboot_log = fxl::StringPrintf("%s\n%s\n\n%s", param.zircon_reboot_log.c_str(),
kNoGracefulReason, param.reboot_reason.c_str()),
.uptime = param.output_uptime,
.is_fatal = true,
}));
SetUpCobaltServer(std::make_unique<stubs::CobaltLoggerFactory>());
ReportOnRebootLog();
const zx::duration expected_uptime =
(param.output_uptime.has_value()) ? param.output_uptime.value() : zx::usec(0);
EXPECT_THAT(ReceivedCobaltEvents(),
UnorderedElementsAreArray({
cobalt::Event(param.output_last_reboot_reason, expected_uptime.to_usecs()),
}));
}
using GracefulReporterTest = ReporterTest<GracefulRebootTestParam>;
INSTANTIATE_TEST_SUITE_P(WithVariousRebootLogs, GracefulReporterTest,
::testing::ValuesIn(std::vector<GracefulRebootTestParam>({
{
"UserRequest",
"USER REQUEST",
cobalt::LastRebootReason::kUserRequest,
},
{
"SystemUpdate",
"SYSTEM UPDATE",
cobalt::LastRebootReason::kSystemUpdate,
},
{
"ZbiSwap",
"ZBI SWAP",
cobalt::LastRebootReason::kZbiSwap,
},
})),
[](const testing::TestParamInfo<GracefulRebootTestParam>& info) {
return info.param.test_name;
});
TEST_P(GracefulReporterTest, Succeed) {
const auto param = GetParam();
WriteZirconRebootLogContents("ZIRCON REBOOT REASON (NO CRASH)\n\nUPTIME (ms)\n65487494");
if (param.graceful_reboot_log.has_value()) {
WriteGracefulRebootLogContents(param.graceful_reboot_log.value());
}
SetUpCrashReporterServer(std::make_unique<stubs::CrashReporterNoFileExpected>());
SetUpCobaltServer(std::make_unique<stubs::CobaltLoggerFactory>());
ReportOnRebootLog();
const zx::duration expected_uptime = zx::msec(65487494);
EXPECT_THAT(ReceivedCobaltEvents(),
UnorderedElementsAreArray({
cobalt::Event(param.output_last_reboot_reason, expected_uptime.to_usecs()),
}));
}
TEST_P(GracefulReporterTest, Succeed_FDR) {
WriteZirconRebootLogContents("ZIRCON REBOOT REASON (NO CRASH)\n\nUPTIME (ms)\n65487494");
SetAsFdr();
SetUpCrashReporterServer(std::make_unique<stubs::CrashReporterNoFileExpected>());
SetUpCobaltServer(std::make_unique<stubs::CobaltLoggerFactory>());
ReportOnRebootLog();
const zx::duration expected_uptime = zx::msec(65487494);
EXPECT_THAT(
ReceivedCobaltEvents(),
UnorderedElementsAreArray({
cobalt::Event(cobalt::LastRebootReason::kFactoryDataReset, expected_uptime.to_usecs()),
}));
}
using GracefulWithCrashReporterTest = ReporterTest<GracefulRebootWithCrashTestParam>;
INSTANTIATE_TEST_SUITE_P(WithVariousRebootLogs, GracefulWithCrashReporterTest,
::testing::ValuesIn(std::vector<GracefulRebootWithCrashTestParam>({
{
"SessionFailure",
"SESSION FAILURE",
"FINAL REBOOT REASON (SESSION FAILURE)",
"fuchsia-session-failure",
zx::msec(65487494),
cobalt::LastRebootReason::kSessionFailure,
false,
},
{
"OOM",
"OUT OF MEMORY",
"FINAL REBOOT REASON (OOM)",
"fuchsia-oom",
zx::msec(65487494),
cobalt::LastRebootReason::kSystemOutOfMemory,
true,
},
{
"SysmgrFailure",
"SYSMGR FAILURE",
"FINAL REBOOT REASON (SYSMGR FAILURE)",
"fuchsia-sysmgr-failure",
zx::msec(65487494),
cobalt::LastRebootReason::kSysmgrFailure,
true,
},
{
"CriticalComponentFailure",
"CRITICAL COMPONENT FAILURE",
"FINAL REBOOT REASON (CRITICAL COMPONENT FAILURE)",
"fuchsia-critical-component-failure",
zx::msec(65487494),
cobalt::LastRebootReason::kCriticalComponentFailure,
true,
},
{
"RetrySystemUpdate",
"RETRY SYSTEM UPDATE",
"FINAL REBOOT REASON (RETRY SYSTEM UPDATE)",
"fuchsia-retry-system-update",
zx::msec(65487494),
cobalt::LastRebootReason::kRetrySystemUpdate,
true,
},
{
"HighTemperature",
"HIGH TEMPERATURE",
"FINAL REBOOT REASON (HIGH TEMPERATURE)",
"fuchsia-reboot-high-temperature",
zx::msec(65487494),
cobalt::LastRebootReason::kHighTemperature,
true,
},
{
"NotSupported",
"NOT SUPPORTED",
"FINAL REBOOT REASON (GENERIC GRACEFUL)",
"fuchsia-undetermined-userspace-reboot",
zx::msec(65487494),
cobalt::LastRebootReason::kGenericGraceful,
true,
},
{
"NotParseable",
"NOT PARSEABLE",
"FINAL REBOOT REASON (GENERIC GRACEFUL)",
"fuchsia-undetermined-userspace-reboot",
zx::msec(65487494),
cobalt::LastRebootReason::kGenericGraceful,
true,
},
{
"None",
"NONE",
"FINAL REBOOT REASON (GENERIC GRACEFUL)",
"fuchsia-undetermined-userspace-reboot",
zx::msec(65487494),
cobalt::LastRebootReason::kGenericGraceful,
true,
},
})),
[](const testing::TestParamInfo<GracefulRebootWithCrashTestParam>& info) {
return info.param.test_name;
});
TEST_P(GracefulWithCrashReporterTest, Succeed) {
const auto param = GetParam();
const std::string zircon_reboot_log = fxl::StringPrintf(
"ZIRCON REBOOT REASON (NO CRASH)\n\nUPTIME (ms)\n%lu", param.output_uptime.to_msecs());
WriteZirconRebootLogContents(zircon_reboot_log);
if (param.graceful_reboot_log != "NONE") {
WriteGracefulRebootLogContents(param.graceful_reboot_log);
}
SetUpCrashReporterServer(
std::make_unique<stubs::CrashReporter>(stubs::CrashReporter::Expectations{
.crash_signature = param.output_crash_signature,
.reboot_log =
fxl::StringPrintf("%s\nGRACEFUL REBOOT REASON (%s)\n\n%s", zircon_reboot_log.c_str(),
param.graceful_reboot_log.c_str(), param.reboot_reason.c_str()),
.uptime = param.output_uptime,
.is_fatal = param.output_is_fatal,
}));
SetUpCobaltServer(std::make_unique<stubs::CobaltLoggerFactory>());
ReportOnRebootLog();
EXPECT_THAT(ReceivedCobaltEvents(),
UnorderedElementsAreArray({
cobalt::Event(param.output_last_reboot_reason, param.output_uptime.to_usecs()),
}));
}
} // namespace
} // namespace last_reboot
} // namespace forensics