// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/dbus/power/power_manager_client.h"
#include <map>
#include <memory>
#include <string>
#include <utility>
#include "base/functional/bind.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/test/power_monitor_test.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "base/unguessable_token.h"
#include "chromeos/dbus/power_manager/backlight.pb.h"
#include "chromeos/dbus/power_manager/suspend.pb.h"
#include "chromeos/dbus/power_manager/thermal.pb.h"
#include "dbus/mock_bus.h"
#include "dbus/mock_object_proxy.h"
#include "dbus/object_path.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/cros_system_api/dbus/power_manager/dbus-constants.h"
#include "third_party/cros_system_api/dbus/service_constants.h"
using ::testing::_;
using ::testing::Return;
using ::testing::SaveArg;
namespace chromeos {
namespace {
// Shorthand for a few commonly-used constants.
const char* kInterface = power_manager::kPowerManagerInterface;
const char* kSuspendImminent = power_manager::kSuspendImminentSignal;
const char* kDarkSuspendImminent = power_manager::kDarkSuspendImminentSignal;
const char* kHandleSuspendReadiness =
power_manager::kHandleSuspendReadinessMethod;
const char* kHandleDarkSuspendReadiness =
power_manager::kHandleDarkSuspendReadinessMethod;
// Matcher that verifies that a dbus::Message has member |name|.
MATCHER_P(HasMember, name, "") {
if (arg->GetMember() != name) {
*result_listener << "has member " << arg->GetMember();
return false;
}
return true;
}
// Matcher that verifies that a dbus::MethodCall has member |method_name| and
// contains a SuspendReadinessInfo protobuf referring to |suspend_id| and
// |delay_id|.
MATCHER_P3(IsSuspendReadiness, method_name, suspend_id, delay_id, "") {
if (arg->GetMember() != method_name) {
*result_listener << "has member " << arg->GetMember();
return false;
}
power_manager::SuspendReadinessInfo proto;
if (!dbus::MessageReader(arg).PopArrayOfBytesAsProto(&proto)) {
*result_listener << "does not contain SuspendReadinessInfo protobuf";
return false;
}
if (proto.suspend_id() != suspend_id) {
*result_listener << "suspend ID is " << proto.suspend_id();
return false;
}
if (proto.delay_id() != delay_id) {
*result_listener << "delay ID is " << proto.delay_id();
return false;
}
return true;
}
// Matcher that verifies a |RequestSuspend| dbus::MethodCall.
MATCHER_P4(IsRequestSuspend, method_name, count, duration, flavor, "") {
if (arg->GetMember() != method_name) {
*result_listener << "has member " << arg->GetMember();
return false;
}
dbus::MessageReader reader(arg);
uint64_t read_count;
if (!reader.PopUint64(&read_count)) {
*result_listener << "missing value 1 (count)";
return false;
}
if (read_count != count) {
*result_listener << "expected count = " << count << ", got " << read_count;
return false;
}
int32_t read_duration;
if (!reader.PopInt32(&read_duration)) {
*result_listener << "missing value 2 (duration)";
return false;
}
if (read_duration != duration) {
*result_listener << "expected duration = " << duration << ", got "
<< read_duration;
return false;
}
uint32_t read_flavor;
if (!reader.PopUint32(&read_flavor)) {
*result_listener << "missing value 1 (count)";
return false;
}
if (read_flavor != flavor) {
*result_listener << "expected flavor = " << flavor << ", got "
<< read_flavor;
return false;
}
return true;
}
// Matcher that verifies that a dbus::MethodCall has member |method_name|.
MATCHER_P(IsRequestRestart, method_name, "") {
if (arg->GetMember() != method_name) {
*result_listener << "has member " << arg->GetMember();
return false;
}
return true;
}
// Matcher that verifies a |SetAmbientLightSensorEnabled| and
// |SetKeyboardAmbientLightSensorEnabled| dbus::MethodCall.
MATCHER_P2(IsAmbientLightSensorEnabled, method_name, sensor_enabled, "") {
if (arg->GetMember() != method_name) {
*result_listener << "has member " << arg->GetMember();
return false;
}
dbus::MessageReader reader(arg);
power_manager::SetAmbientLightSensorEnabledRequest request;
if (!reader.PopArrayOfBytesAsProto(&request)) {
*result_listener << "missing or invalid protobuf";
return false;
}
if (request.sensor_enabled() != sensor_enabled) {
*result_listener << "expected enabled = " << sensor_enabled << ", got "
<< request.sensor_enabled();
return false;
}
return true;
}
// Runs |callback| with |response|. Needed due to ResponseCallback expecting a
// bare pointer rather than an std::unique_ptr.
void RunResponseCallback(dbus::ObjectProxy::ResponseCallback callback,
std::unique_ptr<dbus::Response> response) {
std::move(callback).Run(response.get());
}
// Stub implementation of PowerManagerClient::Observer.
class TestObserver : public PowerManagerClient::Observer {
public:
explicit TestObserver(PowerManagerClient* client) : client_(client) {
client_->AddObserver(this);
}
TestObserver(const TestObserver&) = delete;
TestObserver& operator=(const TestObserver&) = delete;
~TestObserver() override { client_->RemoveObserver(this); }
int num_suspend_imminent() const { return num_suspend_imminent_; }
int num_suspend_done() const { return num_suspend_done_; }
int num_dark_suspend_imminent() const { return num_dark_suspend_imminent_; }
int num_restart_requested() const { return num_restart_requested_; }
const base::UnguessableToken& block_suspend_token() const {
return block_suspend_token_;
}
int32_t ambient_color_temperature() const {
return ambient_color_temperature_;
}
power_manager::BatterySaverModeState battery_saver_mode_state() const {
return battery_saver_mode_state_;
}
const power_manager::AmbientLightSensorChange&
last_ambient_light_sensor_change() const {
return last_ambient_light_sensor_change_;
}
const power_manager::AmbientLightSensorChange&
last_keyboard_ambient_light_sensor_change() const {
return last_keyboard_ambient_light_sensor_change_;
}
void set_should_block_suspend(bool take_callback) {
should_block_suspend_ = take_callback;
}
void set_run_unblock_suspend_immediately(bool run) {
run_unblock_suspend_immediately_ = run;
}
// Runs |block_suspend_token_|.
[[nodiscard]] bool UnblockSuspend() {
if (block_suspend_token_.is_empty())
return false;
client_->UnblockSuspend(block_suspend_token_);
return true;
}
// PowerManagerClient::Observer:
void SuspendImminent(power_manager::SuspendImminent::Reason reason) override {
num_suspend_imminent_++;
if (should_block_suspend_) {
block_suspend_token_ = base::UnguessableToken::Create();
client_->BlockSuspend(block_suspend_token_, FROM_HERE.ToString());
}
if (run_unblock_suspend_immediately_)
CHECK(UnblockSuspend());
}
void SuspendDone(base::TimeDelta sleep_duration) override {
num_suspend_done_++;
}
void DarkSuspendImminent() override {
num_dark_suspend_imminent_++;
if (should_block_suspend_) {
block_suspend_token_ = base::UnguessableToken::Create();
client_->BlockSuspend(block_suspend_token_, FROM_HERE.ToString());
}
if (run_unblock_suspend_immediately_)
CHECK(UnblockSuspend());
}
void AmbientColorChanged(const int32_t color_temperature) override {
ambient_color_temperature_ = color_temperature;
}
void BatterySaverModeStateChanged(
const power_manager::BatterySaverModeState& state) override {
battery_saver_mode_state_ = state;
}
void RestartRequested(power_manager::RequestRestartReason reason) override {
num_restart_requested_++;
}
void AmbientLightSensorEnabledChanged(
const power_manager::AmbientLightSensorChange& change) override {
last_ambient_light_sensor_change_ = change;
}
void KeyboardAmbientLightSensorEnabledChanged(
const power_manager::AmbientLightSensorChange& change) override {
last_keyboard_ambient_light_sensor_change_ = change;
}
private:
raw_ptr<PowerManagerClient> client_; // Not owned.
// Number of times SuspendImminent(), SuspendDone(), DarkSuspendImminent() and
// RestartRequested() have been called.
int num_suspend_imminent_ = 0;
int num_suspend_done_ = 0;
int num_dark_suspend_imminent_ = 0;
int num_restart_requested_ = 0;
// Should SuspendImminent() and DarkSuspendImminent() call |client_|'s
// BlockSuspend() method?
bool should_block_suspend_ = false;
// Should SuspendImminent() and DarkSuspendImminent() unblock the suspend
// synchronously after blocking itit? Only has an effect if
// |should_block_suspend_| is true.
bool run_unblock_suspend_immediately_ = false;
// When non-empty, the token for the outstanding block-suspend registration.
base::UnguessableToken block_suspend_token_;
// Ambient color temperature.
int32_t ambient_color_temperature_ = 0;
// Battery saver mode state.
power_manager::BatterySaverModeState battery_saver_mode_state_;
// Last-set ambient light sensor change.
power_manager::AmbientLightSensorChange last_ambient_light_sensor_change_;
// Last-set keyboard ambient light sensor change.
power_manager::AmbientLightSensorChange
last_keyboard_ambient_light_sensor_change_;
};
// Stub implementation of PowerManagerClient::RenderProcessManagerDelegate.
class TestDelegate : public PowerManagerClient::RenderProcessManagerDelegate {
public:
explicit TestDelegate(PowerManagerClient* client) {
client->SetRenderProcessManagerDelegate(weak_ptr_factory_.GetWeakPtr());
}
TestDelegate(const TestDelegate&) = delete;
TestDelegate& operator=(const TestDelegate&) = delete;
~TestDelegate() override = default;
int num_suspend_imminent() const { return num_suspend_imminent_; }
int num_suspend_done() const { return num_suspend_done_; }
// PowerManagerClient::RenderProcessManagerDelegate:
void SuspendImminent() override { num_suspend_imminent_++; }
void SuspendDone() override { num_suspend_done_++; }
private:
// Number of times SuspendImminent() and SuspendDone() have been called.
int num_suspend_imminent_ = 0;
int num_suspend_done_ = 0;
base::WeakPtrFactory<TestDelegate> weak_ptr_factory_{this};
};
// Local implementation of base::test::PowerMonitorTestObserver to add callback
// to OnThermalStateChange.
class PowerMonitorTestObserverLocal
: public base::test::PowerMonitorTestObserver {
public:
using base::test::PowerMonitorTestObserver::PowerMonitorTestObserver;
PowerMonitorTestObserverLocal(const PowerMonitorTestObserverLocal&) = delete;
PowerMonitorTestObserverLocal& operator=(
const PowerMonitorTestObserverLocal&) = delete;
void OnThermalStateChange(
PowerThermalObserver::DeviceThermalState new_state) override {
base::test::PowerMonitorTestObserver::OnThermalStateChange(new_state);
test_future_.GetCallback().Run(new_state);
}
PowerThermalObserver::DeviceThermalState GetThermalState() {
return test_future_.Take();
}
private:
base::test::TestFuture<PowerThermalObserver::DeviceThermalState> test_future_;
};
} // namespace
class PowerManagerClientTest : public testing::Test {
public:
PowerManagerClientTest() = default;
PowerManagerClientTest(const PowerManagerClientTest&) = delete;
PowerManagerClientTest& operator=(const PowerManagerClientTest&) = delete;
~PowerManagerClientTest() override = default;
void SetUp() override {
dbus::Bus::Options options;
options.bus_type = dbus::Bus::SYSTEM;
bus_ = new dbus::MockBus(options);
proxy_ = new dbus::MockObjectProxy(
bus_.get(), power_manager::kPowerManagerServiceName,
dbus::ObjectPath(power_manager::kPowerManagerServicePath));
// |client_|'s Init() method should request a proxy for communicating with
// powerd.
EXPECT_CALL(*bus_,
GetObjectProxy(
power_manager::kPowerManagerServiceName,
dbus::ObjectPath(power_manager::kPowerManagerServicePath)))
.WillRepeatedly(Return(proxy_.get()));
EXPECT_CALL(*bus_, GetDBusTaskRunner())
.WillRepeatedly(
Return(task_environment_.GetMainThreadTaskRunner().get()));
EXPECT_CALL(*bus_, GetOriginTaskRunner())
.WillRepeatedly(
Return(task_environment_.GetMainThreadTaskRunner().get()));
// Save |client_|'s signal and name-owner-changed callbacks.
EXPECT_CALL(*proxy_, DoConnectToSignal(kInterface, _, _, _))
.WillRepeatedly(Invoke(this, &PowerManagerClientTest::ConnectToSignal));
EXPECT_CALL(*proxy_, SetNameOwnerChangedCallback(_))
.WillRepeatedly(SaveArg<0>(&name_owner_changed_callback_));
// |client_|'s Init() method should register regular and dark suspend
// delays.
EXPECT_CALL(
*proxy_,
DoCallMethod(HasMember(power_manager::kRegisterSuspendDelayMethod), _,
_))
.WillRepeatedly(
Invoke(this, &PowerManagerClientTest::RegisterSuspendDelay));
EXPECT_CALL(
*proxy_,
DoCallMethod(HasMember(power_manager::kRegisterDarkSuspendDelayMethod),
_, _))
.WillRepeatedly(
Invoke(this, &PowerManagerClientTest::RegisterSuspendDelay));
// Init should request the current thermal state
EXPECT_CALL(
*proxy_,
DoCallMethod(HasMember(power_manager::kGetThermalStateMethod), _, _));
// Init should also request a fresh power status.
EXPECT_CALL(
*proxy_,
DoCallMethod(HasMember(power_manager::kGetPowerSupplyPropertiesMethod),
_, _));
PowerManagerClient::Initialize(bus_.get());
client_ = PowerManagerClient::Get();
// Execute callbacks posted by Init().
base::RunLoop().RunUntilIdle();
}
void TearDown() override { PowerManagerClient::Shutdown(); }
void HandleGetBatterySaverModeState(
dbus::MethodCall* method_call,
int timeout_ms,
dbus::ObjectProxy::ResponseCallback* callback) {
power_manager::BatterySaverModeState proto;
proto.set_enabled(true);
auto response = ::dbus::Response::CreateEmpty();
dbus::MessageWriter(response.get()).AppendProtoAsArrayOfBytes(proto);
std::move(*callback).Run(response.get());
}
protected:
// Synchronously passes |signal| to |client_|'s handler, simulating the signal
// being emitted by powerd.
void EmitSignal(dbus::Signal* signal) {
const std::string signal_name = signal->GetMember();
const auto it = signal_callbacks_.find(signal_name);
ASSERT_TRUE(it != signal_callbacks_.end())
<< "Client didn't register for signal " << signal_name;
it->second.Run(signal);
}
// Passes a SuspendImminent or DarkSuspendImminent signal to |client_|.
void EmitSuspendImminentSignal(const std::string& signal_name,
int suspend_id) {
power_manager::SuspendImminent proto;
proto.set_suspend_id(suspend_id);
proto.set_reason(power_manager::SuspendImminent_Reason_OTHER);
dbus::Signal signal(kInterface, signal_name);
dbus::MessageWriter(&signal).AppendProtoAsArrayOfBytes(proto);
EmitSignal(&signal);
}
// Passes a SuspendDone signal to |client_|.
void EmitSuspendDoneSignal(int suspend_id) {
power_manager::SuspendDone proto;
proto.set_suspend_id(suspend_id);
dbus::Signal signal(kInterface, power_manager::kSuspendDoneSignal);
dbus::MessageWriter(&signal).AppendProtoAsArrayOfBytes(proto);
EmitSignal(&signal);
}
// Adds an expectation to |proxy_| for a HandleSuspendReadiness or
// HandleDarkSuspendReadiness method call.
void ExpectSuspendReadiness(const std::string& method_name,
int suspend_id,
int delay_id) {
EXPECT_CALL(
*proxy_.get(),
DoCallMethod(IsSuspendReadiness(method_name, suspend_id, delay_id), _,
_));
}
// Arbitrary delay IDs returned to |client_|.
static const int kSuspendDelayId = 100;
static const int kDarkSuspendDelayId = 200;
base::test::SingleThreadTaskEnvironment task_environment_;
// Mock bus and proxy for simulating calls to powerd.
scoped_refptr<dbus::MockBus> bus_;
scoped_refptr<dbus::MockObjectProxy> proxy_;
raw_ptr<PowerManagerClient, DanglingUntriaged> client_ = nullptr;
// Maps from powerd signal name to the corresponding callback provided by
// |client_|.
std::map<std::string, dbus::ObjectProxy::SignalCallback> signal_callbacks_;
// Callback passed to |proxy_|'s SetNameOwnerChangedCallback() method.
// TODO(derat): Test that |client_| handles powerd restarts.
dbus::ObjectProxy::NameOwnerChangedCallback name_owner_changed_callback_;
private:
// Handles calls to |proxy_|'s ConnectToSignal() method.
void ConnectToSignal(
const std::string& interface_name,
const std::string& signal_name,
dbus::ObjectProxy::SignalCallback signal_callback,
dbus::ObjectProxy::OnConnectedCallback* on_connected_callback) {
CHECK_EQ(interface_name, power_manager::kPowerManagerInterface);
signal_callbacks_[signal_name] = signal_callback;
task_environment_.GetMainThreadTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(std::move(*on_connected_callback), interface_name,
signal_name, true /* success */));
}
// Handles calls to |proxy_|'s CallMethod() method to register suspend delays.
void RegisterSuspendDelay(dbus::MethodCall* method_call,
int timeout_ms,
dbus::ObjectProxy::ResponseCallback* callback) {
power_manager::RegisterSuspendDelayReply proto;
proto.set_delay_id(method_call->GetMember() ==
power_manager::kRegisterDarkSuspendDelayMethod
? kDarkSuspendDelayId
: kSuspendDelayId);
method_call->SetSerial(123); // Arbitrary but needed by FromMethodCall().
std::unique_ptr<dbus::Response> response(
dbus::Response::FromMethodCall(method_call));
CHECK(dbus::MessageWriter(response.get()).AppendProtoAsArrayOfBytes(proto));
task_environment_.GetMainThreadTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(&RunResponseCallback, std::move(*callback),
std::move(response)));
}
};
// Tests that suspend readiness is reported immediately when there are no
// observers.
TEST_F(PowerManagerClientTest, ReportSuspendReadinessWithoutObservers) {
const int kSuspendId = 1;
ExpectSuspendReadiness(kHandleSuspendReadiness, kSuspendId, kSuspendDelayId);
EmitSuspendImminentSignal(kSuspendImminent, kSuspendId);
EmitSuspendDoneSignal(kSuspendId);
}
// Tests that synchronous observers are notified about impending suspend
// attempts and completion.
TEST_F(PowerManagerClientTest, ReportSuspendReadinessWithoutCallbacks) {
TestObserver observer_1(client_);
TestObserver observer_2(client_);
// Observers should be notified when suspend is imminent. Readiness should be
// reported synchronously since GetSuspendReadinessCallback() hasn't been
// called.
const int kSuspendId = 1;
ExpectSuspendReadiness(kHandleSuspendReadiness, kSuspendId, kSuspendDelayId);
EmitSuspendImminentSignal(kSuspendImminent, kSuspendId);
EXPECT_EQ(1, observer_1.num_suspend_imminent());
EXPECT_EQ(0, observer_1.num_suspend_done());
EXPECT_EQ(1, observer_2.num_suspend_imminent());
EXPECT_EQ(0, observer_2.num_suspend_done());
EmitSuspendDoneSignal(kSuspendId);
EXPECT_EQ(1, observer_1.num_suspend_imminent());
EXPECT_EQ(1, observer_1.num_suspend_done());
EXPECT_EQ(1, observer_2.num_suspend_imminent());
EXPECT_EQ(1, observer_2.num_suspend_done());
}
// Tests that readiness is deferred until asynchronous observers have run their
// callbacks.
TEST_F(PowerManagerClientTest, ReportSuspendReadinessWithCallbacks) {
TestObserver observer_1(client_);
observer_1.set_should_block_suspend(true);
TestObserver observer_2(client_);
observer_2.set_should_block_suspend(true);
TestObserver observer_3(client_);
// When observers call GetSuspendReadinessCallback() from their
// SuspendImminent() methods, the HandleSuspendReadiness method call should be
// deferred until all callbacks are run.
const int kSuspendId = 1;
EmitSuspendImminentSignal(kSuspendImminent, kSuspendId);
EXPECT_TRUE(observer_1.UnblockSuspend());
ExpectSuspendReadiness(kHandleSuspendReadiness, kSuspendId, kSuspendDelayId);
EXPECT_TRUE(observer_2.UnblockSuspend());
EmitSuspendDoneSignal(kSuspendId);
EXPECT_EQ(1, observer_1.num_suspend_done());
EXPECT_EQ(1, observer_2.num_suspend_done());
}
// Tests that RenderProcessManagerDelegate is notified about suspend and resume
// in the common case where suspend readiness is reported.
TEST_F(PowerManagerClientTest, NotifyRenderProcessManagerDelegate) {
TestDelegate delegate(client_);
TestObserver observer(client_);
observer.set_should_block_suspend(true);
const int kSuspendId = 1;
EmitSuspendImminentSignal(kSuspendImminent, kSuspendId);
EXPECT_EQ(0, delegate.num_suspend_imminent());
EXPECT_EQ(0, delegate.num_suspend_done());
// The RenderProcessManagerDelegate should be notified that suspend is
// imminent only after observers have reported readiness.
ExpectSuspendReadiness(kHandleSuspendReadiness, kSuspendId, kSuspendDelayId);
EXPECT_TRUE(observer.UnblockSuspend());
EXPECT_EQ(1, delegate.num_suspend_imminent());
EXPECT_EQ(0, delegate.num_suspend_done());
// The delegate should be notified immediately after the attempt completes.
EmitSuspendDoneSignal(kSuspendId);
EXPECT_EQ(1, delegate.num_suspend_imminent());
EXPECT_EQ(1, delegate.num_suspend_done());
}
// Tests that DarkSuspendImminent is handled in a manner similar to
// SuspendImminent.
TEST_F(PowerManagerClientTest, ReportDarkSuspendReadiness) {
TestDelegate delegate(client_);
TestObserver observer(client_);
observer.set_should_block_suspend(true);
const int kSuspendId = 1;
EmitSuspendImminentSignal(kSuspendImminent, kSuspendId);
EXPECT_EQ(1, observer.num_suspend_imminent());
EXPECT_EQ(0, delegate.num_suspend_imminent());
ExpectSuspendReadiness(kHandleSuspendReadiness, kSuspendId, kSuspendDelayId);
EXPECT_TRUE(observer.UnblockSuspend());
EXPECT_EQ(1, delegate.num_suspend_imminent());
// The RenderProcessManagerDelegate shouldn't be notified about dark suspend
// attempts.
const int kDarkSuspendId = 5;
EmitSuspendImminentSignal(kDarkSuspendImminent, kDarkSuspendId);
EXPECT_EQ(1, observer.num_dark_suspend_imminent());
EXPECT_EQ(1, delegate.num_suspend_imminent());
EXPECT_EQ(0, delegate.num_suspend_done());
ExpectSuspendReadiness(kHandleDarkSuspendReadiness, kDarkSuspendId,
kDarkSuspendDelayId);
EXPECT_TRUE(observer.UnblockSuspend());
EXPECT_EQ(0, delegate.num_suspend_done());
EmitSuspendDoneSignal(kSuspendId);
EXPECT_EQ(1, observer.num_suspend_done());
EXPECT_EQ(1, delegate.num_suspend_done());
}
// Tests the case where a SuspendDone signal is received while a readiness
// callback is still pending.
TEST_F(PowerManagerClientTest, SuspendCancelledWhileCallbackPending) {
TestDelegate delegate(client_);
TestObserver observer(client_);
observer.set_should_block_suspend(true);
const int kSuspendId = 1;
EmitSuspendImminentSignal(kSuspendImminent, kSuspendId);
EXPECT_EQ(1, observer.num_suspend_imminent());
// If the suspend attempt completes (probably due to cancellation) before the
// observer has run its readiness callback, the observer (but not the
// delegate, which hasn't been notified about suspend being imminent yet)
// should be notified about completion.
EmitSuspendDoneSignal(kSuspendId);
EXPECT_EQ(1, observer.num_suspend_done());
EXPECT_EQ(0, delegate.num_suspend_done());
// Ensure that the delegate doesn't receive late notification of suspend being
// imminent if the readiness callback runs at this point, since that would
// leave the renderers in a frozen state (http://crbug.com/646912). There's an
// implicit expectation that powerd doesn't get notified about readiness here,
// too.
EXPECT_TRUE(observer.UnblockSuspend());
EXPECT_EQ(0, delegate.num_suspend_imminent());
EXPECT_EQ(0, delegate.num_suspend_done());
}
// Tests the case where a SuspendDone signal is received while a dark suspend
// readiness callback is still pending.
TEST_F(PowerManagerClientTest, SuspendDoneWhileDarkSuspendCallbackPending) {
TestDelegate delegate(client_);
TestObserver observer(client_);
observer.set_should_block_suspend(true);
const int kSuspendId = 1;
EmitSuspendImminentSignal(kSuspendImminent, kSuspendId);
ExpectSuspendReadiness(kHandleSuspendReadiness, kSuspendId, kSuspendDelayId);
EXPECT_TRUE(observer.UnblockSuspend());
EXPECT_EQ(1, delegate.num_suspend_imminent());
const int kDarkSuspendId = 5;
EmitSuspendImminentSignal(kDarkSuspendImminent, kDarkSuspendId);
EXPECT_EQ(1, observer.num_dark_suspend_imminent());
// The delegate should be notified if the attempt completes now.
EmitSuspendDoneSignal(kSuspendId);
EXPECT_EQ(1, observer.num_suspend_done());
EXPECT_EQ(1, delegate.num_suspend_done());
// Dark suspend readiness shouldn't be reported even if the callback runs at
// this point, since the suspend attempt is already done. The delegate also
// shouldn't receive any more calls.
EXPECT_TRUE(observer.UnblockSuspend());
EXPECT_EQ(1, delegate.num_suspend_imminent());
EXPECT_EQ(1, delegate.num_suspend_done());
}
// Tests the case where dark suspend is announced while readiness hasn't been
// reported for the initial regular suspend attempt.
TEST_F(PowerManagerClientTest, DarkSuspendImminentWhileCallbackPending) {
TestDelegate delegate(client_);
TestObserver observer(client_);
observer.set_should_block_suspend(true);
// Announce that suspend is imminent and grab, but don't run, the readiness
// callback.
const int kSuspendId = 1;
EmitSuspendImminentSignal(kSuspendImminent, kSuspendId);
EXPECT_EQ(1, observer.num_suspend_imminent());
base::UnguessableToken regular_token = observer.block_suspend_token();
// Before readiness is reported, announce that dark suspend is imminent.
const int kDarkSuspendId = 1;
EmitSuspendImminentSignal(kDarkSuspendImminent, kDarkSuspendId);
EXPECT_EQ(1, observer.num_dark_suspend_imminent());
base::UnguessableToken dark_token = observer.block_suspend_token();
// Complete the suspend attempt and run both of the earlier callbacks. Neither
// should result in readiness being reported.
EmitSuspendDoneSignal(kSuspendId);
EXPECT_EQ(1, observer.num_suspend_done());
client_->UnblockSuspend(regular_token);
client_->UnblockSuspend(dark_token);
}
// Tests that PowerManagerClient handles a single observer that requests a
// suspend-readiness callback and then runs it synchronously from within
// SuspendImminent() instead of running it asynchronously:
// http://crosbug.com/p/58295
TEST_F(PowerManagerClientTest, SyncCallbackWithSingleObserver) {
TestObserver observer(client_);
observer.set_should_block_suspend(true);
observer.set_run_unblock_suspend_immediately(true);
const int kSuspendId = 1;
ExpectSuspendReadiness(kHandleSuspendReadiness, kSuspendId, kSuspendDelayId);
EmitSuspendImminentSignal(kSuspendImminent, kSuspendId);
EmitSuspendDoneSignal(kSuspendId);
}
// Tests the case where one observer reports suspend readiness by running its
// callback before a second observer even gets notified about the suspend
// attempt. We shouldn't report suspend readiness until the second observer has
// been notified and confirmed readiness.
TEST_F(PowerManagerClientTest, SyncCallbackWithMultipleObservers) {
TestObserver observer1(client_);
observer1.set_should_block_suspend(true);
observer1.set_run_unblock_suspend_immediately(true);
TestObserver observer2(client_);
observer2.set_should_block_suspend(true);
const int kSuspendId = 1;
EmitSuspendImminentSignal(kSuspendImminent, kSuspendId);
ExpectSuspendReadiness(kHandleSuspendReadiness, kSuspendId, kSuspendDelayId);
EXPECT_TRUE(observer2.UnblockSuspend());
EmitSuspendDoneSignal(kSuspendId);
}
// Tests that observers are notified about changes in ambient color temperature.
TEST_F(PowerManagerClientTest, ChangeAmbientColorTemperature) {
TestObserver observer(client_);
constexpr int32_t kTemperature = 6500;
dbus::Signal signal(kInterface,
power_manager::kAmbientColorTemperatureChangedSignal);
dbus::MessageWriter(&signal).AppendInt32(kTemperature);
EmitSignal(&signal);
EXPECT_EQ(kTemperature, observer.ambient_color_temperature());
}
// Tests that base::PowerMonitor observers are notified about thermal event.
TEST_F(PowerManagerClientTest, ChangeThermalState) {
base::test::ScopedPowerMonitorTestSource power_monitor_source;
PowerMonitorTestObserverLocal observer;
base::PowerMonitor::AddPowerThermalObserver(&observer);
typedef struct {
power_manager::ThermalEvent::ThermalState dbus_state;
base::PowerThermalObserver::DeviceThermalState expected_state;
} ThermalDBusTestType;
ThermalDBusTestType thermal_states[] = {
{.dbus_state = power_manager::ThermalEvent_ThermalState_NOMINAL,
.expected_state =
base::PowerThermalObserver::DeviceThermalState::kNominal},
{.dbus_state = power_manager::ThermalEvent_ThermalState_FAIR,
.expected_state = base::PowerThermalObserver::DeviceThermalState::kFair},
{.dbus_state = power_manager::ThermalEvent_ThermalState_SERIOUS,
.expected_state =
base::PowerThermalObserver::DeviceThermalState::kSerious},
{.dbus_state = power_manager::ThermalEvent_ThermalState_CRITICAL,
.expected_state =
base::PowerThermalObserver::DeviceThermalState::kCritical},
// Testing of power thermal state 'Unknown' cannot be the first one
// since the initial state in the PowerMonitor is 'Unknown' and the
// notifications are deduplicated and not sent if unchanged.
{.dbus_state = power_manager::ThermalEvent_ThermalState_UNKNOWN,
.expected_state =
base::PowerThermalObserver::DeviceThermalState::kUnknown},
};
for (const auto& p : thermal_states) {
power_manager::ThermalEvent proto;
proto.set_thermal_state(p.dbus_state);
proto.set_timestamp(0);
dbus::Signal signal(kInterface, power_manager::kThermalEventSignal);
dbus::MessageWriter(&signal).AppendProtoAsArrayOfBytes(proto);
EmitSignal(&signal);
EXPECT_EQ(observer.GetThermalState(), p.expected_state);
}
base::PowerMonitor::RemovePowerThermalObserver(&observer);
}
// Test that |RequestSuspend| calls the DBus method with the same name.
TEST_F(PowerManagerClientTest, RequestSuspend) {
const uint64_t expected_count = -1ULL;
const int32_t expected_duration = 5;
const auto expected_flavor = power_manager::REQUEST_SUSPEND_DEFAULT;
EXPECT_CALL(*proxy_.get(),
DoCallMethod(IsRequestSuspend("RequestSuspend", expected_count,
expected_duration, expected_flavor),
_, _));
client_->RequestSuspend(std::nullopt, expected_duration, expected_flavor);
const uint64_t expected_count2 = 18446744073709550592ULL;
const int32_t expected_duration2 = -5;
const auto expected_flavor2 = power_manager::REQUEST_SUSPEND_TO_DISK;
EXPECT_CALL(
*proxy_.get(),
DoCallMethod(IsRequestSuspend("RequestSuspend", expected_count2,
expected_duration2, expected_flavor2),
_, _));
client_->RequestSuspend(expected_count2, expected_duration2,
expected_flavor2);
}
// Test that |RequestRestart| calls |RestartRequested| method for observers.
TEST_F(PowerManagerClientTest, ObserverCalledAfterRequestRestart) {
TestObserver observer(client_);
EXPECT_CALL(*proxy_.get(),
DoCallMethod(IsRequestRestart("RequestRestart"), _, _));
EXPECT_EQ(0, observer.num_restart_requested());
client_->RequestRestart(
power_manager::RequestRestartReason::REQUEST_RESTART_OTHER,
"test restart");
EXPECT_EQ(1, observer.num_restart_requested());
}
// Tests that |(Get|Set)BatterySaverModeState| call the DBus methods with the
// same names.
TEST_F(PowerManagerClientTest, GetSetBatterySaverModeState) {
EXPECT_CALL(
*proxy_,
DoCallMethod(HasMember(power_manager::kSetBatterySaverModeState), _, _));
power_manager::SetBatterySaverModeStateRequest proto;
proto.set_enabled(true);
client_->SetBatterySaverModeState(proto);
EXPECT_CALL(
*proxy_,
DoCallMethod(HasMember(power_manager::kGetBatterySaverModeState), _, _))
.WillOnce(Invoke(
this, &PowerManagerClientTest::HandleGetBatterySaverModeState));
client_->GetBatterySaverModeState(base::BindOnce(
[](std::optional<power_manager::BatterySaverModeState> state) {
ASSERT_TRUE(state.has_value());
EXPECT_TRUE(state->enabled());
}));
}
// Tests that observers are notified about changes in Battery Saver Mode state.
TEST_F(PowerManagerClientTest, BatterySaverModeStateChanged) {
TestObserver observer(client_);
power_manager::BatterySaverModeState proto;
proto.set_enabled(true);
dbus::Signal signal(kInterface, power_manager::kBatterySaverModeStateChanged);
dbus::MessageWriter(&signal).AppendProtoAsArrayOfBytes(proto);
EmitSignal(&signal);
EXPECT_EQ(proto.enabled(), observer.battery_saver_mode_state().enabled());
}
// Tests that |SetAmbientLightSensorEnabled| calls the DBus method with the same
// name.
TEST_F(PowerManagerClientTest, SetAmbientLightSensorEnabled) {
power_manager::SetAmbientLightSensorEnabledRequest request;
// Test with sensor disabled
request.set_sensor_enabled(false);
EXPECT_CALL(*proxy_.get(),
DoCallMethod(IsAmbientLightSensorEnabled(
"SetAmbientLightSensorEnabled", false),
_, _));
client_->SetAmbientLightSensorEnabled(request);
// Test with sensor enabled
request.set_sensor_enabled(true);
EXPECT_CALL(*proxy_.get(),
DoCallMethod(IsAmbientLightSensorEnabled(
"SetAmbientLightSensorEnabled", true),
_, _));
client_->SetAmbientLightSensorEnabled(request);
}
// Tests that |SetKeyboardAmbientLightSensorEnabled| calls the DBus method
// with the same name.
TEST_F(PowerManagerClientTest, SetKeyboardAmbientLightSensorEnabled) {
power_manager::SetAmbientLightSensorEnabledRequest request;
// Test with sensor disabled
request.set_sensor_enabled(false);
EXPECT_CALL(*proxy_.get(),
DoCallMethod(IsAmbientLightSensorEnabled(
"SetKeyboardAmbientLightSensorEnabled", false),
_, _));
client_->SetKeyboardAmbientLightSensorEnabled(request);
// Test with sensor enabled
request.set_sensor_enabled(true);
EXPECT_CALL(*proxy_.get(),
DoCallMethod(IsAmbientLightSensorEnabled(
"SetKeyboardAmbientLightSensorEnabled", true),
_, _));
client_->SetKeyboardAmbientLightSensorEnabled(request);
}
TEST_F(PowerManagerClientTest, GetKeyboardAmbientLightSensorEnabled) {
// The dbus method is set up to simulate a response of true from the service.
EXPECT_CALL(
*proxy_,
DoCallMethod(
HasMember(power_manager::kGetKeyboardAmbientLightSensorEnabledMethod),
_, _))
.WillOnce([](dbus::MethodCall* method_call, int timeout_ms,
dbus::ObjectProxy::ResponseCallback* callback) {
auto response = ::dbus::Response::CreateEmpty();
dbus::MessageWriter(response.get()).AppendBool(true);
std::move(*callback).Run(response.get());
});
// Verify that the callback receives and processes the true value correctly.
client_->GetKeyboardAmbientLightSensorEnabled(
base::BindOnce([](std::optional<bool> is_ambient_light_sensor_enabled) {
EXPECT_TRUE(is_ambient_light_sensor_enabled.value());
}));
// The dbus method is set up to simulate a response of false from the service.
EXPECT_CALL(
*proxy_,
DoCallMethod(
HasMember(power_manager::kGetKeyboardAmbientLightSensorEnabledMethod),
_, _))
.WillOnce([](dbus::MethodCall* method_call, int timeout_ms,
dbus::ObjectProxy::ResponseCallback* callback) {
auto response = ::dbus::Response::CreateEmpty();
dbus::MessageWriter(response.get()).AppendBool(false);
std::move(*callback).Run(response.get());
});
// Verify that the callback receives and processes the false value correctly.
client_->GetKeyboardAmbientLightSensorEnabled(
base::BindOnce([](std::optional<bool> is_ambient_light_sensor_enabled) {
EXPECT_FALSE(is_ambient_light_sensor_enabled.value());
}));
}
// Tests that |HasAmbientLightSensor| calls the DBus method with the same name.
TEST_F(PowerManagerClientTest, HasAmbientLightSensor) {
// Device has an ambient light sensor.
EXPECT_CALL(*proxy_,
DoCallMethod(
HasMember(power_manager::kHasAmbientLightSensorMethod), _, _))
.WillOnce([](dbus::MethodCall* method_call, int timeout_ms,
dbus::ObjectProxy::ResponseCallback* callback) {
auto response = ::dbus::Response::CreateEmpty();
dbus::MessageWriter(response.get()).AppendBool(true);
std::move(*callback).Run(response.get());
});
client_->HasAmbientLightSensor(
base::BindOnce([](std::optional<bool> has_ambient_light_sensor) {
EXPECT_TRUE(has_ambient_light_sensor.value());
}));
// Device does not have an ambient light sensor.
EXPECT_CALL(*proxy_,
DoCallMethod(
HasMember(power_manager::kHasAmbientLightSensorMethod), _, _))
.WillOnce([](dbus::MethodCall* method_call, int timeout_ms,
dbus::ObjectProxy::ResponseCallback* callback) {
auto response = ::dbus::Response::CreateEmpty();
dbus::MessageWriter(response.get()).AppendBool(false);
std::move(*callback).Run(response.get());
});
client_->HasAmbientLightSensor(
base::BindOnce([](std::optional<bool> has_ambient_light_sensor) {
EXPECT_FALSE(has_ambient_light_sensor.value());
}));
}
// Tests that observers are notified about changes to the Ambient Light Sensor
// status.
TEST_F(PowerManagerClientTest, AmbientLightSensorEnabledChanged) {
TestObserver observer(client_);
EXPECT_FALSE(
observer.last_ambient_light_sensor_change().has_sensor_enabled());
EXPECT_FALSE(observer.last_ambient_light_sensor_change().has_cause());
{
// When PowerManagerClient receives a signal saying that the Ambient Light
// Sensor is disabled, observers should be notified.
power_manager::AmbientLightSensorChange proto;
proto.set_sensor_enabled(false);
proto.set_cause(
power_manager::AmbientLightSensorChange_Cause_BRIGHTNESS_USER_REQUEST);
dbus::Signal signal(kInterface,
power_manager::kAmbientLightSensorEnabledChangedSignal);
dbus::MessageWriter(&signal).AppendProtoAsArrayOfBytes(proto);
EmitSignal(&signal);
EXPECT_TRUE(
observer.last_ambient_light_sensor_change().has_sensor_enabled());
EXPECT_EQ(proto.sensor_enabled(),
observer.last_ambient_light_sensor_change().sensor_enabled());
// The change cause should be USER_REQUEST_SETTINGS_APP because the change
// was triggered via the PowerManagerClient function.
EXPECT_TRUE(observer.last_ambient_light_sensor_change().has_cause());
EXPECT_EQ(proto.cause(),
observer.last_ambient_light_sensor_change().cause());
}
{
// When PowerManagerClient receives a signal saying that the Ambient Light
// Sensor is enabled, observers should be notified.
power_manager::AmbientLightSensorChange proto;
proto.set_sensor_enabled(true);
proto.set_cause(
power_manager::
AmbientLightSensorChange_Cause_USER_REQUEST_SETTINGS_APP);
dbus::Signal signal(kInterface,
power_manager::kAmbientLightSensorEnabledChangedSignal);
dbus::MessageWriter(&signal).AppendProtoAsArrayOfBytes(proto);
EmitSignal(&signal);
EXPECT_TRUE(
observer.last_ambient_light_sensor_change().has_sensor_enabled());
EXPECT_EQ(proto.sensor_enabled(),
observer.last_ambient_light_sensor_change().sensor_enabled());
// The change cause should be USER_REQUEST_SETTINGS_APP because the change
// was triggered via the PowerManagerClient function.
EXPECT_TRUE(observer.last_ambient_light_sensor_change().has_cause());
EXPECT_EQ(proto.cause(),
observer.last_ambient_light_sensor_change().cause());
}
}
// Tests that observers are notified about changes to the Keyboard ambient Light
// Sensor status.
TEST_F(PowerManagerClientTest, KeyboardAmbientLightSensorEnabledChanged) {
TestObserver observer(client_);
EXPECT_FALSE(observer.last_keyboard_ambient_light_sensor_change()
.has_sensor_enabled());
EXPECT_FALSE(
observer.last_keyboard_ambient_light_sensor_change().has_cause());
{
// When PowerManagerClient receives a signal saying that the Keyboard
// Ambient Light Sensor is disabled, observers should be notified.
power_manager::AmbientLightSensorChange proto;
proto.set_sensor_enabled(false);
proto.set_cause(
power_manager::AmbientLightSensorChange_Cause_BRIGHTNESS_USER_REQUEST);
dbus::Signal signal(
kInterface,
power_manager::kKeyboardAmbientLightSensorEnabledChangedSignal);
dbus::MessageWriter(&signal).AppendProtoAsArrayOfBytes(proto);
EmitSignal(&signal);
EXPECT_TRUE(observer.last_keyboard_ambient_light_sensor_change()
.has_sensor_enabled());
EXPECT_EQ(
proto.sensor_enabled(),
observer.last_keyboard_ambient_light_sensor_change().sensor_enabled());
// The change cause should be USER_REQUEST_SETTINGS_APP because the change
// was triggered via the PowerManagerClient function.
EXPECT_TRUE(
observer.last_keyboard_ambient_light_sensor_change().has_cause());
EXPECT_EQ(proto.cause(),
observer.last_keyboard_ambient_light_sensor_change().cause());
}
{
// When PowerManagerClient receives a signal saying that the Ambient Light
// Sensor is enabled, observers should be notified.
power_manager::AmbientLightSensorChange proto;
proto.set_sensor_enabled(true);
proto.set_cause(
power_manager::
AmbientLightSensorChange_Cause_USER_REQUEST_SETTINGS_APP);
dbus::Signal signal(
kInterface,
power_manager::kKeyboardAmbientLightSensorEnabledChangedSignal);
dbus::MessageWriter(&signal).AppendProtoAsArrayOfBytes(proto);
EmitSignal(&signal);
EXPECT_TRUE(observer.last_keyboard_ambient_light_sensor_change()
.has_sensor_enabled());
EXPECT_EQ(
proto.sensor_enabled(),
observer.last_keyboard_ambient_light_sensor_change().sensor_enabled());
// The change cause should be USER_REQUEST_SETTINGS_APP because the change
// was triggered via the PowerManagerClient function.
EXPECT_TRUE(
observer.last_keyboard_ambient_light_sensor_change().has_cause());
EXPECT_EQ(proto.cause(),
observer.last_keyboard_ambient_light_sensor_change().cause());
}
}
// Tests that |GetAmbientLightSensorEnabled| calls the DBus method with the
// same name.
TEST_F(PowerManagerClientTest, GetAmbientLightSensorEnabled) {
// Set up the DBus method kGetAmbientLightSensorEnabledMethod to return that
// the ambient light sensor is enabled.
EXPECT_CALL(
*proxy_,
DoCallMethod(
HasMember(power_manager::kGetAmbientLightSensorEnabledMethod), _, _))
.WillOnce([](dbus::MethodCall* method_call, int timeout_ms,
dbus::ObjectProxy::ResponseCallback* callback) {
auto response = ::dbus::Response::CreateEmpty();
// Return that the ambient light sensor is enabled.
dbus::MessageWriter(response.get()).AppendBool(true);
std::move(*callback).Run(response.get());
});
// GetAmbientLightSensorEnabled should call its callback indicating that the
// ambient light sensor is enabled.
client_->GetAmbientLightSensorEnabled(
base::BindOnce([](std::optional<bool> is_ambient_light_sensor_enabled) {
EXPECT_TRUE(is_ambient_light_sensor_enabled.value());
}));
// Set up the DBus method kGetAmbientLightSensorEnabledMethod to return that
// the ambient light sensor is not enabled.
EXPECT_CALL(
*proxy_,
DoCallMethod(
HasMember(power_manager::kGetAmbientLightSensorEnabledMethod), _, _))
.WillOnce([](dbus::MethodCall* method_call, int timeout_ms,
dbus::ObjectProxy::ResponseCallback* callback) {
auto response = ::dbus::Response::CreateEmpty();
// Return that the ambient light sensor is not enabled.
dbus::MessageWriter(response.get()).AppendBool(false);
std::move(*callback).Run(response.get());
});
// GetAmbientLightSensorEnabled should call its callback indicating that the
// ambient light sensor is not enabled.
client_->GetAmbientLightSensorEnabled(
base::BindOnce([](std::optional<bool> is_ambient_light_sensor_enabled) {
EXPECT_FALSE(is_ambient_light_sensor_enabled.value());
}));
}
} // namespace chromeos