blob: cd682fdde925239c88b383cbfd53e8258f411ad5 [file] [log] [blame]
// Copyright 2013 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "power_manager/powerd/system/peripheral_battery_watcher.h"
#include <string>
#include <sys/resource.h>
#include <base/check.h>
#include <base/compiler_specific.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/files/scoped_temp_dir.h>
#include <base/strings/string_number_conversions.h>
#include <base/strings/string_split.h>
#include <chromeos/dbus/service_constants.h>
#include <gtest/gtest.h>
#include <metrics/metrics_library_mock.h>
#include "power_manager/common/metrics_constants.h"
#include "power_manager/common/metrics_sender.h"
#include "power_manager/common/test_main_loop_runner.h"
#include "power_manager/powerd/system/dbus_wrapper_stub.h"
#include "power_manager/powerd/system/mock_bluez_battery_provider.h"
#include "power_manager/powerd/system/udev_stub.h"
#include "power_manager/powerd/testing/test_environment.h"
#include "power_manager/proto_bindings/peripheral_battery_status.pb.h"
using ::testing::_;
using ::testing::Gt;
using ::testing::Return;
using ::testing::StrictMock;
namespace power_manager::system {
using std::string;
namespace {
// Abort if it an expected battery update hasn't been received after this long.
constexpr base::TimeDelta kUpdateTimeout = base::Seconds(3);
// Shorter update timeout to use when failure is expected.
constexpr base::TimeDelta kShortUpdateTimeout = base::Milliseconds(100);
const char kDeviceModelName[] = "Test HID Mouse";
const char kWacomUevent[] = "HID_UNIQ=aa:aa:aa:aa:aa:aa";
constexpr char kPeripheralBatterySysname[] = "hid-someperipheral-battery";
constexpr char kPeripheralBatterySerialNumber1[] = "31245";
constexpr char kPeripheralBatterySerialNumber2[] = "DG-0123456789ABCDEF";
constexpr char kBluetoothBatterySysname[] = "hid-11:22:33:aa:bb:cc-battery";
constexpr char kWacomBatterySysname[] = "wacom_battery_1";
constexpr char kNonPeripheralBatterySysname[] = "AC";
constexpr char kPeripheralChargerBatterySysname[] = "peripheral0";
// TODO(b/215381232): Temporarily support both 'PCHG' name and 'peripheral' name
// till upstream kernel driver is merged.
constexpr char kPeripheralChargerBatteryPCHGSysname[] = "PCHG0";
int GetNumberOfOpenFiles() {
std::string status;
CHECK(base::ReadFileToString(base::FilePath("/proc/self/status"), &status));
base::StringPairs pairs;
base::SplitStringIntoKeyValuePairs(status, ':', '\n', &pairs);
for (const auto& pair : pairs) {
const auto& key = pair.first;
if (key == "FDSize") {
const auto value_str =
base::TrimWhitespaceASCII(pair.second, base::TRIM_ALL);
int value;
CHECK(base::StringToInt(value_str, &value));
return value;
}
}
NOTREACHED();
return 0;
}
class TestWrapper : public DBusWrapperStub {
public:
TestWrapper() = default;
TestWrapper(const TestWrapper&) = delete;
TestWrapper& operator=(const TestWrapper&) = delete;
~TestWrapper() override = default;
// Runs |loop_| until battery status is sent through D-Bus.
bool RunUntilSignalSent(const base::TimeDelta& timeout) {
return loop_runner_.StartLoop(timeout);
}
void EmitBareSignal(const std::string& signal_name) override {
DBusWrapperStub::EmitBareSignal(signal_name);
loop_runner_.StopLoop();
}
void EmitSignalWithProtocolBuffer(
const std::string& signal_name,
const google::protobuf::MessageLite& protobuf) override {
DBusWrapperStub::EmitSignalWithProtocolBuffer(signal_name, protobuf);
loop_runner_.StopLoop();
}
private:
TestMainLoopRunner loop_runner_;
};
} // namespace
class PeripheralBatteryWatcherTest : public TestEnvironment {
public:
PeripheralBatteryWatcherTest() = default;
PeripheralBatteryWatcherTest(const PeripheralBatteryWatcherTest&) = delete;
PeripheralBatteryWatcherTest& operator=(const PeripheralBatteryWatcherTest&) =
delete;
~PeripheralBatteryWatcherTest() override = default;
void SetUp() override {
auto bluez_battery_provider = std::make_unique<MockBluezBatteryProvider>();
bluez_battery_provider_ = bluez_battery_provider.get();
battery_.SetBluezBatteryProviderForTest(std::move(bluez_battery_provider));
CHECK(temp_dir_.CreateUniqueTempDir());
// Create a fake peripheral directory.
base::FilePath device_dir =
temp_dir_.GetPath().Append(kPeripheralBatterySysname);
CHECK(base::CreateDirectory(device_dir));
scope_file_ = device_dir.Append(PeripheralBatteryWatcher::kScopeFile);
WriteFile(scope_file_, PeripheralBatteryWatcher::kScopeValueDevice);
status_file_ = device_dir.Append(PeripheralBatteryWatcher::kStatusFile);
model_name_file_ =
device_dir.Append(PeripheralBatteryWatcher::kModelNameFile);
WriteFile(model_name_file_, kDeviceModelName);
peripheral_capacity_file_ =
device_dir.Append(PeripheralBatteryWatcher::kCapacityFile);
peripheral_sn_file_ =
device_dir.Append(PeripheralBatteryWatcher::kSerialNumberFile);
// Create a fake Bluetooth directory (distinguished by the name)
device_dir = temp_dir_.GetPath().Append(kBluetoothBatterySysname);
CHECK(base::CreateDirectory(device_dir));
WriteFile(device_dir.Append(PeripheralBatteryWatcher::kScopeFile),
PeripheralBatteryWatcher::kScopeValueDevice);
WriteFile(device_dir.Append(PeripheralBatteryWatcher::kModelNameFile),
kDeviceModelName);
bluetooth_capacity_file_ =
device_dir.Append(PeripheralBatteryWatcher::kCapacityFile);
// Create a fake wacom directory.
device_dir = temp_dir_.GetPath().Append(kWacomBatterySysname);
CHECK(base::CreateDirectory(device_dir.Append("powers")));
WriteFile(device_dir.Append(PeripheralBatteryWatcher::kScopeFile),
PeripheralBatteryWatcher::kScopeValueDevice);
WriteFile(device_dir.Append(PeripheralBatteryWatcher::kModelNameFile),
kDeviceModelName);
wacom_capacity_file_ =
device_dir.Append(PeripheralBatteryWatcher::kCapacityFile);
// Create a fake non-peripheral directory (there is no "scope" file.)
device_dir = temp_dir_.GetPath().Append(kNonPeripheralBatterySysname);
CHECK(base::CreateDirectory(device_dir));
WriteFile(device_dir.Append(PeripheralBatteryWatcher::kModelNameFile),
kDeviceModelName);
non_peripheral_capacity_file_ =
device_dir.Append(PeripheralBatteryWatcher::kCapacityFile);
battery_.set_battery_path_for_testing(temp_dir_.GetPath());
test_wrapper_.SetMethodCallback(base::BindRepeating(
&PeripheralBatteryWatcherTest::HandleBluetoothManagerRegisterCallback,
base::Unretained(this)));
}
void TearDown() override {
// Make sure async file readers are cleaned up.
task_env()->RunUntilIdle();
}
protected:
void WriteFile(const base::FilePath& path, const string& str) {
ASSERT_EQ(str.size(), base::WriteFile(path, str.data(), str.size()));
}
// TODO(b/215381232): Temporarily support both 'PCHG' name and 'peripheral'
// name till upstream kernel driver is merged.
void SetupPeripheralChargerDirectory(bool use_pchg = false) {
// Create a fake peripheral-charger directory (it is named peripheral.)
base::FilePath device_dir = temp_dir_.GetPath().Append(
use_pchg ? kPeripheralChargerBatteryPCHGSysname
: kPeripheralChargerBatterySysname);
CHECK(base::CreateDirectory(device_dir));
scope_file_ = device_dir.Append(PeripheralBatteryWatcher::kScopeFile);
WriteFile(scope_file_, PeripheralBatteryWatcher::kScopeValueDevice);
peripheral_charger_capacity_file_ =
device_dir.Append(PeripheralBatteryWatcher::kCapacityFile);
peripheral_charger_status_file_ =
device_dir.Append(PeripheralBatteryWatcher::kStatusFile);
peripheral_charger_health_file_ =
device_dir.Append(PeripheralBatteryWatcher::kHealthFile);
}
std::unique_ptr<dbus::Response> HandleBluetoothManagerRegisterCallback(
dbus::ObjectProxy* proxy, dbus::MethodCall* method_call) {
std::unique_ptr<dbus::Response> response =
dbus::Response::FromMethodCall(method_call);
return response;
}
// Temporary directory mimicking a /sys directory containing a set of sensor
// devices.
base::ScopedTempDir temp_dir_;
base::FilePath scope_file_;
base::FilePath status_file_;
base::FilePath peripheral_capacity_file_;
base::FilePath peripheral_sn_file_;
base::FilePath model_name_file_;
base::FilePath non_peripheral_capacity_file_;
base::FilePath bluetooth_capacity_file_;
base::FilePath wacom_capacity_file_;
base::FilePath peripheral_charger_capacity_file_;
base::FilePath peripheral_charger_status_file_;
base::FilePath peripheral_charger_health_file_;
TestWrapper test_wrapper_;
UdevStub udev_;
PeripheralBatteryWatcher battery_;
MockBluezBatteryProvider* bluez_battery_provider_;
TestMainLoopRunner loop_runner_;
};
TEST_F(PeripheralBatteryWatcherTest, Basic) {
std::string level = base::NumberToString(80);
WriteFile(peripheral_capacity_file_, level);
battery_.Init(&test_wrapper_, &udev_);
ASSERT_TRUE(test_wrapper_.RunUntilSignalSent(kUpdateTimeout));
EXPECT_EQ(1, test_wrapper_.num_sent_signals());
PeripheralBatteryStatus proto;
EXPECT_TRUE(test_wrapper_.GetSentSignal(0, kPeripheralBatteryStatusSignal,
&proto, nullptr));
EXPECT_EQ(80, proto.level());
EXPECT_EQ(kDeviceModelName, proto.name());
EXPECT_TRUE(proto.has_charge_status());
EXPECT_EQ(PeripheralBatteryStatus_ChargeStatus_CHARGE_STATUS_UNKNOWN,
proto.charge_status());
EXPECT_TRUE(proto.has_active_update());
EXPECT_FALSE(proto.active_update());
}
TEST_F(PeripheralBatteryWatcherTest, Bluetooth) {
std::string level = base::NumberToString(80);
WriteFile(bluetooth_capacity_file_, level);
// Bluetooth battery update should not sent any signal, but update to BlueZ.
EXPECT_CALL(*bluez_battery_provider_,
UpdateDeviceBattery("11:22:33:aa:bb:cc", 80));
ON_CALL(*bluez_battery_provider_,
UpdateDeviceBattery("11:22:33:aa:bb:cc", 80))
.WillByDefault([this](const std::string& address, int level) {
this->loop_runner_.StopLoop();
});
battery_.Init(&test_wrapper_, &udev_);
ASSERT_TRUE(loop_runner_.StartLoop(kUpdateTimeout));
EXPECT_EQ(0, test_wrapper_.num_sent_signals());
}
TEST_F(PeripheralBatteryWatcherTest, Wacom) {
// Wacom not detected as a Bluetooth device, treat it as a generic peripheral.
std::string level = base::NumberToString(80);
WriteFile(wacom_capacity_file_, level);
battery_.Init(&test_wrapper_, &udev_);
ASSERT_TRUE(test_wrapper_.RunUntilSignalSent(kUpdateTimeout));
EXPECT_EQ(1, test_wrapper_.num_sent_signals());
PeripheralBatteryStatus proto;
EXPECT_TRUE(test_wrapper_.GetSentSignal(0, kPeripheralBatteryStatusSignal,
&proto, nullptr));
}
TEST_F(PeripheralBatteryWatcherTest, WacomWithBluetooth) {
// Wacom detected as a Bluetooth device (having HID_UNIQ= in powers/uevent).
base::FilePath device_dir = temp_dir_.GetPath().Append(kWacomBatterySysname);
WriteFile(device_dir.Append(PeripheralBatteryWatcher::kPowersUeventFile),
kWacomUevent);
std::string level = base::NumberToString(70);
WriteFile(wacom_capacity_file_, level);
// Bluetooth battery update should not sent any signal, but update to BlueZ.
EXPECT_CALL(*bluez_battery_provider_,
UpdateDeviceBattery("aa:aa:aa:aa:aa:aa", 70));
ON_CALL(*bluez_battery_provider_,
UpdateDeviceBattery("aa:aa:aa:aa:aa:aa", 70))
.WillByDefault([this](const std::string& address, int level) {
this->loop_runner_.StopLoop();
});
battery_.Init(&test_wrapper_, &udev_);
ASSERT_TRUE(loop_runner_.StartLoop(kUpdateTimeout));
EXPECT_EQ(0, test_wrapper_.num_sent_signals());
}
TEST_F(PeripheralBatteryWatcherTest, NoLevelReading) {
battery_.Init(&test_wrapper_, &udev_);
// Without writing battery level to the peripheral_capacity_file_, the loop
// will timeout.
EXPECT_FALSE(test_wrapper_.RunUntilSignalSent(kShortUpdateTimeout));
}
TEST_F(PeripheralBatteryWatcherTest, SkipUnknownStatus) {
// Batteries with unknown statuses should be skipped: http://b/64397082
WriteFile(peripheral_capacity_file_, base::NumberToString(0));
WriteFile(status_file_, PeripheralBatteryWatcher::kStatusValueUnknown);
battery_.Init(&test_wrapper_, &udev_);
ASSERT_FALSE(test_wrapper_.RunUntilSignalSent(kShortUpdateTimeout));
}
TEST_F(PeripheralBatteryWatcherTest, AllowOtherStatus) {
// Batteries with other statuses should be reported.
WriteFile(peripheral_capacity_file_, base::NumberToString(20));
WriteFile(status_file_, PeripheralBatteryWatcher::kStatusValueDischarging);
battery_.Init(&test_wrapper_, &udev_);
ASSERT_TRUE(test_wrapper_.RunUntilSignalSent(kUpdateTimeout));
EXPECT_EQ(1, test_wrapper_.num_sent_signals());
PeripheralBatteryStatus proto;
EXPECT_TRUE(test_wrapper_.GetSentSignal(0, kPeripheralBatteryStatusSignal,
&proto, nullptr));
EXPECT_EQ(20, proto.level());
EXPECT_TRUE(proto.has_charge_status());
EXPECT_EQ(PeripheralBatteryStatus_ChargeStatus_CHARGE_STATUS_DISCHARGING,
proto.charge_status());
}
TEST_F(PeripheralBatteryWatcherTest, UdevEvents) {
// Initial reading of battery statuses.
WriteFile(peripheral_capacity_file_, base::NumberToString(80));
battery_.Init(&test_wrapper_, &udev_);
ASSERT_TRUE(test_wrapper_.RunUntilSignalSent(kUpdateTimeout));
EXPECT_EQ(1, test_wrapper_.num_sent_signals());
PeripheralBatteryStatus proto;
EXPECT_TRUE(test_wrapper_.GetSentSignal(0, kPeripheralBatteryStatusSignal,
&proto, nullptr));
EXPECT_EQ(80, proto.level());
EXPECT_EQ(kDeviceModelName, proto.name());
EXPECT_TRUE(proto.has_active_update());
EXPECT_FALSE(proto.active_update());
// An udev ADD event appear for a peripheral device.
WriteFile(peripheral_capacity_file_, base::NumberToString(70));
udev_.NotifySubsystemObservers({{PeripheralBatteryWatcher::kUdevSubsystem, "",
kPeripheralBatterySysname, ""},
UdevEvent::Action::ADD});
// Check that powerd reads the battery information and sends an update signal.
ASSERT_TRUE(test_wrapper_.RunUntilSignalSent(kUpdateTimeout));
ASSERT_EQ(2, test_wrapper_.num_sent_signals());
EXPECT_TRUE(test_wrapper_.GetSentSignal(1, kPeripheralBatteryStatusSignal,
&proto, nullptr));
EXPECT_EQ(70, proto.level());
EXPECT_EQ(kDeviceModelName, proto.name());
EXPECT_TRUE(proto.has_active_update());
EXPECT_TRUE(proto.active_update());
// An udev CHANGE event appear for a peripheral device.
WriteFile(peripheral_capacity_file_, base::NumberToString(60));
udev_.NotifySubsystemObservers({{PeripheralBatteryWatcher::kUdevSubsystem, "",
kPeripheralBatterySysname, ""},
UdevEvent::Action::CHANGE});
// Check that powerd reads the battery information and sends an update signal.
ASSERT_TRUE(test_wrapper_.RunUntilSignalSent(kUpdateTimeout));
ASSERT_EQ(3, test_wrapper_.num_sent_signals());
EXPECT_TRUE(test_wrapper_.GetSentSignal(2, kPeripheralBatteryStatusSignal,
&proto, nullptr));
EXPECT_EQ(60, proto.level());
EXPECT_EQ(kDeviceModelName, proto.name());
EXPECT_TRUE(proto.has_active_update());
EXPECT_TRUE(proto.active_update());
// An udev REMOVE event appear for a peripheral device.
WriteFile(peripheral_capacity_file_, base::NumberToString(60));
udev_.NotifySubsystemObservers({{PeripheralBatteryWatcher::kUdevSubsystem, "",
kPeripheralBatterySysname, ""},
UdevEvent::Action::REMOVE});
// A REMOVE event should not trigger battery update signal.
EXPECT_FALSE(test_wrapper_.RunUntilSignalSent(kShortUpdateTimeout));
}
TEST_F(PeripheralBatteryWatcherTest, NonPeripheralUdevEvents) {
// Initial reading of battery statuses.
WriteFile(peripheral_capacity_file_, base::NumberToString(80));
battery_.Init(&test_wrapper_, &udev_);
ASSERT_TRUE(test_wrapper_.RunUntilSignalSent(kUpdateTimeout));
EXPECT_EQ(1, test_wrapper_.num_sent_signals());
PeripheralBatteryStatus proto;
EXPECT_TRUE(test_wrapper_.GetSentSignal(0, kPeripheralBatteryStatusSignal,
&proto, nullptr));
EXPECT_EQ(80, proto.level());
EXPECT_EQ(kDeviceModelName, proto.name());
// An udev event appear for a non-peripheral device. Check that it is ignored.
WriteFile(non_peripheral_capacity_file_, base::NumberToString(50));
udev_.NotifySubsystemObservers({{PeripheralBatteryWatcher::kUdevSubsystem, "",
kNonPeripheralBatterySysname, ""},
UdevEvent::Action::CHANGE});
EXPECT_FALSE(test_wrapper_.RunUntilSignalSent(kShortUpdateTimeout));
}
TEST_F(PeripheralBatteryWatcherTest, RefreshAllBatteries) {
std::string level = base::NumberToString(80);
WriteFile(peripheral_capacity_file_, level);
battery_.Init(&test_wrapper_, &udev_);
ASSERT_TRUE(test_wrapper_.RunUntilSignalSent(kUpdateTimeout));
EXPECT_EQ(1, test_wrapper_.num_sent_signals());
PeripheralBatteryStatus proto;
EXPECT_TRUE(test_wrapper_.GetSentSignal(0, kPeripheralBatteryStatusSignal,
&proto, nullptr));
EXPECT_EQ(80, proto.level());
EXPECT_EQ(kDeviceModelName, proto.name());
EXPECT_TRUE(proto.has_charge_status());
EXPECT_EQ(PeripheralBatteryStatus_ChargeStatus_CHARGE_STATUS_UNKNOWN,
proto.charge_status());
EXPECT_TRUE(proto.has_active_update());
EXPECT_FALSE(proto.active_update());
// RefreshAllPeripheralBattery is called.
dbus::MethodCall method_call(kPowerManagerInterface,
kRefreshAllPeripheralBatteryMethod);
std::unique_ptr<dbus::Response> response =
test_wrapper_.CallExportedMethodSync(&method_call);
ASSERT_TRUE(response);
ASSERT_EQ(dbus::Message::MESSAGE_METHOD_RETURN, response->GetMessageType());
ASSERT_TRUE(test_wrapper_.RunUntilSignalSent(kUpdateTimeout));
EXPECT_EQ(2, test_wrapper_.num_sent_signals());
EXPECT_TRUE(test_wrapper_.GetSentSignal(1, kPeripheralBatteryStatusSignal,
&proto, nullptr));
EXPECT_EQ(80, proto.level());
EXPECT_EQ(kDeviceModelName, proto.name());
EXPECT_TRUE(proto.has_charge_status());
EXPECT_EQ(PeripheralBatteryStatus_ChargeStatus_CHARGE_STATUS_UNKNOWN,
proto.charge_status());
EXPECT_TRUE(proto.has_active_update());
EXPECT_FALSE(proto.active_update());
}
TEST_F(PeripheralBatteryWatcherTest, Charger) {
SetupPeripheralChargerDirectory();
// Chargers should be reported.
WriteFile(peripheral_charger_capacity_file_, base::NumberToString(60));
WriteFile(peripheral_charger_status_file_,
PeripheralBatteryWatcher::kStatusValueCharging);
WriteFile(peripheral_charger_health_file_,
PeripheralBatteryWatcher::kHealthValueGood);
battery_.Init(&test_wrapper_, &udev_);
ASSERT_TRUE(test_wrapper_.RunUntilSignalSent(kUpdateTimeout));
EXPECT_EQ(1, test_wrapper_.num_sent_signals());
PeripheralBatteryStatus proto;
EXPECT_TRUE(test_wrapper_.GetSentSignal(0, kPeripheralBatteryStatusSignal,
&proto, nullptr));
EXPECT_EQ(60, proto.level());
EXPECT_EQ(PeripheralBatteryStatus_ChargeStatus_CHARGE_STATUS_CHARGING,
proto.charge_status());
}
TEST_F(PeripheralBatteryWatcherTest, ChargerFull) {
SetupPeripheralChargerDirectory();
// Chargers should be reported.
WriteFile(peripheral_charger_capacity_file_, base::NumberToString(100));
WriteFile(peripheral_charger_status_file_,
PeripheralBatteryWatcher::kStatusValueFull);
WriteFile(peripheral_charger_health_file_,
PeripheralBatteryWatcher::kHealthValueGood);
battery_.Init(&test_wrapper_, &udev_);
ASSERT_TRUE(test_wrapper_.RunUntilSignalSent(kUpdateTimeout));
EXPECT_EQ(1, test_wrapper_.num_sent_signals());
PeripheralBatteryStatus proto;
EXPECT_TRUE(test_wrapper_.GetSentSignal(0, kPeripheralBatteryStatusSignal,
&proto, nullptr));
EXPECT_EQ(100, proto.level());
EXPECT_TRUE(proto.has_charge_status());
EXPECT_EQ(PeripheralBatteryStatus_ChargeStatus_CHARGE_STATUS_FULL,
proto.charge_status());
}
TEST_F(PeripheralBatteryWatcherTest, ChargerDetached) {
SetupPeripheralChargerDirectory();
// Chargers should be reported.
WriteFile(peripheral_charger_capacity_file_, base::NumberToString(0));
WriteFile(peripheral_charger_status_file_,
PeripheralBatteryWatcher::kStatusValueUnknown);
// Leave health missing
battery_.Init(&test_wrapper_, &udev_);
ASSERT_TRUE(test_wrapper_.RunUntilSignalSent(kUpdateTimeout));
EXPECT_EQ(1, test_wrapper_.num_sent_signals());
PeripheralBatteryStatus proto;
EXPECT_TRUE(test_wrapper_.GetSentSignal(0, kPeripheralBatteryStatusSignal,
&proto, nullptr));
EXPECT_EQ(0, proto.level());
EXPECT_TRUE(proto.has_charge_status());
EXPECT_EQ(PeripheralBatteryStatus_ChargeStatus_CHARGE_STATUS_UNKNOWN,
proto.charge_status());
}
TEST_F(PeripheralBatteryWatcherTest, ChargerError) {
SetupPeripheralChargerDirectory();
// Chargers health error should be reported.
WriteFile(peripheral_charger_capacity_file_, base::NumberToString(50));
WriteFile(peripheral_charger_status_file_,
PeripheralBatteryWatcher::kStatusValueCharging);
WriteFile(peripheral_charger_health_file_, "Hot");
battery_.Init(&test_wrapper_, &udev_);
ASSERT_TRUE(test_wrapper_.RunUntilSignalSent(kUpdateTimeout));
EXPECT_EQ(1, test_wrapper_.num_sent_signals());
PeripheralBatteryStatus proto;
EXPECT_TRUE(test_wrapper_.GetSentSignal(0, kPeripheralBatteryStatusSignal,
&proto, nullptr));
EXPECT_EQ(50, proto.level());
EXPECT_TRUE(proto.has_charge_status());
EXPECT_EQ(PeripheralBatteryStatus_ChargeStatus_CHARGE_STATUS_ERROR,
proto.charge_status());
}
// TODO(b/215381232): Temporarily support both 'PCHG' name and 'peripheral' name
// till upstream kernel driver is merged. Remove test case when upstream kernel
// driver is merged.
TEST_F(PeripheralBatteryWatcherTest, Charger_PCHG) {
SetupPeripheralChargerDirectory(/*use_pchg=*/true);
// Chargers should be reported.
WriteFile(peripheral_charger_capacity_file_, base::NumberToString(60));
WriteFile(peripheral_charger_status_file_,
PeripheralBatteryWatcher::kStatusValueCharging);
WriteFile(peripheral_charger_health_file_,
PeripheralBatteryWatcher::kHealthValueGood);
battery_.Init(&test_wrapper_, &udev_);
ASSERT_TRUE(test_wrapper_.RunUntilSignalSent(kUpdateTimeout));
EXPECT_EQ(1, test_wrapper_.num_sent_signals());
PeripheralBatteryStatus proto;
EXPECT_TRUE(test_wrapper_.GetSentSignal(0, kPeripheralBatteryStatusSignal,
&proto, nullptr));
EXPECT_EQ(60, proto.level());
EXPECT_EQ(PeripheralBatteryStatus_ChargeStatus_CHARGE_STATUS_CHARGING,
proto.charge_status());
}
// TODO(b/215381232): Temporarily support both 'PCHG' name and 'peripheral' name
// till upstream kernel driver is merged.Remove test case when upstream kernel
// driver is merged.
TEST_F(PeripheralBatteryWatcherTest, ChargerFull_PCHG) {
SetupPeripheralChargerDirectory(/*use_pchg=*/true);
// Chargers should be reported.
WriteFile(peripheral_charger_capacity_file_, base::NumberToString(100));
WriteFile(peripheral_charger_status_file_,
PeripheralBatteryWatcher::kStatusValueFull);
WriteFile(peripheral_charger_health_file_,
PeripheralBatteryWatcher::kHealthValueGood);
battery_.Init(&test_wrapper_, &udev_);
ASSERT_TRUE(test_wrapper_.RunUntilSignalSent(kUpdateTimeout));
EXPECT_EQ(1, test_wrapper_.num_sent_signals());
PeripheralBatteryStatus proto;
EXPECT_TRUE(test_wrapper_.GetSentSignal(0, kPeripheralBatteryStatusSignal,
&proto, nullptr));
EXPECT_EQ(100, proto.level());
EXPECT_TRUE(proto.has_charge_status());
EXPECT_EQ(PeripheralBatteryStatus_ChargeStatus_CHARGE_STATUS_FULL,
proto.charge_status());
}
TEST_F(PeripheralBatteryWatcherTest, UdevEventsWithoutSerial) {
// TODO(kenalba): trim this down
// Initial reading of battery statuses.
WriteFile(peripheral_capacity_file_, base::NumberToString(80));
battery_.Init(&test_wrapper_, &udev_);
ASSERT_TRUE(test_wrapper_.RunUntilSignalSent(kUpdateTimeout));
EXPECT_EQ(1, test_wrapper_.num_sent_signals());
PeripheralBatteryStatus proto;
EXPECT_TRUE(test_wrapper_.GetSentSignal(0, kPeripheralBatteryStatusSignal,
&proto, nullptr));
EXPECT_EQ(80, proto.level());
EXPECT_EQ(kDeviceModelName, proto.name());
EXPECT_TRUE(proto.has_active_update());
EXPECT_FALSE(proto.active_update());
EXPECT_FALSE(proto.has_serial_number());
// An udev ADD event appear for a peripheral device.
WriteFile(peripheral_capacity_file_, base::NumberToString(70));
udev_.NotifySubsystemObservers({{PeripheralBatteryWatcher::kUdevSubsystem, "",
kPeripheralBatterySysname, ""},
UdevEvent::Action::ADD});
// Check that powerd reads the battery information and sends an update signal.
ASSERT_TRUE(test_wrapper_.RunUntilSignalSent(kUpdateTimeout));
ASSERT_EQ(2, test_wrapper_.num_sent_signals());
EXPECT_TRUE(test_wrapper_.GetSentSignal(1, kPeripheralBatteryStatusSignal,
&proto, nullptr));
EXPECT_EQ(70, proto.level());
EXPECT_EQ(kDeviceModelName, proto.name());
EXPECT_TRUE(proto.has_active_update());
EXPECT_TRUE(proto.active_update());
EXPECT_FALSE(proto.has_serial_number());
// An udev CHANGE event appear for a peripheral device.
WriteFile(peripheral_capacity_file_, base::NumberToString(60));
udev_.NotifySubsystemObservers({{PeripheralBatteryWatcher::kUdevSubsystem, "",
kPeripheralBatterySysname, ""},
UdevEvent::Action::CHANGE});
// Check that powerd reads the battery information and sends an update signal.
ASSERT_TRUE(test_wrapper_.RunUntilSignalSent(kUpdateTimeout));
ASSERT_EQ(3, test_wrapper_.num_sent_signals());
EXPECT_TRUE(test_wrapper_.GetSentSignal(2, kPeripheralBatteryStatusSignal,
&proto, nullptr));
EXPECT_EQ(60, proto.level());
EXPECT_EQ(kDeviceModelName, proto.name());
EXPECT_TRUE(proto.has_active_update());
EXPECT_TRUE(proto.active_update());
EXPECT_FALSE(proto.has_serial_number());
// An udev REMOVE event appear for a peripheral device.
WriteFile(peripheral_capacity_file_, base::NumberToString(60));
udev_.NotifySubsystemObservers({{PeripheralBatteryWatcher::kUdevSubsystem, "",
kPeripheralBatterySysname, ""},
UdevEvent::Action::REMOVE});
// A REMOVE event should not trigger battery update signal.
EXPECT_FALSE(test_wrapper_.RunUntilSignalSent(kShortUpdateTimeout));
}
TEST_F(PeripheralBatteryWatcherTest, UdevEventsWithSerial) {
// TODO(kenalba): trim this down
// Initial reading of battery statuses.
WriteFile(peripheral_capacity_file_, base::NumberToString(80));
battery_.Init(&test_wrapper_, &udev_);
ASSERT_TRUE(test_wrapper_.RunUntilSignalSent(kUpdateTimeout));
EXPECT_EQ(1, test_wrapper_.num_sent_signals());
PeripheralBatteryStatus proto;
EXPECT_TRUE(test_wrapper_.GetSentSignal(0, kPeripheralBatteryStatusSignal,
&proto, nullptr));
EXPECT_EQ(80, proto.level());
EXPECT_EQ(kDeviceModelName, proto.name());
EXPECT_TRUE(proto.has_active_update());
EXPECT_FALSE(proto.active_update());
EXPECT_FALSE(proto.has_serial_number());
// An udev ADD event appear for a peripheral device.
WriteFile(peripheral_capacity_file_, base::NumberToString(70));
WriteFile(peripheral_sn_file_, kPeripheralBatterySerialNumber1);
udev_.NotifySubsystemObservers({{PeripheralBatteryWatcher::kUdevSubsystem, "",
kPeripheralBatterySysname, ""},
UdevEvent::Action::ADD});
// Check that powerd reads the battery information and sends an update signal.
ASSERT_TRUE(test_wrapper_.RunUntilSignalSent(kUpdateTimeout));
ASSERT_EQ(2, test_wrapper_.num_sent_signals());
EXPECT_TRUE(test_wrapper_.GetSentSignal(1, kPeripheralBatteryStatusSignal,
&proto, nullptr));
EXPECT_EQ(70, proto.level());
EXPECT_EQ(kDeviceModelName, proto.name());
EXPECT_TRUE(proto.has_active_update());
EXPECT_TRUE(proto.active_update());
EXPECT_TRUE(proto.has_active_update());
EXPECT_TRUE(proto.has_serial_number());
EXPECT_EQ(kPeripheralBatterySerialNumber1, proto.serial_number());
// An udev CHANGE event appear for a peripheral device.
WriteFile(peripheral_capacity_file_, base::NumberToString(60));
WriteFile(peripheral_sn_file_, kPeripheralBatterySerialNumber2);
udev_.NotifySubsystemObservers({{PeripheralBatteryWatcher::kUdevSubsystem, "",
kPeripheralBatterySysname, ""},
UdevEvent::Action::CHANGE});
// Check that powerd reads the battery information and sends an update signal.
ASSERT_TRUE(test_wrapper_.RunUntilSignalSent(kUpdateTimeout));
ASSERT_EQ(3, test_wrapper_.num_sent_signals());
EXPECT_TRUE(test_wrapper_.GetSentSignal(2, kPeripheralBatteryStatusSignal,
&proto, nullptr));
EXPECT_EQ(60, proto.level());
EXPECT_EQ(kDeviceModelName, proto.name());
EXPECT_TRUE(proto.has_active_update());
EXPECT_TRUE(proto.active_update());
EXPECT_TRUE(proto.has_serial_number());
EXPECT_EQ(kPeripheralBatterySerialNumber2, proto.serial_number());
// An udev REMOVE event appear for a peripheral device.
WriteFile(peripheral_capacity_file_, base::NumberToString(60));
udev_.NotifySubsystemObservers({{PeripheralBatteryWatcher::kUdevSubsystem, "",
kPeripheralBatterySysname, ""},
UdevEvent::Action::REMOVE});
// A REMOVE event should not trigger battery update signal.
EXPECT_FALSE(test_wrapper_.RunUntilSignalSent(kShortUpdateTimeout));
}
TEST_F(PeripheralBatteryWatcherTest, SpammyUdevEvents) {
// This is a regression test to make sure we don't keep opening new file
// descriptors in response to the same udev device being reconnected many
// times.
WriteFile(peripheral_capacity_file_, base::NumberToString(50));
battery_.Init(&test_wrapper_, &udev_);
ASSERT_TRUE(test_wrapper_.RunUntilSignalSent(kUpdateTimeout));
const size_t kDevicesToAdd = 32;
int fd_count = GetNumberOfOpenFiles();
rlimit rlim_orig;
getrlimit(RLIMIT_NOFILE, &rlim_orig);
// Temporarily drop the open file count limit.
rlimit rlim = rlim_orig;
rlim.rlim_cur = fd_count + kDevicesToAdd / 2;
setrlimit(RLIMIT_NOFILE, &rlim);
for (size_t i = 0; i < kDevicesToAdd; i++) {
udev_.NotifySubsystemObservers({{PeripheralBatteryWatcher::kUdevSubsystem,
"", kPeripheralBatterySysname, ""},
UdevEvent::Action::ADD});
ASSERT_TRUE(test_wrapper_.RunUntilSignalSent(kUpdateTimeout));
}
// Make sure we didn't leak file descriptors and can still open a file.
WriteFile(peripheral_capacity_file_, base::NumberToString(40));
// Restore the original file count limit.
setrlimit(RLIMIT_NOFILE, &rlim_orig);
}
TEST_F(PeripheralBatteryWatcherTest, ReadLatencyMetrics) {
StrictMock<MetricsLibraryMock> metrics_lib;
MetricsSender metrics_sender{metrics_lib};
EXPECT_CALL(metrics_lib, SendToUMA("Power.PeripheralReadLatencyMs", Gt(0),
metrics::kPeripheralReadLatencyMsMin,
metrics::kPeripheralReadLatencyMsMax,
metrics::kDefaultBuckets))
.Times(1)
.WillOnce(Return(true))
.RetiresOnSaturation();
WriteFile(peripheral_capacity_file_, base::NumberToString(50));
battery_.Init(&test_wrapper_, &udev_);
ASSERT_TRUE(test_wrapper_.RunUntilSignalSent(kUpdateTimeout));
}
} // namespace power_manager::system