blob: 4e8bff0882feea2f1747beccb8d58e7f9afb962f [file] [log] [blame]
// Copyright 2019 The Chromium OS 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 "biod/updater/cros_fp_updater.h"
#include <string>
#include <unordered_set>
#include <vector>
#include <base/files/file.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <base/stl_util.h>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <chromeos/ec/ec_commands.h>
#include <cros_config/fake_cros_config.h>
#include "biod/cros_fp_firmware.h"
#include "biod/updater/update_reason.h"
#include "biod/updater/update_status.h"
#include "biod/utils.h"
using ::testing::_;
using ::testing::AtLeast;
using ::testing::DefaultValue;
using ::testing::DoAll;
using ::testing::Eq;
using ::testing::NotNull;
using ::testing::Ref;
using ::testing::Return;
using ::testing::SetArgPointee;
namespace {
constexpr char kTestImageROVersion[] = "nocturne_fp_v2.2.64-58cf5974e";
constexpr char kTestImageRWVersion[] = "nocturne_fp_v2.2.110-b936c0a3c";
const std::vector<enum ec_current_image> kEcCurrentImageEnums = {
EC_IMAGE_UNKNOWN,
EC_IMAGE_RO,
EC_IMAGE_RW,
};
class MockCrosFpDeviceUpdate : public biod::CrosFpDeviceUpdate {
public:
MOCK_METHOD(base::Optional<biod::CrosFpDeviceInterface::EcVersion>,
GetVersion,
(),
(const, override));
MOCK_METHOD(bool, IsFlashProtectEnabled, (bool*), (const, override));
MOCK_METHOD(bool,
Flash,
(const biod::CrosFpFirmware&, enum ec_current_image),
(const, override));
};
class MockCrosFpBootUpdateCtrl : public biod::CrosFpBootUpdateCtrl {
public:
MOCK_METHOD(bool, TriggerBootUpdateSplash, (), (const, override));
MOCK_METHOD(bool, ScheduleReboot, (), (const, override));
};
class MockCrosFpFirmware : public biod::CrosFpFirmware {
public:
MockCrosFpFirmware() { set_status(biod::CrosFpFirmware::Status::kOk); }
void SetMockFwVersion(const ImageVersion& version) { set_version(version); }
};
} // namespace
namespace biod {
namespace updater {
class CrosFpUpdaterTest : public ::testing::Test {
protected:
void SetUp() override {
DefaultValue<bool>::Set(true);
// Lay down default rules to ensure an error is logged if an interface
// if called without explicitly specifying it.
EXPECT_CALL(dev_update_, GetVersion()).Times(0);
EXPECT_CALL(dev_update_, IsFlashProtectEnabled(_)).Times(0);
EXPECT_CALL(dev_update_, Flash(_, _)).Times(0);
EXPECT_CALL(boot_ctrl_, TriggerBootUpdateSplash()).Times(0);
EXPECT_CALL(boot_ctrl_, ScheduleReboot()).Times(0);
}
void TearDown() override { DefaultValue<bool>::Clear(); }
// Setup an environment where a device GetVersion and IsFlashProtected
// always succeed and report preset values corresponding to a preset
// mock firmware.
void SetupEnvironment(bool flash_protect,
bool ro_mismatch,
bool rw_mismatch,
enum ec_current_image ec_image = EC_IMAGE_RW) {
CrosFpFirmware::ImageVersion img_ver = {kTestImageROVersion,
kTestImageRWVersion};
if (ro_mismatch) {
img_ver.ro_version += "NEW";
}
if (rw_mismatch) {
img_ver.rw_version += "NEW";
}
fw_.SetMockFwVersion(img_ver);
EXPECT_CALL(dev_update_, GetVersion())
.WillOnce(Return(biod::CrosFpDeviceInterface::EcVersion{
.ro_version = kTestImageROVersion,
.rw_version = kTestImageRWVersion,
.current_image = ec_image,
}));
EXPECT_CALL(dev_update_, IsFlashProtectEnabled(NotNull()))
.WillOnce(DoAll(SetArgPointee<0>(flash_protect), Return(true)));
}
UpdateResult RunUpdater() { return DoUpdate(dev_update_, boot_ctrl_, fw_); }
CrosFpUpdaterTest() = default;
CrosFpUpdaterTest(const CrosFpUpdaterTest&) = delete;
CrosFpUpdaterTest& operator=(const CrosFpUpdaterTest&) = delete;
~CrosFpUpdaterTest() override = default;
MockCrosFpDeviceUpdate dev_update_;
MockCrosFpBootUpdateCtrl boot_ctrl_;
MockCrosFpFirmware fw_;
};
// EcCurrentImageToString Tests
TEST(CrosFpDeviceUpdateTest, NonblankEcCurrentImageString) {
// Given a EC Image enumeration
for (auto image : kEcCurrentImageEnums) {
// when we ask for the human readable string
std::string msg = CrosFpDeviceUpdate::EcCurrentImageToString(image);
// expect it to not be "".
EXPECT_FALSE(msg.empty()) << "Status " << base::to_underlying(image)
<< " converts to a blank status string.";
}
}
TEST(CrosFpDeviceUpdateTest, UniqueEcCurrentImageString) {
// Given a set of EC Image enumeration strings
std::unordered_set<std::string> status_msgs;
for (auto image : kEcCurrentImageEnums) {
status_msgs.insert(CrosFpDeviceUpdate::EcCurrentImageToString(image));
}
// expect the set to contain the same number of unique strings
// as there are original ec image enumerations.
EXPECT_EQ(status_msgs.size(), kEcCurrentImageEnums.size())
<< "There are one or more non-unique ec image strings.";
}
// DoUpdate Tests
// Failure code paths
TEST_F(CrosFpUpdaterTest, GetDeviceVersionFails) {
// Given a device which fails to report its version,
EXPECT_CALL(dev_update_, GetVersion()).WillOnce(Return(base::nullopt));
// expect the updater to report a get version failure with no update reason.
auto result = RunUpdater();
EXPECT_EQ(result.status, UpdateStatus::kUpdateFailedGetVersion);
EXPECT_EQ(result.reason, UpdateReason::kNone);
}
TEST_F(CrosFpUpdaterTest, GetFlashProtectFails) {
// Given a device which reports its version, but fails to
// report its flash protect status,
EXPECT_CALL(dev_update_, GetVersion())
.WillOnce(Return(biod::CrosFpDeviceInterface::EcVersion()));
EXPECT_CALL(dev_update_, IsFlashProtectEnabled(NotNull()))
.WillOnce(Return(false));
// expect the updater to report a flash protect failure
// with no update reason.
auto result = RunUpdater();
EXPECT_EQ(result.status, UpdateStatus::kUpdateFailedFlashProtect);
EXPECT_EQ(result.reason, UpdateReason::kNone);
}
TEST_F(CrosFpUpdaterTest, FPDisabled_ROMismatch_ROUpdateFail) {
// Given an environment where
SetupEnvironment(
// flash-protect is disabled,
false,
// RO needs to be updated,
true, false);
// and flashing operations fail,
ON_CALL(dev_update_, Flash(_, _)).WillByDefault(Return(false));
// expect the boot splash to be triggered,
EXPECT_CALL(boot_ctrl_, TriggerBootUpdateSplash());
// but no reboot requested (avoid boot loop),
EXPECT_CALL(boot_ctrl_, ScheduleReboot()).Times(0);
// an attempted RO flash,
EXPECT_CALL(dev_update_, Flash(Ref(fw_), EC_IMAGE_RO));
// and the updater to report an RO update failure with
// an RO version mismatch update reason.
auto result = RunUpdater();
EXPECT_EQ(result.status, UpdateStatus::kUpdateFailedRO);
EXPECT_EQ(result.reason, UpdateReason::kMismatchROVersion);
}
TEST_F(CrosFpUpdaterTest, FPDisabled_RORWMismatch_ROUpdateFail) {
// Given an environment where
SetupEnvironment(
// flash-protect is disabled,
false,
// RO needs to be updated,
true,
// RW needs to be updated,
true);
// flashing operations fail,
ON_CALL(dev_update_, Flash(_, _)).WillByDefault(Return(false));
// expect the boot splash to be triggered,
EXPECT_CALL(boot_ctrl_, TriggerBootUpdateSplash());
// but no reboot requested (avoid boot loop),
EXPECT_CALL(boot_ctrl_, ScheduleReboot()).Times(0);
// an attempted RO flash (but no RW flash),
EXPECT_CALL(dev_update_, Flash(Ref(fw_), EC_IMAGE_RO));
EXPECT_CALL(dev_update_, Flash(Ref(fw_), EC_IMAGE_RW)).Times(0);
// and the updater to report an RO update failure with
// an RO version mismatch update reason.
auto result = RunUpdater();
EXPECT_EQ(result.status, UpdateStatus::kUpdateFailedRO);
EXPECT_EQ(result.reason, UpdateReason::kMismatchROVersion);
}
TEST_F(CrosFpUpdaterTest, FPEnabled_RWMismatch_RWUpdateFail) {
// Given an environment where
SetupEnvironment(
// flash-protect is enabled,
true, false,
// RW needs to be updated,
true);
// flashing operations fail,
ON_CALL(dev_update_, Flash(_, _)).WillByDefault(Return(false));
// expect the boot splash to be triggered,
EXPECT_CALL(boot_ctrl_, TriggerBootUpdateSplash());
// but no reboot requested (avoid boot loop),
EXPECT_CALL(boot_ctrl_, ScheduleReboot()).Times(0);
// an attempted RW flash,
EXPECT_CALL(dev_update_, Flash(Ref(fw_), EC_IMAGE_RW));
// and the updater to report an RW update failure with
// an RW version mismatch update reason.
auto result = RunUpdater();
EXPECT_EQ(result.status, UpdateStatus::kUpdateFailedRW);
EXPECT_EQ(result.reason, UpdateReason::kMismatchRWVersion);
}
TEST_F(CrosFpUpdaterTest, FPDisabled_RORWMismatch_BootCtrlsBothFail) {
// Given an environment where
SetupEnvironment(
// flash-protect is disabled,
false,
// RO needs to be updated,
true,
// RW needs to be updated,
true);
// both boot control functions fail,
ON_CALL(boot_ctrl_, TriggerBootUpdateSplash()).WillByDefault(Return(false));
ON_CALL(boot_ctrl_, ScheduleReboot()).WillByDefault(Return(false));
// expect both boot control functions to be attempted,
EXPECT_CALL(boot_ctrl_, TriggerBootUpdateSplash()).Times(AtLeast(1));
EXPECT_CALL(boot_ctrl_, ScheduleReboot()).Times(AtLeast(1));
// both firmware images to be flashed,
EXPECT_CALL(dev_update_, Flash(Ref(fw_), EC_IMAGE_RW));
EXPECT_CALL(dev_update_, Flash(Ref(fw_), EC_IMAGE_RO));
// and the updater to report a success with an
// RO and RW version mismatch update reason.
auto result = RunUpdater();
EXPECT_EQ(result.status, UpdateStatus::kUpdateSucceeded);
EXPECT_EQ(result.reason, UpdateReason::kMismatchROVersion |
UpdateReason::kMismatchRWVersion);
}
// Abnormal code paths
TEST_F(CrosFpUpdaterTest, CurrentROImage_RORWMatch_UpdateRW) {
// Given an environment where
SetupEnvironment(true, false, false,
// the current boot is stuck in RO,
EC_IMAGE_RO);
// expect both boot controls to be triggered,
EXPECT_CALL(boot_ctrl_, TriggerBootUpdateSplash());
EXPECT_CALL(boot_ctrl_, ScheduleReboot());
// an attempted RW flash,
EXPECT_CALL(dev_update_, Flash(Ref(fw_), EC_IMAGE_RW));
// and the updater to report a success with an
// RO active image update reason.
auto result = RunUpdater();
EXPECT_EQ(result.status, UpdateStatus::kUpdateSucceeded);
EXPECT_EQ(result.reason, UpdateReason::kActiveImageRO);
}
// Normal code paths
TEST_F(CrosFpUpdaterTest, FPDisabled_RORWMatch_NoUpdate) {
// Given an environment where no updates are necessary
SetupEnvironment(
// and flash-protect is disabled,
false, false, false);
// expect neither boot control functions to be attempted,
EXPECT_CALL(boot_ctrl_, TriggerBootUpdateSplash()).Times(0);
EXPECT_CALL(boot_ctrl_, ScheduleReboot()).Times(0);
// no firmware images flashed,
EXPECT_CALL(dev_update_, Flash(_, _)).Times(0);
// and the updater to report an update not necessary with
// no update reason.
auto result = RunUpdater();
EXPECT_EQ(result.status, UpdateStatus::kUpdateNotNecessary);
EXPECT_EQ(result.reason, UpdateReason::kNone);
}
TEST_F(CrosFpUpdaterTest, FPEnabled_RORWMatch_NoUpdate) {
// Given an environment where no updates are necessary
SetupEnvironment(
// and flash-protect is enabled,
true, false, false);
// expect neither boot control functions to be attempted,
EXPECT_CALL(boot_ctrl_, TriggerBootUpdateSplash()).Times(0);
EXPECT_CALL(boot_ctrl_, ScheduleReboot()).Times(0);
// no firmware images flashed,
EXPECT_CALL(dev_update_, Flash(_, _)).Times(0);
// and the updater to report an update not necessary with
// no update reason.
auto result = RunUpdater();
EXPECT_EQ(result.status, UpdateStatus::kUpdateNotNecessary);
EXPECT_EQ(result.reason, UpdateReason::kNone);
}
TEST_F(CrosFpUpdaterTest, FPEnabled_ROMismatch_NoUpdate) {
// Given an environment where
SetupEnvironment(
// flash-protect is enabled
true,
// and RO needs to be updated,
true, false);
// expect neither boot control functions to be attempted,
EXPECT_CALL(boot_ctrl_, TriggerBootUpdateSplash()).Times(0);
EXPECT_CALL(boot_ctrl_, ScheduleReboot()).Times(0);
// no firmware images flashed,
EXPECT_CALL(dev_update_, Flash(_, _)).Times(0);
// and the updater to report an update not necessary with
// no update reason.
auto result = RunUpdater();
EXPECT_EQ(result.status, UpdateStatus::kUpdateNotNecessary);
EXPECT_EQ(result.reason, UpdateReason::kNone);
}
TEST_F(CrosFpUpdaterTest, RWMismatch_UpdateRW) {
// Given an environment where
SetupEnvironment(true, false,
// RW needs to be updated,
true);
// expect both boot control functions to be triggered,
EXPECT_CALL(boot_ctrl_, TriggerBootUpdateSplash());
EXPECT_CALL(boot_ctrl_, ScheduleReboot());
// RW to be flashed,
EXPECT_CALL(dev_update_, Flash(Ref(fw_), EC_IMAGE_RW));
// and the updater to report a success with an
// RW version mismatch update reason.
auto result = RunUpdater();
EXPECT_EQ(result.status, UpdateStatus::kUpdateSucceeded);
EXPECT_EQ(result.reason, UpdateReason::kMismatchRWVersion);
}
TEST_F(CrosFpUpdaterTest, FPDisabled_ROMismatch_UpdateRO) {
// Given an environment where
SetupEnvironment(
// flash-protect is disabled
false,
// and RO needs to be updated,
true, false);
// expect both boot control functions to be triggered,
EXPECT_CALL(boot_ctrl_, TriggerBootUpdateSplash());
EXPECT_CALL(boot_ctrl_, ScheduleReboot());
// RO to be flashed,
EXPECT_CALL(dev_update_, Flash(Ref(fw_), EC_IMAGE_RO));
// and the updater to report a success with an
// RO version mismatch update reason.
auto result = RunUpdater();
EXPECT_EQ(result.status, UpdateStatus::kUpdateSucceeded);
EXPECT_EQ(result.reason, UpdateReason::kMismatchROVersion);
}
TEST_F(CrosFpUpdaterTest, FPDisabled_RORWMismatch_UpdateRORW) {
// Given an environment where
SetupEnvironment(
// flash-protect is disabled,
false,
// RO needs to be updated,
true,
// RW needs to be updated,
true);
// expect both boot control functions to be triggered,
EXPECT_CALL(boot_ctrl_, TriggerBootUpdateSplash()).Times(AtLeast(1));
EXPECT_CALL(boot_ctrl_, ScheduleReboot()).Times(AtLeast(1));
// both firmware images to be flashed,
EXPECT_CALL(dev_update_, Flash(Ref(fw_), EC_IMAGE_RO));
EXPECT_CALL(dev_update_, Flash(Ref(fw_), EC_IMAGE_RW));
// and the updater to report a success with an
// RW and RO version mismatch update reason.
auto result = RunUpdater();
EXPECT_EQ(result.status, UpdateStatus::kUpdateSucceeded);
EXPECT_EQ(result.reason, UpdateReason::kMismatchROVersion |
UpdateReason::kMismatchRWVersion);
}
} // namespace updater
} // namespace biod