| // 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 |