Merge lp:~xavi-garcia-mena/indicator-sound/test-branch-gmenuharness into lp:indicator-sound/15.10

Proposed by Xavi Garcia
Status: Merged
Approved by: Pete Woods
Approved revision: 519
Merged at revision: 508
Proposed branch: lp:~xavi-garcia-mena/indicator-sound/test-branch-gmenuharness
Merge into: lp:indicator-sound/15.10
Prerequisite: lp:~xavi-garcia-mena/indicator-sound/next-play-prev-buttons-bug-1373313
Diff against target: 5638 lines (+5002/-121)
56 files modified
debian/control (+11/-0)
include/CMakeLists.txt (+1/-0)
include/unity/CMakeLists.txt (+1/-0)
include/unity/gmenuharness/MatchResult.h (+66/-0)
include/unity/gmenuharness/MatchUtils.h (+42/-0)
include/unity/gmenuharness/MenuItemMatcher.h (+143/-0)
include/unity/gmenuharness/MenuMatcher.h (+95/-0)
src/CMakeLists.txt (+10/-9)
src/gmenuharness/CMakeLists.txt (+17/-0)
src/gmenuharness/MatchResult.cpp (+187/-0)
src/gmenuharness/MatchUtils.cpp (+77/-0)
src/gmenuharness/MenuItemMatcher.cpp (+1008/-0)
src/gmenuharness/MenuMatcher.cpp (+208/-0)
src/media-player-mpris.vala (+1/-1)
src/volume-control-pulse.vala (+5/-4)
tests/CMakeLists.txt (+113/-107)
tests/dbus-types/CMakeLists.txt (+48/-0)
tests/dbus-types/com.ubuntu.AccountsService.Sound.xml (+9/-0)
tests/dbus-types/dbus-types.h (+44/-0)
tests/dbus-types/org.PulseAudio.Ext.StreamRestore1.xml (+7/-0)
tests/dbus-types/org.freedesktop.Accounts.xml (+8/-0)
tests/dbus-types/org.freedesktop.DBus.Properties.xml (+22/-0)
tests/dbus-types/org.gtk.Actions.xml (+13/-0)
tests/dbus-types/pulseaudio-volume.cpp (+156/-0)
tests/dbus-types/pulseaudio-volume.h (+69/-0)
tests/integration/CMakeLists.txt (+132/-0)
tests/integration/indicator-sound-test-base.cpp (+411/-0)
tests/integration/indicator-sound-test-base.h (+105/-0)
tests/integration/main.cpp (+58/-0)
tests/integration/test-indicator.cpp (+637/-0)
tests/integration/touch-stream-restore.table (+4/-0)
tests/integration/utils/dbus-pulse-volume.cpp (+232/-0)
tests/integration/utils/dbus-pulse-volume.h (+57/-0)
tests/integration/utils/get-volume.cpp (+33/-0)
tests/integration/utils/set-volume.cpp (+36/-0)
tests/service-mocks/CMakeLists.txt (+2/-0)
tests/service-mocks/DBusPropertiesNotifier.cpp (+41/-0)
tests/service-mocks/DBusPropertiesNotifier.h (+48/-0)
tests/service-mocks/accounts-mock/AccountsDefs.h (+37/-0)
tests/service-mocks/accounts-mock/AccountsMock.cpp (+40/-0)
tests/service-mocks/accounts-mock/AccountsMock.h (+50/-0)
tests/service-mocks/accounts-mock/AccountsServiceSoundMock.cpp (+48/-0)
tests/service-mocks/accounts-mock/AccountsServiceSoundMock.h (+58/-0)
tests/service-mocks/accounts-mock/CMakeLists.txt (+42/-0)
tests/service-mocks/accounts-mock/com.ubuntu.AccountsService.Sound.Mock.xml (+6/-0)
tests/service-mocks/accounts-mock/main.cpp (+63/-0)
tests/service-mocks/accounts-mock/org.freedesktop.Accounts.Mock.xml (+13/-0)
tests/service-mocks/media-player-mpris-mock/CMakeLists.txt (+63/-0)
tests/service-mocks/media-player-mpris-mock/MediaPlayerMprisDefs.h (+37/-0)
tests/service-mocks/media-player-mpris-mock/MediaPlayerMprisMock.cpp (+103/-0)
tests/service-mocks/media-player-mpris-mock/MediaPlayerMprisMock.h (+77/-0)
tests/service-mocks/media-player-mpris-mock/applications/testplayer1.desktop (+21/-0)
tests/service-mocks/media-player-mpris-mock/main.cpp (+64/-0)
tests/service-mocks/media-player-mpris-mock/org.mpris.MediaPlayer2.Player.xml (+24/-0)
tests/service-mocks/media-player-mpris-mock/org.mpris.MediaPlayer2.xml (+6/-0)
tests/service-mocks/media-player-mpris-mock/player-update.cpp (+93/-0)
To merge this branch: bzr merge lp:~xavi-garcia-mena/indicator-sound/test-branch-gmenuharness
Reviewer Review Type Date Requested Status
Charles Kerr (community) Approve
Pete Woods (community) Approve
PS Jenkins bot (community) continuous-integration Approve
Review via email: mp+274214@code.launchpad.net

This proposal supersedes a proposal from 2015-09-24.

Commit message

This branch adds integration tests to the sound indicator.
It uses the gmenuharness library to check the Gmenus obtained with the sound indicator.

Description of the change

This branch adds integration tests to the sound indicator.
It uses the gmenuharness library to check the Gmenus obtained with the sound indicator.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Pete Woods (pete-woods) wrote :

Looks good to me

review: Approve
Revision history for this message
Charles Kerr (charlesk) wrote :

Mostly looks good.

