Chromium Code Reviews| Index: chromeos/dbus/power_manager_client_unittest.cc | 
| diff --git a/chromeos/dbus/power_manager_client_unittest.cc b/chromeos/dbus/power_manager_client_unittest.cc | 
| new file mode 100644 | 
| index 0000000000000000000000000000000000000000..c2555975882fabfc207177c60e78dcdd3eca84a2 | 
| --- /dev/null | 
| +++ b/chromeos/dbus/power_manager_client_unittest.cc | 
| @@ -0,0 +1,374 @@ | 
| +// Copyright 2016 The Chromium 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 "chromeos/dbus/power_manager_client.h" | 
| + | 
| +#include <map> | 
| +#include <string> | 
| + | 
| +#include "base/bind.h" | 
| +#include "base/macros.h" | 
| +#include "base/memory/ref_counted.h" | 
| +#include "base/memory/weak_ptr.h" | 
| +#include "base/message_loop/message_loop.h" | 
| +#include "base/run_loop.h" | 
| +#include "chromeos/dbus/power_manager/suspend.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/service_constants.h" | 
| + | 
| +using ::testing::_; | 
| +using ::testing::Return; | 
| + | 
| +namespace chromeos { | 
| + | 
| +namespace { | 
| + | 
| +// Shorthand for a few commonly-used constants. | 
| +const char* kInterface = power_manager::kPowerManagerInterface; | 
| +const char* kSuspendImminent = power_manager::kSuspendImminentSignal; | 
| +const char* kHandleSuspendReadiness = | 
| + power_manager::kHandleSuspendReadinessMethod; | 
| + | 
| +// 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; | 
| +} | 
| + | 
| +// 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) { | 
| + 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() override { client_->RemoveObserver(this); } | 
| + | 
| + int num_suspend_imminent() const { return num_suspend_imminent_; } | 
| + int num_suspend_done() const { return num_suspend_done_; } | 
| + | 
| + void set_take_suspend_readiness_callback(bool take_callback) { | 
| + take_suspend_readiness_callback_ = take_callback; | 
| + } | 
| + | 
| + // Runs |suspend_readiness_callback_|. | 
| + bool RunSuspendReadinessCallback() WARN_UNUSED_RESULT { | 
| + if (suspend_readiness_callback_.is_null()) | 
| + return false; | 
| + | 
| + auto cb = suspend_readiness_callback_; | 
| + suspend_readiness_callback_.Reset(); | 
| + cb.Run(); | 
| + return true; | 
| + } | 
| + | 
| + // PowerManagerClient::Observer: | 
| + void SuspendImminent() override { | 
| + num_suspend_imminent_++; | 
| + if (take_suspend_readiness_callback_) | 
| + suspend_readiness_callback_ = client_->GetSuspendReadinessCallback(); | 
| + } | 
| + void SuspendDone(const base::TimeDelta& sleep_duration) override { | 
| + num_suspend_done_++; | 
| + } | 
| + | 
| + private: | 
| + PowerManagerClient* client_; // Not owned. | 
| + | 
| + // Number of times SuspendImminent() and SuspendDone() have been called. | 
| + int num_suspend_imminent_ = 0; | 
| + int num_suspend_done_ = 0; | 
| + | 
| + // Should SuspendImminent() call |client_|'s GetSuspendReadinessCallback() | 
| + // method? | 
| + bool take_suspend_readiness_callback_ = false; | 
| + | 
| + // Callback returned by |client_|'s GetSuspendReadinessCallback() method. | 
| + base::Closure suspend_readiness_callback_; | 
| + | 
| + DISALLOW_COPY_AND_ASSIGN(TestObserver); | 
| +}; | 
| + | 
| +// Stub implementation of PowerManagerClient::RenderProcessManagerDelegate. | 
| +class TestDelegate : public PowerManagerClient::RenderProcessManagerDelegate { | 
| + public: | 
| + TestDelegate(PowerManagerClient* client) : weak_ptr_factory_(this) { | 
| 
 
Chirantan Ekbote
2016/09/14 22:31:30
explicit?
 
Daniel Erat
2016/09/14 22:42:13
whoops, yes. thanks.
 
 | 
| + client->SetRenderProcessManagerDelegate(weak_ptr_factory_.GetWeakPtr()); | 
| + } | 
| + ~TestDelegate() override {} | 
| + | 
| + 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_; | 
| + | 
| + DISALLOW_COPY_AND_ASSIGN(TestDelegate); | 
| +}; | 
| + | 
| +} // namespace | 
| + | 
| +class PowerManagerClientTest : public testing::Test { | 
| + public: | 
| + PowerManagerClientTest() {} | 
| + ~PowerManagerClientTest() override {} | 
| + | 
| + 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_.get(), | 
| + GetObjectProxy( | 
| + power_manager::kPowerManagerServiceName, | 
| + dbus::ObjectPath(power_manager::kPowerManagerServicePath))) | 
| + .WillRepeatedly(Return(proxy_.get())); | 
| + | 
| + // Save |client_|'s signal callbacks. | 
| + EXPECT_CALL(*proxy_.get(), ConnectToSignal(kInterface, _, _, _)) | 
| + .WillRepeatedly(Invoke(this, &PowerManagerClientTest::ConnectToSignal)); | 
| + | 
| + // |client_|'s Init() method should register regular and dark suspend | 
| + // delays. | 
| + EXPECT_CALL( | 
| + *proxy_.get(), | 
| + CallMethod(HasMember(power_manager::kRegisterSuspendDelayMethod), _, _)) | 
| + .WillRepeatedly( | 
| + Invoke(this, &PowerManagerClientTest::RegisterSuspendDelay)); | 
| + EXPECT_CALL( | 
| + *proxy_.get(), | 
| + CallMethod(HasMember(power_manager::kRegisterDarkSuspendDelayMethod), _, | 
| + _)) | 
| + .WillRepeatedly( | 
| + Invoke(this, &PowerManagerClientTest::RegisterSuspendDelay)); | 
| + | 
| + client_.reset(PowerManagerClient::Create(REAL_DBUS_CLIENT_IMPLEMENTATION)); | 
| + client_->Init(bus_.get()); | 
| + | 
| + // Execute callbacks posted by Init(). | 
| + base::RunLoop().RunUntilIdle(); | 
| + } | 
| + | 
| + void TearDown() override { client_.reset(); } | 
| + | 
| + 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, | 
| 
 
