Merge lp:~ted/indicator-messages/sanity-testing into lp:indicator-messages/15.04

Proposed by Ted Gould on 2015-03-07
Status: Merged
Approved by: Charles Kerr on 2015-03-11
Approved revision: 462
Merged at revision: 441
Proposed branch: lp:~ted/indicator-messages/sanity-testing
Merge into: lp:indicator-messages/15.04
Diff against target: 1226 lines (+1098/-11)
10 files modified
Makefile.am (+2/-2)
configure.ac (+3/-1)
debian/control (+1/-0)
po/POTFILES.in (+0/-1)
tests/Makefile.am (+53/-7)
tests/accounts-service-mock.h (+134/-0)
tests/applications/test.desktop (+3/-0)
tests/applications/test2.desktop (+5/-0)
tests/indicator-fixture.h (+688/-0)
tests/indicator-test.cpp (+209/-0)
To merge this branch: bzr merge lp:~ted/indicator-messages/sanity-testing
Reviewer Review Type Date Requested Status
Charles Kerr (community) 2015-03-07 Approve on 2015-03-11
PS Jenkins bot (community) continuous-integration Approve on 2015-03-10
Review via email: mp+252197@code.launchpad.net

Commit message

Sanity tests to test basic features of the messaging menu

Description of the change

Some basic testing so that we have some fallback from stupid mistakes, but not exhaustive by any means.

NOTE: A large part of this MR is framework code copied from indicator-sound. It should go into a shared library eventually, but it isn't there today.

To post a comment you must log in.
Charles Kerr (charlesk) wrote :

A few inline comments below, no showstoppers.

Overall I'm very impressed with the IndicatorFixture you've got here.

If I was the one who reviewed it before in i-sound, maybe it was before my time on autopilot and I didn't realize what I was seeing.

When you get back from Galveston, let's look into making a shared repo so that other indicators can use this.

Ted Gould (ted) wrote :
Download full text (3.4 KiB)

On Tue, 2015-03-10 at 18:42 +0000, Charles Kerr wrote:

> > + -DSCHEMA_DIR="\"$(abs_builddir)/gsettings-schemas-compiled/\"" \
>
> Might make sense to define this directory as its own variable, so that it can be used below in schemas-compiled.stamp?
>
> Also, we're using $(abs_builddir)/gsettings-schema-compiled here but $(builddir)/gsettings-schema-compiled below... I think they're the same in this MP but it's probably better to use abs_builddir everywhere?

Makes sense. r460

> > $(top_builddir)/common/indicator-messages-service.h \
> > $(top_srcdir)/src/gactionmuxer.c \
> > - $(top_srcdir)/src/gactionmuxer.h
> > + $(top_srcdir)/src/gactionmuxer.h \
>
> How did this work before?
>
> > $(top_srcdir)/src/dbus-data.h

I think because it was header files and you don't *actually* need to
include them in the source list unless you are doing a make dist to
build your tarball. If you always use the Bazaar branch you'll never
notice them being missing.

> > + operator DbusTestDbusMock* () {
> > + return mock;
> > + }
>
> overloading operator() like this seems excessive.

I was trying to make the tests read nicely in that otherwise you end up
having to do all the GObject casts. The reality is that this one isn't
too useful, but the DbusTestTask gets passed to libdbustest all the
time. Not as much with this fixture, but in the other i-sound tests it
is used like that more (mock comes from i-sound)

> > + std::shared_ptr<GMenuModel> _menu;
> > + std::shared_ptr<GActionGroup> _actions;
> > + DbusTestService * _session_service;
> > + DbusTestService * _system_service;
> > + DbusTestTask * _test_indicator;
> > + DbusTestTask * _test_dummy;
> > + GDBusConnection * _session;
> > + GDBusConnection * _system;
> > +
>
> http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier

Eh, sure, one underscore seems like it doesn't break the rules. Not
perfect for sure.

> > + std::for_each(mocks.begin(), mocks.end(), [this](std::shared_ptr<DbusTestTask> task) {
>
> "for (auto task : mocks) {" would be clearer
>
> > + if (dbus_test_task_get_bus(task.get()) == DBUS_TEST_SERVICE_BUS_SYSTEM) {
> > + dbus_test_service_add_task(_system_service, task.get());
> > + } else {
> > + dbus_test_service_add_task(_session_service, task.get());
> > + }
> > + });

Good point. r461

> > + virtual ~PerRunData (void) {
>
> dtor doesn't need a void argument?

The compiler at least seems to want the parameter list. The void is just
making it look less like line noise :-)

> > +static void
> > +messageReplyActivate (GObject * obj, gchar * name, GVariant * value, gpointer user_data) {
> > + std::string * res = reinterpret_cast<std::string *>(user_data);
>
> "auto res =" would avoid redundancy
>
> > + *res = g_variant_get_string(value, nullptr);
> > +}

You just want me to give into your auto everywhere world! r462

> > + EXPECT_EVENTUALLY_ACTION_ENABLED("remove-all", false);
> > +}
>
> EXPECT_ is used as the default in these tests, but most of the time followup tests don't make sense if the ones that preceded them failed & should use ASSERT_ instead

So, yes....

Read more...

460. By Ted Gould on 2015-03-10

Making the schema compilation directory into its own variable

461. By Ted Gould on 2015-03-10

Remove useless for_each call

462. By Ted Gould on 2015-03-10

Pedantic use of auto

Charles Kerr (charlesk) wrote :

LGTM.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Makefile.am'
2--- Makefile.am 2013-08-20 10:38:10 +0000
3+++ Makefile.am 2015-03-10 21:21:53 +0000
4@@ -12,10 +12,10 @@
5
6 if BUILD_TESTS
7 SUBDIRS += \
8- test
9+ tests
10
11 # build src first
12-test: src
13+tests: src libmessaging-menu
14
15 endif
16
17
18=== modified file 'configure.ac'
19--- configure.ac 2014-09-17 20:13:13 +0000
20+++ configure.ac 2015-03-10 21:21:53 +0000
21@@ -48,6 +48,8 @@
22
23 PKG_CHECK_MODULES(GIO, gio-unix-2.0 >= $GIO_UNIX_REQUIRED_VERSION)
24
25+PKG_CHECK_MODULES(DBUSTEST, dbustest-1)
26+
27 AC_SUBST(APPLET_CFLAGS)
28 AC_SUBST(APPLET_LIBS)
29
30@@ -172,7 +174,7 @@
31 data/icons/scalable/categories/Makefile
32 data/upstart/Makefile
33 po/Makefile.in
34-test/Makefile
35+tests/Makefile
36 libmessaging-menu/Makefile
37 libmessaging-menu/messaging-menu.pc
38 doc/Makefile
39
40=== modified file 'debian/control'
41--- debian/control 2014-10-08 14:45:43 +0000
42+++ debian/control 2015-03-10 21:21:53 +0000
43@@ -11,6 +11,7 @@
44 gtk-doc-tools,
45 intltool,
46 libaccountsservice-dev,
47+ libdbustest1-dev,
48 libgirepository1.0-dev (>= 0.9.12),
49 libgtest-dev,
50 python3-dbusmock,
51
52=== modified file 'po/POTFILES.in'
53--- po/POTFILES.in 2013-10-01 10:29:49 +0000
54+++ po/POTFILES.in 2015-03-10 21:21:53 +0000
55@@ -1,5 +1,4 @@
56 [encoding: UTF-8]
57-test/indicator-messages-service-activate.c
58 src/im-phone-menu.c
59 src/messages-service.c
60 src/im-desktop-menu.c
61
62=== renamed directory 'test' => 'tests'
63=== removed directory 'tests'
64=== modified file 'tests/Makefile.am'
65--- test/Makefile.am 2013-08-28 10:23:31 +0000
66+++ tests/Makefile.am 2015-03-10 21:21:53 +0000
67@@ -1,5 +1,6 @@
68
69-check_LIBRARIES = libgtest.a
70+CLEANFILES=
71+check_LTLIBRARIES = libgtest.la
72 check_PROGRAMS = test-gactionmuxer
73
74 TESTS = $(check_PROGRAMS)
75@@ -7,15 +8,22 @@
76 AM_CPPFLAGS = $(GTEST_CPPFLAGS) \
77 -I${top_srcdir}/src
78
79-nodist_libgtest_a_SOURCES = \
80+######################################
81+# Google Test
82+######################################
83+
84+nodist_libgtest_la_SOURCES = \
85 $(GTEST_SOURCE)/src/gtest-all.cc \
86 $(GTEST_SOURCE)/src/gtest_main.cc
87-libgtest_a_CPPFLAGS = \
88+libgtest_la_CPPFLAGS = \
89 $(GTEST_CPPFLAGS) -w \
90 $(AM_CPPFLAGS)
91-libgtest_a_CXXFLAGS = \
92+libgtest_la_CXXFLAGS = \
93 $(AM_CXXFLAGS)
94
95+######################################
96+# GActionMixer
97+######################################
98
99 test_gactionmuxer_SOURCES = \
100 test-gactionmuxer.cpp
101@@ -27,8 +35,46 @@
102 test_gactionmuxer_LDADD = \
103 $(APPLET_LIBS) \
104 libindicator-messages-service.la \
105- libgtest.a
106-
107+ libgtest.la
108+
109+######################################
110+# Indicator Test
111+######################################
112+
113+SCHEMA_COMPILED_DIR="$(abs_builddir)/gsettings-schemas-compiled/"
114+
115+indicator_test_SOURCES = \
116+ indicator-test.cpp
117+
118+indicator_test_CPPFLAGS = \
119+ -DINDICATOR_MESSAGES_SERVICE_BINARY="\"$(abs_top_builddir)/src/indicator-messages-service\"" \
120+ -DSCHEMA_DIR="\"$(SCHEMA_COMPILED_DIR)\"" \
121+ -DXDG_DATA_DIRS="\"$(abs_srcdir)/\"" \
122+ -I$(top_srcdir)/libmessaging-menu \
123+ -std=c++11 \
124+ $(APPLET_CFLAGS) \
125+ $(DBUSTEST_CFLAGS) \
126+ $(AM_CPPFLAGS)
127+
128+indicator_test_LDADD = \
129+ $(APPLET_LIBS) \
130+ $(DBUSTEST_LIBS) \
131+ $(top_builddir)/libmessaging-menu/libmessaging-menu.la \
132+ libgtest.la \
133+ -lc -lpthread
134+
135+indicator-test.cpp: schemas-compiled.stamp
136+
137+schemas-compiled.stamp: $(top_srcdir)/data/*gschema.xml
138+ @rm -rf $(SCHEMA_COMPILED_DIR)
139+ @mkdir -p $(SCHEMA_COMPILED_DIR)
140+ @cp -f $(top_srcdir)/data/*gschema.xml $(SCHEMA_COMPILED_DIR)
141+ @`pkg-config gio-2.0 --variable glib_compile_schemas` $(SCHEMA_COMPILED_DIR)
142+ @touch schemas-compiled.stamp
143+
144+CLEANFILES += schemas-compiled.stamp
145+TESTS += indicator-test
146+check_PROGRAMS += indicator-test
147
148 ######################################
149 # Lib containing code under test
150@@ -41,7 +87,7 @@
151 $(top_builddir)/common/indicator-messages-service.c \
152 $(top_builddir)/common/indicator-messages-service.h \
153 $(top_srcdir)/src/gactionmuxer.c \
154- $(top_srcdir)/src/gactionmuxer.h
155+ $(top_srcdir)/src/gactionmuxer.h \
156 $(top_srcdir)/src/dbus-data.h
157
158 libindicator_messages_service_ladir = \
159
160=== added file 'tests/accounts-service-mock.h'
161--- tests/accounts-service-mock.h 1970-01-01 00:00:00 +0000
162+++ tests/accounts-service-mock.h 2015-03-10 21:21:53 +0000
163@@ -0,0 +1,134 @@
164+/*
165+ * Copyright © 2014 Canonical Ltd.
166+ *
167+ * This program is free software; you can redistribute it and/or modify
168+ * it under the terms of the GNU General Public License as published by
169+ * the Free Software Foundation; version 3.
170+ *
171+ * This program is distributed in the hope that it will be useful,
172+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
173+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
174+ * GNU General Public License for more details.
175+ *
176+ * You should have received a copy of the GNU General Public License
177+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
178+ *
179+ * Authors:
180+ * Ted Gould <ted@canonical.com>
181+ */
182+
183+#include <memory>
184+#include <libdbustest/dbus-test.h>
185+
186+class AccountsServiceMock
187+{
188+ DbusTestDbusMock * mock = nullptr;
189+ DbusTestDbusMockObject * soundobj = nullptr;
190+ DbusTestDbusMockObject * userobj = nullptr;
191+ DbusTestDbusMockObject * syssoundobj = nullptr;
192+ DbusTestDbusMockObject * privacyobj = nullptr;
193+
194+ public:
195+ AccountsServiceMock () {
196+ mock = dbus_test_dbus_mock_new("org.freedesktop.Accounts");
197+
198+ dbus_test_task_set_bus(DBUS_TEST_TASK(mock), DBUS_TEST_SERVICE_BUS_SYSTEM);
199+
200+ DbusTestDbusMockObject * baseobj = dbus_test_dbus_mock_get_object(mock, "/org/freedesktop/Accounts", "org.freedesktop.Accounts", NULL);
201+
202+ dbus_test_dbus_mock_object_add_method(mock, baseobj,
203+ "CacheUser", G_VARIANT_TYPE_STRING, G_VARIANT_TYPE_OBJECT_PATH,
204+ "ret = dbus.ObjectPath('/user')\n", NULL);
205+ dbus_test_dbus_mock_object_add_method(mock, baseobj,
206+ "FindUserById", G_VARIANT_TYPE_INT64, G_VARIANT_TYPE_OBJECT_PATH,
207+ "ret = dbus.ObjectPath('/user')\n", NULL);
208+ dbus_test_dbus_mock_object_add_method(mock, baseobj,
209+ "FindUserByName", G_VARIANT_TYPE_STRING, G_VARIANT_TYPE_OBJECT_PATH,
210+ "ret = dbus.ObjectPath('/user')\n", NULL);
211+ dbus_test_dbus_mock_object_add_method(mock, baseobj,
212+ "ListCachedUsers", NULL, G_VARIANT_TYPE_OBJECT_PATH_ARRAY,
213+ "ret = [ dbus.ObjectPath('/user') ]\n", NULL);
214+ dbus_test_dbus_mock_object_add_method(mock, baseobj,
215+ "UncacheUser", G_VARIANT_TYPE_STRING, NULL,
216+ "", NULL);
217+
218+ userobj = dbus_test_dbus_mock_get_object(mock, "/user", "org.freedesktop.Accounts.User", NULL);
219+ dbus_test_dbus_mock_object_add_property(mock, userobj,
220+ "UserName", G_VARIANT_TYPE_STRING,
221+ g_variant_new_string(g_get_user_name()), NULL);
222+ dbus_test_dbus_mock_object_add_method(mock, baseobj,
223+ "SetXHasMessages", G_VARIANT_TYPE_BOOLEAN, nullptr,
224+ "", NULL);
225+
226+ soundobj = dbus_test_dbus_mock_get_object(mock, "/user", "com.canonical.indicator.sound.AccountsService", NULL);
227+ dbus_test_dbus_mock_object_add_property(mock, soundobj,
228+ "Timestamp", G_VARIANT_TYPE_UINT64,
229+ g_variant_new_uint64(0), NULL);
230+ dbus_test_dbus_mock_object_add_property(mock, soundobj,
231+ "PlayerName", G_VARIANT_TYPE_STRING,
232+ g_variant_new_string(""), NULL);
233+ dbus_test_dbus_mock_object_add_property(mock, soundobj,
234+ "PlayerIcon", G_VARIANT_TYPE_VARIANT,
235+ g_variant_new_variant(g_variant_new_string("")), NULL);
236+ dbus_test_dbus_mock_object_add_property(mock, soundobj,
237+ "Running", G_VARIANT_TYPE_BOOLEAN,
238+ g_variant_new_boolean(FALSE), NULL);
239+ dbus_test_dbus_mock_object_add_property(mock, soundobj,
240+ "State", G_VARIANT_TYPE_STRING,
241+ g_variant_new_string(""), NULL);
242+ dbus_test_dbus_mock_object_add_property(mock, soundobj,
243+ "Title", G_VARIANT_TYPE_STRING,
244+ g_variant_new_string(""), NULL);
245+ dbus_test_dbus_mock_object_add_property(mock, soundobj,
246+ "Artist", G_VARIANT_TYPE_STRING,
247+ g_variant_new_string(""), NULL);
248+ dbus_test_dbus_mock_object_add_property(mock, soundobj,
249+ "Album", G_VARIANT_TYPE_STRING,
250+ g_variant_new_string(""), NULL);
251+ dbus_test_dbus_mock_object_add_property(mock, soundobj,
252+ "ArtUrl", G_VARIANT_TYPE_STRING,
253+ g_variant_new_string(""), NULL);
254+
255+ syssoundobj = dbus_test_dbus_mock_get_object(mock, "/user", "com.ubuntu.touch.AccountsService.Sound", NULL);
256+ dbus_test_dbus_mock_object_add_property(mock, syssoundobj,
257+ "SilentMode", G_VARIANT_TYPE_BOOLEAN,
258+ g_variant_new_boolean(FALSE), NULL);
259+
260+ privacyobj = dbus_test_dbus_mock_get_object(mock, "/user", "com.ubuntu.touch.AccountsService.SecurityPrivacy", NULL);
261+ dbus_test_dbus_mock_object_add_property(mock, privacyobj,
262+ "MessagesWelcomeScreen", G_VARIANT_TYPE_BOOLEAN,
263+ g_variant_new_boolean(true), NULL);
264+ dbus_test_dbus_mock_object_add_property(mock, privacyobj,
265+ "StatsWelcomeScreen", G_VARIANT_TYPE_BOOLEAN,
266+ g_variant_new_boolean(true), NULL);
267+ }
268+
269+ ~AccountsServiceMock () {
270+ g_debug("Destroying the Accounts Service Mock");
271+ g_clear_object(&mock);
272+ }
273+
274+ void setSilentMode (bool modeValue) {
275+ dbus_test_dbus_mock_object_update_property(mock, syssoundobj,
276+ "SilentMode", g_variant_new_boolean(modeValue ? TRUE : FALSE),
277+ NULL);
278+ }
279+
280+ operator std::shared_ptr<DbusTestTask> () {
281+ return std::shared_ptr<DbusTestTask>(
282+ DBUS_TEST_TASK(g_object_ref(mock)),
283+ [](DbusTestTask * task) { g_clear_object(&task); });
284+ }
285+
286+ operator DbusTestTask* () {
287+ return DBUS_TEST_TASK(mock);
288+ }
289+
290+ operator DbusTestDbusMock* () {
291+ return mock;
292+ }
293+
294+ DbusTestDbusMockObject * get_sound () {
295+ return soundobj;
296+ }
297+};
298
299=== modified file 'tests/applications/test.desktop'
300--- test/applications/test.desktop 2013-02-27 00:34:14 +0000
301+++ tests/applications/test.desktop 2015-03-10 21:21:53 +0000
302@@ -1,2 +1,5 @@
303 [Desktop Entry]
304 Type=Application
305+Name=Test
306+Exec=test
307+Icon=test-app
308
309=== added file 'tests/applications/test2.desktop'
310--- tests/applications/test2.desktop 1970-01-01 00:00:00 +0000
311+++ tests/applications/test2.desktop 2015-03-10 21:21:53 +0000
312@@ -0,0 +1,5 @@
313+[Desktop Entry]
314+Type=Application
315+Name=Test
316+Exec=test
317+Icon=test-app
318
319=== added file 'tests/indicator-fixture.h'
320--- tests/indicator-fixture.h 1970-01-01 00:00:00 +0000
321+++ tests/indicator-fixture.h 2015-03-10 21:21:53 +0000
322@@ -0,0 +1,688 @@
323+/*
324+ * Copyright © 2014 Canonical Ltd.
325+ *
326+ * This program is free software; you can redistribute it and/or modify
327+ * it under the terms of the GNU General Public License as published by
328+ * the Free Software Foundation; version 3.
329+ *
330+ * This program is distributed in the hope that it will be useful,
331+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
332+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
333+ * GNU General Public License for more details.
334+ *
335+ * You should have received a copy of the GNU General Public License
336+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
337+ *
338+ * Authors:
339+ * Ted Gould <ted@canonical.com>
340+ */
341+
342+#include <memory>
343+#include <algorithm>
344+#include <string>
345+#include <functional>
346+#include <future>
347+
348+#include <gtest/gtest.h>
349+#include <gio/gio.h>
350+#include <libdbustest/dbus-test.h>
351+
352+class IndicatorFixture : public ::testing::Test
353+{
354+ private:
355+ std::string _indicatorPath;
356+ std::string _indicatorAddress;
357+ std::vector<std::shared_ptr<DbusTestTask>> _mocks;
358+ protected:
359+ std::chrono::milliseconds _eventuallyTime;
360+
361+ private:
362+ class PerRunData {
363+ public:
364+ /* We're private in the fixture but other than that we don't care,
365+ we don't leak out. This object's purpose isn't to hide data it is
366+ to make the lifecycle of the items more clear. */
367+ std::shared_ptr<GMenuModel> _menu;
368+ std::shared_ptr<GActionGroup> _actions;
369+ DbusTestService * _session_service;
370+ DbusTestService * _system_service;
371+ DbusTestTask * _test_indicator;
372+ DbusTestTask * _test_dummy;
373+ GDBusConnection * _session;
374+ GDBusConnection * _system;
375+
376+ PerRunData (const std::string& indicatorPath, const std::string& indicatorAddress, std::vector<std::shared_ptr<DbusTestTask>>& mocks)
377+ : _menu(nullptr)
378+ , _session(nullptr)
379+ {
380+ _session_service = dbus_test_service_new(nullptr);
381+ dbus_test_service_set_bus(_session_service, DBUS_TEST_SERVICE_BUS_SESSION);
382+
383+ _system_service = dbus_test_service_new(nullptr);
384+ dbus_test_service_set_bus(_system_service, DBUS_TEST_SERVICE_BUS_SYSTEM);
385+
386+ _test_indicator = DBUS_TEST_TASK(dbus_test_process_new(indicatorPath.c_str()));
387+ dbus_test_task_set_name(_test_indicator, "Indicator");
388+ dbus_test_service_add_task(_session_service, _test_indicator);
389+
390+ _test_dummy = dbus_test_task_new();
391+ dbus_test_task_set_wait_for(_test_dummy, indicatorAddress.c_str());
392+ dbus_test_task_set_name(_test_dummy, "Dummy");
393+ dbus_test_service_add_task(_session_service, _test_dummy);
394+
395+ for(auto task : mocks) {
396+ if (dbus_test_task_get_bus(task.get()) == DBUS_TEST_SERVICE_BUS_SYSTEM) {
397+ dbus_test_service_add_task(_system_service, task.get());
398+ } else {
399+ dbus_test_service_add_task(_session_service, task.get());
400+ }
401+ }
402+
403+ g_debug("Starting System Bus");
404+ dbus_test_service_start_tasks(_system_service);
405+ _system = g_bus_get_sync(G_BUS_TYPE_SYSTEM, nullptr, nullptr);
406+ g_dbus_connection_set_exit_on_close(_system, FALSE);
407+
408+ g_debug("Starting Session Bus");
409+ dbus_test_service_start_tasks(_session_service);
410+ _session = g_bus_get_sync(G_BUS_TYPE_SESSION, nullptr, nullptr);
411+ g_dbus_connection_set_exit_on_close(_session, FALSE);
412+ }
413+
414+ virtual ~PerRunData (void) {
415+ _menu.reset();
416+ _actions.reset();
417+
418+ /* D-Bus Test Stuff */
419+ g_clear_object(&_test_dummy);
420+ g_clear_object(&_test_indicator);
421+ g_clear_object(&_session_service);
422+ g_clear_object(&_system_service);
423+
424+ /* Wait for D-Bus session bus to go */
425+ if (!g_dbus_connection_is_closed(_session)) {
426+ g_dbus_connection_close_sync(_session, nullptr, nullptr);
427+ }
428+ g_clear_object(&_session);
429+
430+ if (!g_dbus_connection_is_closed(_system)) {
431+ g_dbus_connection_close_sync(_system, nullptr, nullptr);
432+ }
433+ g_clear_object(&_system);
434+ }
435+ };
436+
437+ std::shared_ptr<PerRunData> run;
438+
439+ public:
440+ virtual ~IndicatorFixture() = default;
441+
442+ IndicatorFixture (const std::string& path,
443+ const std::string& addr)
444+ : _indicatorPath(path)
445+ , _indicatorAddress(addr)
446+ , _eventuallyTime(std::chrono::seconds(5))
447+ {
448+ };
449+
450+
451+ protected:
452+ virtual void SetUp() override
453+ {
454+ run = std::make_shared<PerRunData>(_indicatorPath, _indicatorAddress, _mocks);
455+
456+ _mocks.clear();
457+ }
458+
459+ virtual void TearDown() override
460+ {
461+ run.reset();
462+ }
463+
464+ void addMock (std::shared_ptr<DbusTestTask> mock)
465+ {
466+ _mocks.push_back(mock);
467+ }
468+
469+ std::shared_ptr<DbusTestTask> buildBustleMock (const std::string& filename, DbusTestServiceBus bus = DBUS_TEST_SERVICE_BUS_BOTH)
470+ {
471+ return std::shared_ptr<DbusTestTask>([filename, bus]() {
472+ DbusTestTask * bustle = DBUS_TEST_TASK(dbus_test_bustle_new(filename.c_str()));
473+ dbus_test_task_set_name(bustle, "Bustle");
474+ dbus_test_task_set_bus(bustle, bus);
475+ return bustle;
476+ }(), [](DbusTestTask * bustle) {
477+ g_clear_object(&bustle);
478+ });
479+ }
480+
481+ private:
482+ void waitForCore (GObject * obj, const gchar * signalname) {
483+ auto loop = g_main_loop_new(nullptr, FALSE);
484+
485+ /* Our two exit criteria */
486+ gulong signal = g_signal_connect_swapped(obj, signalname, G_CALLBACK(g_main_loop_quit), loop);
487+ guint timer = g_timeout_add_seconds(5, [](gpointer user_data) -> gboolean {
488+ g_warning("Menu Timeout");
489+ g_main_loop_quit((GMainLoop *)user_data);
490+ return G_SOURCE_CONTINUE;
491+ }, loop);
492+
493+ /* Wait for sync */
494+ g_main_loop_run(loop);
495+
496+ /* Clean up */
497+ g_source_remove(timer);
498+ g_signal_handler_disconnect(obj, signal);
499+
500+ g_main_loop_unref(loop);
501+ }
502+
503+ void menuWaitForItems (const std::shared_ptr<GMenuModel>& menu) {
504+ auto count = g_menu_model_get_n_items(menu.get());
505+
506+ if (count != 0)
507+ return;
508+
509+ waitForCore(G_OBJECT(menu.get()), "items-changed");
510+ }
511+
512+ void agWaitForActions (const std::shared_ptr<GActionGroup>& group) {
513+ auto list = std::shared_ptr<gchar *>(
514+ g_action_group_list_actions(group.get()),
515+ [](gchar ** list) {
516+ g_strfreev(list);
517+ });
518+
519+ if (g_strv_length(list.get()) != 0) {
520+ return;
521+ }
522+
523+ waitForCore(G_OBJECT(group.get()), "action-added");
524+ }
525+
526+ testing::AssertionResult expectEventually (std::function<testing::AssertionResult(void)> &testfunc) {
527+ auto loop = std::shared_ptr<GMainLoop>(g_main_loop_new(nullptr, FALSE), [](GMainLoop * loop) { if (loop != nullptr) g_main_loop_unref(loop); });
528+
529+ std::promise<testing::AssertionResult> retpromise;
530+ auto retfuture = retpromise.get_future();
531+ auto start = std::chrono::steady_clock::now();
532+
533+ /* The core of the idle function as an object so we can use the C++-isms
534+ of attaching the variables and make this code reasonably readable */
535+ std::function<void(void)> idlefunc = [&loop, &retpromise, &testfunc, &start, this]() -> void {
536+ auto result = testfunc();
537+
538+ if (result == false && _eventuallyTime > (std::chrono::steady_clock::now() - start)) {
539+ return;
540+ }
541+
542+ retpromise.set_value(result);
543+ g_main_loop_quit(loop.get());
544+ };
545+
546+ auto idlesrc = g_idle_add([](gpointer data) -> gboolean {
547+ auto func = reinterpret_cast<std::function<void(void)> *>(data);
548+ (*func)();
549+ return G_SOURCE_CONTINUE;
550+ }, &idlefunc);
551+
552+ g_main_loop_run(loop.get());
553+ g_source_remove(idlesrc);
554+
555+ return retfuture.get();
556+ }
557+
558+ protected:
559+ void setMenu (const std::string& path) {
560+ run->_menu.reset();
561+
562+ g_debug("Getting Menu: %s:%s", _indicatorAddress.c_str(), path.c_str());
563+ run->_menu = std::shared_ptr<GMenuModel>(G_MENU_MODEL(g_dbus_menu_model_get(run->_session, _indicatorAddress.c_str(), path.c_str())), [](GMenuModel * modelptr) {
564+ g_clear_object(&modelptr);
565+ });
566+
567+ menuWaitForItems(run->_menu);
568+ }
569+
570+ void setActions (const std::string& path) {
571+ run->_actions.reset();
572+
573+ run->_actions = std::shared_ptr<GActionGroup>(G_ACTION_GROUP(g_dbus_action_group_get(run->_session, _indicatorAddress.c_str(), path.c_str())), [](GActionGroup * groupptr) {
574+ g_clear_object(&groupptr);
575+ });
576+
577+ agWaitForActions(run->_actions);
578+ }
579+
580+ void activateAction (const std::string &name, std::shared_ptr<GVariant> &parameter) {
581+ g_action_group_activate_action(run->_actions.get(), name.c_str(), parameter.get());
582+ }
583+
584+ void activateAction (const std::string &name, GVariant * parameter = nullptr) {
585+ std::shared_ptr<GVariant> param;
586+
587+ if (parameter != nullptr)
588+ param = std::shared_ptr<GVariant>(g_variant_ref_sink(parameter), [](GVariant * var) {
589+ g_variant_unref(var);
590+ });
591+
592+ return activateAction(name, param);
593+ }
594+
595+ testing::AssertionResult expectActionExists (const gchar * nameStr, const std::string& name) {
596+ bool hasit = g_action_group_has_action(run->_actions.get(), name.c_str());
597+
598+ if (!hasit) {
599+ auto result = testing::AssertionFailure();
600+ result <<
601+ " Action: " << nameStr << std::endl <<
602+ " Expected: " << "Exists" << std::endl <<
603+ " Actual: " << "No action found" << std::endl;
604+
605+ return result;
606+ }
607+
608+ auto result = testing::AssertionSuccess();
609+ return result;
610+ }
611+
612+ template <typename... Args> testing::AssertionResult expectEventuallyActionExists (Args&& ... args) {
613+ std::function<testing::AssertionResult(void)> func = [&]() {
614+ return expectActionExists(std::forward<Args>(args)...);
615+ };
616+ return expectEventually(func);
617+ }
618+
619+ testing::AssertionResult expectActionDoesNotExist (const gchar * nameStr, const std::string& name) {
620+ bool hasit = g_action_group_has_action(run->_actions.get(), name.c_str());
621+
622+ if (hasit) {
623+ auto result = testing::AssertionFailure();
624+ result <<
625+ " Action: " << nameStr << std::endl <<
626+ " Expected: " << "No action found" << std::endl <<
627+ " Actual: " << "Exists" << std::endl;
628+
629+ return result;
630+ }
631+
632+ auto result = testing::AssertionSuccess();
633+ return result;
634+ }
635+
636+ template <typename... Args> testing::AssertionResult expectEventuallyActionDoesNotExist (Args&& ... args) {
637+ std::function<testing::AssertionResult(void)> func = [&]() {
638+ return expectActionDoesNotExist(std::forward<Args>(args)...);
639+ };
640+ return expectEventually(func);
641+ }
642+
643+ testing::AssertionResult expectActionEnabled (const char * nameStr, const char * typeStr, const std::string& name, bool enabled) {
644+ auto aenabled = g_action_group_get_action_enabled(run->_actions.get(), name.c_str());
645+
646+ if (enabled != aenabled) {
647+ auto result = testing::AssertionFailure();
648+ result <<
649+ " Action: " << nameStr << std::endl <<
650+ " Expected: " << enabled << std::endl <<
651+ " Actual: " << aenabled << std::endl;
652+
653+ return result;
654+ }
655+
656+ auto result = testing::AssertionSuccess();
657+ return result;
658+ }
659+
660+ template <typename... Args> testing::AssertionResult expectEventuallyActionEnabled (Args&& ... args) {
661+ std::function<testing::AssertionResult(void)> func = [&]() {
662+ return expectActionEnabled(std::forward<Args>(args)...);
663+ };
664+ return expectEventually(func);
665+ }
666+
667+ testing::AssertionResult expectActionStateType (const char * nameStr, const char * typeStr, const std::string& name, const GVariantType * type) {
668+ auto atype = g_action_group_get_action_state_type(run->_actions.get(), name.c_str());
669+ bool same = false;
670+
671+ if (atype != nullptr) {
672+ same = g_variant_type_equal(atype, type);
673+ }
674+
675+ if (!same) {
676+ auto result = testing::AssertionFailure();
677+ result <<
678+ " Action: " << nameStr << std::endl <<
679+ " Expected: " << typeStr << std::endl <<
680+ " Actual: " << (atype == nullptr ? "(null)" : g_variant_type_peek_string(atype)) << std::endl;
681+
682+ return result;
683+ }
684+
685+ auto result = testing::AssertionSuccess();
686+ return result;
687+ }
688+
689+ template <typename... Args> testing::AssertionResult expectEventuallyActionStateType (Args&& ... args) {
690+ std::function<testing::AssertionResult(void)> func = [&]() {
691+ return expectActionStateType(std::forward<Args>(args)...);
692+ };
693+ return expectEventually(func);
694+ }
695+
696+ testing::AssertionResult expectActionActivationType (const char * nameStr, const char * typeStr, const std::string& name, const GVariantType * type) {
697+ auto atype = g_action_group_get_action_parameter_type(run->_actions.get(), name.c_str());
698+ bool same = false;
699+
700+ if (atype != nullptr) {
701+ same = g_variant_type_equal(atype, type);
702+ }
703+
704+ if (!same) {
705+ auto result = testing::AssertionFailure();
706+ result <<
707+ " Action: " << nameStr << std::endl <<
708+ " Expected: " << typeStr << std::endl <<
709+ " Actual: " << (atype == nullptr ? "(null)" : g_variant_type_peek_string(atype)) << std::endl;
710+
711+ return result;
712+ }
713+
714+ auto result = testing::AssertionSuccess();
715+ return result;
716+ }
717+
718+ template <typename... Args> testing::AssertionResult expectEventuallyActionActivationType (Args&& ... args) {
719+ std::function<testing::AssertionResult(void)> func = [&]() {
720+ return expectActionActivationType(std::forward<Args>(args)...);
721+ };
722+ return expectEventually(func);
723+ }
724+
725+ testing::AssertionResult expectActionStateIs (const char * nameStr, const char * valueStr, const std::string& name, std::shared_ptr<GVariant> varref) {
726+ auto aval = std::shared_ptr<GVariant>(g_action_group_get_action_state(run->_actions.get(), name.c_str()), [] (GVariant * varptr) {
727+ if (varptr != nullptr)
728+ g_variant_unref(varptr);
729+ });
730+ bool match = false;
731+
732+ if (aval != nullptr) {
733+ match = g_variant_equal(aval.get(), varref.get());
734+ }
735+
736+ if (!match) {
737+ gchar * attstr = nullptr;
738+
739+ if (aval != nullptr) {
740+ attstr = g_variant_print(aval.get(), TRUE);
741+ } else {
742+ attstr = g_strdup("nullptr");
743+ }
744+
745+ auto result = testing::AssertionFailure();
746+ result <<
747+ " Action: " << nameStr << std::endl <<
748+ " Expected: " << valueStr << std::endl <<
749+ " Actual: " << attstr << std::endl;
750+
751+ g_free(attstr);
752+
753+ return result;
754+ } else {
755+ auto result = testing::AssertionSuccess();
756+ return result;
757+ }
758+ }
759+
760+ testing::AssertionResult expectActionStateIs (const char * nameStr, const char * valueStr, const std::string& name, GVariant * value) {
761+ auto varref = std::shared_ptr<GVariant>(g_variant_ref_sink(value), [](GVariant * varptr) {
762+ if (varptr != nullptr)
763+ g_variant_unref(varptr);
764+ });
765+ return expectActionStateIs(nameStr, valueStr, name, varref);
766+ }
767+
768+ testing::AssertionResult expectActionStateIs (const char * nameStr, const char * valueStr, const std::string& name, bool value) {
769+ GVariant * var = g_variant_new_boolean(value);
770+ return expectActionStateIs(nameStr, valueStr, name, var);
771+ }
772+
773+ testing::AssertionResult expectActionStateIs (const char * nameStr, const char * valueStr, const std::string& name, std::string value) {
774+ GVariant * var = g_variant_new_string(value.c_str());
775+ return expectActionStateIs(nameStr, valueStr, name, var);
776+ }
777+
778+ testing::AssertionResult expectActionStateIs (const char * nameStr, const char * valueStr, const std::string& name, const char * value) {
779+ GVariant * var = g_variant_new_string(value);
780+ return expectActionStateIs(nameStr, valueStr, name, var);
781+ }
782+
783+ testing::AssertionResult expectActionStateIs (const char * nameStr, const char * valueStr, const std::string& name, double value) {
784+ GVariant * var = g_variant_new_double(value);
785+ return expectActionStateIs(nameStr, valueStr, name, var);
786+ }
787+
788+ testing::AssertionResult expectActionStateIs (const char * nameStr, const char * valueStr, const std::string& name, float value) {
789+ GVariant * var = g_variant_new_double(value);
790+ return expectActionStateIs(nameStr, valueStr, name, var);
791+ }
792+
793+ template <typename... Args> testing::AssertionResult expectEventuallyActionStateIs (Args&& ... args) {
794+ std::function<testing::AssertionResult(void)> func = [&]() {
795+ return expectActionStateIs(std::forward<Args>(args)...);
796+ };
797+ return expectEventually(func);
798+ }
799+
800+
801+ private:
802+ std::shared_ptr<GVariant> getMenuAttributeVal (int location, std::shared_ptr<GMenuModel>& menu, const std::string& attribute, std::shared_ptr<GVariant>& value) {
803+ if (!(location < g_menu_model_get_n_items(menu.get()))) {
804+ return nullptr;
805+ }
806+
807+ if (location >= g_menu_model_get_n_items(menu.get()))
808+ return nullptr;
809+
810+ auto menuval = std::shared_ptr<GVariant>(g_menu_model_get_item_attribute_value(menu.get(), location, attribute.c_str(), g_variant_get_type(value.get())), [](GVariant * varptr) {
811+ if (varptr != nullptr)
812+ g_variant_unref(varptr);
813+ });
814+
815+ return menuval;
816+ }
817+
818+ std::shared_ptr<GVariant> getMenuAttributeRecurse (std::vector<int>::const_iterator menuLocation, std::vector<int>::const_iterator menuEnd, const std::string& attribute, std::shared_ptr<GVariant>& value, std::shared_ptr<GMenuModel>& menu) {
819+ if (menuLocation == menuEnd)
820+ return nullptr;
821+
822+ if (menuLocation + 1 == menuEnd)
823+ return getMenuAttributeVal(*menuLocation, menu, attribute, value);
824+
825+ auto clearfunc = [](GMenuModel * modelptr) {
826+ g_clear_object(&modelptr);
827+ };
828+
829+ auto submenu = std::shared_ptr<GMenuModel>(g_menu_model_get_item_link(menu.get(), *menuLocation, G_MENU_LINK_SUBMENU), clearfunc);
830+
831+ if (submenu == nullptr)
832+ submenu = std::shared_ptr<GMenuModel>(g_menu_model_get_item_link(menu.get(), *menuLocation, G_MENU_LINK_SECTION), clearfunc);
833+
834+ if (submenu == nullptr)
835+ return nullptr;
836+
837+ menuWaitForItems(submenu);
838+
839+ return getMenuAttributeRecurse(menuLocation + 1, menuEnd, attribute, value, submenu);
840+ }
841+
842+ protected:
843+ testing::AssertionResult expectMenuAttribute (const char * menuLocationStr, const char * attributeStr, const char * valueStr, const std::vector<int> menuLocation, const std::string& attribute, GVariant * value) {
844+ auto varref = std::shared_ptr<GVariant>(g_variant_ref_sink(value), [](GVariant * varptr) {
845+ if (varptr != nullptr)
846+ g_variant_unref(varptr);
847+ });
848+
849+ auto attrib = getMenuAttributeRecurse(menuLocation.cbegin(), menuLocation.cend(), attribute, varref, run->_menu);
850+ bool same = false;
851+
852+ if (attrib != nullptr && varref != nullptr) {
853+ same = g_variant_equal(attrib.get(), varref.get());
854+ }
855+
856+ if (!same) {
857+ gchar * attstr = nullptr;
858+
859+ if (attrib != nullptr) {
860+ attstr = g_variant_print(attrib.get(), TRUE);
861+ } else {
862+ attstr = g_strdup("nullptr");
863+ }
864+
865+ auto result = testing::AssertionFailure();
866+ result <<
867+ " Menu: " << menuLocationStr << std::endl <<
868+ " Attribute: " << attributeStr << std::endl <<
869+ " Expected: " << valueStr << std::endl <<
870+ " Actual: " << attstr << std::endl;
871+
872+ g_free(attstr);
873+
874+ return result;
875+ } else {
876+ auto result = testing::AssertionSuccess();
877+ return result;
878+ }
879+ }
880+
881+ testing::AssertionResult expectMenuAttribute (const char * menuLocationStr, const char * attributeStr, const char * valueStr, const std::vector<int> menuLocation, const std::string& attribute, bool value) {
882+ GVariant * var = g_variant_new_boolean(value);
883+ return expectMenuAttribute(menuLocationStr, attributeStr, valueStr, menuLocation, attribute, var);
884+ }
885+
886+ testing::AssertionResult expectMenuAttribute (const char * menuLocationStr, const char * attributeStr, const char * valueStr, const std::vector<int> menuLocation, const std::string& attribute, std::string value) {
887+ GVariant * var = g_variant_new_string(value.c_str());
888+ return expectMenuAttribute(menuLocationStr, attributeStr, valueStr, menuLocation, attribute, var);
889+ }
890+
891+ testing::AssertionResult expectMenuAttribute (const char * menuLocationStr, const char * attributeStr, const char * valueStr, const std::vector<int> menuLocation, const std::string& attribute, const char * value) {
892+ GVariant * var = g_variant_new_string(value);
893+ return expectMenuAttribute(menuLocationStr, attributeStr, valueStr, menuLocation, attribute, var);
894+ }
895+
896+ template <typename... Args> testing::AssertionResult expectEventuallyMenuAttribute (Args&& ... args) {
897+ std::function<testing::AssertionResult(void)> func = [&]() {
898+ return expectMenuAttribute(std::forward<Args>(args)...);
899+ };
900+ return expectEventually(func);
901+ }
902+
903+ /* Eventually Helpers */
904+ #define _EVENTUALLY_HELPER(oper) \
905+ template <typename... Args> testing::AssertionResult expectEventually##oper (Args&& ... args) { \
906+ std::function<testing::AssertionResult(void)> func = [&]() { \
907+ return testing::internal::CmpHelper##oper(std::forward<Args>(args)...); \
908+ }; \
909+ return expectEventually(func); \
910+ }
911+
912+ _EVENTUALLY_HELPER(EQ);
913+ _EVENTUALLY_HELPER(NE);
914+ _EVENTUALLY_HELPER(LT);
915+ _EVENTUALLY_HELPER(GT);
916+ _EVENTUALLY_HELPER(STREQ);
917+ _EVENTUALLY_HELPER(STRNE);
918+
919+ #undef _EVENTUALLY_HELPER
920+};
921+
922+/* Menu Attrib */
923+#define ASSERT_MENU_ATTRIB(menu, attrib, value) \
924+ ASSERT_PRED_FORMAT3(IndicatorFixture::expectMenuAttribute, menu, attrib, value)
925+
926+#define EXPECT_MENU_ATTRIB(menu, attrib, value) \
927+ EXPECT_PRED_FORMAT3(IndicatorFixture::expectMenuAttribute, menu, attrib, value)
928+
929+#define EXPECT_EVENTUALLY_MENU_ATTRIB(menu, attrib, value) \
930+ EXPECT_PRED_FORMAT3(IndicatorFixture::expectEventuallyMenuAttribute, menu, attrib, value)
931+
932+/* Action Exists */
933+#define ASSERT_ACTION_EXISTS(action) \
934+ ASSERT_PRED_FORMAT1(IndicatorFixture::expectActionExists, action)
935+
936+#define EXPECT_ACTION_EXISTS(action) \
937+ EXPECT_PRED_FORMAT1(IndicatorFixture::expectActionExists, action)
938+
939+#define EXPECT_EVENTUALLY_ACTION_EXISTS(action) \
940+ EXPECT_PRED_FORMAT1(IndicatorFixture::expectEventuallyActionExists, action)
941+
942+/* Action Does Not Exist */
943+#define ASSERT_ACTION_DOES_NOT_EXIST(action) \
944+ ASSERT_PRED_FORMAT1(IndicatorFixture::expectActionDoesNotExist, action)
945+
946+#define EXPECT_ACTION_DOES_NOT_EXIST(action) \
947+ EXPECT_PRED_FORMAT1(IndicatorFixture::expectActionDoesNotExist, action)
948+
949+#define EXPECT_EVENTUALLY_ACTION_DOES_NOT_EXIST(action) \
950+ EXPECT_PRED_FORMAT1(IndicatorFixture::expectEventuallyActionDoesNotExist, action)
951+
952+/* Action Enabled */
953+#define ASSERT_ACTION_ENABLED(action, state) \
954+ ASSERT_PRED_FORMAT2(IndicatorFixture::expectActionEnabled, action, state)
955+
956+#define EXPECT_ACTION_ENABLED(action, state) \
957+ EXPECT_PRED_FORMAT2(IndicatorFixture::expectActionEnabled, action, state)
958+
959+#define EXPECT_EVENTUALLY_ACTION_ENABLED(action, state) \
960+ EXPECT_PRED_FORMAT2(IndicatorFixture::expectEventuallyActionEnabled, action, state)
961+
962+/* Action State */
963+#define ASSERT_ACTION_STATE(action, value) \
964+ ASSERT_PRED_FORMAT2(IndicatorFixture::expectActionStateIs, action, value)
965+
966+#define EXPECT_ACTION_STATE(action, value) \
967+ EXPECT_PRED_FORMAT2(IndicatorFixture::expectActionStateIs, action, value)
968+
969+#define EXPECT_EVENTUALLY_ACTION_STATE(action, value) \
970+ EXPECT_PRED_FORMAT2(IndicatorFixture::expectEventuallyActionStateIs, action, value)
971+
972+/* Action State Type */
973+#define ASSERT_ACTION_STATE_TYPE(action, type) \
974+ ASSERT_PRED_FORMAT2(IndicatorFixture::expectActionStateType, action, type)
975+
976+#define EXPECT_ACTION_STATE_TYPE(action, type) \
977+ EXPECT_PRED_FORMAT2(IndicatorFixture::expectActionStateType, action, type)
978+
979+#define EXPECT_EVENTUALLY_ACTION_STATE_TYPE(action, type) \
980+ EXPECT_PRED_FORMAT2(IndicatorFixture::expectEventuallyActionStateType, action, type)
981+
982+/* Action Activation Type */
983+#define ASSERT_ACTION_ACTIVATION_TYPE(action, type) \
984+ ASSERT_PRED_FORMAT2(IndicatorFixture::expectActionActivationType, action, type)
985+
986+#define EXPECT_ACTION_ACTIVATION_TYPE(action, type) \
987+ EXPECT_PRED_FORMAT2(IndicatorFixture::expectActionActivationType, action, type)
988+
989+#define EXPECT_EVENTUALLY_ACTION_ACTIVATION_TYPE(action, type) \
990+ EXPECT_PRED_FORMAT2(IndicatorFixture::expectEventuallyActionActivationType, action, type)
991+
992+/* Helpers */
993+
994+#define EXPECT_EVENTUALLY_EQ(expected, actual) \
995+ EXPECT_PRED_FORMAT2(IndicatorFixture::expectEventuallyEQ, expected, actual)
996+
997+#define EXPECT_EVENTUALLY_NE(expected, actual) \
998+ EXPECT_PRED_FORMAT2(IndicatorFixture::expectEventuallyNE, expected, actual)
999+
1000+#define EXPECT_EVENTUALLY_LT(expected, actual) \
1001+ EXPECT_PRED_FORMAT2(IndicatorFixture::expectEventuallyLT, expected, actual)
1002+
1003+#define EXPECT_EVENTUALLY_GT(expected, actual) \
1004+ EXPECT_PRED_FORMAT2(IndicatorFixture::expectEventuallyGT, expected, actual)
1005+
1006+#define EXPECT_EVENTUALLY_STREQ(expected, actual) \
1007+ EXPECT_PRED_FORMAT2(IndicatorFixture::expectEventuallySTREQ, expected, actual)
1008+
1009+#define EXPECT_EVENTUALLY_STRNE(expected, actual) \
1010+ EXPECT_PRED_FORMAT2(IndicatorFixture::expectEventuallySTRNE, expected, actual)
1011
1012=== added file 'tests/indicator-test.cpp'
1013--- tests/indicator-test.cpp 1970-01-01 00:00:00 +0000
1014+++ tests/indicator-test.cpp 2015-03-10 21:21:53 +0000
1015@@ -0,0 +1,209 @@
1016+/*
1017+ * Copyright © 2015 Canonical Ltd.
1018+ *
1019+ * This program is free software; you can redistribute it and/or modify
1020+ * it under the terms of the GNU General Public License as published by
1021+ * the Free Software Foundation; version 3.
1022+ *
1023+ * This program is distributed in the hope that it will be useful,
1024+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1025+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1026+ * GNU General Public License for more details.
1027+ *
1028+ * You should have received a copy of the GNU General Public License
1029+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1030+ *
1031+ * Authors:
1032+ * Ted Gould <ted@canonical.com>
1033+ */
1034+
1035+#include <gtest/gtest.h>
1036+#include <gio/gio.h>
1037+
1038+#include "indicator-fixture.h"
1039+#include "accounts-service-mock.h"
1040+
1041+#include "messaging-menu-app.h"
1042+#include "messaging-menu-message.h"
1043+
1044+class IndicatorTest : public IndicatorFixture
1045+{
1046+protected:
1047+ IndicatorTest (void) :
1048+ IndicatorFixture(INDICATOR_MESSAGES_SERVICE_BINARY, "com.canonical.indicator.messages")
1049+ {
1050+ }
1051+
1052+ std::shared_ptr<AccountsServiceMock> as;
1053+
1054+ virtual void SetUp() override
1055+ {
1056+ g_setenv("GSETTINGS_SCHEMA_DIR", SCHEMA_DIR, TRUE);
1057+ g_setenv("GSETTINGS_BACKEND", "memory", TRUE);
1058+
1059+ g_setenv("XDG_DATA_DIRS", XDG_DATA_DIRS, TRUE);
1060+
1061+ as = std::make_shared<AccountsServiceMock>();
1062+ addMock(*as);
1063+
1064+ IndicatorFixture::SetUp();
1065+ }
1066+
1067+ virtual void TearDown() override
1068+ {
1069+ as.reset();
1070+
1071+ IndicatorFixture::TearDown();
1072+ }
1073+
1074+};
1075+
1076+
1077+TEST_F(IndicatorTest, RootAction) {
1078+ setActions("/com/canonical/indicator/messages");
1079+
1080+ EXPECT_EVENTUALLY_ACTION_EXISTS("messages");
1081+ EXPECT_ACTION_STATE_TYPE("messages", G_VARIANT_TYPE("a{sv}"));
1082+ EXPECT_ACTION_STATE("messages", g_variant_new_parsed("{'icon': <('themed', <['indicator-messages-offline', 'indicator-messages', 'indicator']>)>, 'title': <'Notifications'>, 'accessible-desc': <'Messages'>, 'visible': <false>}"));
1083+}
1084+
1085+TEST_F(IndicatorTest, SingleMessage) {
1086+ setActions("/com/canonical/indicator/messages");
1087+
1088+ auto app = std::shared_ptr<MessagingMenuApp>(messaging_menu_app_new("test.desktop"), [](MessagingMenuApp * app) { g_clear_object(&app); });
1089+ ASSERT_NE(nullptr, app);
1090+ messaging_menu_app_register(app.get());
1091+
1092+ EXPECT_EVENTUALLY_ACTION_EXISTS("test.launch");
1093+
1094+ auto msg = std::shared_ptr<MessagingMenuMessage>(messaging_menu_message_new(
1095+ "testid",
1096+ nullptr, /* no icon */
1097+ "Test Title",
1098+ "A subtitle too",
1099+ "You only like me for my body",
1100+ 0), [](MessagingMenuMessage * msg) { g_clear_object(&msg); });
1101+ messaging_menu_app_append_message(app.get(), msg.get(), nullptr, FALSE);
1102+
1103+ EXPECT_EVENTUALLY_ACTION_EXISTS("test.msg.testid");
1104+
1105+ setMenu("/com/canonical/indicator/messages/phone");
1106+
1107+ EXPECT_EVENTUALLY_MENU_ATTRIB(std::vector<int>({0, 0, 0}), "x-canonical-type", "com.canonical.indicator.messages.messageitem");
1108+ EXPECT_MENU_ATTRIB(std::vector<int>({0, 0, 0}), "label", "Test Title");
1109+ EXPECT_MENU_ATTRIB(std::vector<int>({0, 0, 0}), "x-canonical-message-id", "testid");
1110+ EXPECT_MENU_ATTRIB(std::vector<int>({0, 0, 0}), "x-canonical-subtitle", "A subtitle too");
1111+ EXPECT_MENU_ATTRIB(std::vector<int>({0, 0, 0}), "x-canonical-text", "You only like me for my body");
1112+}
1113+
1114+static void
1115+messageReplyActivate (GObject * obj, gchar * name, GVariant * value, gpointer user_data) {
1116+ auto res = reinterpret_cast<std::string *>(user_data);
1117+ *res = g_variant_get_string(value, nullptr);
1118+}
1119+
1120+TEST_F(IndicatorTest, MessageReply) {
1121+ setActions("/com/canonical/indicator/messages");
1122+
1123+ auto app = std::shared_ptr<MessagingMenuApp>(messaging_menu_app_new("test.desktop"), [](MessagingMenuApp * app) { g_clear_object(&app); });
1124+ ASSERT_NE(nullptr, app);
1125+ messaging_menu_app_register(app.get());
1126+
1127+ EXPECT_EVENTUALLY_ACTION_EXISTS("test.launch");
1128+
1129+ auto msg = std::shared_ptr<MessagingMenuMessage>(messaging_menu_message_new(
1130+ "messageid",
1131+ nullptr, /* no icon */
1132+ "Reply Message",
1133+ "A message to reply to",
1134+ "In-app replies are for wimps, reply here to save yourself time and be cool.",
1135+ 0), [](MessagingMenuMessage * msg) { g_clear_object(&msg); });
1136+ messaging_menu_message_add_action(msg.get(),
1137+ "replyid",
1138+ "Reply",
1139+ G_VARIANT_TYPE_STRING,
1140+ nullptr);
1141+ messaging_menu_app_append_message(app.get(), msg.get(), nullptr, FALSE);
1142+
1143+ EXPECT_EVENTUALLY_ACTION_EXISTS("test.msg.messageid");
1144+ EXPECT_EVENTUALLY_ACTION_EXISTS("test.msg-actions.messageid.replyid");
1145+ EXPECT_ACTION_ACTIVATION_TYPE("test.msg-actions.messageid.replyid", G_VARIANT_TYPE_STRING);
1146+
1147+ EXPECT_ACTION_ENABLED("remove-all", true);
1148+
1149+ setMenu("/com/canonical/indicator/messages/phone");
1150+
1151+ EXPECT_EVENTUALLY_MENU_ATTRIB(std::vector<int>({0, 0, 0}), "x-canonical-type", "com.canonical.indicator.messages.messageitem");
1152+
1153+ std::string activateResponse;
1154+ g_signal_connect(msg.get(), "activate", G_CALLBACK(messageReplyActivate), &activateResponse);
1155+
1156+ activateAction("test.msg-actions.messageid.replyid", g_variant_new_string("Reply to me"));
1157+
1158+ EXPECT_EVENTUALLY_EQ("Reply to me", activateResponse);
1159+
1160+ EXPECT_EVENTUALLY_ACTION_ENABLED("remove-all", false);
1161+}
1162+
1163+TEST_F(IndicatorTest, IconNotification) {
1164+ auto normalicon = std::shared_ptr<GVariant>(g_variant_ref_sink(g_variant_new_parsed("{'icon': <('themed', <['indicator-messages-offline', 'indicator-messages', 'indicator']>)>, 'title': <'Notifications'>, 'accessible-desc': <'Messages'>, 'visible': <true>}")), [](GVariant *var) {if (var != nullptr) g_variant_unref(var); });
1165+ auto blueicon = std::shared_ptr<GVariant>(g_variant_ref_sink(g_variant_new_parsed("{'icon': <('themed', <['indicator-messages-new-offline', 'indicator-messages-new', 'indicator-messages', 'indicator']>)>, 'title': <'Notifications'>, 'accessible-desc': <'New Messages'>, 'visible': <true>}")), [](GVariant *var) {if (var != nullptr) g_variant_unref(var); });
1166+
1167+ setActions("/com/canonical/indicator/messages");
1168+
1169+ auto app = std::shared_ptr<MessagingMenuApp>(messaging_menu_app_new("test.desktop"), [](MessagingMenuApp * app) { g_clear_object(&app); });
1170+ ASSERT_NE(nullptr, app);
1171+ messaging_menu_app_register(app.get());
1172+
1173+ EXPECT_EVENTUALLY_ACTION_EXISTS("test.launch");
1174+
1175+ EXPECT_ACTION_STATE("messages", normalicon);
1176+
1177+ auto app2 = std::shared_ptr<MessagingMenuApp>(messaging_menu_app_new("test2.desktop"), [](MessagingMenuApp * app) { g_clear_object(&app); });
1178+ ASSERT_NE(nullptr, app2);
1179+ messaging_menu_app_register(app2.get());
1180+
1181+ EXPECT_EVENTUALLY_ACTION_EXISTS("test2.launch");
1182+
1183+ messaging_menu_app_append_source_with_count(app2.get(),
1184+ "countsource",
1185+ nullptr,
1186+ "Count Source",
1187+ 500);
1188+ messaging_menu_app_draw_attention(app2.get(), "countsource");
1189+
1190+ EXPECT_EVENTUALLY_ACTION_STATE("messages", blueicon);
1191+
1192+ auto msg = std::shared_ptr<MessagingMenuMessage>(messaging_menu_message_new(
1193+ "messageid",
1194+ nullptr, /* no icon */
1195+ "Message",
1196+ "A secret message",
1197+ "asdfa;lkweraoweprijas;dvlknasvdoiewur;aslkd",
1198+ 0), [](MessagingMenuMessage * msg) { g_clear_object(&msg); });
1199+ messaging_menu_message_set_draws_attention(msg.get(), true);
1200+ messaging_menu_app_append_message(app.get(), msg.get(), nullptr, FALSE);
1201+
1202+ EXPECT_EVENTUALLY_ACTION_EXISTS("test.msg.messageid");
1203+ EXPECT_ACTION_STATE("messages", blueicon);
1204+
1205+ messaging_menu_app_unregister(app2.get());
1206+ app2.reset();
1207+
1208+ EXPECT_EVENTUALLY_ACTION_DOES_NOT_EXIST("test2.msg.countsource");
1209+ EXPECT_ACTION_STATE("messages", blueicon);
1210+
1211+ messaging_menu_app_remove_message(app.get(), msg.get());
1212+
1213+ EXPECT_EVENTUALLY_ACTION_STATE("messages", normalicon);
1214+ EXPECT_ACTION_ENABLED("remove-all", false);
1215+
1216+ messaging_menu_app_append_message(app.get(), msg.get(), nullptr, FALSE);
1217+
1218+ EXPECT_EVENTUALLY_ACTION_STATE("messages", blueicon);
1219+ EXPECT_ACTION_ENABLED("remove-all", true);
1220+
1221+ activateAction("remove-all");
1222+
1223+ EXPECT_EVENTUALLY_ACTION_STATE("messages", normalicon);
1224+}
1225
1226=== renamed file 'tests/manual' => 'tests/manual'

Subscribers

People subscribed via source and target branches