I did have some questions / suggestions but they appear to be in blocks of code that I've already reviewed in other branches so I won't duplicate them here.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/control'
2--- debian/control 2015-02-27 17:52:05 +0000
3+++ debian/control 2015-10-13 07:44:39 +0000
4@@ -5,11 +5,13 @@
5 XSBC-Original-Maintainer: Conor Curran <conor.curran@canonical.com>
6 Build-Depends: debhelper (>= 9.0),
7 cmake,
8+ cmake-extras (>= 0.4),
9 dbus,
10 dbus-test-runner (>> 14.04.0+14.04.20150120.1),
11 dh-translations,
12 gir1.2-accountsservice-1.0,
13 gnome-common,
14+ google-mock (>= 1.6.0+svn437),
15 gsettings-ubuntu-schemas,
16 autotools-dev,
17 valac (>= 0.20),
18@@ -18,13 +20,22 @@
19 libgirepository1.0-dev,
20 libglib2.0-dev (>= 2.22.3),
21 libgtest-dev,
22+ libqtdbusmock1-dev (>= 0.3),
23+ libqtdbustest1-dev,
24+ libunity-api-dev,
25 liburl-dispatcher1-dev,
26 libpulse-dev (>= 1:4.0-0ubuntu21),
27 libpulse-mainloop-glib0 (>= 0.9.18),
28 libnotify-dev,
29 libgee-dev,
30 libxml2-dev,
31+ pulseaudio,
32 python3-dbusmock,
33+ qt5-default,
34+ qtbase5-dev,
35+ qtbase5-dev-tools,
36+ qtdeclarative5-dev,
37+ qtdeclarative5-dev-tools,
38 Standards-Version: 3.9.4
39 Homepage: https://launchpad.net/indicator-sound
40 # If you aren't a member of ~indicator-applet-developers but need to upload
41
42=== added directory 'include'
43=== added file 'include/CMakeLists.txt'
44--- include/CMakeLists.txt 1970-01-01 00:00:00 +0000
45+++ include/CMakeLists.txt 2015-10-13 07:44:39 +0000
46@@ -0,0 +1,1 @@
47+add_subdirectory(unity)
48
49=== added directory 'include/unity'
50=== added file 'include/unity/CMakeLists.txt'
51--- include/unity/CMakeLists.txt 1970-01-01 00:00:00 +0000
52+++ include/unity/CMakeLists.txt 2015-10-13 07:44:39 +0000
53@@ -0,0 +1,1 @@
54+add_subdirectory(gmenuharness)
55
56=== added directory 'include/unity/gmenuharness'
57=== added file 'include/unity/gmenuharness/MatchResult.h'
58--- include/unity/gmenuharness/MatchResult.h 1970-01-01 00:00:00 +0000
59+++ include/unity/gmenuharness/MatchResult.h 2015-10-13 07:44:39 +0000
60@@ -0,0 +1,66 @@
61+/*
62+ * Copyright © 2014 Canonical Ltd.
63+ *
64+ * This program is free software: you can redistribute it and/or modify it
65+ * under the terms of the GNU Lesser General Public License version 3,
66+ * as published by the Free Software Foundation.
67+ *
68+ * This program is distributed in the hope that it will be useful,
69+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
70+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
71+ * GNU Lesser General Public License for more details.
72+ *
73+ * You should have received a copy of the GNU Lesser General Public License
74+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
75+ *
76+ * Authored by: Pete Woods <pete.woods@canonical.com>
77+ */
78+
79+#pragma once
80+
81+#include <vector>
82+#include <memory>
83+#include <string>
84+
85+namespace unity
86+{
87+
88+namespace gmenuharness
89+{
90+
91+class MatchResult
92+{
93+public:
94+ MatchResult();
95+
96+ MatchResult(MatchResult&& other);
97+
98+ MatchResult(const MatchResult& other);
99+
100+ MatchResult& operator=(const MatchResult& other);
101+
102+ MatchResult& operator=(MatchResult&& other);
103+
104+ ~MatchResult() = default;
105+
106+ MatchResult createChild() const;
107+
108+ void failure(const std::vector<unsigned int>& location, const std::string& message);
109+
110+ void merge(const MatchResult& other);
111+
112+ bool success() const;
113+
114+ bool hasTimedOut() const;
115+
116+ std::string concat_failures() const;
117+
118+protected:
119+ struct Priv;
120+
121+ std::shared_ptr<Priv> p;
122+};
123+
124+} // namespace gmenuharness
125+
126+} // namespace unity
127
128=== added file 'include/unity/gmenuharness/MatchUtils.h'
129--- include/unity/gmenuharness/MatchUtils.h 1970-01-01 00:00:00 +0000
130+++ include/unity/gmenuharness/MatchUtils.h 2015-10-13 07:44:39 +0000
131@@ -0,0 +1,42 @@
132+/*
133+ * Copyright © 2014 Canonical Ltd.
134+ *
135+ * This program is free software: you can redistribute it and/or modify it
136+ * under the terms of the GNU Lesser General Public License version 3,
137+ * as published by the Free Software Foundation.
138+ *
139+ * This program is distributed in the hope that it will be useful,
140+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
141+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
142+ * GNU Lesser General Public License for more details.
143+ *
144+ * You should have received a copy of the GNU Lesser General Public License
145+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
146+ *
147+ * Authored by: Pete Woods <pete.woods@canonical.com>
148+ */
149+
150+#pragma once
151+
152+#include <memory>
153+#include <string>
154+
155+#include <gio/gio.h>
156+
157+namespace unity
158+{
159+
160+namespace gmenuharness
161+{
162+
163+void waitForCore(GObject* obj, const std::string& signalName, unsigned int timeout = 10);
164+
165+void menuWaitForItems(const std::shared_ptr<GMenuModel>& menu, unsigned int timeout = 10);
166+
167+void g_object_deleter(gpointer object);
168+
169+void gvariant_deleter(GVariant* varptr);
170+
171+} //namespace gmenuharness
172+
173+} // namespace unity
174
175=== added file 'include/unity/gmenuharness/MenuItemMatcher.h'
176--- include/unity/gmenuharness/MenuItemMatcher.h 1970-01-01 00:00:00 +0000
177+++ include/unity/gmenuharness/MenuItemMatcher.h 2015-10-13 07:44:39 +0000
178@@ -0,0 +1,143 @@
179+/*
180+ * Copyright © 2014 Canonical Ltd.
181+ *
182+ * This program is free software: you can redistribute it and/or modify it
183+ * under the terms of the GNU Lesser General Public License version 3,
184+ * as published by the Free Software Foundation.
185+ *
186+ * This program is distributed in the hope that it will be useful,
187+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
188+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
189+ * GNU Lesser General Public License for more details.
190+ *
191+ * You should have received a copy of the GNU Lesser General Public License
192+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
193+ *
194+ * Authored by: Pete Woods <pete.woods@canonical.com>
195+ */
196+
197+#pragma once
198+
199+#include <map>
200+#include <memory>
201+#include <string>
202+
203+#include <gio/gio.h>
204+
205+namespace unity
206+{
207+
208+namespace gmenuharness
209+{
210+
211+class MatchResult;
212+
213+class MenuItemMatcher
214+{
215+public:
216+ enum class Mode
217+ {
218+ all,
219+ starts_with,
220+ ends_with
221+ };
222+
223+ enum class Type
224+ {
225+ plain,
226+ checkbox,
227+ radio
228+ };
229+
230+ static MenuItemMatcher checkbox();
231+
232+ static MenuItemMatcher radio();
233+
234+ MenuItemMatcher();
235+
236+ ~MenuItemMatcher();
237+
238+ MenuItemMatcher(const MenuItemMatcher& other);
239+
240+ MenuItemMatcher(MenuItemMatcher&& other);
241+
242+ MenuItemMatcher& operator=(const MenuItemMatcher& other);
243+
244+ MenuItemMatcher& operator=(MenuItemMatcher&& other);
245+
246+ MenuItemMatcher& type(Type type);
247+
248+ MenuItemMatcher& label(const std::string& label);
249+
250+ MenuItemMatcher& action(const std::string& action);
251+
252+ MenuItemMatcher& state_icons(const std::vector<std::string>& state);
253+
254+ MenuItemMatcher& icon(const std::string& icon);
255+
256+ MenuItemMatcher& themed_icon(const std::string& iconName, const std::vector<std::string>& icons);
257+
258+ MenuItemMatcher& widget(const std::string& widget);
259+
260+ MenuItemMatcher& pass_through_attribute(const std::string& actionName, const std::shared_ptr<GVariant>& value);
261+
262+ MenuItemMatcher& pass_through_boolean_attribute(const std::string& actionName, bool value);
263+
264+ MenuItemMatcher& pass_through_string_attribute(const std::string& actionName, const std::string& value);
265+
266+ MenuItemMatcher& pass_through_double_attribute(const std::string& actionName, double value);
267+
268+ MenuItemMatcher& round_doubles(double maxDifference);
269+
270+ MenuItemMatcher& attribute(const std::string& name, const std::shared_ptr<GVariant>& value);
271+
272+ MenuItemMatcher& boolean_attribute(const std::string& name, bool value);
273+
274+ MenuItemMatcher& string_attribute(const std::string& name, const std::string& value);
275+
276+ MenuItemMatcher& int32_attribute(const std::string& name, int value);
277+
278+ MenuItemMatcher& int64_attribute(const std::string& name, int value);
279+
280+ MenuItemMatcher& double_attribute(const std::string& name, double value);
281+
282+ MenuItemMatcher& attribute_not_set(const std::string& name);
283+
284+ MenuItemMatcher& toggled(bool toggled);
285+
286+ MenuItemMatcher& mode(Mode mode);
287+
288+ MenuItemMatcher& submenu();
289+
290+ MenuItemMatcher& section();
291+
292+ MenuItemMatcher& is_empty();
293+
294+ MenuItemMatcher& has_exactly(std::size_t children);
295+
296+ MenuItemMatcher& item(const MenuItemMatcher& item);
297+
298+ MenuItemMatcher& item(MenuItemMatcher&& item);
299+
300+ MenuItemMatcher& pass_through_activate(const std::string& action, const std::shared_ptr<GVariant>& parameter = nullptr);
301+
302+ MenuItemMatcher& activate(const std::shared_ptr<GVariant>& parameter = nullptr);
303+
304+ MenuItemMatcher& set_pass_through_action_state(const std::string& action, const std::shared_ptr<GVariant>& state);
305+
306+ MenuItemMatcher& set_action_state(const std::shared_ptr<GVariant>& state);
307+
308+ void match(MatchResult& matchResult, const std::vector<unsigned int>& location,
309+ const std::shared_ptr<GMenuModel>& menu,
310+ std::map<std::string, std::shared_ptr<GActionGroup>>& actions,
311+ int index) const;
312+
313+protected:
314+ struct Priv;
315+
316+ std::shared_ptr<Priv> p;
317+};
318+
319+} // namespace gmenuharness
320+
321+} // namespace unity
322
323=== added file 'include/unity/gmenuharness/MenuMatcher.h'
324--- include/unity/gmenuharness/MenuMatcher.h 1970-01-01 00:00:00 +0000
325+++ include/unity/gmenuharness/MenuMatcher.h 2015-10-13 07:44:39 +0000
326@@ -0,0 +1,95 @@
327+/*
328+ * Copyright © 2014 Canonical Ltd.
329+ *
330+ * This program is free software: you can redistribute it and/or modify it
331+ * under the terms of the GNU Lesser General Public License version 3,
332+ * as published by the Free Software Foundation.
333+ *
334+ * This program is distributed in the hope that it will be useful,
335+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
336+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
337+ * GNU Lesser General Public License for more details.
338+ *
339+ * You should have received a copy of the GNU Lesser General Public License
340+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
341+ *
342+ * Authored by: Pete Woods <pete.woods@canonical.com>
343+ */
344+
345+#pragma once
346+
347+#define EXPECT_MATCHRESULT(statement) \
348+do {\
349+ auto result = (statement);\
350+ GTEST_TEST_BOOLEAN_(result.success(), #statement, false, true, \
351+ GTEST_NONFATAL_FAILURE_) << result.concat_failures().c_str(); \
352+} while (0)
353+
354+#include <unity/gmenuharness/MatchResult.h>
355+#include <unity/gmenuharness/MenuItemMatcher.h>
356+
357+#include <memory>
358+#include <vector>
359+
360+namespace unity
361+{
362+
363+namespace gmenuharness
364+{
365+
366+class MenuMatcher
367+{
368+public:
369+ class Parameters
370+ {
371+ public:
372+ Parameters(
373+ const std::string& busName,
374+ const std::vector<std::pair<std::string, std::string>>& actions,
375+ const std::string& menuObjectPath);
376+
377+ ~Parameters();
378+
379+ Parameters(const Parameters& other);
380+
381+ Parameters(Parameters&& other);
382+
383+ Parameters& operator=(const Parameters& other);
384+
385+ Parameters& operator=(Parameters&& other);
386+
387+ protected:
388+ friend MenuMatcher;
389+
390+ struct Priv;
391+
392+ std::shared_ptr<Priv> p;
393+ };
394+
395+ MenuMatcher(const Parameters& parameters);
396+
397+ ~MenuMatcher();
398+
399+ MenuMatcher(const MenuMatcher& other) = delete;
400+
401+ MenuMatcher(MenuMatcher&& other) = delete;
402+
403+ MenuMatcher& operator=(const MenuMatcher& other) = delete;
404+
405+ MenuMatcher& operator=(MenuMatcher&& other) = delete;
406+
407+ MenuMatcher& item(const MenuItemMatcher& item);
408+
409+ MatchResult match() const;
410+
411+ void match(MatchResult& matchResult) const;
412+
413+protected:
414+ struct Priv;
415+
416+ std::shared_ptr<Priv> p;
417+};
418+
419+} // gmenuharness
420+
421+} // unity
422
423=== modified file 'src/CMakeLists.txt'
424--- src/CMakeLists.txt 2015-02-19 16:23:01 +0000
425+++ src/CMakeLists.txt 2015-10-13 07:44:39 +0000
426@@ -8,12 +8,12 @@
427 set(VAPI_PATH "${CMAKE_CURRENT_BINARY_DIR}/indicator-sound-service.vapi")
428
429 vapi_gen(accounts-service
430- LIBRARY
431- accounts-service
432- PACKAGES
433- gio-2.0
434- INPUT
435- /usr/share/gir-1.0/AccountsService-1.0.gir
436+ LIBRARY
437+ accounts-service
438+ PACKAGES
439+ gio-2.0
440+ INPUT
441+ /usr/share/gir-1.0/AccountsService-1.0.gir
442 )
443
444 vala_init(indicator-sound-service
445@@ -70,7 +70,7 @@
446 media-player-user.vala
447 DEPENDS
448 media-player
449- accounts-service-sound-settings
450+ accounts-service-sound-settings
451 greeter-broadcast
452 )
453 vala_add(indicator-sound-service
454@@ -164,8 +164,8 @@
455 )
456
457 add_library(
458- indicator-sound-service-lib STATIC
459- ${INDICATOR_SOUND_SOURCES}
460+ indicator-sound-service-lib STATIC
461+ ${INDICATOR_SOUND_SOURCES}
462 )
463
464 target_link_libraries(
465@@ -206,3 +206,4 @@
466 RUNTIME DESTINATION ${CMAKE_INSTALL_LIBEXECDIR}/indicator-sound/
467 )
468
469+add_subdirectory(gmenuharness)
470
471=== added directory 'src/gmenuharness'
472=== added file 'src/gmenuharness/CMakeLists.txt'
473--- src/gmenuharness/CMakeLists.txt 1970-01-01 00:00:00 +0000
474+++ src/gmenuharness/CMakeLists.txt 2015-10-13 07:44:39 +0000
475@@ -0,0 +1,17 @@
476+pkg_check_modules(UNITY_API libunity-api>=0.1.3 REQUIRED)
477+include_directories(${UNITY_API_INCLUDE_DIRS})
478+
479+include_directories("${CMAKE_SOURCE_DIR}/include")
480+
481+add_library(
482+ gmenuharness-shared SHARED
483+ MatchResult.cpp
484+ MatchUtils.cpp
485+ MenuItemMatcher.cpp
486+ MenuMatcher.cpp
487+)
488+
489+target_link_libraries(
490+ gmenuharness-shared
491+ ${GLIB_LDFLAGS}
492+)
493
494=== added file 'src/gmenuharness/MatchResult.cpp'
495--- src/gmenuharness/MatchResult.cpp 1970-01-01 00:00:00 +0000
496+++ src/gmenuharness/MatchResult.cpp 2015-10-13 07:44:39 +0000
497@@ -0,0 +1,187 @@
498+/*
499+ * Copyright © 2014 Canonical Ltd.
500+ *
501+ * This program is free software: you can redistribute it and/or modify it
502+ * under the terms of the GNU Lesser General Public License version 3,
503+ * as published by the Free Software Foundation.
504+ *
505+ * This program is distributed in the hope that it will be useful,
506+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
507+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
508+ * GNU Lesser General Public License for more details.
509+ *
510+ * You should have received a copy of the GNU Lesser General Public License
511+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
512+ *
513+ * Authored by: Pete Woods <pete.woods@canonical.com>
514+ */
515+
516+#include <unity/gmenuharness/MatchResult.h>
517+
518+#include <chrono>
519+#include <map>
520+#include <sstream>
521+#include <iostream>
522+
523+using namespace std;
524+
525+namespace unity
526+{
527+
528+namespace gmenuharness
529+{
530+
531+namespace
532+{
533+
534+
535+static void printLocation(ostream& ss, const vector<unsigned int>& location, bool first)
536+{
537+ for (int i : location)
538+ {
539+ ss << " ";
540+ if (first)
541+ {
542+ ss << i;
543+ }
544+ else
545+ {
546+ ss << " ";
547+ }
548+ }
549+ ss << " ";
550+}
551+
552+struct compare_vector
553+{
554+ bool operator()(const vector<unsigned int>& a,
555+ const vector<unsigned int>& b) const
556+ {
557+ auto p1 = a.begin();
558+ auto p2 = b.begin();
559+
560+ while (p1 != a.end())
561+ {
562+ if (p2 == b.end())
563+ {
564+ return false;
565+ }
566+ if (*p2 > *p1)
567+ {
568+ return true;
569+ }
570+ if (*p1 > *p2)
571+ {
572+ return false;
573+ }
574+
575+ ++p1;
576+ ++p2;
577+ }
578+
579+ if (p2 != b.end())
580+ {
581+ return true;
582+ }
583+
584+ return false;
585+ }
586+};
587+}
588+
589+struct MatchResult::Priv
590+{
591+ bool m_success = true;
592+
593+ map<vector<unsigned int>, vector<string>, compare_vector> m_failures;
594+
595+ chrono::time_point<chrono::system_clock> m_timeout = chrono::system_clock::now() + chrono::seconds(10);
596+};
597+
598+MatchResult::MatchResult() :
599+ p(new Priv)
600+{
601+}
602+
603+MatchResult::MatchResult(MatchResult&& other)
604+{
605+ *this = move(other);
606+}
607+
608+MatchResult::MatchResult(const MatchResult& other) :
609+ p(new Priv)
610+{
611+ *this = other;
612+}
613+
614+MatchResult& MatchResult::operator=(const MatchResult& other)
615+{
616+ p->m_success = other.p->m_success;
617+ p->m_failures= other.p->m_failures;
618+ return *this;
619+}
620+
621+MatchResult& MatchResult::operator=(MatchResult&& other)
622+{
623+ p = move(other.p);
624+ return *this;
625+}
626+
627+MatchResult MatchResult::createChild() const
628+{
629+ MatchResult child;
630+ child.p->m_timeout = p->m_timeout;
631+ return child;
632+}
633+
634+void MatchResult::failure(const vector<unsigned int>& location, const string& message)
635+{
636+ p->m_success = false;
637+ auto it = p->m_failures.find(location);
638+ if (it == p->m_failures.end())
639+ {
640+ it = p->m_failures.insert(make_pair(location, vector<string>())).first;
641+ }
642+ it->second.emplace_back(message);
643+}
644+
645+void MatchResult::merge(const MatchResult& other)
646+{
647+ p->m_success &= other.p->m_success;
648+ for (const auto& e : other.p->m_failures)
649+ {
650+ p->m_failures.insert(make_pair(e.first, e.second));
651+ }
652+}
653+
654+bool MatchResult::success() const
655+{
656+ return p->m_success;
657+}
658+
659+bool MatchResult::hasTimedOut() const
660+{
661+ auto now = chrono::system_clock::now();
662+ return (now >= p->m_timeout);
663+}
664+
665+string MatchResult::concat_failures() const
666+{
667+ stringstream ss;
668+ ss << "Failed expectations:" << endl;
669+ for (const auto& failure : p->m_failures)
670+ {
671+ bool first = true;
672+ for (const string& s: failure.second)
673+ {
674+ printLocation(ss, failure.first, first);
675+ first = false;
676+ ss << s << endl;
677+ }
678+ }
679+ return ss.str();
680+}
681+
682+} // namespace gmenuharness
683+
684+} // namespace unity
685
686=== added file 'src/gmenuharness/MatchUtils.cpp'
687--- src/gmenuharness/MatchUtils.cpp 1970-01-01 00:00:00 +0000
688+++ src/gmenuharness/MatchUtils.cpp 2015-10-13 07:44:39 +0000
689@@ -0,0 +1,77 @@
690+/*
691+ * Copyright © 2014 Canonical Ltd.
692+ *
693+ * This program is free software: you can redistribute it and/or modify it
694+ * under the terms of the GNU Lesser General Public License version 3,
695+ * as published by the Free Software Foundation.
696+ *
697+ * This program is distributed in the hope that it will be useful,
698+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
699+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
700+ * GNU Lesser General Public License for more details.
701+ *
702+ * You should have received a copy of the GNU Lesser General Public License
703+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
704+ *
705+ * Authored by: Pete Woods <pete.woods@canonical.com>
706+ */
707+
708+#include <unity/gmenuharness/MatchUtils.h>
709+
710+#include <unity/util/ResourcePtr.h>
711+
712+using namespace std;
713+namespace util = unity::util;
714+
715+namespace unity
716+{
717+
718+namespace gmenuharness
719+{
720+
721+void waitForCore (GObject * obj, const string& signalName, unsigned int timeout) {
722+ shared_ptr<GMainLoop> loop(g_main_loop_new(nullptr, false), &g_main_loop_unref);
723+
724+ /* Our two exit criteria */
725+ util::ResourcePtr<gulong, function<void(gulong)>> signal(
726+ g_signal_connect_swapped(obj, signalName.c_str(),
727+ G_CALLBACK(g_main_loop_quit), loop.get()),
728+ [obj](gulong s)
729+ {
730+ g_signal_handler_disconnect(obj, s);
731+ });
732+
733+ util::ResourcePtr<guint, function<void(guint)>> timer(g_timeout_add(timeout,
734+ [](gpointer user_data) -> gboolean
735+ {
736+ g_main_loop_quit((GMainLoop *)user_data);
737+ return G_SOURCE_CONTINUE;
738+ },
739+ loop.get()),
740+ &g_source_remove);
741+
742+ /* Wait for sync */
743+ g_main_loop_run(loop.get());
744+}
745+
746+void menuWaitForItems(const shared_ptr<GMenuModel>& menu, unsigned int timeout)
747+{
748+ waitForCore(G_OBJECT(menu.get()), "items-changed", timeout);
749+}
750+
751+void g_object_deleter(gpointer object)
752+{
753+ g_clear_object(&object);
754+}
755+
756+void gvariant_deleter(GVariant* varptr)
757+{
758+ if (varptr != nullptr)
759+ {
760+ g_variant_unref(varptr);
761+ }
762+}
763+
764+} // namespace gmenuharness
765+
766+} // namespace unity
767
768=== added file 'src/gmenuharness/MenuItemMatcher.cpp'
769--- src/gmenuharness/MenuItemMatcher.cpp 1970-01-01 00:00:00 +0000
770+++ src/gmenuharness/MenuItemMatcher.cpp 2015-10-13 07:44:39 +0000
771@@ -0,0 +1,1008 @@
772+/*
773+ * Copyright © 2014 Canonical Ltd.
774+ *
775+ * This program is free software: you can redistribute it and/or modify it
776+ * under the terms of the GNU Lesser General Public License version 3,
777+ * as published by the Free Software Foundation.
778+ *
779+ * This program is distributed in the hope that it will be useful,
780+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
781+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
782+ * GNU Lesser General Public License for more details.
783+ *
784+ * You should have received a copy of the GNU Lesser General Public License
785+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
786+ *
787+ * Authored by: Pete Woods <pete.woods@canonical.com>
788+ */
789+
790+#include <unity/gmenuharness/MatchResult.h>
791+#include <unity/gmenuharness/MatchUtils.h>
792+#include <unity/gmenuharness/MenuItemMatcher.h>
793+
794+#include <iostream>
795+#include <vector>
796+#include <map>
797+
798+using namespace std;
799+
800+namespace unity
801+{
802+
803+namespace gmenuharness
804+{
805+
806+namespace
807+{
808+
809+enum class LinkType
810+{
811+ any,
812+ section,
813+ submenu
814+};
815+
816+static string bool_to_string(bool value)
817+{
818+ return value? "true" : "false";
819+}
820+
821+static shared_ptr<GVariant> get_action_group_attribute(const shared_ptr<GActionGroup>& actionGroup, const gchar* attribute)
822+{
823+ shared_ptr<GVariant> value(
824+ g_action_group_get_action_state(actionGroup.get(), attribute),
825+ &gvariant_deleter);
826+ return value;
827+}
828+
829+static shared_ptr<GVariant> get_attribute(const shared_ptr<GMenuItem> menuItem, const gchar* attribute)
830+{
831+ shared_ptr<GVariant> value(
832+ g_menu_item_get_attribute_value(menuItem.get(), attribute, nullptr),
833+ &gvariant_deleter);
834+ return value;
835+}
836+
837+static string get_string_attribute(const shared_ptr<GMenuItem> menuItem, const gchar* attribute)
838+{
839+ string result;
840+ char* temp = nullptr;
841+ if (g_menu_item_get_attribute(menuItem.get(), attribute, "s", &temp))
842+ {
843+ result = temp;
844+ g_free(temp);
845+ }
846+ return result;
847+}
848+
849+static pair<string, string> split_action(const string& action)
850+{
851+ auto index = action.find('.');
852+
853+ if (index == string::npos)
854+ {
855+ return make_pair(string(), action);
856+ }
857+
858+ return make_pair(action.substr(0, index), action.substr(index + 1, action.size()));
859+}
860+
861+static string type_to_string(MenuItemMatcher::Type type)
862+{
863+ switch(type)
864+ {
865+ case MenuItemMatcher::Type::plain:
866+ return "plain";
867+ case MenuItemMatcher::Type::checkbox:
868+ return "checkbox";
869+ case MenuItemMatcher::Type::radio:
870+ return "radio";
871+ }
872+
873+ return string();
874+}
875+}
876+
877+struct MenuItemMatcher::Priv
878+{
879+ void all(MatchResult& matchResult, const vector<unsigned int>& location,
880+ const shared_ptr<GMenuModel>& menu,
881+ map<string, shared_ptr<GActionGroup>>& actions)
882+ {
883+ int count = g_menu_model_get_n_items(menu.get());
884+
885+ if (m_items.size() != (unsigned int) count)
886+ {
887+ matchResult.failure(
888+ location,
889+ "Expected " + to_string(m_items.size())
890+ + " children, but found " + to_string(count));
891+ return;
892+ }
893+
894+ for (size_t i = 0; i < m_items.size(); ++i)
895+ {
896+ const auto& matcher = m_items.at(i);
897+ matcher.match(matchResult, location, menu, actions, i);
898+ }
899+ }
900+
901+ void startsWith(MatchResult& matchResult, const vector<unsigned int>& location,
902+ const shared_ptr<GMenuModel>& menu,
903+ map<string, shared_ptr<GActionGroup>>& actions)
904+ {
905+ int count = g_menu_model_get_n_items(menu.get());
906+ if (m_items.size() > (unsigned int) count)
907+ {
908+ matchResult.failure(
909+ location,
910+ "Expected at least " + to_string(m_items.size())
911+ + " children, but found " + to_string(count));
912+ return;
913+ }
914+
915+ for (size_t i = 0; i < m_items.size(); ++i)
916+ {
917+ const auto& matcher = m_items.at(i);
918+ matcher.match(matchResult, location, menu, actions, i);
919+ }
920+ }
921+
922+ void endsWith(MatchResult& matchResult, const vector<unsigned int>& location,
923+ const shared_ptr<GMenuModel>& menu,
924+ map<string, shared_ptr<GActionGroup>>& actions)
925+ {
926+ int count = g_menu_model_get_n_items(menu.get());
927+ if (m_items.size() > (unsigned int) count)
928+ {
929+ matchResult.failure(
930+ location,
931+ "Expected at least " + to_string(m_items.size())
932+ + " children, but found " + to_string(count));
933+ return;
934+ }
935+
936+ // match the last N items
937+ size_t j;
938+ for (size_t i = count - m_items.size(), j = 0; i < count && j < m_items.size(); ++i, ++j)
939+ {
940+ const auto& matcher = m_items.at(j);
941+ matcher.match(matchResult, location, menu, actions, i);
942+ }
943+ }
944+
945+ Type m_type = Type::plain;
946+
947+ Mode m_mode = Mode::all;
948+
949+ LinkType m_linkType = LinkType::any;
950+
951+ shared_ptr<size_t> m_expectedSize;
952+
953+ shared_ptr<string> m_label;
954+
955+ shared_ptr<string> m_icon;
956+
957+ map<shared_ptr<string>, vector<std::string>> m_themed_icons;
958+
959+ shared_ptr<string> m_action;
960+
961+ vector<std::string> m_state_icons;
962+
963+ vector<pair<string, shared_ptr<GVariant>>> m_attributes;
964+
965+ vector<string> m_not_exist_attributes;
966+
967+ vector<pair<string, shared_ptr<GVariant>>> m_pass_through_attributes;
968+
969+ shared_ptr<bool> m_isToggled;
970+
971+ vector<MenuItemMatcher> m_items;
972+
973+ vector<pair<string, shared_ptr<GVariant>>> m_activations;
974+
975+ vector<pair<string, shared_ptr<GVariant>>> m_setActionStates;
976+
977+ double m_maxDifference = 0.0;
978+};
979+
980+MenuItemMatcher MenuItemMatcher::checkbox()
981+{
982+ MenuItemMatcher matcher;
983+ matcher.type(Type::checkbox);
984+ return matcher;
985+}
986+
987+MenuItemMatcher MenuItemMatcher::radio()
988+{
989+ MenuItemMatcher matcher;
990+ matcher.type(Type::radio);
991+ return matcher;
992+}
993+
994+MenuItemMatcher::MenuItemMatcher() :
995+ p(new Priv)
996+{
997+}
998+
999+MenuItemMatcher::~MenuItemMatcher()
1000+{
1001+}
1002+
1003+MenuItemMatcher::MenuItemMatcher(const MenuItemMatcher& other) :
1004+ p(new Priv)
1005+{
1006+ *this = other;
1007+}
1008+
1009+MenuItemMatcher::MenuItemMatcher(MenuItemMatcher&& other)
1010+{
1011+ *this = move(other);
1012+}
1013+
1014+MenuItemMatcher& MenuItemMatcher::operator=(const MenuItemMatcher& other)
1015+{
1016+ p->m_type = other.p->m_type;
1017+ p->m_mode = other.p->m_mode;
1018+ p->m_expectedSize = other.p->m_expectedSize;
1019+ p->m_label = other.p->m_label;
1020+ p->m_icon = other.p->m_icon;
1021+ p->m_themed_icons = other.p->m_themed_icons;
1022+ p->m_action = other.p->m_action;
1023+ p->m_state_icons = other.p->m_state_icons;
1024+ p->m_attributes = other.p->m_attributes;
1025+ p->m_not_exist_attributes = other.p->m_not_exist_attributes;
1026+ p->m_pass_through_attributes = other.p->m_pass_through_attributes;
1027+ p->m_isToggled = other.p->m_isToggled;
1028+ p->m_linkType = other.p->m_linkType;
1029+ p->m_items = other.p->m_items;
1030+ p->m_activations = other.p->m_activations;
1031+ p->m_setActionStates = other.p->m_setActionStates;
1032+ p->m_maxDifference = other.p->m_maxDifference;
1033+ return *this;
1034+}
1035+
1036+MenuItemMatcher& MenuItemMatcher::operator=(MenuItemMatcher&& other)
1037+{
1038+ p = move(other.p);
1039+ return *this;
1040+}
1041+
1042+MenuItemMatcher& MenuItemMatcher::type(Type type)
1043+{
1044+ p->m_type = type;
1045+ return *this;
1046+}
1047+
1048+MenuItemMatcher& MenuItemMatcher::label(const string& label)
1049+{
1050+ p->m_label = make_shared<string>(label);
1051+ return *this;
1052+}
1053+
1054+MenuItemMatcher& MenuItemMatcher::action(const string& action)
1055+{
1056+ p->m_action = make_shared<string>(action);
1057+ return *this;
1058+}
1059+
1060+MenuItemMatcher& MenuItemMatcher::state_icons(const std::vector<std::string>& state_icons)
1061+{
1062+ p->m_state_icons = state_icons;
1063+ return *this;
1064+}
1065+
1066+MenuItemMatcher& MenuItemMatcher::icon(const string& icon)
1067+{
1068+ p->m_icon = make_shared<string>(icon);
1069+ return *this;
1070+}
1071+
1072+MenuItemMatcher& MenuItemMatcher::themed_icon(const std::string& iconName, const std::vector<std::string>& icons)
1073+{
1074+ p->m_themed_icons[make_shared<string>(iconName)] = icons;
1075+ return *this;
1076+}
1077+
1078+MenuItemMatcher& MenuItemMatcher::widget(const string& widget)
1079+{
1080+ return string_attribute("x-canonical-type", widget);
1081+}
1082+
1083+MenuItemMatcher& MenuItemMatcher::pass_through_attribute(const string& actionName, const shared_ptr<GVariant>& value)
1084+{
1085+ p->m_pass_through_attributes.emplace_back(actionName, value);
1086+ return *this;
1087+}
1088+
1089+MenuItemMatcher& MenuItemMatcher::pass_through_boolean_attribute(const string& actionName, bool value)
1090+{
1091+ return pass_through_attribute(
1092+ actionName,
1093+ shared_ptr<GVariant>(g_variant_new_boolean(value),
1094+ &gvariant_deleter));
1095+}
1096+
1097+MenuItemMatcher& MenuItemMatcher::pass_through_string_attribute(const string& actionName, const string& value)
1098+{
1099+ return pass_through_attribute(
1100+ actionName,
1101+ shared_ptr<GVariant>(g_variant_new_string(value.c_str()),
1102+ &gvariant_deleter));
1103+}
1104+
1105+MenuItemMatcher& MenuItemMatcher::pass_through_double_attribute(const std::string& actionName, double value)
1106+{
1107+ return pass_through_attribute(
1108+ actionName,
1109+ shared_ptr<GVariant>(g_variant_new_double(value),
1110+ &gvariant_deleter));
1111+}
1112+
1113+MenuItemMatcher& MenuItemMatcher::round_doubles(double maxDifference)
1114+{
1115+ p->m_maxDifference = maxDifference;
1116+ return *this;
1117+}
1118+
1119+MenuItemMatcher& MenuItemMatcher::attribute(const string& name, const shared_ptr<GVariant>& value)
1120+{
1121+ p->m_attributes.emplace_back(name, value);
1122+ return *this;
1123+}
1124+
1125+MenuItemMatcher& MenuItemMatcher::boolean_attribute(const string& name, bool value)
1126+{
1127+ return attribute(
1128+ name,
1129+ shared_ptr<GVariant>(g_variant_new_boolean(value),
1130+ &gvariant_deleter));
1131+}
1132+
1133+MenuItemMatcher& MenuItemMatcher::string_attribute(const string& name, const string& value)
1134+{
1135+ return attribute(
1136+ name,
1137+ shared_ptr<GVariant>(g_variant_new_string(value.c_str()),
1138+ &gvariant_deleter));
1139+}
1140+
1141+MenuItemMatcher& MenuItemMatcher::int32_attribute(const std::string& name, int value)
1142+{
1143+ return attribute(
1144+ name,
1145+ shared_ptr<GVariant>(g_variant_new_int32 (value),
1146+ &gvariant_deleter));
1147+}
1148+
1149+MenuItemMatcher& MenuItemMatcher::int64_attribute(const std::string& name, int value)
1150+{
1151+ return attribute(
1152+ name,
1153+ shared_ptr<GVariant>(g_variant_new_int64 (value),
1154+ &gvariant_deleter));
1155+}
1156+
1157+MenuItemMatcher& MenuItemMatcher::double_attribute(const std::string& name, double value)
1158+{
1159+ return attribute(
1160+ name,
1161+ shared_ptr<GVariant>(g_variant_new_double (value),
1162+ &gvariant_deleter));
1163+}
1164+
1165+MenuItemMatcher& MenuItemMatcher::attribute_not_set(const std::string& name)
1166+{
1167+ p->m_not_exist_attributes.emplace_back (name);
1168+ return *this;
1169+}
1170+
1171+MenuItemMatcher& MenuItemMatcher::toggled(bool isToggled)
1172+{
1173+ p->m_isToggled = make_shared<bool>(isToggled);
1174+ return *this;
1175+}
1176+
1177+MenuItemMatcher& MenuItemMatcher::mode(Mode mode)
1178+{
1179+ p->m_mode = mode;
1180+ return *this;
1181+}
1182+
1183+MenuItemMatcher& MenuItemMatcher::submenu()
1184+{
1185+ p->m_linkType = LinkType::submenu;
1186+ return *this;
1187+}
1188+
1189+MenuItemMatcher& MenuItemMatcher::section()
1190+{
1191+ p->m_linkType = LinkType::section;
1192+ return *this;
1193+}
1194+
1195+MenuItemMatcher& MenuItemMatcher::is_empty()
1196+{
1197+ return has_exactly(0);
1198+}
1199+
1200+MenuItemMatcher& MenuItemMatcher::has_exactly(size_t children)
1201+{
1202+ p->m_expectedSize = make_shared<size_t>(children);
1203+ return *this;
1204+}
1205+
1206+MenuItemMatcher& MenuItemMatcher::item(const MenuItemMatcher& item)
1207+{
1208+ p->m_items.emplace_back(item);
1209+ return *this;
1210+}
1211+
1212+MenuItemMatcher& MenuItemMatcher::item(MenuItemMatcher&& item)
1213+{
1214+ p->m_items.emplace_back(item);
1215+ return *this;
1216+}
1217+
1218+MenuItemMatcher& MenuItemMatcher::pass_through_activate(std::string const& action, const shared_ptr<GVariant>& parameter)
1219+{
1220+ p->m_activations.emplace_back(action, parameter);
1221+ return *this;
1222+}
1223+
1224+MenuItemMatcher& MenuItemMatcher::activate(const shared_ptr<GVariant>& parameter)
1225+{
1226+ p->m_activations.emplace_back(string(), parameter);
1227+ return *this;
1228+}
1229+
1230+MenuItemMatcher& MenuItemMatcher::set_pass_through_action_state(const std::string& action, const std::shared_ptr<GVariant>& state)
1231+{
1232+ p->m_setActionStates.emplace_back(action, state);
1233+ return *this;
1234+}
1235+
1236+MenuItemMatcher& MenuItemMatcher::set_action_state(const std::shared_ptr<GVariant>& state)
1237+{
1238+ p->m_setActionStates.emplace_back("", state);
1239+ return *this;
1240+}
1241+
1242+void MenuItemMatcher::match(
1243+ MatchResult& matchResult,
1244+ const vector<unsigned int>& parentLocation,
1245+ const shared_ptr<GMenuModel>& menu,
1246+ map<string, shared_ptr<GActionGroup>>& actions,
1247+ int index) const
1248+{
1249+ shared_ptr<GMenuItem> menuItem(g_menu_item_new_from_model(menu.get(), index), &g_object_deleter);
1250+
1251+ vector<unsigned int> location(parentLocation);
1252+ location.emplace_back(index);
1253+
1254+ string action = get_string_attribute(menuItem, G_MENU_ATTRIBUTE_ACTION);
1255+
1256+ bool isCheckbox = false;
1257+ bool isRadio = false;
1258+ bool isToggled = false;
1259+
1260+ pair<string, string> idPair;
1261+ shared_ptr<GActionGroup> actionGroup;
1262+ shared_ptr<GVariant> state;
1263+
1264+ if (!action.empty())
1265+ {
1266+ idPair = split_action(action);
1267+ actionGroup = actions[idPair.first];
1268+ state = shared_ptr<GVariant>(g_action_group_get_action_state(actionGroup.get(),
1269+ idPair.second.c_str()),
1270+ &gvariant_deleter);
1271+ auto attributeTarget = get_attribute(menuItem, G_MENU_ATTRIBUTE_TARGET);
1272+
1273+ if (attributeTarget && state)
1274+ {
1275+ isToggled = g_variant_equal(state.get(), attributeTarget.get());
1276+ isRadio = true;
1277+ }
1278+ else if (state
1279+ && g_variant_is_of_type(state.get(), G_VARIANT_TYPE_BOOLEAN))
1280+ {
1281+ isToggled = g_variant_get_boolean(state.get());
1282+ isCheckbox = true;
1283+ }
1284+ }
1285+
1286+ Type actualType = Type::plain;
1287+ if (isCheckbox)
1288+ {
1289+ actualType = Type::checkbox;
1290+ }
1291+ else if (isRadio)
1292+ {
1293+ actualType = Type::radio;
1294+ }
1295+
1296+ if (actualType != p->m_type)
1297+ {
1298+ matchResult.failure(
1299+ location,
1300+ "Expected " + type_to_string(p->m_type) + ", found "
1301+ + type_to_string(actualType));
1302+ }
1303+
1304+ // check themed icons
1305+ map<shared_ptr<string>, vector<string>>::iterator iter;
1306+ for (iter = p->m_themed_icons.begin(); iter != p->m_themed_icons.end(); ++iter)
1307+ {
1308+ auto icon_val = g_menu_item_get_attribute_value(menuItem.get(), (*iter).first->c_str(), nullptr);
1309+ if (!icon_val)
1310+ {
1311+ matchResult.failure(
1312+ location,
1313+ "Expected themed icon " + (*(*iter).first) + " was not found");
1314+ }
1315+
1316+ auto gicon = g_icon_deserialize(icon_val);
1317+ if (!gicon || !G_IS_THEMED_ICON(gicon))
1318+ {
1319+ matchResult.failure(
1320+ location,
1321+ "Expected attribute " + (*(*iter).first) + " is not a themed icon");
1322+ }
1323+ auto iconNames = g_themed_icon_get_names(G_THEMED_ICON(gicon));
1324+ int nb_icons = 0;
1325+ while(iconNames[nb_icons])
1326+ {
1327+ ++nb_icons;
1328+ }
1329+
1330+ if (nb_icons != (*iter).second.size())
1331+ {
1332+ matchResult.failure(
1333+ location,
1334+ "Expected " + to_string((*iter).second.size()) +
1335+ " icons for themed icon [" + (*(*iter).first) +
1336+ "], but " + to_string(nb_icons) + " were found.");
1337+ }
1338+ else
1339+ {
1340+ // now compare all the icons
1341+ for (int i = 0; i < nb_icons; ++i)
1342+ {
1343+ if (string(iconNames[i]) != (*iter).second[i])
1344+ {
1345+ matchResult.failure(
1346+ location,
1347+ "Icon at position " + to_string(i) +
1348+ " for themed icon [" + (*(*iter).first) +
1349+ "], mismatchs. Expected: " + iconNames[i] + " but found " + (*iter).second[i]);
1350+ }
1351+ }
1352+ }
1353+ g_object_unref(gicon);
1354+ }
1355+
1356+ string label = get_string_attribute(menuItem, G_MENU_ATTRIBUTE_LABEL);
1357+ if (p->m_label && (*p->m_label) != label)
1358+ {
1359+ matchResult.failure(
1360+ location,
1361+ "Expected label '" + *p->m_label + "', but found '" + label
1362+ + "'");
1363+ }
1364+
1365+ string icon = get_string_attribute(menuItem, G_MENU_ATTRIBUTE_ICON);
1366+ if (p->m_icon && (*p->m_icon) != icon)
1367+ {
1368+ matchResult.failure(
1369+ location,
1370+ "Expected icon '" + *p->m_icon + "', but found '" + icon + "'");
1371+ }
1372+
1373+ if (p->m_action && (*p->m_action) != action)
1374+ {
1375+ matchResult.failure(
1376+ location,
1377+ "Expected action '" + *p->m_action + "', but found '" + action
1378+ + "'");
1379+ }
1380+
1381+ if (!p->m_state_icons.empty() && !state)
1382+ {
1383+ matchResult.failure(
1384+ location,
1385+ "Expected state icons but no state was found");
1386+ }
1387+ else if (!p->m_state_icons.empty() && state &&
1388+ !g_variant_is_of_type(state.get(), G_VARIANT_TYPE_VARDICT))
1389+ {
1390+ matchResult.failure(
1391+ location,
1392+ "Expected state icons vardict, found "
1393+ + type_to_string(actualType));
1394+ }
1395+ else if (!p->m_state_icons.empty() && state &&
1396+ g_variant_is_of_type(state.get(), G_VARIANT_TYPE_VARDICT))
1397+ {
1398+ std::vector<std::string> actual_state_icons;
1399+ GVariantIter it;
1400+ gchar* key;
1401+ GVariant* value;
1402+
1403+ g_variant_iter_init(&it, state.get());
1404+ while (g_variant_iter_loop(&it, "{sv}", &key, &value))
1405+ {
1406+ if (std::string(key) == "icon") {
1407+ auto gicon = g_icon_deserialize(value);
1408+ if (gicon && G_IS_THEMED_ICON(gicon))
1409+ {
1410+ auto iconNames = g_themed_icon_get_names(G_THEMED_ICON(gicon));
1411+ // Just take the first icon in the list (there is only ever one)
1412+ actual_state_icons.push_back(iconNames[0]);
1413+ g_object_unref(gicon);
1414+ }
1415+ }
1416+ else if (std::string(key) == "icons" && g_variant_is_of_type(value, G_VARIANT_TYPE("av")))
1417+ {
1418+ // If we find "icons" in the map, clear any icons we may have found in "icon",
1419+ // then break from the loop as we have found all icons now.
1420+ actual_state_icons.clear();
1421+ GVariantIter icon_it;
1422+ GVariant* icon_value;
1423+
1424+ g_variant_iter_init(&icon_it, value);
1425+ while (g_variant_iter_loop(&icon_it, "v", &icon_value))
1426+ {
1427+ auto gicon = g_icon_deserialize(icon_value);
1428+ if (gicon && G_IS_THEMED_ICON(gicon))
1429+ {
1430+ auto iconNames = g_themed_icon_get_names(G_THEMED_ICON(gicon));
1431+ // Just take the first icon in the list (there is only ever one)
1432+ actual_state_icons.push_back(iconNames[0]);
1433+ g_object_unref(gicon);
1434+ }
1435+ }
1436+ // We're breaking out of g_variant_iter_loop here so clean up
1437+ g_variant_unref(value);
1438+ g_free(key);
1439+ break;
1440+ }
1441+ }
1442+
1443+ if (p->m_state_icons != actual_state_icons)
1444+ {
1445+ std::string expected_icons;
1446+ for (unsigned int i = 0; i < p->m_state_icons.size(); ++i)
1447+ {
1448+ expected_icons += i == 0 ? p->m_state_icons[i] : ", " + p->m_state_icons[i];
1449+ }
1450+ std::string actual_icons;
1451+ for (unsigned int i = 0; i < actual_state_icons.size(); ++i)
1452+ {
1453+ actual_icons += i == 0 ? actual_state_icons[i] : ", " + actual_state_icons[i];
1454+ }
1455+ matchResult.failure(
1456+ location,
1457+ "Expected state_icons == {" + expected_icons
1458+ + "} but found {" + actual_icons + "}");
1459+ }
1460+ }
1461+
1462+ for (const auto& e: p->m_pass_through_attributes)
1463+ {
1464+ string actionName = get_string_attribute(menuItem, e.first.c_str());
1465+ if (actionName.empty())
1466+ {
1467+ matchResult.failure(
1468+ location,
1469+ "Could not find action name '" + e.first + "'");
1470+ }
1471+ else
1472+ {
1473+ auto passThroughIdPair = split_action(actionName);
1474+ auto actionGroup = actions[passThroughIdPair.first];
1475+ if (actionGroup)
1476+ {
1477+ auto value = get_action_group_attribute(
1478+ actionGroup, passThroughIdPair.second.c_str());
1479+ if (!value)
1480+ {
1481+ matchResult.failure(
1482+ location,
1483+ "Expected pass-through attribute '" + e.first
1484+ + "' was not present");
1485+ }
1486+ else if (!g_variant_is_of_type(e.second.get(), g_variant_get_type(value.get())))
1487+ {
1488+ std::string expectedType = g_variant_get_type_string(e.second.get());
1489+ std::string actualType = g_variant_get_type_string(value.get());
1490+ matchResult.failure(
1491+ location,
1492+ "Expected pass-through attribute type '" + expectedType
1493+ + "' but found '" + actualType + "'");
1494+ }
1495+ else if (g_variant_compare(e.second.get(), value.get()))
1496+ {
1497+ bool reportMismatch = true;
1498+ if (g_strcmp0(g_variant_get_type_string(value.get()),"d") == 0 && p->m_maxDifference)
1499+ {
1500+ auto actualDouble = g_variant_get_double(value.get());
1501+ auto expectedDouble = g_variant_get_double(e.second.get());
1502+ auto difference = actualDouble-expectedDouble;
1503+ if (difference < 0) difference = difference * -1.0;
1504+ if (difference <= p->m_maxDifference)
1505+ {
1506+ reportMismatch = false;
1507+ }
1508+ }
1509+ if (reportMismatch)
1510+ {
1511+ gchar* expectedString = g_variant_print(e.second.get(), true);
1512+ gchar* actualString = g_variant_print(value.get(), true);
1513+ matchResult.failure(
1514+ location,
1515+ "Expected pass-through attribute '" + e.first
1516+ + "' == " + expectedString + " but found "
1517+ + actualString);
1518+
1519+ g_free(expectedString);
1520+ g_free(actualString);
1521+ }
1522+ }
1523+ }
1524+ else
1525+ {
1526+ matchResult.failure(location, "Could not find action group for ID '" + passThroughIdPair.first + "'");
1527+ }
1528+ }
1529+ }
1530+
1531+ for (const auto& e: p->m_attributes)
1532+ {
1533+ auto value = get_attribute(menuItem, e.first.c_str());
1534+ if (!value)
1535+ {
1536+ matchResult.failure(location,
1537+ "Expected attribute '" + e.first
1538+ + "' could not be found");
1539+ }
1540+ else if (!g_variant_is_of_type(e.second.get(), g_variant_get_type(value.get())))
1541+ {
1542+ std::string expectedType = g_variant_get_type_string(e.second.get());
1543+ std::string actualType = g_variant_get_type_string(value.get());
1544+ matchResult.failure(
1545+ location,
1546+ "Expected attribute type '" + expectedType
1547+ + "' but found '" + actualType + "'");
1548+ }
1549+ else if (g_variant_compare(e.second.get(), value.get()))
1550+ {
1551+ gchar* expectedString = g_variant_print(e.second.get(), true);
1552+ gchar* actualString = g_variant_print(value.get(), true);
1553+ matchResult.failure(
1554+ location,
1555+ "Expected attribute '" + e.first + "' == " + expectedString
1556+ + ", but found " + actualString);
1557+ g_free(expectedString);
1558+ g_free(actualString);
1559+ }
1560+ }
1561+
1562+ for (const auto& e: p->m_not_exist_attributes)
1563+ {
1564+ auto value = get_attribute(menuItem, e.c_str());
1565+ if (value)
1566+ {
1567+ matchResult.failure(location,
1568+ "Not expected attribute '" + e
1569+ + "' was found");
1570+ }
1571+ }
1572+
1573+ if (p->m_isToggled && (*p->m_isToggled) != isToggled)
1574+ {
1575+ matchResult.failure(
1576+ location,
1577+ "Expected toggled = " + bool_to_string(*p->m_isToggled)
1578+ + ", but found " + bool_to_string(isToggled));
1579+ }
1580+
1581+ if (!matchResult.success())
1582+ {
1583+ return;
1584+ }
1585+
1586+ if (!p->m_items.empty() || p->m_expectedSize)
1587+ {
1588+ shared_ptr<GMenuModel> link;
1589+
1590+ switch (p->m_linkType)
1591+ {
1592+ case LinkType::any:
1593+ {
1594+ link.reset(g_menu_model_get_item_link(menu.get(), (int) index, G_MENU_LINK_SUBMENU), &g_object_deleter);
1595+ if (!link)
1596+ {
1597+ link.reset(g_menu_model_get_item_link(menu.get(), (int) index, G_MENU_LINK_SECTION), &g_object_deleter);
1598+ }
1599+ break;
1600+ }
1601+ case LinkType::submenu:
1602+ {
1603+ link.reset(g_menu_model_get_item_link(menu.get(), (int) index, G_MENU_LINK_SUBMENU), &g_object_deleter);
1604+ break;
1605+ }
1606+ case LinkType::section:
1607+ {
1608+ link.reset(g_menu_model_get_item_link(menu.get(), (int) index, G_MENU_LINK_SECTION), &g_object_deleter);
1609+ break;
1610+ }
1611+ }
1612+
1613+
1614+ if (!link)
1615+ {
1616+ if (p->m_expectedSize)
1617+ {
1618+ matchResult.failure(
1619+ location,
1620+ "Expected " + to_string(*p->m_expectedSize)
1621+ + " children, but found none");
1622+ }
1623+ else
1624+ {
1625+ matchResult.failure(
1626+ location,
1627+ "Expected " + to_string(p->m_items.size())
1628+ + " children, but found none");
1629+ }
1630+ return;
1631+ }
1632+ else
1633+ {
1634+ while (true)
1635+ {
1636+ MatchResult childMatchResult(matchResult.createChild());
1637+
1638+ if (p->m_expectedSize
1639+ && *p->m_expectedSize
1640+ != (unsigned int) g_menu_model_get_n_items(
1641+ link.get()))
1642+ {
1643+ childMatchResult.failure(
1644+ location,
1645+ "Expected " + to_string(*p->m_expectedSize)
1646+ + " child items, but found "
1647+ + to_string(
1648+ g_menu_model_get_n_items(
1649+ link.get())));
1650+ }
1651+ else if (!p->m_items.empty())
1652+ {
1653+ switch (p->m_mode)
1654+ {
1655+ case Mode::all:
1656+ p->all(childMatchResult, location, link, actions);
1657+ break;
1658+ case Mode::starts_with:
1659+ p->startsWith(childMatchResult, location, link, actions);
1660+ break;
1661+ case Mode::ends_with:
1662+ p->endsWith(childMatchResult, location, link, actions);
1663+ break;
1664+ }
1665+ }
1666+
1667+ if (childMatchResult.success())
1668+ {
1669+ matchResult.merge(childMatchResult);
1670+ break;
1671+ }
1672+ else
1673+ {
1674+ if (matchResult.hasTimedOut())
1675+ {
1676+ matchResult.merge(childMatchResult);
1677+ break;
1678+ }
1679+ menuWaitForItems(link);
1680+ }
1681+ }
1682+ }
1683+ }
1684+
1685+
1686+ for (const auto& a: p->m_setActionStates)
1687+ {
1688+ auto stateAction = action;
1689+ auto stateIdPair = idPair;
1690+ auto stateActionGroup = actionGroup;
1691+ if (!a.first.empty())
1692+ {
1693+ stateAction = get_string_attribute(menuItem, a.first.c_str());;
1694+ stateIdPair = split_action(stateAction);
1695+ stateActionGroup = actions[stateIdPair.first];
1696+ }
1697+
1698+ if (stateAction.empty())
1699+ {
1700+ matchResult.failure(
1701+ location,
1702+ "Tried to set action state, but no action was found");
1703+ }
1704+ else if(!stateActionGroup)
1705+ {
1706+ matchResult.failure(
1707+ location,
1708+ "Tried to set action state for action group '" + stateIdPair.first
1709+ + "', but action group wasn't found");
1710+ }
1711+ else if (!g_action_group_has_action(stateActionGroup.get(), stateIdPair.second.c_str()))
1712+ {
1713+ matchResult.failure(
1714+ location,
1715+ "Tried to set action state for action '" + stateAction
1716+ + "', but action was not found");
1717+ }
1718+ else
1719+ {
1720+ g_action_group_change_action_state(stateActionGroup.get(), stateIdPair.second.c_str(),
1721+ g_variant_ref(a.second.get()));
1722+ }
1723+
1724+ // FIXME this is a dodgy way to ensure the action state change gets dispatched
1725+ menuWaitForItems(menu, 100);
1726+ }
1727+
1728+ for (const auto& a: p->m_activations)
1729+ {
1730+ string tmpAction = action;
1731+ auto tmpIdPair = idPair;
1732+ auto tmpActionGroup = actionGroup;
1733+ if (!a.first.empty())
1734+ {
1735+ tmpAction = get_string_attribute(menuItem, a.first.c_str());
1736+ tmpIdPair = split_action(tmpAction);
1737+ tmpActionGroup = actions[tmpIdPair.first];
1738+ }
1739+
1740+ if (tmpAction.empty())
1741+ {
1742+ matchResult.failure(
1743+ location,
1744+ "Tried to activate action, but no action was found");
1745+ }
1746+ else if(!tmpActionGroup)
1747+ {
1748+ matchResult.failure(
1749+ location,
1750+ "Tried to activate action group '" + tmpIdPair.first
1751+ + "', but action group wasn't found");
1752+ }
1753+ else if (!g_action_group_has_action(tmpActionGroup.get(), tmpIdPair.second.c_str()))
1754+ {
1755+ matchResult.failure(
1756+ location,
1757+ "Tried to activate action '" + tmpAction + "', but action was not found");
1758+ }
1759+ else
1760+ {
1761+ if (a.second)
1762+ {
1763+ g_action_group_activate_action(tmpActionGroup.get(), tmpIdPair.second.c_str(),
1764+ g_variant_ref(a.second.get()));
1765+ }
1766+ else
1767+ {
1768+ g_action_group_activate_action(tmpActionGroup.get(), tmpIdPair.second.c_str(), nullptr);
1769+ }
1770+
1771+ // FIXME this is a dodgy way to ensure the activation gets dispatched
1772+ menuWaitForItems(menu, 100);
1773+ }
1774+ }
1775+}
1776+
1777+} // namepsace gmenuharness
1778+
1779+} // namespace unity
1780
1781=== added file 'src/gmenuharness/MenuMatcher.cpp'
1782--- src/gmenuharness/MenuMatcher.cpp 1970-01-01 00:00:00 +0000
1783+++ src/gmenuharness/MenuMatcher.cpp 2015-10-13 07:44:39 +0000
1784@@ -0,0 +1,208 @@
1785+/*
1786+ * Copyright © 2014 Canonical Ltd.
1787+ *
1788+ * This program is free software: you can redistribute it and/or modify it
1789+ * under the terms of the GNU Lesser General Public License version 3,
1790+ * as published by the Free Software Foundation.
1791+ *
1792+ * This program is distributed in the hope that it will be useful,
1793+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1794+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1795+ * GNU Lesser General Public License for more details.
1796+ *
1797+ * You should have received a copy of the GNU Lesser General Public License
1798+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1799+ *
1800+ * Authored by: Pete Woods <pete.woods@canonical.com>
1801+ */
1802+
1803+#include <unity/gmenuharness/MenuMatcher.h>
1804+#include <unity/gmenuharness/MatchUtils.h>
1805+
1806+#include <iostream>
1807+
1808+#include <gio/gio.h>
1809+
1810+using namespace std;
1811+
1812+namespace unity
1813+{
1814+
1815+namespace gmenuharness
1816+{
1817+
1818+namespace
1819+{
1820+
1821+static void gdbus_connection_deleter(GDBusConnection* connection)
1822+{
1823+// if (!g_dbus_connection_is_closed(connection))
1824+// {
1825+// g_dbus_connection_close_sync(connection, nullptr, nullptr);
1826+// }
1827+ g_clear_object(&connection);
1828+}
1829+}
1830+
1831+struct MenuMatcher::Parameters::Priv
1832+{
1833+ string m_busName;
1834+
1835+ vector<pair<string, string>> m_actions;
1836+
1837+ string m_menuObjectPath;
1838+};
1839+
1840+MenuMatcher::Parameters::Parameters(const string& busName,
1841+ const vector<pair<string, string>>& actions,
1842+ const string& menuObjectPath) :
1843+ p(new Priv)
1844+{
1845+ p->m_busName = busName;
1846+ p->m_actions = actions;
1847+ p->m_menuObjectPath = menuObjectPath;
1848+}
1849+
1850+MenuMatcher::Parameters::~Parameters()
1851+{
1852+}
1853+
1854+MenuMatcher::Parameters::Parameters(const Parameters& other) :
1855+ p(new Priv)
1856+{
1857+ *this = other;
1858+}
1859+
1860+MenuMatcher::Parameters::Parameters(Parameters&& other)
1861+{
1862+ *this = move(other);
1863+}
1864+
1865+MenuMatcher::Parameters& MenuMatcher::Parameters::operator=(const Parameters& other)
1866+{
1867+ p->m_busName = other.p->m_busName;
1868+ p->m_actions = other.p->m_actions;
1869+ p->m_menuObjectPath = other.p->m_menuObjectPath;
1870+ return *this;
1871+}
1872+
1873+MenuMatcher::Parameters& MenuMatcher::Parameters::operator=(Parameters&& other)
1874+{
1875+ p = move(other.p);
1876+ return *this;
1877+}
1878+
1879+struct MenuMatcher::Priv
1880+{
1881+ Priv(const Parameters& parameters) :
1882+ m_parameters(parameters)
1883+ {
1884+ }
1885+
1886+ Parameters m_parameters;
1887+
1888+ vector<MenuItemMatcher> m_items;
1889+
1890+ shared_ptr<GDBusConnection> m_system;
1891+
1892+ shared_ptr<GDBusConnection> m_session;
1893+
1894+ shared_ptr<GMenuModel> m_menu;
1895+
1896+ map<string, shared_ptr<GActionGroup>> m_actions;
1897+};
1898+
1899+MenuMatcher::MenuMatcher(const Parameters& parameters) :
1900+ p(new Priv(parameters))
1901+{
1902+ p->m_system.reset(g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, nullptr),
1903+ &gdbus_connection_deleter);
1904+ g_dbus_connection_set_exit_on_close(p->m_system.get(), false);
1905+
1906+ p->m_session.reset(g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr),
1907+ &gdbus_connection_deleter);
1908+ g_dbus_connection_set_exit_on_close(p->m_session.get(), false);
1909+
1910+ p->m_menu.reset(
1911+ G_MENU_MODEL(
1912+ g_dbus_menu_model_get(
1913+ p->m_session.get(),
1914+ p->m_parameters.p->m_busName.c_str(),
1915+ p->m_parameters.p->m_menuObjectPath.c_str())),
1916+ &g_object_deleter);
1917+
1918+ for (const auto& action : p->m_parameters.p->m_actions)
1919+ {
1920+ shared_ptr<GActionGroup> actionGroup(
1921+ G_ACTION_GROUP(
1922+ g_dbus_action_group_get(
1923+ p->m_session.get(),
1924+ p->m_parameters.p->m_busName.c_str(),
1925+ action.second.c_str())),
1926+ &g_object_deleter);
1927+ p->m_actions[action.first] = actionGroup;
1928+ }
1929+}
1930+
1931+MenuMatcher::~MenuMatcher()
1932+{
1933+}
1934+
1935+MenuMatcher& MenuMatcher::item(const MenuItemMatcher& item)
1936+{
1937+ p->m_items.emplace_back(item);
1938+ return *this;
1939+}
1940+
1941+void MenuMatcher::match(MatchResult& matchResult) const
1942+{
1943+ vector<unsigned int> location;
1944+
1945+ while (true)
1946+ {
1947+ MatchResult childMatchResult(matchResult.createChild());
1948+
1949+ int menuSize = g_menu_model_get_n_items(p->m_menu.get());
1950+ if (p->m_items.size() > (unsigned int) menuSize)
1951+ {
1952+ childMatchResult.failure(
1953+ location,
1954+ "Row count mismatch, expected " + to_string(p->m_items.size())
1955+ + " but found " + to_string(menuSize));
1956+ }
1957+ else
1958+ {
1959+ for (size_t i = 0; i < p->m_items.size(); ++i)
1960+ {
1961+ const auto& matcher = p->m_items.at(i);
1962+ matcher.match(childMatchResult, location, p->m_menu, p->m_actions, i);
1963+ }
1964+ }
1965+
1966+ if (childMatchResult.success())
1967+ {
1968+ matchResult.merge(childMatchResult);
1969+ break;
1970+ }
1971+ else
1972+ {
1973+ if (matchResult.hasTimedOut())
1974+ {
1975+ matchResult.merge(childMatchResult);
1976+ break;
1977+ }
1978+ menuWaitForItems(p->m_menu);
1979+ }
1980+ }
1981+}
1982+
1983+MatchResult MenuMatcher::match() const
1984+{
1985+ MatchResult matchResult;
1986+ match(matchResult);
1987+ return matchResult;
1988+}
1989+
1990+} // namespace gmenuharness
1991+
1992+} // namespace unity
1993
1994=== modified file 'src/media-player-mpris.vala'
1995--- src/media-player-mpris.vala 2015-10-13 07:44:39 +0000
1996+++ src/media-player-mpris.vala 2015-10-13 07:44:39 +0000
1997@@ -290,7 +290,7 @@
1998 if (changed_properties.lookup ("PlaybackStatus", "s", null)) {
1999 this.state = this.proxy.PlaybackStatus != null ? this.proxy.PlaybackStatus : "Unknown";
2000 }
2001- if (changed_properties.lookup ("CanGoNext", "b", null) || changed_properties.lookup ("CanGoPrev", "b", null)) {
2002+ if (changed_properties.lookup ("CanGoNext", "b", null) || changed_properties.lookup ("CanGoPrevious", "b", null)) {
2003 this.playbackstatus_changed ();
2004 }
2005
2006
2007=== modified file 'src/volume-control-pulse.vala'
2008--- src/volume-control-pulse.vala 2015-08-12 18:16:40 +0000
2009+++ src/volume-control-pulse.vala 2015-10-13 07:44:39 +0000
2010@@ -275,7 +275,7 @@
2011 }
2012
2013 /* We only care about signals if our internal count is zero */
2014- if (sig_count == 0) {
2015+ //if (sig_count == 0) {
2016 /* Extract volume and make sure it's not a side effect of us setting it */
2017 Variant body = message.get_body ();
2018 Variant varray = body.get_child_value (0);
2019@@ -293,7 +293,7 @@
2020 vol.reason = VolumeControl.VolumeReasons.PULSE_CHANGE;
2021 this.volume = vol;
2022 }
2023- }
2024+ //}
2025 }
2026 }
2027
2028@@ -478,7 +478,8 @@
2029 this.context = new PulseAudio.Context (loop.get_api(), null, props);
2030 this.context.set_state_callback (context_state_callback);
2031
2032- if (context.connect(null, Context.Flags.NOFAIL, null) < 0)
2033+ var server_string = Environment.get_variable("PULSE_SERVER");
2034+ if (context.connect(server_string, Context.Flags.NOFAIL, null) < 0)
2035 warning( "pa_context_connect() failed: %s\n", PulseAudio.strerror(context.errno()));
2036 }
2037
2038@@ -627,7 +628,7 @@
2039 }
2040 set {
2041 var volume_changed = (value.volume != _volume.volume);
2042- debug("Setting volume to %f for profile %d because %d", value.volume, _active_sink_input, value.reason);
2043+ warning("Setting volume to %f for profile %d because %d", value.volume, _active_sink_input, value.reason);
2044
2045 _volume = value;
2046
2047
2048=== modified file 'tests/CMakeLists.txt'
2049--- tests/CMakeLists.txt 2015-08-11 21:42:20 +0000
2050+++ tests/CMakeLists.txt 2015-10-13 07:44:39 +0000
2051@@ -5,10 +5,10 @@
2052
2053 include_directories(${GTEST_INCLUDE_DIR})
2054
2055-add_library (gtest STATIC
2056+add_library (gtest-static STATIC
2057 ${GTEST_SOURCE_DIR}/gtest-all.cc
2058 ${GTEST_SOURCE_DIR}/gtest_main.cc)
2059-target_link_libraries(gtest ${GTEST_LIBS})
2060+target_link_libraries(gtest-static ${GTEST_LIBS})
2061
2062 ###########################
2063 # GSettings Schema
2064@@ -22,7 +22,8 @@
2065 # GSettings:
2066 # compile the indicator-sound schema into a gschemas.compiled file in this directory,
2067 # and help the tests to find that file by setting -DSCHEMA_DIR
2068-set (SCHEMA_DIR "${CMAKE_CURRENT_BINARY_DIR}/gsettings-schemas")
2069+set (XDG_DATA_DIRS "${CMAKE_CURRENT_BINARY_DIR}/gsettings-schemas")
2070+set (SCHEMA_DIR "${XDG_DATA_DIRS}/glib-2.0/schemas")
2071 add_definitions(-DSCHEMA_DIR="${SCHEMA_DIR}")
2072 execute_process (COMMAND ${PKG_CONFIG_EXECUTABLE} gio-2.0 --variable glib_compile_schemas
2073 OUTPUT_VARIABLE COMPILE_SCHEMA_EXECUTABLE
2074@@ -31,6 +32,7 @@
2075 DEPENDS ${CMAKE_SOURCE_DIR}/data/com.canonical.indicator.sound.gschema.xml
2076 COMMAND mkdir -p ${SCHEMA_DIR}
2077 COMMAND cp -f ${CMAKE_SOURCE_DIR}/data/*gschema.xml ${SCHEMA_DIR}
2078+ COMMAND cp -f /usr/share/glib-2.0/schemas/com.ubuntu.sound.gschema.xml ${SCHEMA_DIR}
2079 COMMAND ${COMPILE_SCHEMA_EXECUTABLE} ${SCHEMA_DIR})
2080
2081 ###########################
2082@@ -41,74 +43,74 @@
2083 set(VALA_MOCKS_SYMBOLS_PATH "${CMAKE_CURRENT_BINARY_DIR}/vala-mocks.def")
2084
2085 vala_init(vala-mocks
2086- DEPENDS
2087- indicator-sound-service-lib
2088- PACKAGES
2089- config
2090- gio-2.0
2091- gio-unix-2.0
2092- libxml-2.0
2093- libpulse
2094- libpulse-mainloop-glib
2095- libnotify
2096- accounts-service
2097- indicator-sound-service
2098- OPTIONS
2099- --ccode
2100- --thread
2101- --vapidir=${CMAKE_BINARY_DIR}/src/
2102- --vapidir=${CMAKE_SOURCE_DIR}/vapi/
2103- --vapidir=.
2104-)
2105-
2106-vala_add(vala-mocks
2107- media-player-mock.vala
2108-)
2109-
2110-vala_add(vala-mocks
2111- media-player-list-mock.vala
2112-)
2113-
2114-vala_add(vala-mocks
2115- volume-control-mock.vala
2116+ DEPENDS
2117+ indicator-sound-service-lib
2118+ PACKAGES
2119+ config
2120+ gio-2.0
2121+ gio-unix-2.0
2122+ libxml-2.0
2123+ libpulse
2124+ libpulse-mainloop-glib
2125+ libnotify
2126+ accounts-service
2127+ indicator-sound-service
2128+ OPTIONS
2129+ --ccode
2130+ --thread
2131+ --vapidir=${CMAKE_BINARY_DIR}/src/
2132+ --vapidir=${CMAKE_SOURCE_DIR}/vapi/
2133+ --vapidir=.
2134+)
2135+
2136+vala_add(vala-mocks
2137+ media-player-mock.vala
2138+)
2139+
2140+vala_add(vala-mocks
2141+ media-player-list-mock.vala
2142+)
2143+
2144+vala_add(vala-mocks
2145+ volume-control-mock.vala
2146 )
2147
2148 vala_finish(vala-mocks
2149- SOURCES
2150- vala_mocks_VALA_SOURCES
2151- OUTPUTS
2152- vala_mocks_VALA_C
2153- GENERATE_HEADER
2154- ${VALA_MOCKS_HEADER_PATH}
2155- GENERATE_SYMBOLS
2156- ${VALA_MOCKS_SYMBOLS_PATH}
2157+ SOURCES
2158+ vala_mocks_VALA_SOURCES
2159+ OUTPUTS
2160+ vala_mocks_VALA_C
2161+ GENERATE_HEADER
2162+ ${VALA_MOCKS_HEADER_PATH}
2163+ GENERATE_SYMBOLS
2164+ ${VALA_MOCKS_SYMBOLS_PATH}
2165 )
2166
2167 set_source_files_properties(
2168- ${vala_mocks_VALA_SOURCES}
2169- PROPERTIES
2170- HEADER_FILE_ONLY TRUE
2171+ ${vala_mocks_VALA_SOURCES}
2172+ PROPERTIES
2173+ HEADER_FILE_ONLY TRUE
2174 )
2175
2176 set(
2177- VALA_MOCKS_SOURCES
2178- ${vala_mocks_VALA_SOURCES}
2179- ${vala_mocks_VALA_C}
2180- ${VALA_MOCKS_SYMBOLS_PATH}
2181+ VALA_MOCKS_SOURCES
2182+ ${vala_mocks_VALA_SOURCES}
2183+ ${vala_mocks_VALA_C}
2184+ ${VALA_MOCKS_SYMBOLS_PATH}
2185 )
2186
2187 add_definitions(
2188- -Wno-unused-but-set-variable
2189+ -Wno-unused-but-set-variable
2190 )
2191
2192 add_library(
2193- vala-mocks-lib STATIC
2194- ${VALA_MOCKS_SOURCES}
2195+ vala-mocks-lib STATIC
2196+ ${VALA_MOCKS_SOURCES}
2197 )
2198
2199 target_link_libraries(
2200- vala-mocks-lib
2201- indicator-sound-service-lib
2202+ vala-mocks-lib
2203+ indicator-sound-service-lib
2204 )
2205
2206 include_directories(${CMAKE_CURRENT_BINARY_DIR})
2207@@ -118,9 +120,9 @@
2208 ###########################
2209
2210 add_library(
2211- pulse-mock
2212- SHARED
2213- pa-mock.cpp
2214+ pulse-mock
2215+ SHARED
2216+ pa-mock.cpp
2217 )
2218
2219 target_link_libraries (pulse-mock ${PULSEAUDIO_LIBRARIES})
2220@@ -131,7 +133,7 @@
2221
2222 include_directories(${CMAKE_SOURCE_DIR}/src)
2223 add_executable (name-watch-test name-watch-test.cc ${CMAKE_SOURCE_DIR}/src/bus-watch-namespace.c)
2224-target_link_libraries (name-watch-test gtest ${SOUNDSERVICE_LIBRARIES})
2225+target_link_libraries (name-watch-test gtest-static ${SOUNDSERVICE_LIBRARIES})
2226 add_test(name-watch-test name-watch-test)
2227
2228 ###########################
2229@@ -141,21 +143,21 @@
2230 include_directories(${CMAKE_SOURCE_DIR}/src)
2231 add_executable (accounts-service-user-test accounts-service-user.cc)
2232 target_link_libraries (
2233- accounts-service-user-test
2234- indicator-sound-service-lib
2235- vala-mocks-lib
2236- gtest
2237- ${SOUNDSERVICE_LIBRARIES}
2238- ${TEST_LIBRARIES}
2239+ accounts-service-user-test
2240+ indicator-sound-service-lib
2241+ vala-mocks-lib
2242+ gtest-static
2243+ ${SOUNDSERVICE_LIBRARIES}
2244+ ${TEST_LIBRARIES}
2245 )
2246
2247 # Split tests to work around libaccountservice sucking
2248 add_test(accounts-service-user-test-basic
2249- accounts-service-user-test --gtest_filter=AccountsServiceUserTest.BasicObject
2250+ accounts-service-user-test --gtest_filter=AccountsServiceUserTest.BasicObject
2251 )
2252
2253 add_test(accounts-service-user-test-player
2254- accounts-service-user-test --gtest_filter=AccountsServiceUserTest.SetMediaPlayer
2255+ accounts-service-user-test --gtest_filter=AccountsServiceUserTest.SetMediaPlayer
2256 )
2257
2258 ###########################
2259@@ -165,11 +167,11 @@
2260 include_directories(${CMAKE_SOURCE_DIR}/src)
2261 add_executable (volume-control-test volume-control-test.cc gschemas.compiled)
2262 target_link_libraries (
2263- volume-control-test
2264- indicator-sound-service-lib
2265- pulse-mock
2266- gtest
2267- ${TEST_LIBRARIES}
2268+ volume-control-test
2269+ indicator-sound-service-lib
2270+ pulse-mock
2271+ gtest-static
2272+ ${TEST_LIBRARIES}
2273 )
2274
2275 add_test(volume-control-test volume-control-test)
2276@@ -181,12 +183,12 @@
2277 include_directories(${CMAKE_SOURCE_DIR}/src)
2278 add_executable (sound-menu-test sound-menu.cc)
2279 target_link_libraries (
2280- sound-menu-test
2281- indicator-sound-service-lib
2282- vala-mocks-lib
2283- gtest
2284- ${SOUNDSERVICE_LIBRARIES}
2285- ${TEST_LIBRARIES}
2286+ sound-menu-test
2287+ indicator-sound-service-lib
2288+ vala-mocks-lib
2289+ gtest-static
2290+ ${SOUNDSERVICE_LIBRARIES}
2291+ ${TEST_LIBRARIES}
2292 )
2293
2294 add_test(sound-menu-test sound-menu-test)
2295@@ -198,13 +200,13 @@
2296 include_directories(${CMAKE_SOURCE_DIR}/src)
2297 add_executable (notifications-test notifications-test.cc)
2298 target_link_libraries (
2299- notifications-test
2300- indicator-sound-service-lib
2301- vala-mocks-lib
2302- pulse-mock
2303- gtest
2304- ${SOUNDSERVICE_LIBRARIES}
2305- ${TEST_LIBRARIES}
2306+ notifications-test
2307+ indicator-sound-service-lib
2308+ vala-mocks-lib
2309+ pulse-mock
2310+ gtest-static
2311+ ${SOUNDSERVICE_LIBRARIES}
2312+ ${TEST_LIBRARIES}
2313 )
2314
2315 add_test(notifications-test notifications-test)
2316@@ -216,23 +218,23 @@
2317 include_directories(${CMAKE_SOURCE_DIR}/src)
2318 add_executable (media-player-user-test media-player-user.cc)
2319 target_link_libraries (
2320- media-player-user-test
2321- indicator-sound-service-lib
2322- vala-mocks-lib
2323- gtest
2324- ${SOUNDSERVICE_LIBRARIES}
2325- ${TEST_LIBRARIES}
2326+ media-player-user-test
2327+ indicator-sound-service-lib
2328+ vala-mocks-lib
2329+ gtest-static
2330+ ${SOUNDSERVICE_LIBRARIES}
2331+ ${TEST_LIBRARIES}
2332 )
2333
2334 # Split tests to work around libaccountservice sucking
2335 add_test(media-player-user-test-basic
2336- media-player-user-test --gtest_filter=MediaPlayerUserTest.BasicObject
2337+ media-player-user-test --gtest_filter=MediaPlayerUserTest.BasicObject
2338 )
2339 add_test(media-player-user-test-dataset
2340- media-player-user-test --gtest_filter=MediaPlayerUserTest.DataSet
2341+ media-player-user-test --gtest_filter=MediaPlayerUserTest.DataSet
2342 )
2343 add_test(media-player-user-test-timeout
2344- media-player-user-test --gtest_filter=MediaPlayerUserTest.TimeoutTest
2345+ media-player-user-test --gtest_filter=MediaPlayerUserTest.TimeoutTest
2346 )
2347
2348 ###########################
2349@@ -242,20 +244,20 @@
2350 include_directories(${CMAKE_SOURCE_DIR}/src)
2351 add_executable (greeter-list-test greeter-list.cc)
2352 target_link_libraries (
2353- greeter-list-test
2354- indicator-sound-service-lib
2355- vala-mocks-lib
2356- gtest
2357- ${SOUNDSERVICE_LIBRARIES}
2358- ${TEST_LIBRARIES}
2359+ greeter-list-test
2360+ indicator-sound-service-lib
2361+ vala-mocks-lib
2362+ gtest-static
2363+ ${SOUNDSERVICE_LIBRARIES}
2364+ ${TEST_LIBRARIES}
2365 )
2366
2367 # Split tests to work around libaccountservice sucking
2368 add_test(greeter-list-test-basic
2369- greeter-list-test --gtest_filter=GreeterListTest.BasicObject
2370+ greeter-list-test --gtest_filter=GreeterListTest.BasicObject
2371 )
2372 add_test(greeter-list-test-iterator
2373- greeter-list-test --gtest_filter=GreeterListTest.BasicIterator
2374+ greeter-list-test --gtest_filter=GreeterListTest.BasicIterator
2375 )
2376
2377 ###########################
2378@@ -263,18 +265,22 @@
2379 ###########################
2380
2381 add_definitions(
2382- -DINDICATOR_SOUND_SERVICE_BINARY="${CMAKE_BINARY_DIR}/src/indicator-sound-service"
2383- -DPA_MOCK_LIB="${CMAKE_CURRENT_BINARY_DIR}/libpulse-mock.so"
2384+ -DINDICATOR_SOUND_SERVICE_BINARY="${CMAKE_BINARY_DIR}/src/indicator-sound-service"
2385+ -DPA_MOCK_LIB="${CMAKE_CURRENT_BINARY_DIR}/libpulse-mock.so"
2386 )
2387 add_executable (indicator-test indicator-test.cc gschemas.compiled)
2388 target_link_libraries (
2389- indicator-test
2390- gtest
2391- ${SOUNDSERVICE_LIBRARIES}
2392- ${TEST_LIBRARIES}
2393+ indicator-test
2394+ gtest-static
2395+ ${SOUNDSERVICE_LIBRARIES}
2396+ ${TEST_LIBRARIES}
2397 )
2398
2399 # Split tests to work around libaccountservice sucking
2400 add_test(indcator-test
2401- indicator-test
2402+ indicator-test
2403 )
2404+
2405+add_subdirectory(integration)
2406+add_subdirectory(dbus-types)
2407+add_subdirectory(service-mocks)
2408\ No newline at end of file
2409
2410=== added directory 'tests/dbus-types'
2411=== added file 'tests/dbus-types/CMakeLists.txt'
2412--- tests/dbus-types/CMakeLists.txt 1970-01-01 00:00:00 +0000
2413+++ tests/dbus-types/CMakeLists.txt 2015-10-13 07:44:39 +0000
2414@@ -0,0 +1,48 @@
2415+set(CMAKE_AUTOMOC ON)
2416+set(CMAKE_INCLUDE_CURRENT_DIR ON)
2417+
2418+find_package(Qt5DBus REQUIRED)
2419+include_directories(${Qt5DBus_INCLUDE_DIRS})
2420+
2421+add_definitions(-DQT_NO_KEYWORDS=1)
2422+
2423+set(dbusinterface_streamrestore_xml "org.PulseAudio.Ext.StreamRestore1.xml")
2424+set_source_files_properties(${dbusinterface_streamrestore_xml} PROPERTIES
2425+ CLASSNAME StreamRestoreInterface)
2426+
2427+set(dbusinterface_accounts_xml "org.freedesktop.Accounts.xml")
2428+set_source_files_properties(${dbusinterface_accounts_xml} PROPERTIES
2429+ CLASSNAME AccountsInterface)
2430+
2431+set(dbusinterface_accountssound_xml "com.ubuntu.AccountsService.Sound.xml")
2432+set_source_files_properties(${dbusinterface_accountssound_xml} PROPERTIES
2433+ CLASSNAME AccountsSoundInterface)
2434+
2435+set(dbusinterface_properties_xml "org.freedesktop.DBus.Properties.xml")
2436+set_source_files_properties(${dbusinterface_properties_xml} PROPERTIES
2437+ CLASSNAME DBusPropertiesInterface
2438+ NO_NAMESPACE YES
2439+ INCLUDE "dbus-types.h")
2440+
2441+set(dbusinterface_actions_xml "org.gtk.Actions.xml")
2442+set_source_files_properties(${dbusinterface_actions_xml} PROPERTIES
2443+ CLASSNAME MenusInterface)
2444+
2445+qt5_add_dbus_interface(interface_files ${dbusinterface_streamrestore_xml} stream_restore_interface)
2446+qt5_add_dbus_interface(interface_files ${dbusinterface_properties_xml} dbus_properties_interface)
2447+qt5_add_dbus_interface(interface_files ${dbusinterface_accounts_xml} dbus_accounts_interface)
2448+qt5_add_dbus_interface(interface_files ${dbusinterface_accountssound_xml} dbus_accountssound_interface)
2449+qt5_add_dbus_interface(interface_files ${dbusinterface_actions_xml} dbus_menus_interface)
2450+
2451+add_library(
2452+ sound-indicator-dbus-interfaces
2453+ STATIC
2454+ ${interface_files}
2455+ pulseaudio-volume.cpp
2456+)
2457+
2458+qt5_use_modules(
2459+ sound-indicator-dbus-interfaces
2460+ Core
2461+ DBus
2462+)
2463
2464=== added file 'tests/dbus-types/com.ubuntu.AccountsService.Sound.xml'
2465--- tests/dbus-types/com.ubuntu.AccountsService.Sound.xml 1970-01-01 00:00:00 +0000
2466+++ tests/dbus-types/com.ubuntu.AccountsService.Sound.xml 2015-10-13 07:44:39 +0000
2467@@ -0,0 +1,9 @@
2468+<node>
2469+ <interface name="com.ubuntu.AccountsService.Sound">
2470+ <method name="Set">
2471+ <arg direction="in" type="s" name="interface" />
2472+ <arg direction="in" type="s" name="property" />
2473+ <arg direction="out" type="o" name="path" />
2474+ </method>
2475+ </interface>
2476+</node>
2477
2478=== added file 'tests/dbus-types/dbus-types.h'
2479--- tests/dbus-types/dbus-types.h 1970-01-01 00:00:00 +0000
2480+++ tests/dbus-types/dbus-types.h 2015-10-13 07:44:39 +0000
2481@@ -0,0 +1,44 @@
2482+/*
2483+ * Copyright (C) 2015 Canonical, Ltd.
2484+ *
2485+ * This program is free software: you can redistribute it and/or modify it
2486+ * under the terms of the GNU General Public License version 3, as published
2487+ * by the Free Software Foundation.
2488+ *
2489+ * This program is distributed in the hope that it will be useful, but
2490+ * WITHOUT ANY WARRANTY; without even the implied warranties of
2491+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2492+ * PURPOSE. See the GNU General Public License for more details.
2493+ *
2494+ * You should have received a copy of the GNU General Public License along
2495+ * with this program. If not, see <http://www.gnu.org/licenses/>.
2496+ *
2497+ * Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
2498+ */
2499+#pragma once
2500+
2501+#include <QDBusMetaType>
2502+#include "pulseaudio-volume.h"
2503+
2504+namespace DBusTypes
2505+{
2506+ inline void registerMetaTypes()
2507+ {
2508+ PulseaudioVolume::registerMetaType();
2509+ PulseaudioVolumeArray::registerMetaType();
2510+ }
2511+
2512+ static constexpr char const* DBUS_NAME = "com.canonical.indicator.sound";
2513+
2514+ static constexpr char const* DBUS_PULSE = "org.PulseAudio1";
2515+
2516+ static constexpr char const* ACCOUNTS_SERVICE = "org.freedesktop.Accounts";
2517+
2518+ static constexpr char const* STREAM_RESTORE_NAME = "org.PulseAudio.Ext.StreamRestore1";
2519+
2520+ static constexpr char const* STREAM_RESTORE_PATH = "/org/pulseaudio/stream_restore1";
2521+
2522+ static constexpr char const* STREAM_RESTORE_ENTRY_NAME = "org.PulseAudio.Ext.StreamRestore1.RestoreEntry";
2523+
2524+} // namespace DBusTypes
2525+
2526
2527=== added file 'tests/dbus-types/org.PulseAudio.Ext.StreamRestore1.xml'
2528--- tests/dbus-types/org.PulseAudio.Ext.StreamRestore1.xml 1970-01-01 00:00:00 +0000
2529+++ tests/dbus-types/org.PulseAudio.Ext.StreamRestore1.xml 2015-10-13 07:44:39 +0000
2530@@ -0,0 +1,7 @@
2531+<node>
2532+ <interface name="org.PulseAudio.Ext.StreamRestore1">
2533+ <method name="GetEntryByName">
2534+ <arg direction="in" type="s" name="entry" />
2535+ <arg direction="out" type="o" name="value" />
2536+ </interface>
2537+</node>
2538
2539=== added file 'tests/dbus-types/org.freedesktop.Accounts.xml'
2540--- tests/dbus-types/org.freedesktop.Accounts.xml 1970-01-01 00:00:00 +0000
2541+++ tests/dbus-types/org.freedesktop.Accounts.xml 2015-10-13 07:44:39 +0000
2542@@ -0,0 +1,8 @@
2543+<node>
2544+ <interface name="org.freedesktop.Accounts">
2545+ <method name="FindUserByName">
2546+ <arg direction="in" type="s" name="user" />
2547+ <arg direction="out" type="o" name="path" />
2548+ </method>
2549+ </interface>
2550+</node>
2551
2552=== added file 'tests/dbus-types/org.freedesktop.DBus.Properties.xml'
2553--- tests/dbus-types/org.freedesktop.DBus.Properties.xml 1970-01-01 00:00:00 +0000
2554+++ tests/dbus-types/org.freedesktop.DBus.Properties.xml 2015-10-13 07:44:39 +0000
2555@@ -0,0 +1,22 @@
2556+<node>
2557+ <interface name="org.freedesktop.DBus.Properties">
2558+ <method name="Set">
2559+ <arg direction="in" type="s" name="entry" />
2560+ <arg direction="in" type="s" name="property" />
2561+ <arg direction="in" type="v" name="value" />
2562+ </method>
2563+
2564+ <method name="Get">
2565+ <arg direction="in" type="s" name="entry" />
2566+ <arg direction="in" type="s" name="property" />
2567+ <arg direction="out" type="v" name="value" />
2568+ </method>
2569+
2570+ <signal name="PropertiesChanged">
2571+ <arg type="s" name="interface_name"/>
2572+ <arg type="a{sv}" name="changed_properties"/>
2573+ <annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap"/>
2574+ <arg type="as" name="invalidated_properties"/>
2575+ </signal>
2576+ </interface>
2577+</node>
2578
2579=== added file 'tests/dbus-types/org.gtk.Actions.xml'
2580--- tests/dbus-types/org.gtk.Actions.xml 1970-01-01 00:00:00 +0000
2581+++ tests/dbus-types/org.gtk.Actions.xml 2015-10-13 07:44:39 +0000
2582@@ -0,0 +1,13 @@
2583+<node>
2584+ <interface name="org.gtk.Actions">
2585+ <signal name="Changed">
2586+ <arg name="removedActions" type="as" direction="in" />
2587+ <arg name="actionsFlags" type="a{sb}" direction="in" />
2588+ <annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="QVariantMap"/>
2589+ <arg name="actionsChanged" type="a{sv}" direction="in" />
2590+ <annotation name="org.qtproject.QtDBus.QtTypeName.In2" value="QVariantMap"/>
2591+ <arg name="actionsAdded" type="a{s(bgav)}" direction="in" />
2592+ <annotation name="org.qtproject.QtDBus.QtTypeName.In3" value="QVariantMap"/>
2593+ </signal>
2594+ </interface>
2595+</node>
2596
2597=== added file 'tests/dbus-types/pulseaudio-volume.cpp'
2598--- tests/dbus-types/pulseaudio-volume.cpp 1970-01-01 00:00:00 +0000
2599+++ tests/dbus-types/pulseaudio-volume.cpp 2015-10-13 07:44:39 +0000
2600@@ -0,0 +1,156 @@
2601+/*
2602+ * Copyright (C) 2015 Canonical, Ltd.
2603+ *
2604+ * This program is free software: you can redistribute it and/or modify it
2605+ * under the terms of the GNU General Public License version 3, as published
2606+ * by the Free Software Foundation.
2607+ *
2608+ * This program is distributed in the hope that it will be useful, but
2609+ * WITHOUT ANY WARRANTY; without even the implied warranties of
2610+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2611+ * PURPOSE. See the GNU General Public License for more details.
2612+ *
2613+ * You should have received a copy of the GNU General Public License along
2614+ * with this program. If not, see <http://www.gnu.org/licenses/>.
2615+ *
2616+ * Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
2617+ */
2618+#include "dbus-types.h"
2619+
2620+PulseaudioVolume::PulseaudioVolume() :
2621+ type_(0),
2622+ volume_(10)
2623+{
2624+}
2625+
2626+PulseaudioVolume::PulseaudioVolume(unsigned int type, unsigned int volume) :
2627+ type_(type)
2628+ , volume_(volume)
2629+{
2630+}
2631+
2632+PulseaudioVolume::PulseaudioVolume(const PulseaudioVolume &other) :
2633+ type_(other.type_),
2634+ volume_(other.volume_)
2635+{
2636+}
2637+
2638+PulseaudioVolume& PulseaudioVolume::operator=(const PulseaudioVolume &other)
2639+{
2640+ type_ = other.type_;
2641+ volume_ = other.volume_;
2642+
2643+ return *this;
2644+}
2645+
2646+PulseaudioVolume::~PulseaudioVolume()
2647+{
2648+}
2649+
2650+unsigned int PulseaudioVolume::getType() const
2651+{
2652+ return type_;
2653+}
2654+
2655+unsigned int PulseaudioVolume::getVolume() const
2656+{
2657+ return volume_;
2658+}
2659+
2660+void PulseaudioVolume::registerMetaType()
2661+{
2662+ qRegisterMetaType<PulseaudioVolume>("PulseaudioVolume");
2663+
2664+ qDBusRegisterMetaType<PulseaudioVolume>();
2665+}
2666+
2667+QDBusArgument &operator<<(QDBusArgument &argument, const PulseaudioVolume& volume)
2668+{
2669+ argument.beginStructure();
2670+ argument << volume.type_;
2671+ argument << volume.volume_;
2672+ argument.endStructure();
2673+
2674+ return argument;
2675+}
2676+
2677+const QDBusArgument &operator>>(const QDBusArgument &argument, PulseaudioVolume &volume)
2678+{
2679+ argument.beginStructure();
2680+ argument >> volume.type_;
2681+ argument >> volume.volume_;
2682+ argument.endStructure();
2683+
2684+ return argument;
2685+}
2686+
2687+PulseaudioVolumeArray::PulseaudioVolumeArray()
2688+{
2689+}
2690+
2691+PulseaudioVolumeArray::PulseaudioVolumeArray(const PulseaudioVolumeArray &other) :
2692+ volume_array_(other.volume_array_)
2693+{
2694+}
2695+
2696+PulseaudioVolumeArray& PulseaudioVolumeArray::operator=(const PulseaudioVolumeArray &other)
2697+{
2698+ volume_array_ = other.volume_array_;
2699+
2700+ return *this;
2701+}
2702+
2703+PulseaudioVolumeArray::~PulseaudioVolumeArray()
2704+{
2705+}
2706+
2707+int PulseaudioVolumeArray::getNumItems() const
2708+{
2709+ return volume_array_.size();
2710+}
2711+
2712+PulseaudioVolume PulseaudioVolumeArray::getItem(int i) const
2713+{
2714+ if (i < volume_array_.size())
2715+ {
2716+ return volume_array_[i];
2717+ }
2718+ return PulseaudioVolume();
2719+}
2720+
2721+void PulseaudioVolumeArray::addItem(PulseaudioVolume const &item)
2722+{
2723+ volume_array_.push_back(item);
2724+}
2725+
2726+void PulseaudioVolumeArray::registerMetaType()
2727+{
2728+ qRegisterMetaType<PulseaudioVolumeArray>("PulseaudioVolumeArray");
2729+
2730+ qDBusRegisterMetaType<PulseaudioVolumeArray>();
2731+}
2732+
2733+QDBusArgument &operator<<(QDBusArgument &argument, const PulseaudioVolumeArray& volume)
2734+{
2735+ argument.beginArray( qMetaTypeId<PulseaudioVolume>() );
2736+ for (int i = 0; i < volume.volume_array_.size(); ++ i)
2737+ {
2738+ PulseaudioVolume item = volume.getItem(i);
2739+ argument << item;
2740+ }
2741+ argument.endArray();
2742+ return argument;
2743+}
2744+
2745+const QDBusArgument &operator>>(const QDBusArgument &argument, PulseaudioVolumeArray &volume)
2746+{
2747+ argument.beginArray();
2748+ while ( !argument.atEnd() ) {
2749+ PulseaudioVolume item;
2750+ argument >> item;
2751+ volume.volume_array_.push_back(item);
2752+ }
2753+ argument.endArray();
2754+
2755+ return argument;
2756+}
2757
2758=== added file 'tests/dbus-types/pulseaudio-volume.h'
2759--- tests/dbus-types/pulseaudio-volume.h 1970-01-01 00:00:00 +0000
2760+++ tests/dbus-types/pulseaudio-volume.h 2015-10-13 07:44:39 +0000
2761@@ -0,0 +1,69 @@
2762+/*
2763+ * Copyright (C) 2015 Canonical, Ltd.
2764+ *
2765+ * This program is free software: you can redistribute it and/or modify it
2766+ * under the terms of the GNU General Public License version 3, as published
2767+ * by the Free Software Foundation.
2768+ *
2769+ * This program is distributed in the hope that it will be useful, but
2770+ * WITHOUT ANY WARRANTY; without even the implied warranties of
2771+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2772+ * PURPOSE. See the GNU General Public License for more details.
2773+ *
2774+ * You should have received a copy of the GNU General Public License along
2775+ * with this program. If not, see <http://www.gnu.org/licenses/>.
2776+ *
2777+ * Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
2778+ */
2779+#pragma once
2780+
2781+#include <QtDBus>
2782+
2783+class PulseaudioVolume
2784+{
2785+public:
2786+ PulseaudioVolume();
2787+ PulseaudioVolume(unsigned int type, unsigned int volume);
2788+ PulseaudioVolume(const PulseaudioVolume &other);
2789+ PulseaudioVolume& operator=(const PulseaudioVolume &other);
2790+ ~PulseaudioVolume();
2791+
2792+ friend QDBusArgument &operator<<(QDBusArgument &argument, PulseaudioVolume const & volume);
2793+ friend const QDBusArgument &operator>>(QDBusArgument const & argument, PulseaudioVolume &volume);
2794+
2795+ unsigned int getType() const;
2796+ unsigned int getVolume() const;
2797+
2798+ //register Message with the Qt type system
2799+ static void registerMetaType();
2800+
2801+private:
2802+ unsigned int type_;
2803+ unsigned int volume_;
2804+};
2805+
2806+class PulseaudioVolumeArray
2807+{
2808+public:
2809+ PulseaudioVolumeArray();
2810+ PulseaudioVolumeArray(QString const &interface, QString const &property, QDBusVariant const& value);
2811+ PulseaudioVolumeArray(const PulseaudioVolumeArray &other);
2812+ PulseaudioVolumeArray& operator=(const PulseaudioVolumeArray &other);
2813+ ~PulseaudioVolumeArray();
2814+
2815+ friend QDBusArgument &operator<<(QDBusArgument &argument, PulseaudioVolumeArray const & volume);
2816+ friend const QDBusArgument &operator>>(QDBusArgument const & argument, PulseaudioVolumeArray &volume);
2817+
2818+ int getNumItems() const;
2819+ PulseaudioVolume getItem(int i) const;
2820+ void addItem(PulseaudioVolume const &item);
2821+
2822+ //register Message with the Qt type system
2823+ static void registerMetaType();
2824+
2825+private:
2826+ QVector<PulseaudioVolume> volume_array_;
2827+};
2828+
2829+Q_DECLARE_METATYPE(PulseaudioVolume)
2830+Q_DECLARE_METATYPE(PulseaudioVolumeArray)
2831
2832=== added directory 'tests/integration'
2833=== added file 'tests/integration/CMakeLists.txt'
2834--- tests/integration/CMakeLists.txt 1970-01-01 00:00:00 +0000
2835+++ tests/integration/CMakeLists.txt 2015-10-13 07:44:39 +0000
2836@@ -0,0 +1,132 @@
2837+set(CMAKE_AUTOMOC ON)
2838+set(CMAKE_INCLUDE_CURRENT_DIR ON)
2839+
2840+include(FindGMock)
2841+
2842+#pkg_check_modules(GMENUHARNESS REQUIRED libgmenuharness REQUIRED)
2843+#include_directories(${GMENUHARNESS_INCLUDE_DIRS})
2844+include_directories("${CMAKE_SOURCE_DIR}/include")
2845+
2846+pkg_check_modules(QTDBUSTEST REQUIRED libqtdbustest-1 REQUIRED)
2847+include_directories(${QTDBUSTEST_INCLUDE_DIRS})
2848+
2849+pkg_check_modules(QTDBUSMOCK REQUIRED libqtdbusmock-1 REQUIRED)
2850+include_directories(${QTDBUSMOCK_INCLUDE_DIRS})
2851+
2852+find_package(Qt5Test REQUIRED)
2853+include_directories(${Qt5Test_INCLUDE_DIRS})
2854+
2855+find_package(Qt5DBus REQUIRED)
2856+include_directories(${Qt5DBus_INCLUDE_DIRS})
2857+
2858+include_directories(${CMAKE_CURRENT_SOURCE_DIR})
2859+include_directories(${GMOCK_INCLUDE_DIRS})
2860+include_directories(${GTEST_INCLUDE_DIRS})
2861+
2862+include_directories("${CMAKE_SOURCE_DIR}/tests/dbus-types")
2863+include_directories("${CMAKE_BINARY_DIR}/tests/dbus-types")
2864+
2865+add_definitions(-DSOUND_SERVICE_BIN="${CMAKE_BINARY_DIR}/src/indicator-sound-service"
2866+ -DSTREAM_RESTORE_TABLE="${CMAKE_SOURCE_DIR}/tests/integration/touch-stream-restore.table"
2867+ -DVOLUME_SET_BIN="${CMAKE_BINARY_DIR}/tests/integration/set-volume"
2868+ -DACCOUNTS_SERVICE_BIN="${CMAKE_BINARY_DIR}/tests/service-mocks/accounts-mock/accounts-service-sound"
2869+ -DMEDIA_PLAYER_MPRIS_BIN="${CMAKE_BINARY_DIR}/tests/service-mocks/media-player-mpris-mock/media-player-mpris-mock"
2870+ -DMEDIA_PLAYER_MPRIS_UPDATE_BIN="${CMAKE_BINARY_DIR}/tests/service-mocks/media-player-mpris-mock/media-player-mpris-mock-update"
2871+ -DTEST_SOUND="${CMAKE_SOURCE_DIR}/tests/integration/test-sound.wav"
2872+ -DQT_NO_KEYWORDS=1
2873+ -DXDG_DATA_DIRS="${XDG_DATA_DIRS}"
2874+)
2875+
2876+set(GLIB_REQUIRED_VERSION 2.26)
2877+
2878+pkg_check_modules(
2879+ GLIB REQUIRED
2880+ glib-2.0>=${GLIB_REQUIRED_VERSION}
2881+ gio-2.0>=${GLIB_REQUIRED_VERSION}
2882+)
2883+include_directories(${GLIB_INCLUDE_DIRS})
2884+
2885+set(
2886+ INTEGRATION_TESTS_SRC
2887+ indicator-sound-test-base.cpp
2888+ test-indicator.cpp
2889+ utils/dbus-pulse-volume.cpp
2890+ main.cpp
2891+)
2892+
2893+add_executable(
2894+ integration-tests
2895+ ${INTEGRATION_TESTS_SRC}
2896+)
2897+
2898+qt5_use_modules(
2899+ integration-tests
2900+ Core
2901+ DBus
2902+ Test
2903+)
2904+
2905+target_link_libraries(
2906+ integration-tests
2907+ sound-indicator-dbus-interfaces
2908+ ${QTDBUSMOCK_LDFLAGS}
2909+ ${QTDBUSTEST_LDFLAGS}
2910+ ${GTEST_LIBRARIES}
2911+ ${GMOCK_LIBRARIES}
2912+# ${GMENUHARNESS_LDFLAGS}
2913+ ${GLIB_LDFLAGS}
2914+ gmenuharness-shared
2915+)
2916+
2917+add_test(
2918+ integration-tests
2919+ integration-tests
2920+)
2921+
2922+set(
2923+ SET-VOLUME-SRC
2924+ utils/dbus-pulse-volume.cpp
2925+ utils/set-volume.cpp
2926+)
2927+
2928+set(
2929+ GET-VOLUME-SRC
2930+ utils/dbus-pulse-volume.cpp
2931+ utils/get-volume.cpp
2932+)
2933+
2934+add_executable(
2935+ set-volume
2936+ ${SET-VOLUME-SRC}
2937+)
2938+
2939+add_executable(
2940+ get-volume
2941+ ${GET-VOLUME-SRC}
2942+)
2943+
2944+qt5_use_modules(
2945+ set-volume
2946+ Core
2947+ DBus
2948+ Test
2949+)
2950+
2951+qt5_use_modules(
2952+ get-volume
2953+ Core
2954+ DBus
2955+ Test
2956+)
2957+
2958+target_link_libraries(
2959+ get-volume
2960+ sound-indicator-dbus-interfaces
2961+)
2962+
2963+target_link_libraries(
2964+ set-volume
2965+ sound-indicator-dbus-interfaces
2966+)
2967+
2968+#add_subdirectory(utils)
2969\ No newline at end of file
2970
2971=== added file 'tests/integration/Copy of test-sound.wav'
2972Binary files tests/integration/Copy of test-sound.wav 1970-01-01 00:00:00 +0000 and tests/integration/Copy of test-sound.wav 2015-10-13 07:44:39 +0000 differ
2973=== added file 'tests/integration/indicator-sound-test-base.cpp'
2974--- tests/integration/indicator-sound-test-base.cpp 1970-01-01 00:00:00 +0000
2975+++ tests/integration/indicator-sound-test-base.cpp 2015-10-13 07:44:39 +0000
2976@@ -0,0 +1,411 @@
2977+/*
2978+ * Copyright (C) 2015 Canonical, Ltd.
2979+ *
2980+ * This program is free software: you can redistribute it and/or modify it
2981+ * under the terms of the GNU General Public License version 3, as published
2982+ * by the Free Software Foundation.
2983+ *
2984+ * This program is distributed in the hope that it will be useful, but
2985+ * WITHOUT ANY WARRANTY; without even the implied warranties of
2986+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
2987+ * PURPOSE. See the GNU General Public License for more details.
2988+ *
2989+ * You should have received a copy of the GNU General Public License along
2990+ * with this program. If not, see <http://www.gnu.org/licenses/>.
2991+ *
2992+ * Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
2993+ */
2994+
2995+#include "indicator-sound-test-base.h"
2996+
2997+#include "dbus_menus_interface.h"
2998+#include "dbus_properties_interface.h"
2999+#include "dbus_accounts_interface.h"
3000+#include "dbus_accountssound_interface.h"
3001+#include "dbus-types.h"
3002+
3003+#include <gio/gio.h>
3004+
3005+#include <QSignalSpy>
3006+#include "utils/dbus-pulse-volume.h"
3007+
3008+using namespace QtDBusTest;
3009+using namespace QtDBusMock;
3010+using namespace std;
3011+using namespace testing;
3012+namespace mh = unity::gmenuharness;
3013+
3014+IndicatorSoundTestBase::IndicatorSoundTestBase() :
3015+ dbusMock(dbusTestRunner)
3016+{
3017+}
3018+
3019+IndicatorSoundTestBase::~IndicatorSoundTestBase()
3020+{
3021+
3022+}
3023+
3024+bool IndicatorSoundTestBase::setStreamRestoreVolume(QString const &role, double volume)
3025+{
3026+ QProcess setVolume;
3027+ setVolume.start(VOLUME_SET_BIN, QStringList()
3028+ << role
3029+ << QString("%1").arg(volume));
3030+ if (!setVolume.waitForStarted())
3031+ return false;
3032+
3033+ if (!setVolume.waitForFinished())
3034+ return false;
3035+
3036+ return setVolume.exitCode() == 0;
3037+}
3038+
3039+bool IndicatorSoundTestBase::setSinkVolume(double volume)
3040+{
3041+ QString volume_percentage = QString("%1\%").arg(volume*100);
3042+ QProcess setVolume;
3043+ setVolume.start("pactl", QStringList()
3044+ << "-s"
3045+ << "127.0.0.1"
3046+ << "set-sink-volume"
3047+ << "0"
3048+ << volume_percentage);
3049+ if (!setVolume.waitForStarted())
3050+ return false;
3051+
3052+ if (!setVolume.waitForFinished())
3053+ return false;
3054+
3055+ return setVolume.exitCode() == 0;
3056+}
3057+
3058+bool IndicatorSoundTestBase::clearGSettingsPlayers()
3059+{
3060+ QProcess clearPlayers;
3061+
3062+ clearPlayers.start("gsettings", QStringList()
3063+ << "set"
3064+ << "com.canonical.indicator.sound"
3065+ << "interested-media-players"
3066+ << "[]");
3067+ if (!clearPlayers.waitForStarted())
3068+ return false;
3069+
3070+ if (!clearPlayers.waitForFinished())
3071+ return false;
3072+
3073+ return clearPlayers.exitCode() == 0;
3074+}
3075+
3076+bool IndicatorSoundTestBase::startTestMprisPlayer(QString const& playerName)
3077+{
3078+ testPlayer1.terminate();
3079+ testPlayer1.start(MEDIA_PLAYER_MPRIS_BIN, QStringList()
3080+ << playerName);
3081+ if (!testPlayer1.waitForStarted())
3082+ return false;
3083+
3084+
3085+ return true;
3086+}
3087+
3088+bool IndicatorSoundTestBase::setTestMprisPlayerProperty(QString const &testPlayer, QString const &property, bool value)
3089+{
3090+ QProcess setProperty;
3091+ QString strValue;
3092+ strValue = value ? "true" : "false";
3093+
3094+ setProperty.start(MEDIA_PLAYER_MPRIS_UPDATE_BIN, QStringList()
3095+ << testPlayer
3096+ << property
3097+ << strValue);
3098+ if (!setProperty.waitForStarted())
3099+ return false;
3100+
3101+ if (!setProperty.waitForFinished())
3102+ return false;
3103+
3104+ return setProperty.exitCode() == 0;
3105+}
3106+
3107+bool IndicatorSoundTestBase::startTestSound(QString const &role)
3108+{
3109+ testSoundProcess.terminate();
3110+ testSoundProcess.start("paplay", QStringList()
3111+ << "-s"
3112+ << "127.0.0.1"
3113+ << TEST_SOUND
3114+ << QString("--property=media.role=%1").arg(role));
3115+
3116+ if (!testSoundProcess.waitForStarted())
3117+ return false;
3118+
3119+ return true;
3120+}
3121+
3122+void IndicatorSoundTestBase::stopTestSound()
3123+{
3124+ testSoundProcess.terminate();
3125+}
3126+
3127+void IndicatorSoundTestBase::startPulseDesktop()
3128+{
3129+ try
3130+ {
3131+ pulseaudio.reset(
3132+ new QProcessDBusService(DBusTypes::DBUS_PULSE,
3133+ QDBusConnection::SessionBus,
3134+ "pulseaudio",
3135+ QStringList() << "--start"
3136+ << "-vvvv"
3137+ << "--disable-shm=true"
3138+ << "--daemonize=false"
3139+ << "--use-pid-file=false"
3140+ << "--system=false"
3141+ << "--exit-idle-time=-1"
3142+ << "-n"
3143+ << "--load=module-null-sink"
3144+ << "--log-target=file:/tmp/pulse-daemon.log"
3145+ << "--load=module-dbus-protocol"
3146+ << "--load=module-native-protocol-tcp auth-ip-acl=127.0.0.1"
3147+ ));
3148+ pulseaudio->start(dbusTestRunner.sessionConnection());
3149+ }
3150+ catch (exception const& e)
3151+ {
3152+ cout << "pulseaudio(): " << e.what() << endl;
3153+ throw;
3154+ }
3155+}
3156+
3157+void IndicatorSoundTestBase::startPulsePhone()
3158+{
3159+ try
3160+ {
3161+ pulseaudio.reset(
3162+ new QProcessDBusService(DBusTypes::DBUS_PULSE,
3163+ QDBusConnection::SessionBus,
3164+ "pulseaudio",
3165+ QStringList() << "--start"
3166+ << "-vvvv"
3167+ << "--disable-shm=true"
3168+ << "--daemonize=false"
3169+ << "--use-pid-file=false"
3170+ << "--system=false"
3171+ << "--exit-idle-time=-1"
3172+ << "-n"
3173+ << "--load=module-null-sink"
3174+ << "--log-target=file:/tmp/pulse-daemon.log"
3175+ << QString("--load=module-stream-restore restore_device=false restore_muted=false fallback_table=%1").arg(STREAM_RESTORE_TABLE)
3176+ << "--load=module-dbus-protocol"
3177+ << "--load=module-native-protocol-tcp auth-ip-acl=127.0.0.1"
3178+ ));
3179+ pulseaudio->start(dbusTestRunner.sessionConnection());
3180+ }
3181+ catch (exception const& e)
3182+ {
3183+ cout << "pulseaudio(): " << e.what() << endl;
3184+ throw;
3185+ }
3186+}
3187+
3188+void IndicatorSoundTestBase::startAccountsService()
3189+{
3190+ try
3191+ {
3192+ accountsService.reset(
3193+ new QProcessDBusService(DBusTypes::ACCOUNTS_SERVICE,
3194+ QDBusConnection::SystemBus,
3195+ ACCOUNTS_SERVICE_BIN,
3196+ QStringList()));
3197+ accountsService->start(dbusTestRunner.systemConnection());
3198+
3199+ initializeAccountsInterface();
3200+ }
3201+ catch (exception const& e)
3202+ {
3203+ cout << "accountsService(): " << e.what() << endl;
3204+ throw;
3205+ }
3206+}
3207+
3208+void IndicatorSoundTestBase::startIndicator()
3209+{
3210+ try
3211+ {
3212+ setenv("PULSE_SERVER", "127.0.0.1", true);
3213+ indicator.reset(
3214+ new QProcessDBusService(DBusTypes::DBUS_NAME,
3215+ QDBusConnection::SessionBus,
3216+ SOUND_SERVICE_BIN,
3217+ QStringList()));
3218+ indicator->start(dbusTestRunner.sessionConnection());
3219+ }
3220+ catch (exception const& e)
3221+ {
3222+ cout << "startIndicator(): " << e.what() << endl;
3223+ throw;
3224+ }
3225+}
3226+
3227+mh::MenuMatcher::Parameters IndicatorSoundTestBase::desktopParameters()
3228+{
3229+ return mh::MenuMatcher::Parameters(
3230+ "com.canonical.indicator.sound",
3231+ { { "indicator", "/com/canonical/indicator/sound" } },
3232+ "/com/canonical/indicator/sound/desktop");
3233+}
3234+
3235+mh::MenuMatcher::Parameters IndicatorSoundTestBase::phoneParameters()
3236+{
3237+ return mh::MenuMatcher::Parameters(
3238+ "com.canonical.indicator.sound",
3239+ { { "indicator", "/com/canonical/indicator/sound" } },
3240+ "/com/canonical/indicator/sound/phone");
3241+}
3242+
3243+void IndicatorSoundTestBase::SetUp()
3244+{
3245+ setenv("XDG_DATA_DIRS", XDG_DATA_DIRS, true);
3246+ setenv("DBUS_SYSTEM_BUS_ADDRESS", dbusTestRunner.systemBus().toStdString().c_str(), true);
3247+}
3248+
3249+void IndicatorSoundTestBase::TearDown()
3250+{
3251+ unsetenv("XDG_DATA_DIRS");
3252+ unsetenv("PULSE_SERVER");
3253+ unsetenv("DBUS_SYSTEM_BUS_ADDRESS");
3254+}
3255+
3256+void gvariant_deleter(GVariant* varptr)
3257+{
3258+ if (varptr != nullptr)
3259+ {
3260+ g_variant_unref(varptr);
3261+ }
3262+}
3263+
3264+std::shared_ptr<GVariant> IndicatorSoundTestBase::volume_variant(double volume)
3265+{
3266+ GVariantBuilder builder;
3267+
3268+ g_variant_builder_init(&builder, G_VARIANT_TYPE_VARDICT);
3269+ g_variant_builder_add(&builder,
3270+ "{sv}",
3271+ "title",
3272+ g_variant_new_string("_Sound"));
3273+
3274+ g_variant_builder_add(&builder,
3275+ "{sv}",
3276+ "accessible-desc",
3277+ g_variant_new_string("_Sound"));
3278+
3279+ auto icon = g_themed_icon_new("icon");
3280+ g_variant_builder_add(&builder,
3281+ "{sv}",
3282+ "icon",
3283+ g_icon_serialize(icon));
3284+
3285+ g_variant_builder_add(&builder,
3286+ "{sv}",
3287+ "visible",
3288+ g_variant_new_boolean(true));
3289+ return shared_ptr<GVariant>(g_variant_builder_end(&builder), &gvariant_deleter);
3290+}
3291+
3292+unity::gmenuharness::MenuItemMatcher IndicatorSoundTestBase::volumeSlider(double volume)
3293+{
3294+ return mh::MenuItemMatcher().radio()
3295+ .label("Volume")
3296+ .round_doubles(0.1)
3297+ .int32_attribute("target", 0)
3298+ .double_attribute("min-value", 0.0)
3299+ .double_attribute("max-value", 1.0)
3300+ .double_attribute("step", 0.01)
3301+ .string_attribute("x-canonical-type", "com.canonical.unity.slider")
3302+ .themed_icon("max-icon", {"audio-volume-high-panel", "audio-volume-high", "audio-volume", "audio"})
3303+ .themed_icon("min-icon", {"audio-volume-low-zero-panel", "audio-volume-low-zero", "audio-volume-low", "audio-volume", "audio"})
3304+ .pass_through_double_attribute("action", volume);
3305+}
3306+
3307+unity::gmenuharness::MenuItemMatcher IndicatorSoundTestBase::silentModeSwitch(bool toggled)
3308+{
3309+ return mh::MenuItemMatcher::checkbox()
3310+ .label("Silent Mode")
3311+ .action("indicator.silent-mode")
3312+ .toggled(toggled);
3313+}
3314+
3315+bool IndicatorSoundTestBase::waitMenuChange()
3316+{
3317+ if (signal_spy_menu_changed_)
3318+ {
3319+ return signal_spy_menu_changed_->wait();
3320+ }
3321+ return false;
3322+}
3323+
3324+bool IndicatorSoundTestBase::initializeMenuChangedSignal()
3325+{
3326+ if (!menu_interface_)
3327+ {
3328+ menu_interface_.reset(new MenusInterface("com.canonical.indicator.sound",
3329+ "/com/canonical/indicator/sound",
3330+ dbusTestRunner.sessionConnection(), 0));
3331+ }
3332+ if (menu_interface_)
3333+ {
3334+ qDebug() << "Waiting for signal";
3335+ signal_spy_menu_changed_.reset(new QSignalSpy(menu_interface_.get(), &MenusInterface::Changed));
3336+ }
3337+ if (!menu_interface_ || !signal_spy_menu_changed_)
3338+ {
3339+ return false;
3340+ }
3341+ return true;
3342+}
3343+
3344+bool IndicatorSoundTestBase::waitVolumeChangedInIndicator()
3345+{
3346+ if (signal_spy_volume_changed_)
3347+ {
3348+ return signal_spy_volume_changed_->wait();
3349+ }
3350+ return false;
3351+}
3352+
3353+void IndicatorSoundTestBase::initializeAccountsInterface()
3354+{
3355+ auto username = qgetenv("USER");
3356+ if (username != "")
3357+ {
3358+ std::unique_ptr<AccountsInterface> accountsInterface(new AccountsInterface("org.freedesktop.Accounts",
3359+ "/org/freedesktop/Accounts",
3360+ dbusTestRunner.systemConnection(), 0));
3361+
3362+ QDBusReply<QDBusObjectPath> userResp = accountsInterface->call(QLatin1String("FindUserByName"),
3363+ QLatin1String(username));
3364+
3365+ if (!userResp.isValid())
3366+ {
3367+ qWarning() << "SetVolume::initializeAccountsInterface(): D-Bus error: " << userResp.error().message();
3368+ }
3369+
3370+ auto userPath = userResp.value().path();
3371+ if (userPath != "")
3372+ {
3373+ std::unique_ptr<AccountsSoundInterface> soundInterface(new AccountsSoundInterface("org.freedesktop.Accounts",
3374+ userPath,
3375+ dbusTestRunner.systemConnection(), 0));
3376+
3377+ accounts_interface_.reset(new DBusPropertiesInterface("org.freedesktop.Accounts",
3378+ userPath,
3379+ dbusTestRunner.systemConnection(), 0));
3380+ if (!accounts_interface_->isValid())
3381+ {
3382+ qWarning() << "SetVolume::initializeAccountsInterface(): D-Bus error: " << accounts_interface_->lastError().message();
3383+ }
3384+ signal_spy_volume_changed_.reset(new QSignalSpy(accounts_interface_.get(),&DBusPropertiesInterface::PropertiesChanged));
3385+ }
3386+ }
3387+}
3388
3389=== added file 'tests/integration/indicator-sound-test-base.h'
3390--- tests/integration/indicator-sound-test-base.h 1970-01-01 00:00:00 +0000
3391+++ tests/integration/indicator-sound-test-base.h 2015-10-13 07:44:39 +0000
3392@@ -0,0 +1,105 @@
3393+/*
3394+ * Copyright (C) 2015 Canonical, Ltd.
3395+ *
3396+ * This program is free software: you can redistribute it and/or modify it
3397+ * under the terms of the GNU General Public License version 3, as published
3398+ * by the Free Software Foundation.
3399+ *
3400+ * This program is distributed in the hope that it will be useful, but
3401+ * WITHOUT ANY WARRANTY; without even the implied warranties of
3402+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
3403+ * PURPOSE. See the GNU General Public License for more details.
3404+ *
3405+ * You should have received a copy of the GNU General Public License along
3406+ * with this program. If not, see <http://www.gnu.org/licenses/>.
3407+ *
3408+ * Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
3409+ */
3410+
3411+#pragma once
3412+
3413+#include <libqtdbustest/DBusTestRunner.h>
3414+#include <libqtdbustest/QProcessDBusService.h>
3415+#include <libqtdbusmock/DBusMock.h>
3416+
3417+#include <unity/gmenuharness/MatchUtils.h>
3418+#include <unity/gmenuharness/MenuMatcher.h>
3419+
3420+#include <gmock/gmock.h>
3421+#include <gtest/gtest.h>
3422+
3423+class MenusInterface;
3424+class DBusPulseVolume;
3425+class DBusPropertiesInterface;
3426+class QSignalSpy;
3427+
3428+class IndicatorSoundTestBase: public testing::Test
3429+{
3430+public:
3431+ IndicatorSoundTestBase();
3432+
3433+ ~IndicatorSoundTestBase();
3434+
3435+protected:
3436+ void SetUp() override;
3437+ void TearDown() override;
3438+
3439+ void startIndicator();
3440+ void startPulseDesktop();
3441+ void startPulsePhone();
3442+ void startAccountsService();
3443+
3444+ bool clearGSettingsPlayers();
3445+
3446+ bool startTestMprisPlayer(QString const& playerName);
3447+
3448+ bool setTestMprisPlayerProperty(QString const &testPlayer, QString const &property, bool value);
3449+
3450+ bool setStreamRestoreVolume(QString const &role, double volume);
3451+
3452+ bool setSinkVolume(double volume);
3453+
3454+ bool startTestSound(QString const &role);
3455+
3456+ void stopTestSound();
3457+
3458+ static std::shared_ptr<GVariant> volume_variant(double volume);
3459+
3460+ static unity::gmenuharness::MenuMatcher::Parameters desktopParameters();
3461+
3462+ static unity::gmenuharness::MenuMatcher::Parameters phoneParameters();
3463+
3464+ static unity::gmenuharness::MenuItemMatcher volumeSlider(double volume);
3465+
3466+ static unity::gmenuharness::MenuItemMatcher silentModeSwitch(bool toggled);
3467+
3468+ bool waitMenuChange();
3469+
3470+ bool initializeMenuChangedSignal();
3471+
3472+ bool waitVolumeChangedInIndicator();
3473+
3474+ void initializeAccountsInterface();
3475+
3476+ QtDBusTest::DBusTestRunner dbusTestRunner;
3477+
3478+ QtDBusMock::DBusMock dbusMock;
3479+
3480+ QtDBusTest::DBusServicePtr indicator;
3481+
3482+ QtDBusTest::DBusServicePtr pulseaudio;
3483+
3484+ QtDBusTest::DBusServicePtr accountsService;
3485+
3486+ QProcess testSoundProcess;
3487+
3488+ QProcess testPlayer1;
3489+
3490+ std::unique_ptr<MenusInterface> menu_interface_;
3491+
3492+ std::unique_ptr<DBusPropertiesInterface> accounts_interface_;
3493+
3494+ std::unique_ptr<QSignalSpy> signal_spy_volume_changed_;
3495+
3496+ std::unique_ptr<QSignalSpy> signal_spy_menu_changed_;
3497+};
3498
3499=== added file 'tests/integration/main.cpp'
3500--- tests/integration/main.cpp 1970-01-01 00:00:00 +0000
3501+++ tests/integration/main.cpp 2015-10-13 07:44:39 +0000
3502@@ -0,0 +1,58 @@
3503+/*
3504+ * Copyright © 2014 Canonical Ltd.
3505+ *
3506+ * This program is free software: you can redistribute it and/or modify it
3507+ * under the terms of the GNU Lesser General Public License version 3,
3508+ * as published by the Free Software Foundation.
3509+ *
3510+ * This program is distributed in the hope that it will be useful,
3511+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
3512+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3513+ * GNU Lesser General Public License for more details.
3514+ *
3515+ * You should have received a copy of the GNU Lesser General Public License
3516+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
3517+ *
3518+ * Authors:
3519+ * Pete Woods <pete.woods@canonical.com>
3520+ */
3521+
3522+//#include <config.h>
3523+
3524+#include <QCoreApplication>
3525+#include <QTimer>
3526+#include <gtest/gtest.h>
3527+
3528+#include <libqtdbusmock/DBusMock.h>
3529+
3530+#include "dbus-types.h"
3531+
3532+using namespace QtDBusMock;
3533+
3534+class Runner: public QObject
3535+{
3536+ Q_OBJECT
3537+public Q_SLOTS:
3538+ void run()
3539+ {
3540+ QCoreApplication::exit(RUN_ALL_TESTS());
3541+ }
3542+};
3543+
3544+int main(int argc, char **argv)
3545+{
3546+ qputenv("LANG", "C.UTF-8");
3547+ unsetenv("LC_ALL");
3548+
3549+ QCoreApplication application(argc, argv);
3550+ DBusMock::registerMetaTypes();
3551+ DBusTypes::registerMetaTypes();
3552+ ::testing::InitGoogleTest(&argc, argv);
3553+
3554+ Runner runner;
3555+ QTimer::singleShot(0, &runner, SLOT(run()));
3556+
3557+ return application.exec();
3558+}
3559+
3560+#include "main.moc"
3561
3562=== added file 'tests/integration/test-indicator.cpp'
3563--- tests/integration/test-indicator.cpp 1970-01-01 00:00:00 +0000
3564+++ tests/integration/test-indicator.cpp 2015-10-13 07:44:39 +0000
3565@@ -0,0 +1,637 @@
3566+/*
3567+ * Copyright (C) 2015 Canonical, Ltd.
3568+ *
3569+ * This program is free software: you can redistribute it and/or modify it
3570+ * under the terms of the GNU General Public License version 3, as published
3571+ * by the Free Software Foundation.
3572+ *
3573+ * This program is distributed in the hope that it will be useful, but
3574+ * WITHOUT ANY WARRANTY; without even the implied warranties of
3575+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
3576+ * PURPOSE. See the GNU General Public License for more details.
3577+ *
3578+ * You should have received a copy of the GNU General Public License along
3579+ * with this program. If not, see <http://www.gnu.org/licenses/>.
3580+ *
3581+ * Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
3582+ */
3583+
3584+#include <indicator-sound-test-base.h>
3585+
3586+#include <QDebug>
3587+#include <QTestEventLoop>
3588+#include <QSignalSpy>
3589+
3590+using namespace std;
3591+using namespace testing;
3592+namespace mh = unity::gmenuharness;
3593+namespace
3594+{
3595+
3596+class TestIndicator: public IndicatorSoundTestBase
3597+{
3598+};
3599+
3600+TEST_F(TestIndicator, PhoneChangeRoleVolume)
3601+{
3602+ double INITIAL_VOLUME = 0.0;
3603+
3604+ ASSERT_NO_THROW(startAccountsService());
3605+ ASSERT_NO_THROW(startPulsePhone());
3606+
3607+ // initialize volumes in pulseaudio
3608+ EXPECT_TRUE(setStreamRestoreVolume("alert", INITIAL_VOLUME));
3609+
3610+ // start now the indicator, so it picks the new volumes
3611+ ASSERT_NO_THROW(startIndicator());
3612+
3613+ // Generate a random volume
3614+ QTime now = QTime::currentTime();
3615+ qsrand(now.msec());
3616+ int randInt = qrand() % 100;
3617+ double randomVolume = randInt / 100.0;
3618+
3619+ // set an initial volume to the alert role
3620+ setStreamRestoreVolume("alert", 1.0);
3621+ EXPECT_TRUE(waitVolumeChangedInIndicator());
3622+
3623+ // play a test sound, it should change the role in the indicator
3624+ EXPECT_TRUE(startTestSound("multimedia"));
3625+ EXPECT_TRUE(waitVolumeChangedInIndicator());
3626+
3627+ // set the random volume to the multimedia role
3628+ EXPECT_TRUE(setStreamRestoreVolume("multimedia", randomVolume));
3629+ if (randomVolume != INITIAL_VOLUME)
3630+ {
3631+ EXPECT_TRUE(waitVolumeChangedInIndicator());
3632+ }
3633+
3634+ // check the indicator
3635+ EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
3636+ .item(mh::MenuItemMatcher()
3637+ .action("indicator.root")
3638+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
3639+ .string_attribute("x-canonical-scroll-action", "indicator.scroll")
3640+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
3641+ .string_attribute("submenu-action", "indicator.indicator-shown")
3642+ .mode(mh::MenuItemMatcher::Mode::starts_with)
3643+ .submenu()
3644+ .item(mh::MenuItemMatcher()
3645+ .section()
3646+ .item(silentModeSwitch(false))
3647+ .item(volumeSlider(randomVolume))
3648+ )
3649+ ).match());
3650+
3651+ // check that the last item is Sound Settings
3652+ EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
3653+ .item(mh::MenuItemMatcher()
3654+ .action("indicator.root")
3655+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
3656+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
3657+ .mode(mh::MenuItemMatcher::Mode::ends_with)
3658+ .submenu()
3659+ .item(mh::MenuItemMatcher()
3660+ .label("Sound Settings…")
3661+ .action("indicator.phone-settings")
3662+ )
3663+ ).match());
3664+
3665+ // stop the test sound, the role should change again to alert
3666+ stopTestSound();
3667+ if (randomVolume != 1.0)
3668+ {
3669+ // we only wait if the volume in the alert and the
3670+ // one set in the multimedia roles differ
3671+ EXPECT_TRUE(waitVolumeChangedInIndicator());
3672+ }
3673+
3674+ // check the initial volume for the alert role
3675+ EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
3676+ .item(mh::MenuItemMatcher()
3677+ .action("indicator.root")
3678+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
3679+ .string_attribute("x-canonical-scroll-action", "indicator.scroll")
3680+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
3681+ .string_attribute("submenu-action", "indicator.indicator-shown")
3682+ .mode(mh::MenuItemMatcher::Mode::starts_with)
3683+ .submenu()
3684+ .item(mh::MenuItemMatcher()
3685+ .section()
3686+ .item(silentModeSwitch(false))
3687+ .item(volumeSlider(1.0))
3688+ )
3689+ ).match());
3690+
3691+ // check that the last item is Sound Settings
3692+ EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
3693+ .item(mh::MenuItemMatcher()
3694+ .action("indicator.root")
3695+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
3696+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
3697+ .mode(mh::MenuItemMatcher::Mode::ends_with)
3698+ .submenu()
3699+ .item(mh::MenuItemMatcher()
3700+ .label("Sound Settings…")
3701+ .action("indicator.phone-settings")
3702+ )
3703+ ).match());
3704+}
3705+
3706+TEST_F(TestIndicator, PhoneBasicInitialVolume)
3707+{
3708+ double INITIAL_VOLUME = 0.0;
3709+
3710+ ASSERT_NO_THROW(startAccountsService());
3711+ EXPECT_TRUE(clearGSettingsPlayers());
3712+ ASSERT_NO_THROW(startPulsePhone());
3713+
3714+ // initialize volumes in pulseaudio
3715+ EXPECT_TRUE(setStreamRestoreVolume("alert", INITIAL_VOLUME));
3716+
3717+ // start now the indicator, so it picks the new volumes
3718+ ASSERT_NO_THROW(startIndicator());
3719+
3720+ EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
3721+ .item(mh::MenuItemMatcher()
3722+ .action("indicator.root")
3723+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
3724+ .string_attribute("x-canonical-scroll-action", "indicator.scroll")
3725+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
3726+ .string_attribute("submenu-action", "indicator.indicator-shown")
3727+ .mode(mh::MenuItemMatcher::Mode::all)
3728+ .submenu()
3729+ .item(mh::MenuItemMatcher()
3730+ .section()
3731+ .item(silentModeSwitch(false))
3732+ .item(volumeSlider(INITIAL_VOLUME))
3733+ )
3734+ .item(mh::MenuItemMatcher()
3735+ .label("Sound Settings…")
3736+ .action("indicator.phone-settings")
3737+ )
3738+ ).match());
3739+}
3740+
3741+TEST_F(TestIndicator, PhoneAddMprisPlayer)
3742+{
3743+ double INITIAL_VOLUME = 0.0;
3744+
3745+ ASSERT_NO_THROW(startAccountsService());
3746+ EXPECT_TRUE(clearGSettingsPlayers());
3747+ ASSERT_NO_THROW(startPulsePhone());
3748+
3749+ // initialize volumes in pulseaudio
3750+ EXPECT_TRUE(setStreamRestoreVolume("alert", INITIAL_VOLUME));
3751+
3752+ // start now the indicator, so it picks the new volumes
3753+ ASSERT_NO_THROW(startIndicator());
3754+
3755+ EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
3756+ .item(mh::MenuItemMatcher()
3757+ .action("indicator.root")
3758+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
3759+ .string_attribute("x-canonical-scroll-action", "indicator.scroll")
3760+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
3761+ .string_attribute("submenu-action", "indicator.indicator-shown")
3762+ .mode(mh::MenuItemMatcher::Mode::all)
3763+ .submenu()
3764+ .item(mh::MenuItemMatcher()
3765+ .section()
3766+ .item(silentModeSwitch(false))
3767+ .item(volumeSlider(INITIAL_VOLUME))
3768+ )
3769+ .item(mh::MenuItemMatcher()
3770+ .label("Sound Settings…")
3771+ .action("indicator.phone-settings")
3772+ )
3773+ ).match());
3774+
3775+ // initialize the signal spy
3776+ EXPECT_TRUE(initializeMenuChangedSignal());
3777+
3778+ // start the test player
3779+ EXPECT_TRUE(startTestMprisPlayer("testplayer1"));
3780+
3781+ // wait for the menu change
3782+ EXPECT_TRUE(waitMenuChange());
3783+
3784+ // finally verify that the player is added
3785+ EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
3786+ .item(mh::MenuItemMatcher()
3787+ .action("indicator.root")
3788+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
3789+ .string_attribute("x-canonical-scroll-action", "indicator.scroll")
3790+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
3791+ .string_attribute("submenu-action", "indicator.indicator-shown")
3792+ .mode(mh::MenuItemMatcher::Mode::all)
3793+ .submenu()
3794+ .item(mh::MenuItemMatcher()
3795+ .section()
3796+ .item(silentModeSwitch(false))
3797+ .item(volumeSlider(INITIAL_VOLUME))
3798+ )
3799+ .item(mh::MenuItemMatcher()
3800+ .section()
3801+ .item(mh::MenuItemMatcher()
3802+ .action("indicator.testplayer1.desktop")
3803+ .label("TestPlayer1")
3804+ .themed_icon("icon", {"testplayer"})
3805+ .string_attribute("x-canonical-type", "com.canonical.unity.media-player")
3806+ )
3807+ .item(mh::MenuItemMatcher()
3808+ .string_attribute("x-canonical-previous-action","indicator.previous.testplayer1.desktop")
3809+ .string_attribute("x-canonical-play-action","indicator.play.testplayer1.desktop")
3810+ .string_attribute("x-canonical-next-action","indicator.next.testplayer1.desktop")
3811+ .string_attribute("x-canonical-type","com.canonical.unity.playback-item")
3812+ )
3813+ )
3814+ .item(mh::MenuItemMatcher()
3815+ .label("Sound Settings…")
3816+ .action("indicator.phone-settings")
3817+ )
3818+ ).match());
3819+}
3820+
3821+TEST_F(TestIndicator, DesktopBasicInitialVolume)
3822+{
3823+ double INITIAL_VOLUME = 0.0;
3824+
3825+ ASSERT_NO_THROW(startAccountsService());
3826+ EXPECT_TRUE(clearGSettingsPlayers());
3827+ ASSERT_NO_THROW(startPulseDesktop());
3828+
3829+ // initialize volumes in pulseaudio
3830+ EXPECT_FALSE(setStreamRestoreVolume("alert", INITIAL_VOLUME));
3831+ EXPECT_TRUE(setSinkVolume(INITIAL_VOLUME));
3832+
3833+ // start the test player
3834+ EXPECT_TRUE(startTestMprisPlayer("testplayer1"));
3835+
3836+ // start now the indicator, so it picks the new volumes
3837+ ASSERT_NO_THROW(startIndicator());
3838+
3839+ EXPECT_MATCHRESULT(mh::MenuMatcher(desktopParameters())
3840+ .item(mh::MenuItemMatcher()
3841+ .action("indicator.root")
3842+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
3843+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
3844+ .mode(mh::MenuItemMatcher::Mode::all)
3845+ .submenu()
3846+ .item(mh::MenuItemMatcher()
3847+ .section()
3848+ .item(mh::MenuItemMatcher().checkbox()
3849+ .label("Mute")
3850+ )
3851+ .item(volumeSlider(INITIAL_VOLUME))
3852+ )
3853+ .item(mh::MenuItemMatcher()
3854+ .section()
3855+ .item(mh::MenuItemMatcher()
3856+ .action("indicator.testplayer1.desktop")
3857+ .label("TestPlayer1")
3858+ .themed_icon("icon", {"testplayer"})
3859+ .string_attribute("x-canonical-type", "com.canonical.unity.media-player")
3860+ )
3861+ .item(mh::MenuItemMatcher()
3862+ .string_attribute("x-canonical-previous-action","indicator.previous.testplayer1.desktop")
3863+ .string_attribute("x-canonical-play-action","indicator.play.testplayer1.desktop")
3864+ .string_attribute("x-canonical-next-action","indicator.next.testplayer1.desktop")
3865+ .string_attribute("x-canonical-type","com.canonical.unity.playback-item")
3866+ )
3867+ )
3868+ .item(mh::MenuItemMatcher()
3869+ .label("Sound Settings…")
3870+ )
3871+ ).match());
3872+}
3873+
3874+TEST_F(TestIndicator, DesktopAddMprisPlayer)
3875+{
3876+ double INITIAL_VOLUME = 0.0;
3877+
3878+ ASSERT_NO_THROW(startAccountsService());
3879+ EXPECT_TRUE(clearGSettingsPlayers());
3880+ ASSERT_NO_THROW(startPulseDesktop());
3881+
3882+ // initialize volumes in pulseaudio
3883+ EXPECT_FALSE(setStreamRestoreVolume("alert", INITIAL_VOLUME));
3884+ EXPECT_TRUE(setSinkVolume(INITIAL_VOLUME));
3885+
3886+ // start the test player
3887+ EXPECT_TRUE(startTestMprisPlayer("testplayer1"));
3888+
3889+ // start now the indicator, so it picks the new volumes
3890+ ASSERT_NO_THROW(startIndicator());
3891+
3892+ // check that the player is added
3893+ EXPECT_MATCHRESULT(mh::MenuMatcher(desktopParameters())
3894+ .item(mh::MenuItemMatcher()
3895+ .action("indicator.root")
3896+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
3897+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
3898+ .mode(mh::MenuItemMatcher::Mode::all)
3899+ .submenu()
3900+ .item(mh::MenuItemMatcher()
3901+ .section()
3902+ .item(mh::MenuItemMatcher().checkbox()
3903+ .label("Mute")
3904+ )
3905+ .item(volumeSlider(INITIAL_VOLUME))
3906+ )
3907+ .item(mh::MenuItemMatcher()
3908+ .section()
3909+ .item(mh::MenuItemMatcher()
3910+ .action("indicator.testplayer1.desktop")
3911+ .label("TestPlayer1")
3912+ .themed_icon("icon", {"testplayer"})
3913+ .string_attribute("x-canonical-type", "com.canonical.unity.media-player")
3914+ )
3915+ .item(mh::MenuItemMatcher()
3916+ .string_attribute("x-canonical-previous-action","indicator.previous.testplayer1.desktop")
3917+ .string_attribute("x-canonical-play-action","indicator.play.testplayer1.desktop")
3918+ .string_attribute("x-canonical-next-action","indicator.next.testplayer1.desktop")
3919+ .string_attribute("x-canonical-type","com.canonical.unity.playback-item")
3920+ )
3921+ )
3922+ .item(mh::MenuItemMatcher()
3923+ .label("Sound Settings…")
3924+ )
3925+ ).match());
3926+}
3927+
3928+TEST_F(TestIndicator, DesktopMprisPlayerButtonsState)
3929+{
3930+ double INITIAL_VOLUME = 0.0;
3931+
3932+ ASSERT_NO_THROW(startAccountsService());
3933+ EXPECT_TRUE(clearGSettingsPlayers());
3934+ ASSERT_NO_THROW(startPulseDesktop());
3935+
3936+ // initialize volumes in pulseaudio
3937+ EXPECT_FALSE(setStreamRestoreVolume("alert", INITIAL_VOLUME));
3938+ EXPECT_TRUE(setSinkVolume(INITIAL_VOLUME));
3939+
3940+ // start the test player
3941+ EXPECT_TRUE(startTestMprisPlayer("testplayer1"));
3942+
3943+ // start now the indicator, so it picks the new volumes
3944+ ASSERT_NO_THROW(startIndicator());
3945+
3946+ // check that the player is added
3947+ EXPECT_MATCHRESULT(mh::MenuMatcher(desktopParameters())
3948+ .item(mh::MenuItemMatcher()
3949+ .action("indicator.root")
3950+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
3951+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
3952+ .mode(mh::MenuItemMatcher::Mode::all)
3953+ .submenu()
3954+ .item(mh::MenuItemMatcher()
3955+ .section()
3956+ .item(mh::MenuItemMatcher().checkbox()
3957+ .label("Mute")
3958+ )
3959+ .item(volumeSlider(INITIAL_VOLUME))
3960+ )
3961+ .item(mh::MenuItemMatcher()
3962+ .section()
3963+ .item(mh::MenuItemMatcher()
3964+ .action("indicator.testplayer1.desktop")
3965+ .label("TestPlayer1")
3966+ .themed_icon("icon", {"testplayer"})
3967+ .string_attribute("x-canonical-type", "com.canonical.unity.media-player")
3968+ )
3969+ .item(mh::MenuItemMatcher()
3970+ .string_attribute("x-canonical-previous-action","indicator.previous.testplayer1.desktop")
3971+ .string_attribute("x-canonical-play-action","indicator.play.testplayer1.desktop")
3972+ .string_attribute("x-canonical-next-action","indicator.next.testplayer1.desktop")
3973+ .string_attribute("x-canonical-type","com.canonical.unity.playback-item")
3974+ )
3975+ )
3976+ .item(mh::MenuItemMatcher()
3977+ .label("Sound Settings…")
3978+ )
3979+ ).match());
3980+
3981+ // change the state of CanGoNext
3982+ EXPECT_TRUE(setTestMprisPlayerProperty("testplayer1", "CanGoNext", false));
3983+
3984+ // verify that the action changes
3985+ EXPECT_MATCHRESULT(mh::MenuMatcher(desktopParameters())
3986+ .item(mh::MenuItemMatcher()
3987+ .action("indicator.root")
3988+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
3989+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
3990+ .mode(mh::MenuItemMatcher::Mode::all)
3991+ .submenu()
3992+ .item(mh::MenuItemMatcher()
3993+ .section()
3994+ .item(mh::MenuItemMatcher().checkbox()
3995+ .label("Mute")
3996+ )
3997+ .item(volumeSlider(INITIAL_VOLUME))
3998+ )
3999+ .item(mh::MenuItemMatcher()
4000+ .section()
4001+ .item(mh::MenuItemMatcher()
4002+ .action("indicator.testplayer1.desktop")
4003+ .label("TestPlayer1")
4004+ .themed_icon("icon", {"testplayer"})
4005+ .string_attribute("x-canonical-type", "com.canonical.unity.media-player")
4006+ )
4007+ .item(mh::MenuItemMatcher()
4008+ .string_attribute("x-canonical-previous-action","indicator.previous.testplayer1.desktop")
4009+ .string_attribute("x-canonical-play-action","indicator.play.testplayer1.desktop")
4010+ .attribute_not_set("x-canonical-next-action")
4011+ .string_attribute("x-canonical-type","com.canonical.unity.playback-item")
4012+ )
4013+ )
4014+ .item(mh::MenuItemMatcher()
4015+ .label("Sound Settings…")
4016+ )
4017+ ).match());
4018+
4019+
4020+ // change the state of CanGoPrevious
4021+ EXPECT_TRUE(setTestMprisPlayerProperty("testplayer1", "CanGoPrevious", false));
4022+
4023+ // verify that the action changes
4024+ EXPECT_MATCHRESULT(mh::MenuMatcher(desktopParameters())
4025+ .item(mh::MenuItemMatcher()
4026+ .action("indicator.root")
4027+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
4028+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
4029+ .mode(mh::MenuItemMatcher::Mode::all)
4030+ .submenu()
4031+ .item(mh::MenuItemMatcher()
4032+ .section()
4033+ .item(mh::MenuItemMatcher().checkbox()
4034+ .label("Mute")
4035+ )
4036+ .item(volumeSlider(INITIAL_VOLUME))
4037+ )
4038+ .item(mh::MenuItemMatcher()
4039+ .section()
4040+ .item(mh::MenuItemMatcher()
4041+ .action("indicator.testplayer1.desktop")
4042+ .label("TestPlayer1")
4043+ .themed_icon("icon", {"testplayer"})
4044+ .string_attribute("x-canonical-type", "com.canonical.unity.media-player")
4045+ )
4046+ .item(mh::MenuItemMatcher()
4047+ .attribute_not_set("x-canonical-previous-action")
4048+ .string_attribute("x-canonical-play-action","indicator.play.testplayer1.desktop")
4049+ .attribute_not_set("x-canonical-next-action")
4050+ .string_attribute("x-canonical-type","com.canonical.unity.playback-item")
4051+ )
4052+ )
4053+ .item(mh::MenuItemMatcher()
4054+ .label("Sound Settings…")
4055+ )
4056+ ).match());
4057+
4058+ // set back both to true
4059+ EXPECT_TRUE(setTestMprisPlayerProperty("testplayer1", "CanGoNext", true));
4060+ EXPECT_TRUE(setTestMprisPlayerProperty("testplayer1", "CanGoPrevious", true));
4061+
4062+ EXPECT_MATCHRESULT(mh::MenuMatcher(desktopParameters())
4063+ .item(mh::MenuItemMatcher()
4064+ .action("indicator.root")
4065+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
4066+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
4067+ .mode(mh::MenuItemMatcher::Mode::all)
4068+ .submenu()
4069+ .item(mh::MenuItemMatcher()
4070+ .section()
4071+ .item(mh::MenuItemMatcher().checkbox()
4072+ .label("Mute")
4073+ )
4074+ .item(volumeSlider(INITIAL_VOLUME))
4075+ )
4076+ .item(mh::MenuItemMatcher()
4077+ .section()
4078+ .item(mh::MenuItemMatcher()
4079+ .action("indicator.testplayer1.desktop")
4080+ .label("TestPlayer1")
4081+ .themed_icon("icon", {"testplayer"})
4082+ .string_attribute("x-canonical-type", "com.canonical.unity.media-player")
4083+ )
4084+ .item(mh::MenuItemMatcher()
4085+ .string_attribute("x-canonical-previous-action","indicator.previous.testplayer1.desktop")
4086+ .string_attribute("x-canonical-play-action","indicator.play.testplayer1.desktop")
4087+ .string_attribute("x-canonical-next-action","indicator.next.testplayer1.desktop")
4088+ .string_attribute("x-canonical-type","com.canonical.unity.playback-item")
4089+ )
4090+ )
4091+ .item(mh::MenuItemMatcher()
4092+ .label("Sound Settings…")
4093+ )
4094+ ).match());
4095+}
4096+
4097+TEST_F(TestIndicator, DesktopChangeRoleVolume)
4098+{
4099+ double INITIAL_VOLUME = 0.0;
4100+
4101+ ASSERT_NO_THROW(startAccountsService());
4102+ ASSERT_NO_THROW(startPulseDesktop());
4103+
4104+ // initialize volumes in pulseaudio
4105+ // expect false for stream restore, because the module
4106+ // is not loaded in the desktop pulseaudio instance
4107+ EXPECT_FALSE(setStreamRestoreVolume("mutimedia", INITIAL_VOLUME));
4108+ EXPECT_FALSE(setStreamRestoreVolume("alert", INITIAL_VOLUME));
4109+
4110+ EXPECT_TRUE(setSinkVolume(INITIAL_VOLUME));
4111+
4112+ // start now the indicator, so it picks the new volumes
4113+ ASSERT_NO_THROW(startIndicator());
4114+
4115+ // Generate a random volume
4116+ QTime now = QTime::currentTime();
4117+ qsrand(now.msec());
4118+ int randInt = qrand() % 100;
4119+ double randomVolume = randInt / 100.0;
4120+
4121+ // play a test sound, it should NOT change the role in the indicator
4122+ EXPECT_TRUE(startTestSound("multimedia"));
4123+ EXPECT_FALSE(waitVolumeChangedInIndicator());
4124+
4125+ // set the random volume
4126+ EXPECT_TRUE(setSinkVolume(randomVolume));
4127+ if (randomVolume != INITIAL_VOLUME)
4128+ {
4129+ EXPECT_TRUE(waitVolumeChangedInIndicator());
4130+ }
4131+
4132+ // check the indicator
4133+ EXPECT_MATCHRESULT(mh::MenuMatcher(desktopParameters())
4134+ .item(mh::MenuItemMatcher()
4135+ .action("indicator.root")
4136+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
4137+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
4138+ .mode(mh::MenuItemMatcher::Mode::starts_with)
4139+ .submenu()
4140+ .item(mh::MenuItemMatcher()
4141+ .section()
4142+ .item(mh::MenuItemMatcher().checkbox()
4143+ .label("Mute")
4144+ )
4145+ .item(volumeSlider(randomVolume))
4146+ )
4147+ ).match());
4148+
4149+ // check that the last item is Sound Settings
4150+ EXPECT_MATCHRESULT(mh::MenuMatcher(desktopParameters())
4151+ .item(mh::MenuItemMatcher()
4152+ .action("indicator.root")
4153+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
4154+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
4155+ .mode(mh::MenuItemMatcher::Mode::ends_with)
4156+ .submenu()
4157+ .item(mh::MenuItemMatcher()
4158+ .label("Sound Settings…")
4159+ )
4160+ ).match());
4161+
4162+ // stop the test sound, the role should change again to alert
4163+ stopTestSound();
4164+
4165+ // although we were playing something in the multimedia role
4166+ // the server does not have the streamrestore module, so
4167+ // the volume does not change (the role does not change)
4168+ EXPECT_FALSE(waitVolumeChangedInIndicator());
4169+
4170+ // check the initial volume for the alert role
4171+ EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
4172+ .item(mh::MenuItemMatcher()
4173+ .action("indicator.root")
4174+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
4175+ .string_attribute("x-canonical-scroll-action", "indicator.scroll")
4176+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
4177+ .string_attribute("submenu-action", "indicator.indicator-shown")
4178+ .mode(mh::MenuItemMatcher::Mode::starts_with)
4179+ .submenu()
4180+ .item(mh::MenuItemMatcher()
4181+ .section()
4182+ .item(silentModeSwitch(false))
4183+ .item(volumeSlider(randomVolume))
4184+ )
4185+ ).match());
4186+
4187+ // check that the last item is Sound Settings
4188+ EXPECT_MATCHRESULT(mh::MenuMatcher(phoneParameters())
4189+ .item(mh::MenuItemMatcher()
4190+ .action("indicator.root")
4191+ .string_attribute("x-canonical-type", "com.canonical.indicator.root")
4192+ .string_attribute("x-canonical-secondary-action", "indicator.mute")
4193+ .mode(mh::MenuItemMatcher::Mode::ends_with)
4194+ .submenu()
4195+ .item(mh::MenuItemMatcher()
4196+ .label("Sound Settings…")
4197+ .action("indicator.phone-settings")
4198+ )
4199+ ).match());
4200+}
4201+
4202+} // namespace
4203
4204=== added file 'tests/integration/test-sound.wav'
4205Binary files tests/integration/test-sound.wav 1970-01-01 00:00:00 +0000 and tests/integration/test-sound.wav 2015-10-13 07:44:39 +0000 differ
4206=== added file 'tests/integration/touch-stream-restore.table'
4207--- tests/integration/touch-stream-restore.table 1970-01-01 00:00:00 +0000
4208+++ tests/integration/touch-stream-restore.table 2015-10-13 07:44:39 +0000
4209@@ -0,0 +1,4 @@
4210+sink-input-by-media-role:multimedia -8
4211+sink-input-by-media-role:alert -8
4212+sink-input-by-media-role:alarm -8
4213+sink-input-by-media-role:phone -15
4214
4215=== added directory 'tests/integration/utils'
4216=== added file 'tests/integration/utils/dbus-pulse-volume.cpp'
4217--- tests/integration/utils/dbus-pulse-volume.cpp 1970-01-01 00:00:00 +0000
4218+++ tests/integration/utils/dbus-pulse-volume.cpp 2015-10-13 07:44:39 +0000
4219@@ -0,0 +1,232 @@
4220+/*
4221+ * Copyright (C) 2015 Canonical, Ltd.
4222+ *
4223+ * This program is free software: you can redistribute it and/or modify it
4224+ * under the terms of the GNU General Public License version 3, as published
4225+ * by the Free Software Foundation.
4226+ *
4227+ * This program is distributed in the hope that it will be useful, but
4228+ * WITHOUT ANY WARRANTY; without even the implied warranties of
4229+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
4230+ * PURPOSE. See the GNU General Public License for more details.
4231+ *
4232+ * You should have received a copy of the GNU General Public License along
4233+ * with this program. If not, see <http://www.gnu.org/licenses/>.
4234+ *
4235+ * Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
4236+ */
4237+
4238+#include "dbus-pulse-volume.h"
4239+
4240+#include "dbus_properties_interface.h"
4241+#include "dbus_accounts_interface.h"
4242+#include "dbus_accountssound_interface.h"
4243+#include "stream_restore_interface.h"
4244+
4245+#include <pulse/volume.h>
4246+
4247+#include <QSignalSpy>
4248+
4249+unsigned int volumeDoubleToUint(double volume)
4250+{
4251+ double tmp = (double)(PA_VOLUME_NORM - PA_VOLUME_MUTED) * volume;
4252+ return (unsigned int)tmp + PA_VOLUME_MUTED;
4253+}
4254+
4255+double volumeUIntToDoulbe(uint volume)
4256+{
4257+ double tmp = (double)(volume - PA_VOLUME_MUTED);
4258+ return tmp / (double)(PA_VOLUME_NORM - PA_VOLUME_MUTED);
4259+}
4260+
4261+DBusPulseVolume::DBusPulseVolume() :
4262+ QObject()
4263+{
4264+ std::unique_ptr<DBusPropertiesInterface> basicConnectionInterface(new DBusPropertiesInterface("org.PulseAudio1",
4265+ "/org/pulseaudio/server_lookup1",
4266+ QDBusConnection::sessionBus(), 0));
4267+
4268+ QDBusReply<QVariant> connection_string = basicConnectionInterface->call(QLatin1String("Get"),
4269+ QLatin1String("org.PulseAudio.ServerLookup1"),
4270+ QLatin1String("Address"));
4271+
4272+ if (!connection_string.isValid())
4273+ {
4274+ qWarning() << "DBusPulseVolume::DBusPulseVolume(): D-Bus error: " << connection_string.error().message();
4275+ }
4276+
4277+ connection_.reset(new QDBusConnection(QDBusConnection::connectToPeer(connection_string.value().toString(), "set-volume")));
4278+
4279+ if (connection_->isConnected())
4280+ {
4281+ interface_paths_.reset(new StreamRestoreInterface("org.PulseAudio.Ext.StreamRestore1",
4282+ "/org/pulseaudio/stream_restore1",
4283+ *(connection_.get()), 0));
4284+ if (interface_paths_)
4285+ {
4286+ // get the role paths
4287+ fillRolePath("multimedia");
4288+ fillRolePath("alert");
4289+ fillRolePath("alarm");
4290+ fillRolePath("phone");
4291+ }
4292+
4293+ initializeAccountsInterface();
4294+ }
4295+ else
4296+ {
4297+ qWarning() << "DBusPulseVolume::DBusPulseVolume(): Error connecting: " << connection_->lastError().message();
4298+ }
4299+}
4300+
4301+DBusPulseVolume::~DBusPulseVolume()
4302+{
4303+}
4304+
4305+QString DBusPulseVolume::fillRolePath(QString const &role)
4306+{
4307+ QString role_complete_name = QString("sink-input-by-media-role:") + role;
4308+ // get the role paths
4309+ QDBusReply<QDBusObjectPath> objectPath = interface_paths_->call(QLatin1String("GetEntryByName"), role_complete_name);
4310+ if (!objectPath.isValid())
4311+ {
4312+ qWarning() << "SetVolume::fillRolePath(): D-Bus error: " << objectPath.error().message();
4313+ return "";
4314+ }
4315+
4316+ auto role_info = std::make_shared<RoleInformation>();
4317+ role_info->interface_.reset(new DBusPropertiesInterface("org.PulseAudio.Ext.StreamRestore1.RestoreEntry",
4318+ objectPath.value().path(),
4319+ *(connection_.get()), 0));
4320+ if (!role_info->interface_)
4321+ {
4322+ qWarning() << "SetVolume::fillRolePath() - Error obtaining properties interface";
4323+ return "";
4324+ }
4325+ role_info->path_ = objectPath.value().path();
4326+ roles_map_[role] = role_info;
4327+ return role_info->path_;
4328+}
4329+
4330+bool DBusPulseVolume::setVolume(QString const & role, double volume)
4331+{
4332+ if (!interface_paths_)
4333+ {
4334+ qWarning() << "SetVolume::setVolume(): error: Volume interfaces are not initialized";
4335+ return false;
4336+ }
4337+
4338+ RolesMap::const_iterator iter = roles_map_.find(role);
4339+ if (iter != roles_map_.end())
4340+ {
4341+ QVariant var;
4342+ PulseaudioVolumeArray t;
4343+ PulseaudioVolume vv(0, volumeDoubleToUint(volume));
4344+ t.addItem(vv);
4345+ var.setValue(t);
4346+ QDBusVariant dbusVar(var);
4347+ QDBusReply<void> set_vol = (*iter).second->interface_->call(QLatin1String("Set"),
4348+ QVariant::fromValue(QString("org.PulseAudio.Ext.StreamRestore1.RestoreEntry")),
4349+ QVariant::fromValue(QString("Volume")),
4350+ QVariant::fromValue(dbusVar));
4351+
4352+ if (!set_vol.isValid())
4353+ {
4354+ qWarning() << "SetVolume::setVolume(): D-Bus error: " << set_vol.error().message();
4355+ return false;
4356+ }
4357+
4358+ if (accounts_interface_)
4359+ {
4360+ QDBusVariant dbusVar(QVariant::fromValue(volume));
4361+ QDBusReply<void> set_vol = accounts_interface_->call(QLatin1String("Set"),
4362+ QVariant::fromValue(QString("com.ubuntu.AccountsService.Sound")),
4363+ QVariant::fromValue(QString("Volume")),
4364+ QVariant::fromValue(dbusVar));
4365+ if (!set_vol.isValid())
4366+ {
4367+ qWarning() << "SetVolume::setVolume(): D-Bus error: " << set_vol.error().message();
4368+ return false;
4369+ }
4370+ }
4371+ }
4372+ else
4373+ {
4374+ qWarning() << "SetVolume::setVolume(): role " << role << " was not found.";
4375+ return false;
4376+ }
4377+ return true;
4378+}
4379+
4380+double DBusPulseVolume::getVolume(QString const & role)
4381+{
4382+ if (interface_paths_)
4383+ {
4384+ RolesMap::const_iterator iter = roles_map_.find(role);
4385+ if (iter != roles_map_.end())
4386+ {
4387+ QDBusReply<QVariant> prev_vol = (*iter).second->interface_->call(QLatin1String("Get"),
4388+ QLatin1String("org.PulseAudio.Ext.StreamRestore1.RestoreEntry"),
4389+ QLatin1String("Volume"));
4390+
4391+ if (!prev_vol.isValid())
4392+ {
4393+ qWarning() << "SetVolume::setVolume(): D-Bus error: " << prev_vol.error().message();
4394+ }
4395+ QDBusArgument arg = prev_vol.value().value<QDBusArgument>();
4396+ PulseaudioVolumeArray element;
4397+ arg >> element;
4398+ return volumeUIntToDoulbe(element.getItem(0).getVolume());
4399+ }
4400+ }
4401+ return -1.0;
4402+}
4403+
4404+void DBusPulseVolume::initializeAccountsInterface()
4405+{
4406+ auto username = qgetenv("USER");
4407+ if (username != "")
4408+ {
4409+ qDebug() << "Setting Accounts interface for user: " << username;
4410+ std::unique_ptr<AccountsInterface> setInterface(new AccountsInterface("org.freedesktop.Accounts",
4411+ "/org/freedesktop/Accounts",
4412+ QDBusConnection::systemBus(), 0));
4413+
4414+ QDBusReply<QDBusObjectPath> userResp = setInterface->call(QLatin1String("FindUserByName"),
4415+ QLatin1String(username));
4416+
4417+ if (!userResp.isValid())
4418+ {
4419+ qWarning() << "SetVolume::initializeAccountsInterface(): D-Bus error: " << userResp.error().message();
4420+ }
4421+ auto userPath = userResp.value().path();
4422+ if (userPath != "")
4423+ {
4424+ std::unique_ptr<AccountsSoundInterface> soundInterface(new AccountsSoundInterface("org.freedesktop.Accounts",
4425+ userPath,
4426+ QDBusConnection::systemBus(), 0));
4427+
4428+ accounts_interface_.reset(new DBusPropertiesInterface("org.freedesktop.Accounts",
4429+ userPath,
4430+ soundInterface->connection(), 0));
4431+ if (!accounts_interface_->isValid())
4432+ {
4433+ qWarning() << "SetVolume::initializeAccountsInterface(): D-Bus error: " << accounts_interface_->lastError().message();
4434+ }
4435+ signal_spy_volume_changed_.reset(new QSignalSpy(accounts_interface_.get(),&DBusPropertiesInterface::PropertiesChanged));
4436+ }
4437+ }
4438+}
4439+
4440+bool DBusPulseVolume::waitForVolumeChangedInAccountsService()
4441+{
4442+ if (signal_spy_volume_changed_)
4443+ {
4444+ return signal_spy_volume_changed_->wait();
4445+ }
4446+ else
4447+ {
4448+ qWarning() << "DBusPulseVolume::waitForVolumeChangedInAccountsService(): signal was not instantiated";
4449+ }
4450+ return false;
4451+}
4452
4453=== added file 'tests/integration/utils/dbus-pulse-volume.h'
4454--- tests/integration/utils/dbus-pulse-volume.h 1970-01-01 00:00:00 +0000
4455+++ tests/integration/utils/dbus-pulse-volume.h 2015-10-13 07:44:39 +0000
4456@@ -0,0 +1,57 @@
4457+/*
4458+ * Copyright (C) 2015 Canonical, Ltd.
4459+ *
4460+ * This program is free software: you can redistribute it and/or modify it
4461+ * under the terms of the GNU General Public License version 3, as published
4462+ * by the Free Software Foundation.
4463+ *
4464+ * This program is distributed in the hope that it will be useful, but
4465+ * WITHOUT ANY WARRANTY; without even the implied warranties of
4466+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
4467+ * PURPOSE. See the GNU General Public License for more details.
4468+ *
4469+ * You should have received a copy of the GNU General Public License along
4470+ * with this program. If not, see <http://www.gnu.org/licenses/>.
4471+ *
4472+ * Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
4473+ */
4474+#pragma once
4475+
4476+#include <QObject>
4477+#include <QDBusConnection>
4478+
4479+#include <map>
4480+#include <memory>
4481+
4482+class StreamRestoreInterface;
4483+class DBusPropertiesInterface;
4484+class AccountsInterface;
4485+class AccountsSoundInterface;
4486+class QSignalSpy;
4487+
4488+struct RoleInformation
4489+{
4490+ std::unique_ptr<DBusPropertiesInterface> interface_;
4491+ QString path_;
4492+};
4493+
4494+class DBusPulseVolume : public QObject
4495+{
4496+public:
4497+ DBusPulseVolume();
4498+ ~DBusPulseVolume();
4499+
4500+ bool setVolume(QString const & role, double volume);
4501+ double getVolume(QString const & role);
4502+ bool waitForVolumeChangedInAccountsService();
4503+
4504+protected:
4505+ QString fillRolePath(QString const &role);
4506+ void initializeAccountsInterface();
4507+ std::unique_ptr<QDBusConnection> connection_;
4508+ std::unique_ptr<StreamRestoreInterface> interface_paths_;
4509+ typedef std::map<QString, std::shared_ptr<RoleInformation>> RolesMap;
4510+ RolesMap roles_map_;
4511+ std::unique_ptr<DBusPropertiesInterface> accounts_interface_;
4512+ std::unique_ptr<QSignalSpy> signal_spy_volume_changed_;
4513+};
4514
4515=== added file 'tests/integration/utils/get-volume.cpp'
4516--- tests/integration/utils/get-volume.cpp 1970-01-01 00:00:00 +0000
4517+++ tests/integration/utils/get-volume.cpp 2015-10-13 07:44:39 +0000
4518@@ -0,0 +1,33 @@
4519+/*
4520+ * Copyright (C) 2015 Canonical, Ltd.
4521+ *
4522+ * This program is free software: you can redistribute it and/or modify it
4523+ * under the terms of the GNU General Public License version 3, as published
4524+ * by the Free Software Foundation.
4525+ *
4526+ * This program is distributed in the hope that it will be useful, but
4527+ * WITHOUT ANY WARRANTY; without even the implied warranties of
4528+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
4529+ * PURPOSE. See the GNU General Public License for more details.
4530+ *
4531+ * You should have received a copy of the GNU General Public License along
4532+ * with this program. If not, see <http://www.gnu.org/licenses/>.
4533+ *
4534+ * Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
4535+ */
4536+
4537+#include "dbus-types.h"
4538+
4539+#include <string>
4540+#include "dbus-pulse-volume.h"
4541+
4542+int main(int argc, char **argv)
4543+{
4544+ DBusTypes::registerMetaTypes();
4545+ if (argc == 2)
4546+ {
4547+ DBusPulseVolume volume;
4548+ volume.getVolume(argv[1]);
4549+ }
4550+ return 0;
4551+}
4552
4553=== added file 'tests/integration/utils/set-volume.cpp'
4554--- tests/integration/utils/set-volume.cpp 1970-01-01 00:00:00 +0000
4555+++ tests/integration/utils/set-volume.cpp 2015-10-13 07:44:39 +0000
4556@@ -0,0 +1,36 @@
4557+/*
4558+ * Copyright (C) 2015 Canonical, Ltd.
4559+ *
4560+ * This program is free software: you can redistribute it and/or modify it
4561+ * under the terms of the GNU General Public License version 3, as published
4562+ * by the Free Software Foundation.
4563+ *
4564+ * This program is distributed in the hope that it will be useful, but
4565+ * WITHOUT ANY WARRANTY; without even the implied warranties of
4566+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
4567+ * PURPOSE. See the GNU General Public License for more details.
4568+ *
4569+ * You should have received a copy of the GNU General Public License along
4570+ * with this program. If not, see <http://www.gnu.org/licenses/>.
4571+ *
4572+ * Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
4573+ */
4574+
4575+#include "dbus-types.h"
4576+
4577+#include <string>
4578+#include "dbus-pulse-volume.h"
4579+
4580+int main(int argc, char **argv)
4581+{
4582+ DBusTypes::registerMetaTypes();
4583+ if (argc == 3)
4584+ {
4585+ DBusPulseVolume volume;
4586+ if(!volume.setVolume(argv[1], std::stod(argv[2])))
4587+ {
4588+ return 1;
4589+ }
4590+ }
4591+ return 0;
4592+}
4593
4594=== added directory 'tests/service-mocks'
4595=== added file 'tests/service-mocks/CMakeLists.txt'
4596--- tests/service-mocks/CMakeLists.txt 1970-01-01 00:00:00 +0000
4597+++ tests/service-mocks/CMakeLists.txt 2015-10-13 07:44:39 +0000
4598@@ -0,0 +1,2 @@
4599+add_subdirectory(accounts-mock)
4600+add_subdirectory(media-player-mpris-mock)
4601
4602=== added file 'tests/service-mocks/DBusPropertiesNotifier.cpp'
4603--- tests/service-mocks/DBusPropertiesNotifier.cpp 1970-01-01 00:00:00 +0000
4604+++ tests/service-mocks/DBusPropertiesNotifier.cpp 2015-10-13 07:44:39 +0000
4605@@ -0,0 +1,41 @@
4606+/*
4607+ * Copyright (C) 2015 Canonical, Ltd.
4608+ *
4609+ * This program is free software: you can redistribute it and/or modify it
4610+ * under the terms of the GNU General Public License version 3, as published
4611+ * by the Free Software Foundation.
4612+ *
4613+ * This program is distributed in the hope that it will be useful, but
4614+ * WITHOUT ANY WARRANTY; without even the implied warranties of
4615+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
4616+ * PURPOSE. See the GNU General Public License for more details.
4617+ *
4618+ * You should have received a copy of the GNU General Public License along
4619+ * with this program. If not, see <http://www.gnu.org/licenses/>.
4620+ *
4621+ * Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
4622+ */
4623+
4624+#include "DBusPropertiesNotifier.h"
4625+
4626+#include <QDBusMessage>
4627+
4628+using namespace ubuntu::indicators::testing;
4629+
4630+void DBusPropertiesNotifier::notifyPropertyChanged(QDBusConnection const & connection,
4631+ QString const & interface,
4632+ QString const & path,
4633+ QString const & propertyName,
4634+ QVariant const & propertyValue)
4635+{
4636+ QDBusMessage signal = QDBusMessage::createSignal(
4637+ path,
4638+ "org.freedesktop.DBus.Properties",
4639+ "PropertiesChanged");
4640+ signal << interface;
4641+ QVariantMap changedProps;
4642+ changedProps.insert(propertyName, propertyValue);
4643+ signal << changedProps;
4644+ signal << QStringList();
4645+ connection.send(signal);
4646+}
4647
4648=== added file 'tests/service-mocks/DBusPropertiesNotifier.h'
4649--- tests/service-mocks/DBusPropertiesNotifier.h 1970-01-01 00:00:00 +0000
4650+++ tests/service-mocks/DBusPropertiesNotifier.h 2015-10-13 07:44:39 +0000
4651@@ -0,0 +1,48 @@
4652+/*
4653+ * Copyright (C) 2015 Canonical, Ltd.
4654+ *
4655+ * This program is free software: you can redistribute it and/or modify it
4656+ * under the terms of the GNU General Public License version 3, as published
4657+ * by the Free Software Foundation.
4658+ *
4659+ * This program is distributed in the hope that it will be useful, but
4660+ * WITHOUT ANY WARRANTY; without even the implied warranties of
4661+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
4662+ * PURPOSE. See the GNU General Public License for more details.
4663+ *
4664+ * You should have received a copy of the GNU General Public License along
4665+ * with this program. If not, see <http://www.gnu.org/licenses/>.
4666+ *
4667+ * Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
4668+ */
4669+#pragma once
4670+
4671+#include <QDBusConnection>
4672+
4673+namespace ubuntu
4674+{
4675+
4676+namespace indicators
4677+{
4678+
4679+namespace testing
4680+{
4681+
4682+class DBusPropertiesNotifier
4683+{
4684+public:
4685+ DBusPropertiesNotifier() = default;
4686+ ~DBusPropertiesNotifier() = default;
4687+
4688+ void notifyPropertyChanged(QDBusConnection const & connection,
4689+ QString const & interface,
4690+ QString const & path,
4691+ QString const & propertyName,
4692+ QVariant const & propertyValue);
4693+};
4694+
4695+} // namespace testing
4696+
4697+} // namespace indicators
4698+
4699+} // namespace ubuntu
4700
4701=== added directory 'tests/service-mocks/accounts-mock'
4702=== added file 'tests/service-mocks/accounts-mock/AccountsDefs.h'
4703--- tests/service-mocks/accounts-mock/AccountsDefs.h 1970-01-01 00:00:00 +0000
4704+++ tests/service-mocks/accounts-mock/AccountsDefs.h 2015-10-13 07:44:39 +0000
4705@@ -0,0 +1,37 @@
4706+/*
4707+ * Copyright (C) 2015 Canonical, Ltd.
4708+ *
4709+ * This program is free software: you can redistribute it and/or modify it
4710+ * under the terms of the GNU General Public License version 3, as published
4711+ * by the Free Software Foundation.
4712+ *
4713+ * This program is distributed in the hope that it will be useful, but
4714+ * WITHOUT ANY WARRANTY; without even the implied warranties of
4715+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
4716+ * PURPOSE. See the GNU General Public License for more details.
4717+ *
4718+ * You should have received a copy of the GNU General Public License along
4719+ * with this program. If not, see <http://www.gnu.org/licenses/>.
4720+ *
4721+ * Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
4722+ */
4723+#pragma once
4724+
4725+namespace ubuntu
4726+{
4727+
4728+namespace indicators
4729+{
4730+
4731+namespace testing
4732+{
4733+ constexpr const char ACCOUNTS_SERVICE[] = "org.freedesktop.Accounts";
4734+ constexpr const char USER_PATH[] = "/org/freedesktop/Accounts/UserTest";
4735+ constexpr const char ACCOUNTS_PATH[] = "/org/freedesktop/Accounts";
4736+ constexpr const char ACCOUNTS_SOUND_INTERFACE[] = "com.ubuntu.AccountsService.Sound";
4737+} // namespace testing
4738+
4739+} // namespace indicators
4740+
4741+} // namespace ubuntu
4742+
4743
4744=== added file 'tests/service-mocks/accounts-mock/AccountsMock.cpp'
4745--- tests/service-mocks/accounts-mock/AccountsMock.cpp 1970-01-01 00:00:00 +0000
4746+++ tests/service-mocks/accounts-mock/AccountsMock.cpp 2015-10-13 07:44:39 +0000
4747@@ -0,0 +1,40 @@
4748+/*
4749+ * Copyright (C) 2015 Canonical, Ltd.
4750+ *
4751+ * This program is free software: you can redistribute it and/or modify it
4752+ * under the terms of the GNU General Public License version 3, as published
4753+ * by the Free Software Foundation.
4754+ *
4755+ * This program is distributed in the hope that it will be useful, but
4756+ * WITHOUT ANY WARRANTY; without even the implied warranties of
4757+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
4758+ * PURPOSE. See the GNU General Public License for more details.
4759+ *
4760+ * You should have received a copy of the GNU General Public License along
4761+ * with this program. If not, see <http://www.gnu.org/licenses/>.
4762+ *
4763+ * Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
4764+ */
4765+#include <QDebug>
4766+
4767+#include "AccountsMock.h"
4768+#include "AccountsDefs.h"
4769+
4770+using namespace ubuntu::indicators::testing;
4771+
4772+AccountsMock::AccountsMock(QObject* parent)
4773+ : QObject(parent)
4774+{
4775+}
4776+
4777+AccountsMock::~AccountsMock() = default;
4778+
4779+QDBusObjectPath AccountsMock::FindUserByName(QString const & username) const
4780+{
4781+ return QDBusObjectPath(USER_PATH);
4782+}
4783+
4784+QDBusObjectPath AccountsMock::FindUserById(int64_t uid) const
4785+{
4786+ return QDBusObjectPath(USER_PATH);
4787+}
4788
4789=== added file 'tests/service-mocks/accounts-mock/AccountsMock.h'
4790--- tests/service-mocks/accounts-mock/AccountsMock.h 1970-01-01 00:00:00 +0000
4791+++ tests/service-mocks/accounts-mock/AccountsMock.h 2015-10-13 07:44:39 +0000
4792@@ -0,0 +1,50 @@
4793+/*
4794+ * Copyright (C) 2015 Canonical, Ltd.
4795+ *
4796+ * This program is free software: you can redistribute it and/or modify it
4797+ * under the terms of the GNU General Public License version 3, as published
4798+ * by the Free Software Foundation.
4799+ *
4800+ * This program is distributed in the hope that it will be useful, but
4801+ * WITHOUT ANY WARRANTY; without even the implied warranties of
4802+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
4803+ * PURPOSE. See the GNU General Public License for more details.
4804+ *
4805+ * You should have received a copy of the GNU General Public License along
4806+ * with this program. If not, see <http://www.gnu.org/licenses/>.
4807+ *
4808+ * Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
4809+ */
4810+#pragma once
4811+
4812+#include <QDBusContext>
4813+#include <QDBusObjectPath>
4814+#include <QObject>
4815+
4816+namespace ubuntu
4817+{
4818+
4819+namespace indicators
4820+{
4821+
4822+namespace testing
4823+{
4824+
4825+class AccountsMock : public QObject, protected QDBusContext
4826+{
4827+ Q_OBJECT
4828+
4829+public Q_SLOTS:
4830+ QDBusObjectPath FindUserByName(QString const & username) const;
4831+ QDBusObjectPath FindUserById(int64_t uid) const;
4832+
4833+public:
4834+ AccountsMock(QObject* parent = 0);
4835+ virtual ~AccountsMock();
4836+};
4837+
4838+} // namespace testing
4839+
4840+} // namespace indicators
4841+
4842+} // namespace ubuntu
4843
4844=== added file 'tests/service-mocks/accounts-mock/AccountsServiceSoundMock.cpp'
4845--- tests/service-mocks/accounts-mock/AccountsServiceSoundMock.cpp 1970-01-01 00:00:00 +0000
4846+++ tests/service-mocks/accounts-mock/AccountsServiceSoundMock.cpp 2015-10-13 07:44:39 +0000
4847@@ -0,0 +1,48 @@
4848+/*
4849+ * Copyright (C) 2015 Canonical, Ltd.
4850+ *
4851+ * This program is free software: you can redistribute it and/or modify it
4852+ * under the terms of the GNU General Public License version 3, as published
4853+ * by the Free Software Foundation.
4854+ *
4855+ * This program is distributed in the hope that it will be useful, but
4856+ * WITHOUT ANY WARRANTY; without even the implied warranties of
4857+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
4858+ * PURPOSE. See the GNU General Public License for more details.
4859+ *
4860+ * You should have received a copy of the GNU General Public License along
4861+ * with this program. If not, see <http://www.gnu.org/licenses/>.
4862+ *
4863+ * Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
4864+ */
4865+#include <QDebug>
4866+#include <QDBusMessage>
4867+#include <QDBusConnection>
4868+
4869+#include "AccountsServiceSoundMock.h"
4870+#include "AccountsDefs.h"
4871+
4872+using namespace ubuntu::indicators::testing;
4873+
4874+AccountsServiceSoundMock::AccountsServiceSoundMock(QObject* parent)
4875+ : QObject(parent)
4876+ , volume_(0.0)
4877+{
4878+}
4879+
4880+AccountsServiceSoundMock::~AccountsServiceSoundMock() = default;
4881+
4882+double AccountsServiceSoundMock::volume() const
4883+{
4884+ return volume_;
4885+}
4886+
4887+void AccountsServiceSoundMock::setVolume(double volume)
4888+{
4889+ volume_ = volume;
4890+ notifier_.notifyPropertyChanged(QDBusConnection::systemBus(),
4891+ ACCOUNTS_SOUND_INTERFACE,
4892+ USER_PATH,
4893+ "Volume",
4894+ property("Volume"));
4895+}
4896
4897=== added file 'tests/service-mocks/accounts-mock/AccountsServiceSoundMock.h'
4898--- tests/service-mocks/accounts-mock/AccountsServiceSoundMock.h 1970-01-01 00:00:00 +0000
4899+++ tests/service-mocks/accounts-mock/AccountsServiceSoundMock.h 2015-10-13 07:44:39 +0000
4900@@ -0,0 +1,58 @@
4901+/*
4902+ * Copyright (C) 2015 Canonical, Ltd.
4903+ *
4904+ * This program is free software: you can redistribute it and/or modify it
4905+ * under the terms of the GNU General Public License version 3, as published
4906+ * by the Free Software Foundation.
4907+ *
4908+ * This program is distributed in the hope that it will be useful, but
4909+ * WITHOUT ANY WARRANTY; without even the implied warranties of
4910+ * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
4911+ * PURPOSE. See the GNU General Public License for more details.
4912+ *
4913+ * You should have received a copy of the GNU General Public License along
4914+ * with this program. If not, see <http://www.gnu.org/licenses/>.
4915+ *
4916+ * Author: Xavi Garcia <xavi.garcia.mena@canonical.com>
4917+ */
4918+#pragma once
4919+
4920+#include <QDBusContext>
4921+#include <QObject>
4922+
4923+#include "DBusPropertiesNotifier.h"
4924+
4925+namespace ubuntu
4926+{
4927+
4928+namespace indicators
4929+{
4930+
4931+namespace testing
4932+{
4933+
4934+class DBusPropertiesNotifier;
4935+
4936+class AccountsServiceSoundMock : public QObject, protected QDBusContext
4937+{
4938+ Q_OBJECT
4939+ Q_PROPERTY(double Volume READ volume WRITE setVolume)
4940+
4941+public Q_SLOTS:
4942+ double volume() const;
4943+ void setVolume(double volume);
4944+
4945+public:
4946+ AccountsServiceSoundMock(QObject* parent = 0);
4947+ virtual ~AccountsServiceSoundMock();
4948+
4949+private:
4950+ double volume_;
4951+ DBusPropertiesNotifier notifier_;
4952+};
4953+
4954+} // namespace testing
4955+
4956+} // namespace indicators
4957+
4958+} // namespace ubuntu
4959
4960=== added file 'tests/service-mocks/accounts-mock/CMakeLists.txt'
4961--- tests/service-mocks/accounts-mock/CMakeLists.txt 1970-01-01 00:00:00 +0000
4962+++ tests/service-mocks/accounts-mock/CMakeLists.txt 2015-10-13 07:44:39 +0000
4963@@ -0,0 +1,42 @@
4964+set(CMAKE_AUTOMOC ON)
4965+set(CMAKE_INCLUDE_CURRENT_DIR ON)
4966+
4967+find_package(Qt5DBus REQUIRED)
4968+include_directories(${Qt5DBus_INCLUDE_DIRS}
4969+ "${CMAKE_SOURCE_DIR}/tests/service-mocks")
4970+
4971+add_definitions(-DQT_NO_KEYWORDS=1)
4972+
4973+set(dbusinterface_sound_xml "com.ubuntu.AccountsService.Sound.Mock.xml")
4974+set_source_files_properties(${dbusinterface_sound_xml} PROPERTIES
4975+ CLASSNAME AccountsServiceSoundMockInterface)
4976+
4977+qt5_add_dbus_interface(interface_files ${dbusinterface_sound_xml} accountsservice_sound_interface)
4978+
4979+qt5_add_dbus_adaptor(adaptor_files
4980+ com.ubuntu.AccountsService.Sound.Mock.xml
4981+ AccountsServiceSoundMock.h
4982+ ubuntu::indicators::testing::AccountsServiceSoundMock
4983+ AccountsServiceSoundMockAdaptor)
4984+
4985+qt5_add_dbus_adaptor(adaptor_files
4986+ org.freedesktop.Accounts.Mock.xml
4987+ AccountsMock.h
4988+ ubuntu::indicators::testing::AccountsMock
4989+ AccountsMockAdaptor)
4990+
4991+add_executable(
4992+ accounts-service-sound
4993+ ${interface_files}
4994+ ${adaptor_files}
4995+ AccountsServiceSoundMock.cpp
4996+ AccountsMock.cpp
4997+ ${CMAKE_SOURCE_DIR}/tests/service-mocks/DBusPropertiesNotifier.cpp
4998+ main.cpp
4999+)
5000+
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches