2
* Copyright (C) 2015 Canonical, Ltd.
4
* This program is free software: you can redistribute it and/or modify it
5
* under the terms of the GNU General Public License version 3, as published
6
* by the Free Software Foundation.
8
* This program is distributed in the hope that it will be useful, but
9
* WITHOUT ANY WARRANTY; without even the implied warranties of
10
* MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
11
* PURPOSE. See the GNU General Public License for more details.
13
* You should have received a copy of the GNU General Public License along
14
* with this program. If not, see <http://www.gnu.org/licenses/>.
16
* Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
19
#include "indicator-sound-test-base.h"
21
#include "dbus_menus_interface.h"
22
#include "dbus_properties_interface.h"
23
#include "dbus_accounts_interface.h"
24
#include "dbus_accountssound_interface.h"
25
#include "dbus_notifications_interface.h"
26
#include "dbus-types.h"
33
#include "utils/dbus-pulse-volume.h"
35
using namespace QtDBusTest;
36
using namespace QtDBusMock;
38
using namespace testing;
39
namespace mh = unity::gmenuharness;
43
const int MAX_TIME_WAITING_FOR_NOTIFICATIONS = 2000;
46
IndicatorSoundTestBase::IndicatorSoundTestBase() :
47
dbusMock(dbusTestRunner)
51
IndicatorSoundTestBase::~IndicatorSoundTestBase()
55
void IndicatorSoundTestBase::SetUp()
57
setenv("XDG_DATA_DIRS", XDG_DATA_DIRS, true);
58
setenv("DBUS_SYSTEM_BUS_ADDRESS", dbusTestRunner.systemBus().toStdString().c_str(), true);
59
setenv("DBUS_SESSION_BUS_ADDRESS", dbusTestRunner.sessionBus().toStdString().c_str(), true);
60
dbusMock.registerNotificationDaemon();
62
dbusTestRunner.startServices();
64
auto& notifications = notificationsMockInterface();
65
notifications.AddMethod("org.freedesktop.Notifications",
69
"ret = ['actions', 'body', 'body-markup', 'icon-static', 'image/svg+xml', 'x-canonical-private-synchronous', 'x-canonical-append', 'x-canonical-private-icon-only', 'x-canonical-truncation', 'private-synchronous', 'append', 'private-icon-only', 'truncation']"
73
while (!dbusTestRunner.sessionConnection().interface()->isServiceRegistered("org.freedesktop.Notifications") && waitedTime < MAX_TIME_WAITING_FOR_NOTIFICATIONS)
75
std::this_thread::sleep_for(std::chrono::milliseconds(10));
80
void IndicatorSoundTestBase::TearDown()
82
unsetenv("XDG_DATA_DIRS");
83
unsetenv("PULSE_SERVER");
84
unsetenv("DBUS_SYSTEM_BUS_ADDRESS");
87
void gvariant_deleter(GVariant* varptr)
89
if (varptr != nullptr)
91
g_variant_unref(varptr);
95
std::shared_ptr<GVariant> IndicatorSoundTestBase::volume_variant(double volume)
97
GVariantBuilder builder;
99
g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
100
g_variant_builder_add(&builder,
103
g_variant_new_string("_Sound"));
105
g_variant_builder_add(&builder,
108
g_variant_new_string("_Sound"));
110
auto icon = g_themed_icon_new("icon");
111
g_variant_builder_add(&builder,
114
g_icon_serialize(icon));
116
g_variant_builder_add(&builder,
119
g_variant_new_boolean(true));
120
return shared_ptr<GVariant>(g_variant_builder_end(&builder), &gvariant_deleter);
123
bool IndicatorSoundTestBase::setStreamRestoreVolume(QString const &role, double volume)
126
setVolume.start(VOLUME_SET_BIN, QStringList()
128
<< QString("%1").arg(volume));
129
if (!setVolume.waitForStarted())
132
if (!setVolume.waitForFinished())
135
return setVolume.exitCode() == 0;
138
bool IndicatorSoundTestBase::setSinkVolume(double volume)
140
QString volume_percentage = QString("%1\%").arg(volume*100);
142
setVolume.start("pactl", QStringList()
147
<< volume_percentage);
148
if (!setVolume.waitForStarted())
151
if (!setVolume.waitForFinished())
154
return setVolume.exitCode() == 0;
157
bool IndicatorSoundTestBase::clearGSettingsPlayers()
159
QProcess clearPlayers;
161
clearPlayers.start("gsettings", QStringList()
163
<< "com.canonical.indicator.sound"
164
<< "interested-media-players"
166
if (!clearPlayers.waitForStarted())
169
if (!clearPlayers.waitForFinished())
172
return clearPlayers.exitCode() == 0;
175
bool IndicatorSoundTestBase::startTestMprisPlayer(QString const& playerName)
177
testPlayer1.terminate();
178
testPlayer1.start(MEDIA_PLAYER_MPRIS_BIN, QStringList()
180
if (!testPlayer1.waitForStarted())
187
bool IndicatorSoundTestBase::setTestMprisPlayerProperty(QString const &testPlayer, QString const &property, bool value)
189
QProcess setProperty;
191
strValue = value ? "true" : "false";
193
setProperty.start(MEDIA_PLAYER_MPRIS_UPDATE_BIN, QStringList()
197
if (!setProperty.waitForStarted())
200
if (!setProperty.waitForFinished())
203
return setProperty.exitCode() == 0;
206
bool IndicatorSoundTestBase::startTestSound(QString const &role)
208
testSoundProcess.terminate();
209
testSoundProcess.start("paplay", QStringList()
213
<< QString("--property=media.role=%1").arg(role));
215
if (!testSoundProcess.waitForStarted())
221
void IndicatorSoundTestBase::stopTestSound()
223
testSoundProcess.terminate();
226
void IndicatorSoundTestBase::startPulseDesktop(DevicePortType speakerPort, DevicePortType headphonesPort)
231
new QProcessDBusService(DBusTypes::DBUS_PULSE,
232
QDBusConnection::SessionBus,
234
QStringList() << "--start"
236
<< "--disable-shm=true"
237
<< "--daemonize=false"
238
<< "--use-pid-file=false"
240
<< "--exit-idle-time=-1"
242
<< QString("--load=module-null-sink sink_name=indicator_sound_test_speaker sink_properties=device.bus=%1").arg(getDevicePortString(speakerPort))
243
<< QString("--load=module-null-sink sink_name=indicator_sound_test_headphones sink_properties=device.bus=%1").arg(getDevicePortString(headphonesPort))
244
<< "--log-target=file:/tmp/pulse-daemon.log"
245
<< "--load=module-dbus-protocol"
246
<< "--load=module-native-protocol-tcp auth-ip-acl=127.0.0.1"
248
pulseaudio->start(dbusTestRunner.sessionConnection());
250
catch (exception const& e)
252
cout << "pulseaudio(): " << e.what() << endl;
257
void IndicatorSoundTestBase::startPulsePhone(DevicePortType speakerPort, DevicePortType headphonesPort)
262
new QProcessDBusService(DBusTypes::DBUS_PULSE,
263
QDBusConnection::SessionBus,
265
QStringList() << "--start"
267
<< "--disable-shm=true"
268
<< "--daemonize=false"
269
<< "--use-pid-file=false"
271
<< "--exit-idle-time=-1"
273
<< QString("--load=module-null-sink sink_name=indicator_sound_test_speaker sink_properties=device.bus=%1").arg(getDevicePortString(speakerPort))
274
<< QString("--load=module-null-sink sink_name=indicator_sound_test_headphones sink_properties=device.bus=%1").arg(getDevicePortString(headphonesPort))
275
<< "--log-target=file:/tmp/pulse-daemon.log"
276
<< QString("--load=module-stream-restore restore_device=false restore_muted=false fallback_table=%1").arg(STREAM_RESTORE_TABLE)
277
<< "--load=module-dbus-protocol"
278
<< "--load=module-native-protocol-tcp auth-ip-acl=127.0.0.1"
280
pulseaudio->start(dbusTestRunner.sessionConnection());
282
catch (exception const& e)
284
cout << "pulseaudio(): " << e.what() << endl;
289
void IndicatorSoundTestBase::startAccountsService()
293
accountsService.reset(
294
new QProcessDBusService(DBusTypes::ACCOUNTS_SERVICE,
295
QDBusConnection::SystemBus,
296
ACCOUNTS_SERVICE_BIN,
298
accountsService->start(dbusTestRunner.systemConnection());
300
initializeAccountsInterface();
302
catch (exception const& e)
304
cout << "accountsService(): " << e.what() << endl;
309
void IndicatorSoundTestBase::startIndicator()
313
setenv("PULSE_SERVER", "127.0.0.1", true);
315
new QProcessDBusService(DBusTypes::DBUS_NAME,
316
QDBusConnection::SessionBus,
319
indicator->start(dbusTestRunner.sessionConnection());
321
catch (exception const& e)
323
cout << "startIndicator(): " << e.what() << endl;
328
mh::MenuMatcher::Parameters IndicatorSoundTestBase::desktopParameters()
330
return mh::MenuMatcher::Parameters(
331
"com.canonical.indicator.sound",
332
{ { "indicator", "/com/canonical/indicator/sound" } },
333
"/com/canonical/indicator/sound/desktop");
336
mh::MenuMatcher::Parameters IndicatorSoundTestBase::phoneParameters()
338
return mh::MenuMatcher::Parameters(
339
"com.canonical.indicator.sound",
340
{ { "indicator", "/com/canonical/indicator/sound" } },
341
"/com/canonical/indicator/sound/phone");
344
unity::gmenuharness::MenuItemMatcher IndicatorSoundTestBase::volumeSlider(double volume, QString const &label)
346
return mh::MenuItemMatcher().radio()
347
.label(label.toStdString())
349
.int32_attribute("target", 0)
350
.double_attribute("min-value", 0.0)
351
.double_attribute("max-value", 1.0)
352
.double_attribute("step", 0.01)
353
.string_attribute("x-canonical-type", "com.canonical.unity.slider")
354
.themed_icon("max-icon", {"audio-volume-high-panel", "audio-volume-high", "audio-volume", "audio"})
355
.themed_icon("min-icon", {"audio-volume-low-zero-panel", "audio-volume-low-zero", "audio-volume-low", "audio-volume", "audio"})
356
.pass_through_double_attribute("action", volume);
359
unity::gmenuharness::MenuItemMatcher IndicatorSoundTestBase::silentModeSwitch(bool toggled)
361
return mh::MenuItemMatcher::checkbox()
362
.label("Silent Mode")
363
.action("indicator.silent-mode")
367
bool IndicatorSoundTestBase::waitMenuChange()
369
if (signal_spy_menu_changed_)
371
return signal_spy_menu_changed_->wait();
376
bool IndicatorSoundTestBase::initializeMenuChangedSignal()
378
if (!menu_interface_)
380
menu_interface_.reset(new MenusInterface("com.canonical.indicator.sound",
381
"/com/canonical/indicator/sound",
382
dbusTestRunner.sessionConnection(), 0));
386
qDebug() << "Waiting for signal";
387
signal_spy_menu_changed_.reset(new QSignalSpy(menu_interface_.get(), &MenusInterface::Changed));
389
if (!menu_interface_ || !signal_spy_menu_changed_)
396
bool IndicatorSoundTestBase::waitVolumeChangedInIndicator()
398
if (signal_spy_volume_changed_)
400
return signal_spy_volume_changed_->wait();
405
void IndicatorSoundTestBase::initializeAccountsInterface()
407
auto username = qgetenv("USER");
410
main_accounts_interface_.reset(new AccountsInterface("org.freedesktop.Accounts",
411
"/org/freedesktop/Accounts",
412
dbusTestRunner.systemConnection(), 0));
414
QDBusReply<QDBusObjectPath> userResp = main_accounts_interface_->call(QLatin1String("FindUserByName"),
415
QLatin1String(username));
417
if (!userResp.isValid())
419
qWarning() << "SetVolume::initializeAccountsInterface(): D-Bus error: " << userResp.error().message();
422
auto userPath = userResp.value().path();
425
std::unique_ptr<AccountsSoundInterface> soundInterface(new AccountsSoundInterface("org.freedesktop.Accounts",
427
dbusTestRunner.systemConnection(), 0));
429
accounts_interface_.reset(new DBusPropertiesInterface("org.freedesktop.Accounts",
431
dbusTestRunner.systemConnection(), 0));
432
if (!accounts_interface_->isValid())
434
qWarning() << "SetVolume::initializeAccountsInterface(): D-Bus error: " << accounts_interface_->lastError().message();
436
signal_spy_volume_changed_.reset(new QSignalSpy(accounts_interface_.get(),&DBusPropertiesInterface::PropertiesChanged));
441
OrgFreedesktopDBusMockInterface& IndicatorSoundTestBase::notificationsMockInterface()
443
return dbusMock.mockInterface("org.freedesktop.Notifications",
444
"/org/freedesktop/Notifications",
445
"org.freedesktop.Notifications",
446
QDBusConnection::SessionBus);
449
bool IndicatorSoundTestBase::setActionValue(const QString & action, QVariant value)
451
QDBusInterface actionsInterface(DBusTypes::DBUS_NAME,
452
DBusTypes::MAIN_SERVICE_PATH,
453
DBusTypes::ACTIONS_INTERFACE,
454
dbusTestRunner.sessionConnection());
456
QDBusVariant dbusVar(value);
457
auto resp = actionsInterface.call("SetState",
459
QVariant::fromValue(dbusVar),
460
QVariant::fromValue(QVariantMap()));
462
if (resp.type() == QDBusMessage::ErrorMessage)
464
qCritical() << "IndicatorSoundTestBase::setActionValue(): Failed to set value for action "
467
<< resp.errorMessage();
476
bool IndicatorSoundTestBase::pressNotificationButton(int id, const QString & button)
478
OrgFreedesktopDBusMockInterface actionsInterface("org.freedesktop.Notifications",
479
"/org/freedesktop/Notifications",
480
dbusTestRunner.sessionConnection());
482
actionsInterface.EmitSignal(
483
"org.freedesktop.Notifications",
484
"ActionInvoked", "us", QVariantList() << id << button);
489
bool IndicatorSoundTestBase::qDBusArgumentToMap(QVariant const& variant, QVariantMap& map)
491
if (variant.canConvert<QDBusArgument>())
493
QDBusArgument value(variant.value<QDBusArgument>());
494
if (value.currentType() == QDBusArgument::MapType)
503
void IndicatorSoundTestBase::checkVolumeNotification(double volume, QString const& label, bool isLoud, QVariantList call)
508
icon = "audio-volume-muted";
510
else if (volume <= 0.3)
512
icon = "audio-volume-low";
514
else if (volume <= 0.7)
516
icon = "audio-volume-medium";
520
icon = "audio-volume-high";
523
ASSERT_NE(call.size(), 0);
524
EXPECT_EQ("Notify", call.at(0));
526
QVariantList const& args(call.at(1).toList());
527
ASSERT_EQ(8, args.size());
528
EXPECT_EQ("indicator-sound", args.at(0));
529
EXPECT_EQ(icon, args.at(2));
530
EXPECT_EQ("Volume", args.at(3));
531
EXPECT_EQ(label, args.at(4));
532
EXPECT_EQ(QStringList(), args.at(5));
535
ASSERT_TRUE(qDBusArgumentToMap(args.at(6), hints));
536
ASSERT_TRUE(hints.contains("value"));
537
ASSERT_TRUE(hints.contains("x-canonical-non-shaped-icon"));
538
ASSERT_TRUE(hints.contains("x-canonical-value-bar-tint"));
539
ASSERT_TRUE(hints.contains("x-canonical-private-synchronous"));
541
EXPECT_EQ(volume*100, hints["value"]);
542
EXPECT_EQ(true, hints["x-canonical-non-shaped-icon"]);
543
EXPECT_EQ(isLoud, hints["x-canonical-value-bar-tint"]);
544
EXPECT_EQ(true, hints["x-canonical-private-synchronous"]);
547
void IndicatorSoundTestBase::checkHighVolumeNotification(QVariantList call)
549
ASSERT_NE(call.size(), 0);
550
EXPECT_EQ("Notify", call.at(0));
552
QVariantList const& args(call.at(1).toList());
553
ASSERT_EQ(8, args.size());
554
EXPECT_EQ("indicator-sound", args.at(0));
555
EXPECT_EQ("Volume", args.at(3));
558
void IndicatorSoundTestBase::checkCloseNotification(int id, QVariantList call)
560
EXPECT_EQ("CloseNotification", call.at(0));
561
QVariantList const& args(call.at(1).toList());
562
ASSERT_EQ(1, args.size());
565
void IndicatorSoundTestBase::checkNotificationWithNoArgs(QString const& method, QVariantList call)
567
EXPECT_EQ(method, call.at(0));
568
QVariantList const& args(call.at(1).toList());
569
ASSERT_EQ(0, args.size());
572
int IndicatorSoundTestBase::getNotificationID(QVariantList call)
574
if (call.size() == 0)
578
QVariantList const& args(call.at(1).toList());
579
if (args.size() != 8)
583
if (args.at(0) != "indicator-sound")
589
int id = args.at(1).toInt(&isInt);
597
bool IndicatorSoundTestBase::activateHeadphones(bool headphonesActive)
599
QProcess pacltProcess;
601
QString defaultSinkName = "indicator_sound_test_speaker";
602
QString suspendedSinkName = "indicator_sound_test_headphones";
603
if (headphonesActive)
605
defaultSinkName = "indicator_sound_test_headphones";
606
suspendedSinkName = "indicator_sound_test_speaker";
609
pacltProcess.start("pactl", QStringList() << "-s"
611
<< "set-default-sink"
613
if (!pacltProcess.waitForStarted())
616
if (!pacltProcess.waitForFinished())
619
pacltProcess.start("pactl", QStringList() << "-s"
624
if (!pacltProcess.waitForStarted())
627
if (!pacltProcess.waitForFinished())
630
pacltProcess.start("pactl", QStringList() << "-s"
635
if (!pacltProcess.waitForStarted())
638
if (!pacltProcess.waitForFinished())
641
return pacltProcess.exitCode() == 0;
644
QString IndicatorSoundTestBase::getDevicePortString(DevicePortType port)
651
portString = "wired";
654
portString = "bluetooth";
663
portString = "not_defined";
670
void IndicatorSoundTestBase::checkPortDevicesLabels(DevicePortType speakerPort, DevicePortType headphonesPort)
672
double INITIAL_VOLUME = 0.0;
674
QString speakerString;
675
QString speakerStringMenu;
679
speakerString = "Speakers";
680
speakerStringMenu = "Volume";
683
speakerString = "Bluetooth speaker";
684
speakerStringMenu = "Volume (Bluetooth)";
687
speakerString = "Usb speaker";
688
speakerStringMenu = "Volume (Usb)";
691
speakerString = "HDMI speaker";
692
speakerStringMenu = "Volume (HDMI)";
696
QString headphonesString;
697
QString headphonesStringMenu;
698
switch(headphonesPort)
701
headphonesString = "Headphones";
702
headphonesStringMenu = "Volume (Headphones)";
705
headphonesString = "Bluetooth headphones";
706
headphonesStringMenu = "Volume (Bluetooth headphones)";
709
headphonesString = "Usb headphones";
710
headphonesStringMenu = "Volume (Usb headphones)";
713
headphonesString = "HDMI headphones";
714
headphonesStringMenu = "Volume (HDMI headphones)";
718
QSignalSpy notificationsSpy(¬ificationsMockInterface(),
719
SIGNAL(MethodCalled(const QString &, const QVariantList &)));
721
ASSERT_NO_THROW(startAccountsService());
722
ASSERT_NO_THROW(startPulsePhone(speakerPort, headphonesPort));
724
// initialize volumes in pulseaudio
725
EXPECT_TRUE(setStreamRestoreVolume("alert", INITIAL_VOLUME));
726
EXPECT_TRUE(setStreamRestoreVolume("multimedia", INITIAL_VOLUME));
728
// start now the indicator, so it picks the new volumes
729
ASSERT_NO_THROW(startIndicator());
731
// if the speaker is the normal one it does not emit any notification, as that's
733
// for the rest it notifies the output
734
if (speakerPort != WIRED)
736
WAIT_FOR_SIGNALS(notificationsSpy, 3);
738
// the first time we also have the calls to
739
// GetServerInformation and GetCapabilities
740
checkNotificationWithNoArgs("GetServerInformation", notificationsSpy.at(0));
741
checkNotificationWithNoArgs("GetCapabilities", notificationsSpy.at(1));
742
checkVolumeNotification(INITIAL_VOLUME, speakerString, false, notificationsSpy.at(2));
743
notificationsSpy.clear();
746
notificationsSpy.clear();
747
// activate the headphones
748
EXPECT_TRUE(activateHeadphones(true));
750
if (speakerPort == WIRED)
752
WAIT_FOR_SIGNALS(notificationsSpy, 3);
754
// the first time we also have the calls to
755
// GetServerInformation and GetCapabilities
756
checkNotificationWithNoArgs("GetServerInformation", notificationsSpy.at(0));
757
checkNotificationWithNoArgs("GetCapabilities", notificationsSpy.at(1));
758
checkVolumeNotification(INITIAL_VOLUME, headphonesString, false, notificationsSpy.at(2));
759
notificationsSpy.clear();
763
WAIT_FOR_SIGNALS(notificationsSpy, 2);
764
checkNotificationWithNoArgs("GetCapabilities", notificationsSpy.at(0));
765
checkVolumeNotification(INITIAL_VOLUME, headphonesString, false, notificationsSpy.at(1));
766
notificationsSpy.clear();
769
// check the label in the menu
770
EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
771
.item(mh::MenuItemMatcher()
772
.action("indicator.root")
773
.string_attribute("x-canonical-type", "com.canonical.indicator.root")
774
.string_attribute("x-canonical-scroll-action", "indicator.scroll")
775
.string_attribute("x-canonical-secondary-action", "indicator.mute")
776
.string_attribute("submenu-action", "indicator.indicator-shown")
777
.mode(mh::MenuItemMatcher::Mode::starts_with)
779
.item(mh::MenuItemMatcher()
781
.item(silentModeSwitch(false))
782
.item(volumeSlider(INITIAL_VOLUME, headphonesStringMenu))
786
// deactivate the headphones
787
EXPECT_TRUE(activateHeadphones(false));
789
WAIT_FOR_SIGNALS(notificationsSpy, 2);
790
checkNotificationWithNoArgs("GetCapabilities", notificationsSpy.at(0));
791
checkVolumeNotification(INITIAL_VOLUME, speakerString, false, notificationsSpy.at(1));
792
notificationsSpy.clear();
794
// check the label in the menu
795
EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
796
.item(mh::MenuItemMatcher()
797
.action("indicator.root")
798
.string_attribute("x-canonical-type", "com.canonical.indicator.root")
799
.string_attribute("x-canonical-scroll-action", "indicator.scroll")
800
.string_attribute("x-canonical-secondary-action", "indicator.mute")
801
.string_attribute("submenu-action", "indicator.indicator-shown")
802
.mode(mh::MenuItemMatcher::Mode::starts_with)
804
.item(mh::MenuItemMatcher()
806
.item(silentModeSwitch(false))
807
.item(volumeSlider(INITIAL_VOLUME, speakerStringMenu))
812
bool IndicatorSoundTestBase::setVolumeUntilAccountsIsConnected(double volume)
814
int RETRY_TIME = 5000;
816
setActionValue("volume", QVariant::fromValue(volume));
817
while(!signal_spy_volume_changed_->wait(10) && RETRY_TIME)
820
setActionValue("volume", QVariant::fromValue(volume));
822
return (signal_spy_volume_changed_->count() != 0);