blob: 0c6dddb7afe1f054f9555e61de1fa6718120f777 [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 <memory>
#include <string>
#include <utility>
#include <base/files/file_path.h>
#include <base/files/scoped_temp_dir.h>
#include <base/strings/string_split.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <mojo/core/embedder/embedder.h>
#include "diagnostics/common/file_test_utils.h"
#include "diagnostics/common/mojo_utils.h"
#include "diagnostics/cros_healthd/routines/battery_health/battery_health.h"
#include "diagnostics/cros_healthd/routines/routine_test_utils.h"
#include "mojo/cros_healthd_diagnostics.mojom.h"
namespace diagnostics {
namespace mojo_ipc = ::chromeos::cros_healthd::mojom;
namespace {
using ::testing::UnorderedElementsAreArray;
constexpr int kMaximumCycleCount = 5;
constexpr int kPercentBatteryWearAllowed = 10;
constexpr int kHighCycleCount = 6;
constexpr int kLowCycleCount = 4;
constexpr int kHighChargeFull = 91;
constexpr int kLowChargeFull = 89;
constexpr int kFakeBatteryChargeFullDesign = 100;
constexpr char kFakeManufacturer[] = "Fake Manufacturer";
constexpr int kFakeCurrentNow = 90871023;
constexpr int kFakePresent = 1;
constexpr char kFakeStatus[] = "Full";
constexpr int kFakeVoltageNow = 90872;
constexpr int kFakeChargeNow = 98123;
constexpr char kFullCycleCountPath[] =
"sys/class/power_supply/BAT0/cycle_count";
std::string ConstructOutput() {
std::string output;
int wear_percentage =
100 - (kHighChargeFull * 100 / kFakeBatteryChargeFullDesign);
output += "Wear Percentage: " + std::to_string(wear_percentage) + "\n";
output += "Cycle Count: " + std::to_string(kLowCycleCount) + "\n";
output += "Manufacturer: " + std::string(kFakeManufacturer) + "\n";
output += "Current Now: " + std::to_string(kFakeCurrentNow) + "\n";
output += "Present: " + std::to_string(kFakePresent) + "\n";
output += "Status: " + std::string(kFakeStatus) + "\n";
output += "Voltage Now: " + std::to_string(kFakeVoltageNow) + "\n";
output += "Charge Full: " + std::to_string(kHighChargeFull) + "\n";
output +=
"Charge Full Design: " + std::to_string(kFakeBatteryChargeFullDesign) +
"\n";
output += "Charge Now: " + std::to_string(kFakeChargeNow) + "\n";
return output;
}
} // namespace
class BatteryHealthRoutineTest : public testing::Test {
protected:
BatteryHealthRoutineTest() { mojo::core::Init(); }
BatteryHealthRoutineTest(const BatteryHealthRoutineTest&) = delete;
BatteryHealthRoutineTest& operator=(const BatteryHealthRoutineTest&) = delete;
void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); }
DiagnosticRoutine* routine() { return routine_.get(); }
mojo_ipc::RoutineUpdate* update() { return &update_; }
void CreateRoutine(
uint32_t maximum_cycle_count = kMaximumCycleCount,
uint32_t percent_battery_wear_allowed = kPercentBatteryWearAllowed) {
routine_ = std::make_unique<BatteryHealthRoutine>(
maximum_cycle_count, percent_battery_wear_allowed);
routine_->set_root_dir_for_testing(temp_dir_.GetPath());
}
void RunRoutineAndWaitForExit() {
DCHECK(routine_);
routine_->Start();
// Since the BatteryHealthRoutine has finished by the time Start() returns,
// there is no need to wait.
routine_->PopulateStatusUpdate(&update_, true);
}
void WriteFilesReadByLog() {
WriteFileContents(kBatteryHealthManufacturerPath, kFakeManufacturer);
WriteFileContents(kBatteryHealthCurrentNowPath,
std::to_string(kFakeCurrentNow));
WriteFileContents(kBatteryHealthPresentPath, std::to_string(kFakePresent));
WriteFileContents(kBatteryHealthStatusPath, kFakeStatus);
WriteFileContents(kBatteryHealthVoltageNowPath,
std::to_string(kFakeVoltageNow));
WriteFileContents(kBatteryHealthChargeNowPath,
std::to_string(kFakeChargeNow));
}
void WriteFileContents(const std::string& relative_file_path,
const std::string& file_contents) {
EXPECT_TRUE(
WriteFileAndCreateParentDirs(temp_dir_path()
.AppendASCII(kBatterySysfsPath)
.AppendASCII(relative_file_path),
file_contents));
}
const base::FilePath& temp_dir_path() const { return temp_dir_.GetPath(); }
private:
base::ScopedTempDir temp_dir_;
std::unique_ptr<BatteryHealthRoutine> routine_;
mojo_ipc::RoutineUpdate update_{0, mojo::ScopedHandle(),
mojo_ipc::RoutineUpdateUnion::New()};
};
// Test that the battery health routine fails if the cycle count is too high.
TEST_F(BatteryHealthRoutineTest, HighCycleCount) {
CreateRoutine();
WriteFileContents(kBatteryHealthChargeFullPath,
std::to_string(kHighChargeFull));
WriteFileContents(kBatteryHealthChargeFullDesignPath,
std::to_string(kFakeBatteryChargeFullDesign));
WriteFileContents(kBatteryHealthCycleCountPath,
std::to_string(kHighCycleCount));
RunRoutineAndWaitForExit();
VerifyNonInteractiveUpdate(update()->routine_update_union,
mojo_ipc::DiagnosticRoutineStatusEnum::kFailed,
kBatteryHealthExcessiveCycleCountMessage);
}
// Test that the battery health routine fails if cycle_count is not present.
TEST_F(BatteryHealthRoutineTest, NoCycleCount) {
CreateRoutine();
WriteFileContents(kBatteryHealthChargeFullPath,
std::to_string(kHighChargeFull));
WriteFileContents(kBatteryHealthChargeFullDesignPath,
std::to_string(kFakeBatteryChargeFullDesign));
RunRoutineAndWaitForExit();
VerifyNonInteractiveUpdate(update()->routine_update_union,
mojo_ipc::DiagnosticRoutineStatusEnum::kError,
kBatteryHealthFailedReadingCycleCountMessage);
}
// Test that the battery health routine fails if the wear percentage is too
// high.
TEST_F(BatteryHealthRoutineTest, HighWearPercentage) {
CreateRoutine();
WriteFileContents(kBatteryHealthChargeFullPath,
std::to_string(kLowChargeFull));
WriteFileContents(kBatteryHealthChargeFullDesignPath,
std::to_string(kFakeBatteryChargeFullDesign));
WriteFileContents(kBatteryHealthCycleCountPath,
std::to_string(kLowCycleCount));
RunRoutineAndWaitForExit();
VerifyNonInteractiveUpdate(update()->routine_update_union,
mojo_ipc::DiagnosticRoutineStatusEnum::kFailed,
kBatteryHealthExcessiveWearMessage);
}
// Test that the battery health routine fails if neither charge_full nor
// energy_full are present.
TEST_F(BatteryHealthRoutineTest, NoWearPercentage) {
CreateRoutine();
WriteFileContents(kBatteryHealthCycleCountPath,
std::to_string(kLowCycleCount));
RunRoutineAndWaitForExit();
VerifyNonInteractiveUpdate(
update()->routine_update_union,
mojo_ipc::DiagnosticRoutineStatusEnum::kError,
kBatteryHealthFailedCalculatingWearPercentageMessage);
}
// Test that the battery health routine passes if the cycle count and wear
// percentage are within acceptable limits.
TEST_F(BatteryHealthRoutineTest, GoodParameters) {
CreateRoutine();
WriteFileContents(kBatteryHealthChargeFullPath,
std::to_string(kHighChargeFull));
WriteFileContents(kBatteryHealthChargeFullDesignPath,
std::to_string(kFakeBatteryChargeFullDesign));
WriteFileContents(kBatteryHealthCycleCountPath,
std::to_string(kLowCycleCount));
WriteFilesReadByLog();
RunRoutineAndWaitForExit();
VerifyNonInteractiveUpdate(update()->routine_update_union,
mojo_ipc::DiagnosticRoutineStatusEnum::kPassed,
kBatteryHealthRoutinePassedMessage);
base::StringPairs expected_output_pairs;
base::StringPairs actual_output_pairs;
ASSERT_TRUE(base::SplitStringIntoKeyValuePairs(ConstructOutput(), ':', '\n',
&expected_output_pairs));
auto shm_mapping = diagnostics::GetReadOnlySharedMemoryMappingFromMojoHandle(
std::move(update()->output));
ASSERT_TRUE(shm_mapping.IsValid());
ASSERT_TRUE(base::SplitStringIntoKeyValuePairs(
base::StringPiece(shm_mapping.GetMemoryAs<const char>(),
shm_mapping.mapped_size()),
':', '\n', &actual_output_pairs));
EXPECT_THAT(actual_output_pairs,
UnorderedElementsAreArray(expected_output_pairs));
}
// Test that the battery health routine will find energy-reporting batteries.
TEST_F(BatteryHealthRoutineTest, EnergyReportingBattery) {
CreateRoutine();
WriteFileContents(kBatteryHealthEnergyFullPath,
std::to_string(kHighChargeFull));
WriteFileContents(kBatteryHealthEnergyFullDesignPath,
std::to_string(kFakeBatteryChargeFullDesign));
WriteFileContents(kBatteryHealthCycleCountPath,
std::to_string(kLowCycleCount));
RunRoutineAndWaitForExit();
VerifyNonInteractiveUpdate(update()->routine_update_union,
mojo_ipc::DiagnosticRoutineStatusEnum::kPassed,
kBatteryHealthRoutinePassedMessage);
}
// Test that the battery health routine uses the expected full path to
// cycle_count, relative to the temporary test directory.
TEST_F(BatteryHealthRoutineTest, FullCycleCountPath) {
CreateRoutine();
WriteFileContents(kBatteryHealthChargeFullPath,
std::to_string(kHighChargeFull));
WriteFileContents(kBatteryHealthChargeFullDesignPath,
std::to_string(kFakeBatteryChargeFullDesign));
EXPECT_TRUE(WriteFileAndCreateParentDirs(
temp_dir_path().AppendASCII(kFullCycleCountPath),
std::to_string(kLowCycleCount)));
RunRoutineAndWaitForExit();
VerifyNonInteractiveUpdate(update()->routine_update_union,
mojo_ipc::DiagnosticRoutineStatusEnum::kPassed,
kBatteryHealthRoutinePassedMessage);
}
// Test that the battery health routine catches invalid parameters.
TEST_F(BatteryHealthRoutineTest, InvalidParameters) {
constexpr int kInvalidMaximumWearPercentage = 101;
CreateRoutine(kMaximumCycleCount, kInvalidMaximumWearPercentage);
RunRoutineAndWaitForExit();
VerifyNonInteractiveUpdate(update()->routine_update_union,
mojo_ipc::DiagnosticRoutineStatusEnum::kError,
kBatteryHealthInvalidParametersMessage);
}
// Test that the battery health routine handles a battery whose capacity exceeds
// its design capacity.
TEST_F(BatteryHealthRoutineTest, CapacityExceedsDesignCapacity) {
// When the capacity exceeds the design capacity, the battery shouldn't be
// worn at all.
constexpr int kNotWornPercentage = 0;
CreateRoutine(kMaximumCycleCount, kNotWornPercentage);
// Set the capacity to anything higher than the design capacity.
constexpr int kHigherCapacity = 100;
constexpr int kLowerDesignCapacity = 20;
WriteFileContents(kBatteryHealthChargeFullPath,
std::to_string(kHigherCapacity));
WriteFileContents(kBatteryHealthChargeFullDesignPath,
std::to_string(kLowerDesignCapacity));
EXPECT_TRUE(WriteFileAndCreateParentDirs(
temp_dir_path().AppendASCII(kFullCycleCountPath),
std::to_string(kLowCycleCount)));
RunRoutineAndWaitForExit();
VerifyNonInteractiveUpdate(update()->routine_update_union,
mojo_ipc::DiagnosticRoutineStatusEnum::kPassed,
kBatteryHealthRoutinePassedMessage);
}
// Test that the battery health routine fails when invalid file contents are
// read.
TEST_F(BatteryHealthRoutineTest, InvalidFileContents) {
CreateRoutine();
WriteFileContents(kBatteryHealthChargeFullPath,
std::to_string(kHighChargeFull));
WriteFileContents(kBatteryHealthChargeFullDesignPath,
std::to_string(kFakeBatteryChargeFullDesign));
constexpr char kInvalidUnsignedInt[] = "Invalid unsigned int!";
EXPECT_TRUE(WriteFileAndCreateParentDirs(
temp_dir_path().AppendASCII(kFullCycleCountPath), kInvalidUnsignedInt));
RunRoutineAndWaitForExit();
VerifyNonInteractiveUpdate(update()->routine_update_union,
mojo_ipc::DiagnosticRoutineStatusEnum::kError,
kBatteryHealthFailedReadingCycleCountMessage);
}
// Test that calling resume doesn't crash.
TEST_F(BatteryHealthRoutineTest, Resume) {
CreateRoutine();
routine()->Resume();
}
// Test that calling cancel doesn't crash.
TEST_F(BatteryHealthRoutineTest, Cancel) {
CreateRoutine();
routine()->Cancel();
}
// Test that we can retrieve the status of the battery health routine.
TEST_F(BatteryHealthRoutineTest, GetStatus) {
CreateRoutine();
EXPECT_EQ(routine()->GetStatus(),
mojo_ipc::DiagnosticRoutineStatusEnum::kReady);
}
} // namespace diagnostics