blob: 42cfa84675f0cc528e7392c8dca6041861467446 [file] [log] [blame]
// Copyright 2019 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 "ui/base/mpris/mpris_service_impl.h"
#include <memory>
#include "base/bind.h"
#include "base/containers/flat_map.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_task_environment.h"
#include "components/dbus/dbus_thread_linux.h"
#include "dbus/message.h"
#include "dbus/mock_bus.h"
#include "dbus/mock_exported_object.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/mpris/mpris_service_observer.h"
using ::testing::_;
using ::testing::Invoke;
using ::testing::Return;
using ::testing::Unused;
using ::testing::WithArg;
namespace mpris {
namespace {
constexpr uint32_t kFakeSerial = 123;
} // anonymous namespace
class MockMprisServiceObserver : public MprisServiceObserver {
public:
MockMprisServiceObserver() = default;
~MockMprisServiceObserver() override = default;
// MprisServiceObserver implementation.
MOCK_METHOD0(OnServiceReady, void());
MOCK_METHOD0(OnNext, void());
MOCK_METHOD0(OnPrevious, void());
MOCK_METHOD0(OnPause, void());
MOCK_METHOD0(OnPlayPause, void());
MOCK_METHOD0(OnStop, void());
MOCK_METHOD0(OnPlay, void());
};
class MprisServiceImplTest : public testing::Test, public MprisServiceObserver {
public:
MprisServiceImplTest()
: scoped_task_environment_(
base::test::ScopedTaskEnvironment::MainThreadType::UI) {}
~MprisServiceImplTest() override = default;
void SetUp() override { StartMprisServiceAndWaitForReady(); }
void AddObserver(MockMprisServiceObserver* observer) {
service_->AddObserver(observer);
}
void CallMediaPlayer2PlayerMethodAndBlock(const std::string& method_name) {
EXPECT_TRUE(player_interface_exported_methods_.contains(method_name));
response_wait_loop_ = std::make_unique<base::RunLoop>();
// We need to supply a serial or the test will crash.
dbus::MethodCall method_call(kMprisAPIPlayerInterfaceName, method_name);
method_call.SetSerial(kFakeSerial);
// Call the method and await a response.
player_interface_exported_methods_[method_name].Run(
&method_call, base::BindRepeating(&MprisServiceImplTest::OnResponse,
base::Unretained(this)));
response_wait_loop_->Run();
}
MprisService* GetService() { return service_.get(); }
dbus::MockExportedObject* GetExportedObject() {
return mock_exported_object_.get();
}
private:
void StartMprisServiceAndWaitForReady() {
service_wait_loop_ = std::make_unique<base::RunLoop>();
service_ = std::make_unique<mpris::MprisServiceImpl>();
SetUpMocks();
service_->SetBusForTesting(mock_bus_);
service_->AddObserver(this);
service_->StartService();
service_wait_loop_->Run();
}
// Sets up the mock Bus and ExportedObject. The ExportedObject will store the
// org.mpris.MediaPlayer2.Player exported methods in the
// |player_interface_exported_methods_| map so we can call them for testing.
void SetUpMocks() {
dbus::Bus::Options options;
options.bus_type = dbus::Bus::SESSION;
options.connection_type = dbus::Bus::PRIVATE;
options.dbus_task_runner = dbus_thread_linux::GetTaskRunner();
mock_bus_ = base::MakeRefCounted<dbus::MockBus>(options);
mock_exported_object_ = base::MakeRefCounted<dbus::MockExportedObject>(
mock_bus_.get(), dbus::ObjectPath(kMprisAPIObjectPath));
EXPECT_CALL(*mock_bus_,
GetExportedObject(dbus::ObjectPath(kMprisAPIObjectPath)))
.WillOnce(Return(mock_exported_object_.get()));
EXPECT_CALL(*mock_bus_, RequestOwnership(service_->GetServiceName(), _, _))
.WillOnce(Invoke(this, &MprisServiceImplTest::OnOwnership));
// The service must call ShutdownAndBlock in order to properly clean up the
// DBus service.
EXPECT_CALL(*mock_bus_, ShutdownAndBlock());
EXPECT_CALL(*mock_exported_object_, ExportMethod(_, _, _, _))
.WillRepeatedly(Invoke(this, &MprisServiceImplTest::OnExported));
}
// Tell the service that ownership was successful.
void OnOwnership(const std::string& service_name,
Unused,
dbus::Bus::OnOwnershipCallback callback) {
callback.Run(service_name, true);
}
// Store the exported method if necessary and tell the service that the export
// was successful.
void OnExported(const std::string& interface_name,
const std::string& method_name,
dbus::ExportedObject::MethodCallCallback exported_method,
dbus::ExportedObject::OnExportedCallback callback) {
if (interface_name == kMprisAPIPlayerInterfaceName)
player_interface_exported_methods_[method_name] = exported_method;
callback.Run(interface_name, method_name, true);
}
void OnResponse(std::unique_ptr<dbus::Response> response) {
// A response of nullptr means an error has occurred.
EXPECT_NE(nullptr, response.get());
if (response_wait_loop_)
response_wait_loop_->Quit();
}
// mpris::MprisServiceObserver implementation.
void OnServiceReady() override {
if (service_wait_loop_)
service_wait_loop_->Quit();
}
base::test::ScopedTaskEnvironment scoped_task_environment_;
std::unique_ptr<base::RunLoop> service_wait_loop_;
std::unique_ptr<base::RunLoop> response_wait_loop_;
std::unique_ptr<MprisServiceImpl> service_;
scoped_refptr<dbus::MockBus> mock_bus_;
scoped_refptr<dbus::MockExportedObject> mock_exported_object_;
base::flat_map<std::string, dbus::ExportedObject::MethodCallCallback>
player_interface_exported_methods_;
DISALLOW_COPY_AND_ASSIGN(MprisServiceImplTest);
};
TEST_F(MprisServiceImplTest, ObserverNotifiedOfServiceReadyWhenAdded) {
MockMprisServiceObserver observer;
EXPECT_CALL(observer, OnServiceReady());
AddObserver(&observer);
}
TEST_F(MprisServiceImplTest, ObserverNotifiedOfNextCalls) {
MockMprisServiceObserver observer;
EXPECT_CALL(observer, OnNext());
AddObserver(&observer);
CallMediaPlayer2PlayerMethodAndBlock("Next");
}
TEST_F(MprisServiceImplTest, ObserverNotifiedOfPreviousCalls) {
MockMprisServiceObserver observer;
EXPECT_CALL(observer, OnPrevious());
AddObserver(&observer);
CallMediaPlayer2PlayerMethodAndBlock("Previous");
}
TEST_F(MprisServiceImplTest, ObserverNotifiedOfPauseCalls) {
MockMprisServiceObserver observer;
EXPECT_CALL(observer, OnPause());
AddObserver(&observer);
CallMediaPlayer2PlayerMethodAndBlock("Pause");
}
TEST_F(MprisServiceImplTest, ObserverNotifiedOfPlayPauseCalls) {
MockMprisServiceObserver observer;
EXPECT_CALL(observer, OnPlayPause());
AddObserver(&observer);
CallMediaPlayer2PlayerMethodAndBlock("PlayPause");
}
TEST_F(MprisServiceImplTest, ObserverNotifiedOfStopCalls) {
MockMprisServiceObserver observer;
EXPECT_CALL(observer, OnStop());
AddObserver(&observer);
CallMediaPlayer2PlayerMethodAndBlock("Stop");
}
TEST_F(MprisServiceImplTest, ObserverNotifiedOfPlayCalls) {
MockMprisServiceObserver observer;
EXPECT_CALL(observer, OnPlay());
AddObserver(&observer);
CallMediaPlayer2PlayerMethodAndBlock("Play");
}
TEST_F(MprisServiceImplTest, ChangingPropertyEmitsSignal) {
base::RunLoop wait_for_signal;
// The returned signal should give the changed property.
EXPECT_CALL(*GetExportedObject(), SendSignal(_))
.WillOnce(WithArg<0>([&wait_for_signal](dbus::Signal* signal) {
EXPECT_NE(nullptr, signal);
dbus::MessageReader reader(signal);
std::string interface_name;
ASSERT_TRUE(reader.PopString(&interface_name));
EXPECT_EQ(kMprisAPIPlayerInterfaceName, interface_name);
dbus::MessageReader changed_properties_reader(nullptr);
ASSERT_TRUE(reader.PopArray(&changed_properties_reader));
dbus::MessageReader dict_entry_reader(nullptr);
ASSERT_TRUE(changed_properties_reader.PopDictEntry(&dict_entry_reader));
// The changed property name should be "CanPlay".
std::string property_name;
ASSERT_TRUE(dict_entry_reader.PopString(&property_name));
EXPECT_EQ("CanPlay", property_name);
// The new value should be true.
bool value;
ASSERT_TRUE(dict_entry_reader.PopVariantOfBool(&value));
EXPECT_EQ(true, value);
// CanPlay should be the only entry.
EXPECT_FALSE(changed_properties_reader.HasMoreData());
wait_for_signal.Quit();
}));
// CanPlay is initialized as false, so setting it to true should emit an
// org.freedesktop.DBus.Properties.PropertiesChanged signal.
GetService()->SetCanPlay(true);
wait_for_signal.Run();
// Setting it to true again should not re-signal.
GetService()->SetCanPlay(true);
}
TEST_F(MprisServiceImplTest, ChangingMetadataEmitsSignal) {
base::RunLoop wait_for_signal;
// The returned signal should give the changed property.
EXPECT_CALL(*GetExportedObject(), SendSignal(_))
.WillOnce(WithArg<0>([&wait_for_signal](dbus::Signal* signal) {
ASSERT_NE(nullptr, signal);
dbus::MessageReader reader(signal);
std::string interface_name;
ASSERT_TRUE(reader.PopString(&interface_name));
EXPECT_EQ(kMprisAPIPlayerInterfaceName, interface_name);
dbus::MessageReader changed_properties_reader(nullptr);
ASSERT_TRUE(reader.PopArray(&changed_properties_reader));
dbus::MessageReader dict_entry_reader(nullptr);
ASSERT_TRUE(changed_properties_reader.PopDictEntry(&dict_entry_reader));
// The changed property name should be "Metadata".
std::string property_name;
ASSERT_TRUE(dict_entry_reader.PopString(&property_name));
EXPECT_EQ("Metadata", property_name);
// The new metadata should have the new title.
dbus::MessageReader metadata_variant_reader(nullptr);
ASSERT_TRUE(dict_entry_reader.PopVariant(&metadata_variant_reader));
dbus::MessageReader metadata_reader(nullptr);
ASSERT_TRUE(metadata_variant_reader.PopArray(&metadata_reader));
dbus::MessageReader metadata_entry_reader(nullptr);
ASSERT_TRUE(metadata_reader.PopDictEntry(&metadata_entry_reader));
std::string metadata_property_name;
ASSERT_TRUE(metadata_entry_reader.PopString(&metadata_property_name));
EXPECT_EQ("xesam:title", metadata_property_name);
std::string value;
ASSERT_TRUE(metadata_entry_reader.PopVariantOfString(&value));
EXPECT_EQ("Foo", value);
// Metadata should be the only changed property.
EXPECT_FALSE(changed_properties_reader.HasMoreData());
wait_for_signal.Quit();
}));
// Setting the title should emit an
// org.freedesktop.DBus.Properties.PropertiesChanged signal.
GetService()->SetTitle(base::ASCIIToUTF16("Foo"));
wait_for_signal.Run();
// Setting the title to the same value as before should not emit a new signal.
GetService()->SetTitle(base::ASCIIToUTF16("Foo"));
}
} // namespace mpris