blob: 714b7240971b683694116ee50a5482ec7c60bccd [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/feedback/reboot_log/reboot_log.h"
#include <lib/syslog/cpp/macros.h>
#include <string>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "src/developer/forensics/feedback/reboot_log/graceful_reboot_reason.h"
#include "src/developer/forensics/testing/unit_test_fixture.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 feedback {
namespace {
using GracefulRebootReason = fuchsia::hardware::power::statecontrol::RebootReason;
struct RebootReasonTestParam {
std::string test_name;
std::optional<std::string> zircon_reboot_log;
std::optional<GracefulRebootReason> graceful_reboot_reason;
RebootReason output_reboot_reason;
};
struct UptimeTestParam {
std::string test_name;
std::optional<std::string> zircon_reboot_log;
std::optional<zx::duration> output_uptime;
};
struct CriticalProcessTestParam {
std::string test_name;
std::optional<std::string> zircon_reboot_log;
std::optional<std::string> output_critical_process;
};
struct RebootLogStrTestParam {
std::string test_name;
std::optional<std::string> zircon_reboot_log;
std::optional<GracefulRebootReason> graceful_reboot_reason;
std::optional<std::string> output_reboot_log_str;
};
template <typename TestParam>
class RebootLogTest : public UnitTestFixture, public testing::WithParamInterface<TestParam> {
protected:
void WriteZirconRebootLogContents(const std::string& contents) {
FX_CHECK(tmp_dir_.NewTempFileWithData(contents, &zircon_reboot_log_path_))
<< "Failed to create temporary Zircon reboot log";
}
void WriteGracefulRebootLogContents(const std::string& contents) {
FX_CHECK(tmp_dir_.NewTempFileWithData(contents, &graceful_reboot_log_path_))
<< "Failed to create temporary graceful reboot log";
}
void WriteGracefulRebootLogContents(const GracefulRebootReason reason) {
FX_CHECK(tmp_dir_.NewTempFileWithData("", &graceful_reboot_log_path_))
<< "Failed to create temporary graceful reboot log";
cobalt::Logger cobalt(dispatcher(), services(), &clock_);
FX_CHECK(
files::WriteFile(graceful_reboot_log_path_, ToFileContent(ToGracefulRebootReason(reason))));
}
std::string zircon_reboot_log_path_;
std::string graceful_reboot_log_path_;
private:
timekeeper::TestClock clock_;
files::ScopedTempDir tmp_dir_;
};
using RebootLogReasonTest = RebootLogTest<RebootReasonTestParam>;
INSTANTIATE_TEST_SUITE_P(
WithVariousRebootLogs, RebootLogReasonTest,
::testing::ValuesIn(std::vector<RebootReasonTestParam>({
{
"ZirconCleanNoGraceful",
"ZIRCON REBOOT REASON (NO CRASH)\n\nUPTIME (ms)\n1234",
std::nullopt,
RebootReason::kGenericGraceful,
},
{
"ZirconCleanGracefulUserRequest",
"ZIRCON REBOOT REASON (NO CRASH)\n\nUPTIME (ms)\n1234",
GracefulRebootReason::USER_REQUEST,
RebootReason::kUserRequest,
},
{
"ZirconCleanGracefulSystemUpdate",
"ZIRCON REBOOT REASON (NO CRASH)\n\nUPTIME (ms)\n1234",
GracefulRebootReason::SYSTEM_UPDATE,
RebootReason::kSystemUpdate,
},
{
"ZirconCleanGracefulHighTemperature",
"ZIRCON REBOOT REASON (NO CRASH)\n\nUPTIME (ms)\n1234",
GracefulRebootReason::HIGH_TEMPERATURE,
RebootReason::kHighTemperature,
},
{
"ZirconCleanGracefulSessionFailure",
"ZIRCON REBOOT REASON (NO CRASH)\n\nUPTIME (ms)\n1234",
GracefulRebootReason::SESSION_FAILURE,
RebootReason::kSessionFailure,
},
{
"ZirconCleanGracefulNotSupported",
"ZIRCON REBOOT REASON (NO CRASH)\n\nUPTIME (ms)\n1234",
static_cast<GracefulRebootReason>(1000u),
RebootReason::kGenericGraceful,
},
{
"Cold",
std::nullopt,
GracefulRebootReason::USER_REQUEST,
RebootReason::kCold,
},
{
"KernelPanic",
"ZIRCON REBOOT REASON (KERNEL PANIC)\n\nUPTIME (ms)\n1234",
GracefulRebootReason::USER_REQUEST,
RebootReason::kKernelPanic,
},
{
"OOM",
"ZIRCON REBOOT REASON (OOM)\n\nUPTIME (ms)\n1234",
GracefulRebootReason::USER_REQUEST,
RebootReason::kOOM,
},
{
"SwWatchdog",
"ZIRCON REBOOT REASON (SW WATCHDOG)\n\nUPTIME (ms)\n1234",
GracefulRebootReason::USER_REQUEST,
RebootReason::kSoftwareWatchdogTimeout,
},
{
"HwWatchdog",
"ZIRCON REBOOT REASON (HW WATCHDOG)\n\nUPTIME (ms)\n1234",
GracefulRebootReason::USER_REQUEST,
RebootReason::kHardwareWatchdogTimeout,
},
{
"Brownout",
"ZIRCON REBOOT REASON (BROWNOUT)\n\nUPTIME (ms)\n1234",
GracefulRebootReason::USER_REQUEST,
RebootReason::kBrownout,
},
{
"Spontaneous",
"ZIRCON REBOOT REASON (UNKNOWN)\n\nUPTIME (ms)\n1234",
GracefulRebootReason::USER_REQUEST,
RebootReason::kSpontaneous,
},
{
"RootJobTermination",
"ZIRCON REBOOT REASON (USERSPACE ROOT JOB TERMINATION)\n\nUPTIME (ms)\n1234",
GracefulRebootReason::USER_REQUEST,
RebootReason::kRootJobTermination,
},
{
"NotParseable",
"NOT PARSEABLE",
GracefulRebootReason::USER_REQUEST,
RebootReason::kNotParseable,
},
})),
[](const testing::TestParamInfo<RebootReasonTestParam>& info) { return info.param.test_name; });
TEST_P(RebootLogReasonTest, Succeed) {
const auto param = GetParam();
if (param.zircon_reboot_log.has_value()) {
WriteZirconRebootLogContents(param.zircon_reboot_log.value());
}
if (param.graceful_reboot_reason.has_value()) {
WriteGracefulRebootLogContents(param.graceful_reboot_reason.value());
}
const RebootLog reboot_log(RebootLog::ParseRebootLog(
zircon_reboot_log_path_, graceful_reboot_log_path_, /*not_a_fdr=*/true));
EXPECT_EQ(reboot_log.RebootReason(), param.output_reboot_reason);
}
TEST_F(RebootLogReasonTest, Succeed_ZirconCleanGracefulFdr) {
WriteZirconRebootLogContents("ZIRCON REBOOT REASON (NO CRASH)\n\nUPTIME (ms)\n1234");
WriteGracefulRebootLogContents(GracefulRebootReason::SYSTEM_UPDATE);
const RebootLog reboot_log(RebootLog::ParseRebootLog(
zircon_reboot_log_path_, graceful_reboot_log_path_, /*not_a_fdr=*/false));
EXPECT_EQ(reboot_log.RebootReason(), RebootReason::kFdr);
}
TEST_F(RebootLogReasonTest, Succeed_ZirconCleanGracefulNotParseable) {
WriteZirconRebootLogContents("ZIRCON REBOOT REASON (NO CRASH)\n\nUPTIME (ms)\n1234");
WriteGracefulRebootLogContents("NOT PARSEABLE");
const RebootLog reboot_log(RebootLog::ParseRebootLog(
zircon_reboot_log_path_, graceful_reboot_log_path_, /*not_a_fdr=*/true));
EXPECT_EQ(reboot_log.RebootReason(), RebootReason::kGenericGraceful);
ASSERT_TRUE(reboot_log.Uptime().has_value());
EXPECT_EQ(*reboot_log.Uptime(), zx::msec(1234));
}
using RebootLogUptimeTest = RebootLogTest<UptimeTestParam>;
INSTANTIATE_TEST_SUITE_P(WithVariousRebootLogs, RebootLogUptimeTest,
::testing::ValuesIn(std::vector<UptimeTestParam>({
{
"WellFormedLog",
"ZIRCON REBOOT REASON (NO CRASH)\n\nUPTIME (ms)\n1234",
zx::msec(1234),
},
{
"NoZirconRebootLog",
std::nullopt,
std::nullopt,
},
{
"EmptyZirconRebootLog",
"",
std::nullopt,
},
{
"TooFewLines",
"BAD REBOOT REASON (NO CRASH)\n\nUPTIME (ms)\n",
std::nullopt,
},
{
"BadUptimeString",
"BAD REBOOT REASON (NO CRASH)\n\nDOWNTIME (ms)\n1234",
std::nullopt,
},
})),
[](const testing::TestParamInfo<UptimeTestParam>& info) {
return info.param.test_name;
});
TEST_P(RebootLogUptimeTest, Succeed) {
const auto param = GetParam();
if (param.zircon_reboot_log.has_value()) {
WriteZirconRebootLogContents(param.zircon_reboot_log.value());
}
const RebootLog reboot_log(RebootLog::ParseRebootLog(
zircon_reboot_log_path_, graceful_reboot_log_path_, /*not_a_fdr=*/true));
if (param.output_uptime.has_value()) {
ASSERT_TRUE(reboot_log.Uptime().has_value());
EXPECT_EQ(*reboot_log.Uptime(), param.output_uptime.value());
} else {
EXPECT_FALSE(reboot_log.Uptime().has_value());
}
}
using RebootLogCriticalProcessTest = RebootLogTest<CriticalProcessTestParam>;
INSTANTIATE_TEST_SUITE_P(WithVariousRebootLogs, RebootLogCriticalProcessTest,
::testing::ValuesIn(std::vector<CriticalProcessTestParam>({
{
"WellFormedLog",
"ZIRCON REBOOT REASON (NO CRASH)\n\nUPTIME (ms)\n1234\n"
"ROOT JOB TERMINATED BY CRITICAL PROCESS DEATH: foo (1)",
"foo",
},
{
"NoZirconRebootLog",
std::nullopt,
std::nullopt,
},
{
"EmptyZirconRebootLog",
"",
std::nullopt,
},
{
"TooFewLines",
"BAD REBOOT REASON (NO CRASH)\n\nUPTIME (ms)\n",
std::nullopt,
},
{
"BadCriticalProcessString",
"ZIRCON REBOOT REASON (NO CRASH)\n\nUPTIME (ms)\n1234\n"
"ROOT JOB TERMINATED BY CRITICAL PROCESS ALIVE: foo (1)",
std::nullopt,
},
})),
[](const testing::TestParamInfo<CriticalProcessTestParam>& info) {
return info.param.test_name;
});
TEST_P(RebootLogCriticalProcessTest, Succeed) {
const auto param = GetParam();
if (param.zircon_reboot_log.has_value()) {
WriteZirconRebootLogContents(param.zircon_reboot_log.value());
}
const RebootLog reboot_log(RebootLog::ParseRebootLog(
zircon_reboot_log_path_, graceful_reboot_log_path_, /*not_a_fdr=*/true));
if (param.output_critical_process.has_value()) {
ASSERT_TRUE(reboot_log.CriticalProcess().has_value());
EXPECT_EQ(*reboot_log.CriticalProcess(), param.output_critical_process.value());
} else {
EXPECT_FALSE(reboot_log.CriticalProcess().has_value());
}
}
using RebootLogStrTest = RebootLogTest<RebootLogStrTestParam>;
INSTANTIATE_TEST_SUITE_P(
WithVariousRebootLogs, RebootLogStrTest,
::testing::ValuesIn(std::vector<RebootLogStrTestParam>({
{
"ConcatenatesZirconAndGraceful",
"ZIRCON REBOOT REASON (NO CRASH)\n\nUPTIME (ms)\n1234",
GracefulRebootReason::USER_REQUEST,
"ZIRCON REBOOT REASON (NO CRASH)\n\nUPTIME (ms)\n1234\nGRACEFUL REBOOT REASON (USER "
"REQUEST)\n\nFINAL REBOOT REASON (USER REQUEST)",
},
{
// This test is the same as the above test, but is used to show that there may be an
// ungraceful zircon reboot reason and a graceful reboot reason.
"ConcatenatesZirconUngracefulAndGraceful",
"ZIRCON REBOOT REASON (KERNEL PANIC)\n\nUPTIME (ms)\n1234",
GracefulRebootReason::USER_REQUEST,
"ZIRCON REBOOT REASON (KERNEL PANIC)\n\nUPTIME (ms)\n1234\nGRACEFUL REBOOT REASON "
"(USER REQUEST)\n\nFINAL REBOOT REASON (KERNEL PANIC)",
},
{
"NoGracefulRebootLog",
"ZIRCON REBOOT REASON (NO CRASH)\n\nUPTIME (ms)\n1234",
std::nullopt,
"ZIRCON REBOOT REASON (NO CRASH)\n\nUPTIME (ms)\n1234\nGRACEFUL REBOOT REASON "
"(NONE)\n\nFINAL REBOOT REASON (GENERIC GRACEFUL)",
},
{
"NoZirconRebootLog",
std::nullopt,
GracefulRebootReason::USER_REQUEST,
"GRACEFUL REBOOT REASON (USER REQUEST)\n\nFINAL REBOOT REASON (COLD)",
},
})),
[](const testing::TestParamInfo<RebootLogStrTestParam>& info) { return info.param.test_name; });
TEST_P(RebootLogStrTest, Succeed) {
const auto param = GetParam();
if (param.zircon_reboot_log.has_value()) {
WriteZirconRebootLogContents(param.zircon_reboot_log.value());
}
if (param.graceful_reboot_reason.has_value()) {
WriteGracefulRebootLogContents(param.graceful_reboot_reason.value());
}
const RebootLog reboot_log(RebootLog::ParseRebootLog(
zircon_reboot_log_path_, graceful_reboot_log_path_, /*not_a_fdr=*/true));
if (param.output_reboot_log_str.has_value()) {
EXPECT_EQ(reboot_log.RebootLogStr(), param.output_reboot_log_str.value());
} else {
}
}
TEST_F(RebootLogStrTest, Succeed_SetGracefulFDR) {
WriteZirconRebootLogContents("ZIRCON REBOOT REASON (NO CRASH)\n\nUPTIME (ms)\n1234");
WriteGracefulRebootLogContents(GracefulRebootReason::FACTORY_DATA_RESET);
const RebootLog reboot_log(RebootLog::ParseRebootLog(
zircon_reboot_log_path_, graceful_reboot_log_path_, /*not_a_fdr=*/true));
EXPECT_EQ(reboot_log.RebootLogStr(),
"ZIRCON REBOOT REASON (NO CRASH)\n\nUPTIME (ms)\n1234\n"
"GRACEFUL REBOOT REASON (FACTORY DATA RESET)\n\n"
"FINAL REBOOT REASON (FACTORY DATA RESET)");
}
TEST_F(RebootLogStrTest, Succeed_InferFDR) {
WriteZirconRebootLogContents("ZIRCON REBOOT REASON (NO CRASH)\n\nUPTIME (ms)\n1234");
const RebootLog reboot_log(RebootLog::ParseRebootLog(
zircon_reboot_log_path_, graceful_reboot_log_path_, /*not_a_fdr=*/false));
EXPECT_EQ(reboot_log.RebootReason(), RebootReason::kFdr);
EXPECT_EQ(reboot_log.RebootLogStr(),
"ZIRCON REBOOT REASON (NO CRASH)\n\nUPTIME (ms)\n1234\n"
"GRACEFUL REBOOT REASON (NONE)\n\nFINAL REBOOT REASON (FACTORY DATA RESET)");
}
} // namespace
} // namespace feedback
} // namespace forensics