Chirantan Ekbote
2016/09/14 22:31:30
So far all callers of this function pass in kSuspe
 
Daniel Erat
2016/09/14 22:42:13
yep, exactly. i'll mention dark suspend in the TOD
 
 | 
| + int suspend_id) { | 
| + power_manager::SuspendImminent proto; | 
| + proto.set_suspend_id(suspend_id); | 
| + 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(), | 
| + CallMethod(IsSuspendReadiness(method_name, suspend_id, delay_id), _, | 
| + _)); | 
| + } | 
| + | 
| + // Arbitrary delay IDs returned to |client_|. | 
| + static const int kSuspendDelayId = 100; | 
| + static const int kDarkSuspendDelayId = 200; | 
| + | 
| + base::MessageLoop message_loop_; | 
| + | 
| + // Mock bus and proxy for simulating calls to powerd. | 
| + scoped_refptr<dbus::MockBus> bus_; | 
| + scoped_refptr<dbus::MockObjectProxy> proxy_; | 
| + | 
| + std::unique_ptr<PowerManagerClient> client_; | 
| + | 
| + // Maps from powerd signal name to the corresponding callback provided by | 
| + // |client_|. | 
| + std::map<std::string, dbus::ObjectProxy::SignalCallback> signal_callbacks_; | 
| + | 
| + 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; | 
| + | 
| + message_loop_.task_runner()->PostTask( | 
| + FROM_HERE, base::Bind(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)); | 
| + | 
| + message_loop_.task_runner()->PostTask( | 
| + FROM_HERE, | 
| + base::Bind(&RunResponseCallback, callback, base::Passed(&response))); | 
| + } | 
| + | 
| + DISALLOW_COPY_AND_ASSIGN(PowerManagerClientTest); | 
| +}; | 
| + | 
| +// Suspend readiness should be reported immediately when there are no observers. | 
| +TEST_F(PowerManagerClientTest, ReportSuspendReadinessWithoutObservers) { | 
| + const int kSuspendId = 1; | 
| + ExpectSuspendReadiness(kHandleSuspendReadiness, kSuspendId, kSuspendDelayId); | 
| + EmitSuspendImminentSignal(kSuspendImminent, kSuspendId); | 
| + EmitSuspendDoneSignal(kSuspendId); | 
| +} | 
| + | 
| +// Observers should be notified when suspend is imminent and done. Readiness | 
| +// should be reported synchronously when GetSuspendReadinessCallback() hasn't | 
| +// been called. | 
| +TEST_F(PowerManagerClientTest, ReportSuspendReadinessWithoutCallbacks) { | 
| + TestObserver observer_1(client_.get()); | 
| + TestObserver observer_2(client_.get()); | 
| + | 
| + 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()); | 
| +} | 
| + | 
| +// When observers call GetSuspendReadinessCallback() from their | 
| +// SuspendImminent() methods, the HandleSuspendReadiness method call should be | 
| +// deferred until all callbacks are run. | 
| +TEST_F(PowerManagerClientTest, ReportSuspendReadinessWithCallbacks) { | 
| + TestObserver observer_1(client_.get()); | 
| + observer_1.set_take_suspend_readiness_callback(true); | 
| + TestObserver observer_2(client_.get()); | 
| + observer_2.set_take_suspend_readiness_callback(true); | 
| + TestObserver observer_3(client_.get()); | 
| + | 
| + const int kSuspendId = 1; | 
| + EmitSuspendImminentSignal(kSuspendImminent, kSuspendId); | 
| + EXPECT_TRUE(observer_1.RunSuspendReadinessCallback()); | 
| + ExpectSuspendReadiness(kHandleSuspendReadiness, kSuspendId, kSuspendDelayId); | 
| + EXPECT_TRUE(observer_2.RunSuspendReadinessCallback()); | 
| + EmitSuspendDoneSignal(kSuspendId); | 
| +} | 
| + | 
| +// The RenderProcessManagerDelegate should be notified that suspend is imminent | 
| +// only after observers have reported readiness. | 
| +TEST_F(PowerManagerClientTest, NotifyRenderProcessManagerDelegate) { | 
| + TestDelegate delegate(client_.get()); | 
| + TestObserver observer(client_.get()); | 
| + observer.set_take_suspend_readiness_callback(true); | 
| + | 
| + const int kSuspendId = 1; | 
| + EmitSuspendImminentSignal(kSuspendImminent, kSuspendId); | 
| + EXPECT_EQ(0, delegate.num_suspend_imminent()); | 
| + EXPECT_EQ(0, delegate.num_suspend_done()); | 
| + | 
| + ExpectSuspendReadiness(kHandleSuspendReadiness, kSuspendId, kSuspendDelayId); | 
| + EXPECT_TRUE(observer.RunSuspendReadinessCallback()); | 
| + EXPECT_EQ(1, delegate.num_suspend_imminent()); | 
| + EXPECT_EQ(0, delegate.num_suspend_done()); | 
| + | 
| + EmitSuspendDoneSignal(kSuspendId); | 
| + EXPECT_EQ(1, delegate.num_suspend_imminent()); | 
| + EXPECT_EQ(1, delegate.num_suspend_done()); | 
| +} | 
| + | 
| +// TODO(derat): Add tests around SuspendDone being received while readiness | 
| +// callbacks are still outstanding (see http://crbug.com/646912). | 
| + | 
| +} // namespace chromeos |