blob: 1ce953665dbfcc3b8b6e689cbb4dd94b40f8ce31 [file] [log] [blame]
// Copyright (c) 2013 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 "power_manager/powerd/system/peripheral_battery_watcher.h"
#include <string>
#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 <chromeos/dbus/service_constants.h>
#include <gtest/gtest.h>
#include "power_manager/common/test_main_loop_runner.h"
#include "power_manager/powerd/system/dbus_wrapper_stub.h"
#include "power_manager/powerd/system/udev_stub.h"
#include "power_manager/proto_bindings/peripheral_battery_status.pb.h"
namespace power_manager {
namespace system {
using std::string;
namespace {
// Abort if it an expected battery update hasn't been received after this long.
constexpr base::TimeDelta kUpdateTimeout = base::TimeDelta::FromSeconds(3);
// Shorter update timeout to use when failure is expected.
constexpr base::TimeDelta kShortUpdateTimeout =
base::TimeDelta::FromMilliseconds(100);
const char kDeviceModelName[] = "Test HID Mouse";
constexpr char kPeripheralBatterySysname[] = "hid-someperipheral-battery";
constexpr char kBluetoothBatterySysname[] = "hid-11:22:33:aa:bb:cc-battery";
constexpr char kNonPeripheralBatterySysname[] = "AC";
class TestWrapper : public DBusWrapperStub {
public:
TestWrapper() {}
TestWrapper(const TestWrapper&) = delete;
TestWrapper& operator=(const TestWrapper&) = delete;
~TestWrapper() override {}
// 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 ::testing::Test {
public:
PeripheralBatteryWatcherTest() {}
PeripheralBatteryWatcherTest(const PeripheralBatteryWatcherTest&) = delete;
PeripheralBatteryWatcherTest& operator=(const PeripheralBatteryWatcherTest&) =
delete;
~PeripheralBatteryWatcherTest() override {}
void SetUp() override {
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);
// 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);
battery_.set_battery_path_for_testing(temp_dir_.GetPath());
// 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());
}
protected:
void WriteFile(const base::FilePath& path, const string& str) {
ASSERT_EQ(str.size(), base::WriteFile(path, str.data(), str.size()));
}
// 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 model_name_file_;
base::FilePath non_peripheral_capacity_file_;
base::FilePath bluetooth_capacity_file_;
TestWrapper test_wrapper_;
UdevStub udev_;
PeripheralBatteryWatcher battery_;
};
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());
}
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_, "Discharging");
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());
}
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());
// 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());
// 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());
// 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, RefreshBluetoothBattery) {
battery_.Init(&test_wrapper_, &udev_);
// Initialize non-Bluetooth peripheral.
WriteFile(peripheral_capacity_file_, base::NumberToString(90));
// Initialize Bluetooth peripheral.
WriteFile(bluetooth_capacity_file_, base::NumberToString(80));
// RefreshBluetoothBattery is called.
dbus::MethodCall method_call(kPowerManagerInterface,
kRefreshBluetoothBatteryMethod);
dbus::MessageWriter(&method_call).AppendString("11:22:33:AA:BB:CC");
std::unique_ptr<dbus::Response> response =
test_wrapper_.CallExportedMethodSync(&method_call);
ASSERT_TRUE(response);
ASSERT_EQ(dbus::Message::MESSAGE_METHOD_RETURN, response->GetMessageType());
// Check that powerd reads the battery information and sends an update signal.
ASSERT_TRUE(test_wrapper_.RunUntilSignalSent(kUpdateTimeout));
ASSERT_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());
// RefreshBluetoothBattery is called for non-Bluetooth device.
dbus::MethodCall method_call2(kPowerManagerInterface,
kRefreshBluetoothBatteryMethod);
dbus::MessageWriter(&method_call2).AppendString("someperipheral");
response = test_wrapper_.CallExportedMethodSync(&method_call2);
ASSERT_TRUE(response);
ASSERT_EQ(dbus::Message::MESSAGE_METHOD_RETURN, response->GetMessageType());
// Check that powerd ignores the request.
EXPECT_FALSE(test_wrapper_.RunUntilSignalSent(kShortUpdateTimeout));
// RefreshBluetoothBattery is called for non-existing device.
dbus::MethodCall method_call3(kPowerManagerInterface,
kRefreshBluetoothBatteryMethod);
dbus::MessageWriter(&method_call3).AppendString("non-existing");
response = test_wrapper_.CallExportedMethodSync(&method_call3);
ASSERT_TRUE(response);
ASSERT_EQ(dbus::Message::MESSAGE_METHOD_RETURN, response->GetMessageType());
// Check that powerd ignores the request.
EXPECT_FALSE(test_wrapper_.RunUntilSignalSent(kShortUpdateTimeout));
}
} // namespace system
} // namespace power_manager