Merge lp:~gerboland/qtmir/fix-lifecycle-exempt-keeps-wakelock into lp:qtmir

Proposed by Gerry Boland
Status: Merged
Approved by: Albert Astals Cid
Approved revision: 336
Merged at revision: 327
Proposed branch: lp:~gerboland/qtmir/fix-lifecycle-exempt-keeps-wakelock
Merge into: lp:qtmir
Diff against target: 1165 lines (+646/-246)
13 files modified
CMakeLists.txt (+2/-0)
debian/control (+2/-0)
src/common/abstractdbusservicemonitor.cpp (+2/-3)
src/common/abstractdbusservicemonitor.h (+1/-7)
src/modules/Unity/Application/application.cpp (+3/-1)
src/modules/Unity/Application/application_manager.cpp (+5/-2)
src/modules/Unity/Application/sharedwakelock.cpp (+120/-57)
src/modules/Unity/Application/sharedwakelock.h (+14/-9)
tests/modules/Application/application_test.cpp (+7/-7)
tests/modules/ApplicationManager/application_manager_test.cpp (+78/-0)
tests/modules/SharedWakelock/CMakeLists.txt (+6/-0)
tests/modules/SharedWakelock/sharedwakelock_test.cpp (+365/-146)
tests/modules/common/mock_shared_wakelock.h (+41/-14)
To merge this branch: bzr merge lp:~gerboland/qtmir/fix-lifecycle-exempt-keeps-wakelock
Reviewer Review Type Date Requested Status
Albert Astals Cid (community) Approve
PS Jenkins bot (community) continuous-integration Approve
Michał Sawicz Abstain
Gerry Boland (community) Abstain
Michael Zanetti (community) Approve
Review via email: mp+250459@code.launchpad.net

Commit message

Refactor wakelock handling. Lifecycle exempt apps now release wakelock when shell tries to suspend them

The previous Wakelock RAII design was faulty as it was wrapping an asynchronous service. It made it possible for wakelocks to be acquired and not be released.

This refactors SharedWakelock to hold a single instance of Wakelock, and Wakelock always holds a DBus connection.

Testing now includes testing the DBus calls are actually emitted.

Adds dependency on libqtdbusmock1-dev and libqtdbustest1-dev

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Michael Zanetti (mzanetti) wrote :

Tested this and it works fine. If possible I think we could update the comment to make it a bit more clear what's going on with the implicit wake locks. (see inline)

review: Approve
Revision history for this message
Michael Zanetti (mzanetti) wrote :

ack

review: Approve
Revision history for this message
Michał Sawicz (saviq) wrote :

With this MP in silo 19, I start music app and I can see a permanent wakelock in `sudo powerd-cli list`, that only goes away if I put the music app in background.

review: Needs Fixing
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Michał Sawicz (saviq) wrote :

Should we have a test for setFocused(false)?

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Andrew Hayzen (ahayzen) wrote :

Hey, just wanted to check the following usecase is not broken with this code...

1) Open Music-App
2) Start playing a set of songs
3) Switch to another application
4) Suspend/lock the phone
5) Wait until the track ends, the next track should start playing

The main reason left that we have the lifecycle exception is to be able to tell media-hub what the next track is to play, this should be fixed once background-playlists are implemented in media-hub.

Revision history for this message
Gerry Boland (gerboland) wrote :

Hey Andrew
the music app/pulseaudio will be responsible for having its own wakelock, so that audio will continue to play when the screen is blanked. I tested this and it was working ok with this.
-G

Revision history for this message
Gerry Boland (gerboland) wrote :

2 wakelocks somehow acquired with this by the Music app, something wrong.

When I start Music app, here's logging info I get:
SharedWakelock::acquire qtmir::Application(0x22ab7f8)
Wakelock object created!
SharedWakelock::release qtmir::Application(0x22ab7f8)
Wakelock object destroyed!
qtmir.sessions: Wakelock released
SharedWakelock::acquire qtmir::Application(0x22ab7f8)
Wakelock object created!
qtmir.sessions: Wakelock acquired "8d92eec1-9711-4abb-a021-0004544065dd"
SharedWakelock::release qtmir::Application(0x22ab7f8)
Wakelock object destroyed!
qtmir.sessions: Wakelock released

which results in 2 wakelocks held by QtMir. Suspect it the fact that Wakelock object is created and then destroyed rapidly means the cookie is not returned before the Wakelock object is destroyed. Need to fix that

review: Needs Fixing
Revision history for this message
Gerry Boland (gerboland) wrote :

This unfortunately turned into a refactoring. The previous design relied on RAII, where the existence of the Wakelock object was supposed to correspond to a wakelock being held.

This design was flawed because of the asynchronous communication between powerd and qtmir. The problem case:
1. create a Wakelock object. This calls an async method of powerd to acquire a wakelock, which will eventually return a cookie.
2. destroy the Wakelock object. As no cookie saved, Wakelock has no cleanup to do
3. the promised cookie from powerd arrives, there's no Wakelock object to receive it. Cookie is lost, and wakelock remains held by powerd.

So I had to abandon the RAII design and instead have a single permanent Wakelock object which always stays connected to DBus.

To test this more thoroughly, am using the QDBusMock & QDBusTest tools.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Gerry Boland (gerboland) :
review: Abstain
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Michał Sawicz (saviq) :
review: Abstain
Revision history for this message
Albert Astals Cid (aacid) wrote :

 * Did you perform an exploratory manual test run of the code change and any related functionality?
Yes

 * Did CI run pass?
Yes

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Albert Astals Cid (aacid) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2014-12-09 14:12:57 +0000
3+++ CMakeLists.txt 2015-03-06 14:17:46 +0000
4@@ -77,6 +77,8 @@
5 pkg_check_modules(GIO gio-2.0)
6 pkg_check_modules(GIO_UNIX gio-unix-2.0)
7 pkg_check_modules(LTTNG lttng-ust)
8+pkg_check_modules(QTDBUSTEST libqtdbustest-1 REQUIRED)
9+pkg_check_modules(QTDBUSMOCK libqtdbusmock-1 REQUIRED)
10
11
12 # We expect this to be set via debian/rules for GLES builds
13
14=== modified file 'debian/control'
15--- debian/control 2015-02-09 16:28:59 +0000
16+++ debian/control 2015-03-06 14:17:46 +0000
17@@ -22,6 +22,8 @@
18 libmirserver-dev (>= 0.11.0),
19 libmtdev-dev,
20 libprocess-cpp-dev,
21+ libqtdbusmock1-dev (>= 0.2),
22+ libqtdbustest1-dev (>= 0.2),
23 libqt5sensors5-dev,
24 libubuntu-app-launch2-dev,
25 libubuntu-application-api-dev (>= 2.1.0),
26
27=== modified file 'src/common/abstractdbusservicemonitor.cpp'
28--- src/common/abstractdbusservicemonitor.cpp 2015-01-08 12:52:41 +0000
29+++ src/common/abstractdbusservicemonitor.cpp 2015-03-06 14:17:46 +0000
30@@ -41,14 +41,13 @@
31 };
32
33 AbstractDBusServiceMonitor::AbstractDBusServiceMonitor(const QString &service, const QString &path,
34- const QString &interface, const Bus bus,
35+ const QString &interface, const QDBusConnection &connection,
36 QObject *parent)
37 : QObject(parent)
38 , m_service(service)
39 , m_path(path)
40 , m_interface(interface)
41- , m_busConnection((bus == SystemBus) ? QDBusConnection::systemBus()
42- : QDBusConnection::sessionBus())
43+ , m_busConnection(connection)
44 , m_watcher(new QDBusServiceWatcher(service, m_busConnection))
45 , m_dbusInterface(nullptr)
46 {
47
48=== modified file 'src/common/abstractdbusservicemonitor.h'
49--- src/common/abstractdbusservicemonitor.h 2015-01-08 12:52:41 +0000
50+++ src/common/abstractdbusservicemonitor.h 2015-03-06 14:17:46 +0000
51@@ -31,17 +31,11 @@
52 class Q_DECL_EXPORT AbstractDBusServiceMonitor : public QObject
53 {
54 Q_OBJECT
55- Q_ENUMS(Bus)
56 Q_PROPERTY(bool serviceAvailable READ serviceAvailable NOTIFY serviceAvailableChanged)
57
58 public:
59- enum Bus {
60- SessionBus,
61- SystemBus,
62- };
63-
64 explicit AbstractDBusServiceMonitor(const QString &service, const QString &path, const QString &interface,
65- const Bus bus = SessionBus,
66+ const QDBusConnection &connection = QDBusConnection::sessionBus(),
67 QObject *parent = 0);
68 ~AbstractDBusServiceMonitor();
69
70
71=== modified file 'src/modules/Unity/Application/application.cpp'
72--- src/modules/Unity/Application/application.cpp 2015-01-08 12:35:41 +0000
73+++ src/modules/Unity/Application/application.cpp 2015-03-06 14:17:46 +0000
74@@ -319,7 +319,9 @@
75 void Application::setFocused(bool focused)
76 {
77 qCDebug(QTMIR_APPLICATIONS) << "Application::setFocused - appId=" << appId() << "focused=" << focused;
78- holdWakelock(true);
79+ if (focused) {
80+ holdWakelock(true);
81+ }
82
83 if (m_focused != focused) {
84 m_focused = focused;
85
86=== modified file 'src/modules/Unity/Application/application_manager.cpp'
87--- src/modules/Unity/Application/application_manager.cpp 2015-01-27 12:13:48 +0000
88+++ src/modules/Unity/Application/application_manager.cpp 2015-03-06 14:17:46 +0000
89@@ -372,9 +372,12 @@
90 return false;
91 qCDebug(QTMIR_APPLICATIONS) << "ApplicationManager::suspendApplication - appId=" << application->appId();
92
93- // Present in exceptions list, return.
94- if (!m_lifecycleExceptions.filter(application->appId().section('_',0,0)).empty())
95+ // Present in exceptions list, explicitly release wakelock and return. There's no need to keep the wakelock
96+ // as the process is never suspended and thus has no cleanup to perform when (for example) the display is blanked
97+ if (!m_lifecycleExceptions.filter(application->appId().section('_',0,0)).empty()) {
98+ m_sharedWakelock->release(application);
99 return false;
100+ }
101
102 if (m_forceDashActive && application->appId() == "unity8-dash") {
103 return false;
104
105=== modified file 'src/modules/Unity/Application/sharedwakelock.cpp'
106--- src/modules/Unity/Application/sharedwakelock.cpp 2015-01-12 16:51:00 +0000
107+++ src/modules/Unity/Application/sharedwakelock.cpp 2015-03-06 14:17:46 +0000
108@@ -29,57 +29,129 @@
109 const char cookieFile[] = "/tmp/qtmir_powerd_cookie";
110
111 /**
112- * @brief The Wakelock class - on creation acquires a system wakelock, on destruction releases it
113- * Designed in the spirit of RAII. Should the PowerD service vanish from the bus, the wakelock
114- * will be re-acquired when it re-joins the bus.
115+ * @brief The Wakelock class - wraps a single system wakelock
116+ * Should the PowerD service vanish from the bus, the wakelock will be re-acquired when it re-joins the bus.
117 */
118 class Wakelock : public AbstractDBusServiceMonitor
119 {
120 Q_OBJECT
121 public:
122- Wakelock() noexcept
123- : AbstractDBusServiceMonitor("com.canonical.powerd", "/com/canonical/powerd", "com.canonical.powerd", SystemBus)
124+ Wakelock(const QDBusConnection &connection) noexcept
125+ : AbstractDBusServiceMonitor("com.canonical.powerd", "/com/canonical/powerd", "com.canonical.powerd", connection)
126+ , m_wakelockEnabled(false)
127 {
128 // (re-)acquire wake lock when powerd (re-)appears on the bus
129 QObject::connect(this, &Wakelock::serviceAvailableChanged,
130- this, &Wakelock::acquireWakelock);
131-
132- if (!serviceAvailable()) {
133- qWarning() << "com.canonical.powerd DBus interface not available, waiting for it";
134- return;
135- }
136+ this, &Wakelock::onServiceAvailableChanged);
137
138 // WORKAROUND: if shell crashed while it held a wakelock, due to bug lp:1409722 powerd will not have released
139 // the wakelock for it. As workaround, we save the cookie to file and restore it if possible.
140- QFile cookie(cookieFile);
141- if (cookie.exists() && cookie.open(QFile::ReadOnly | QFile::Text)) {
142- m_cookie = cookie.readAll();
143- } else {
144- acquireWakelock(true);
145+ QFile cookieCache(cookieFile);
146+ if (cookieCache.exists() && cookieCache.open(QFile::ReadOnly | QFile::Text)) {
147+ m_wakelockEnabled = true;
148+ m_cookie = cookieCache.readAll();
149 }
150 }
151
152 virtual ~Wakelock() noexcept
153 {
154+ release();
155+ }
156+
157+ Q_SIGNAL void enabledChanged(bool);
158+ bool enabled() const
159+ {
160+ return m_wakelockEnabled;
161+ }
162+
163+ void acquire()
164+ {
165+ if (m_wakelockEnabled) { // wakelock already requested/set
166+ return;
167+ }
168+ m_wakelockEnabled = true;
169+
170+ acquireWakelock();
171+ }
172+
173+ void release()
174+ {
175 QFile::remove(cookieFile);
176
177+ if (!m_wakelockEnabled) { // no wakelock already requested/set
178+ return;
179+ }
180+ m_wakelockEnabled = false;
181+ Q_EMIT enabledChanged(false);
182+
183 if (!serviceAvailable()) {
184- qWarning() << "com.canonical.powerd DBus interface not available";
185+ qWarning() << "com.canonical.powerd DBus interface not available, presuming no wakelocks held";
186 return;
187 }
188
189 if (!m_cookie.isEmpty()) {
190- dbusInterface()->asyncCall("clearSysState", m_cookie);
191+ dbusInterface()->asyncCall("clearSysState", QString(m_cookie));
192+ qCDebug(QTMIR_SESSIONS) << "Wakelock released" << m_cookie;
193+ m_cookie.clear();
194 }
195- qCDebug(QTMIR_SESSIONS) << "Wakelock released";
196 }
197
198 private Q_SLOTS:
199- void acquireWakelock(bool available)
200+ void onServiceAvailableChanged(bool available)
201 {
202- if (!available) {
203- m_cookie.clear(); // clear cookie so that when powerd re-appears, new cookie will be set
204+ // Assumption is if service vanishes & reappears on the bus, it has lost its wakelock state and
205+ // we must re-acquire if necessary
206+ if (!m_wakelockEnabled) {
207+ return;
208+ }
209+
210+ if (available) {
211+ acquireWakelock();
212+ } else {
213+ m_cookie.clear();
214 QFile::remove(cookieFile);
215+ }
216+ }
217+
218+ void onWakeLockAcquired(QDBusPendingCallWatcher *call)
219+ {
220+ QDBusPendingReply<QString> reply = *call;
221+ if (reply.isError()) {
222+ qCDebug(QTMIR_SESSIONS) << "Wakelock was NOT acquired, error:"
223+ << QDBusError::errorString(reply.error().type());
224+ if (m_wakelockEnabled) {
225+ m_wakelockEnabled = false;
226+ Q_EMIT enabledChanged(false);
227+ }
228+
229+ call->deleteLater();
230+ return;
231+ }
232+ QByteArray cookie = reply.argumentAt<0>().toLatin1();
233+ call->deleteLater();
234+
235+ if (!m_wakelockEnabled || !m_cookie.isEmpty()) {
236+ // notified wakelock was created, but we either don't want it, or already have one - release it immediately
237+ dbusInterface()->asyncCall("clearSysState", QString(cookie));
238+ return;
239+ }
240+
241+ m_cookie = cookie;
242+
243+ // see WORKAROUND above for why we save cookie to disk
244+ QFile cookieCache(cookieFile);
245+ cookieCache.open(QFile::WriteOnly | QFile::Text);
246+ cookieCache.write(m_cookie);
247+
248+ qCDebug(QTMIR_SESSIONS) << "Wakelock acquired" << m_cookie;
249+ Q_EMIT enabledChanged(true);
250+ }
251+
252+private:
253+ void acquireWakelock()
254+ {
255+ if (!serviceAvailable()) {
256+ qWarning() << "com.canonical.powerd DBus interface not available, waiting for it";
257 return;
258 }
259
260@@ -90,29 +162,8 @@
261 this, &Wakelock::onWakeLockAcquired);
262 }
263
264- void onWakeLockAcquired(QDBusPendingCallWatcher *call)
265- {
266- if (m_cookie.isEmpty()) { // don't overwrite existing cookie
267- QDBusPendingReply<QString> reply = *call;
268- if (reply.isError()) {
269- qCDebug(QTMIR_SESSIONS) << "Wakelock was NOT acquired, error:"
270- << QDBusError::errorString(reply.error().type());
271- } else {
272- m_cookie = reply.argumentAt<0>();
273-
274- // see WORKAROUND above for why we save cookie to disk
275- QFile cookie(cookieFile);
276- cookie.open(QFile::WriteOnly | QFile::Text);
277- cookie.write(m_cookie.toLatin1());
278-
279- qCDebug(QTMIR_SESSIONS) << "Wakelock acquired" << m_cookie;
280- }
281- }
282- call->deleteLater();
283- }
284-
285-private:
286- QString m_cookie;
287+ QByteArray m_cookie;
288+ bool m_wakelockEnabled;
289
290 Q_DISABLE_COPY(Wakelock)
291 };
292@@ -132,36 +183,48 @@
293 * Note a caller cannot have multiple shares of the wakelock. Multiple calls to acquire are ignored.
294 */
295
296-QObject* SharedWakelock::createWakelock()
297-{
298- return new Wakelock;
299-}
300-
301+SharedWakelock::SharedWakelock(const QDBusConnection &connection)
302+ : m_wakelock(new Wakelock(connection))
303+{
304+ connect(m_wakelock.data(), &Wakelock::enabledChanged,
305+ this, &SharedWakelock::enabledChanged);
306+}
307+
308+// Define empty deconstructor here, as QScopedPointer<Wakelock> requires the destructor of the Wakelock class
309+// to be defined first.
310+SharedWakelock::~SharedWakelock()
311+{
312+}
313+
314+bool SharedWakelock::enabled() const
315+{
316+ return m_wakelock->enabled();
317+}
318
319 void SharedWakelock::acquire(const QObject *caller)
320 {
321- if (m_owners.contains(caller) || caller == nullptr) {
322+ if (caller == nullptr || m_owners.contains(caller)) {
323 return;
324 }
325
326 // register a slot to remove itself from owners list if destroyed
327 QObject::connect(caller, &QObject::destroyed, this, &SharedWakelock::release);
328
329- if (m_wakelock.isNull()) {
330- m_wakelock.reset(createWakelock());
331- }
332+ m_wakelock->acquire();
333
334 m_owners.insert(caller);
335 }
336
337 void SharedWakelock::release(const QObject *caller)
338 {
339- if (!m_owners.remove(caller) || caller == nullptr) {
340+ if (caller == nullptr || !m_owners.remove(caller)) {
341 return;
342 }
343
344- if (m_owners.empty() && m_wakelock) {
345- m_wakelock.reset();
346+ QObject::disconnect(caller, &QObject::destroyed, this, 0);
347+
348+ if (m_owners.empty()) {
349+ m_wakelock->release();
350 }
351 }
352
353
354=== modified file 'src/modules/Unity/Application/sharedwakelock.h'
355--- src/modules/Unity/Application/sharedwakelock.h 2015-01-09 12:24:53 +0000
356+++ src/modules/Unity/Application/sharedwakelock.h 2015-03-06 14:17:46 +0000
357@@ -19,26 +19,31 @@
358 #ifndef WAKELOCK_H
359 #define WAKELOCK_H
360
361-#include <QObject>
362+#include <QDBusConnection>
363 #include <QSet>
364 #include <QScopedPointer>
365
366 namespace qtmir {
367
368+class Wakelock;
369 class SharedWakelock : public QObject
370 {
371 Q_OBJECT
372+ Q_PROPERTY(bool enabled READ enabled NOTIFY enabledChanged)
373 public:
374- SharedWakelock() = default;
375- virtual ~SharedWakelock() noexcept = default;
376-
377- void acquire(const QObject *caller);
378- Q_SLOT void release(const QObject *caller);
379+ SharedWakelock(const QDBusConnection& connection = QDBusConnection::systemBus());
380+ virtual ~SharedWakelock();
381+
382+ virtual bool enabled() const;
383+
384+ virtual void acquire(const QObject *caller);
385+ Q_SLOT virtual void release(const QObject *caller);
386+
387+Q_SIGNALS:
388+ void enabledChanged(bool enabled);
389
390 protected:
391- virtual QObject* createWakelock(); // override to test
392-
393- QScopedPointer<QObject> m_wakelock;
394+ QScopedPointer<Wakelock> m_wakelock;
395 QSet<const QObject *> m_owners;
396
397 private:
398
399=== modified file 'tests/modules/Application/application_test.cpp'
400--- tests/modules/Application/application_test.cpp 2015-01-09 13:59:34 +0000
401+++ tests/modules/Application/application_test.cpp 2015-03-06 14:17:46 +0000
402@@ -34,7 +34,7 @@
403 {
404 using namespace ::testing;
405
406- EXPECT_CALL(sharedWakelock, createWakelock()).Times(1);
407+ EXPECT_CALL(sharedWakelock, acquire(_)).Times(1);
408
409 startApplication(123, "app");
410 applicationManager.focusApplication("app");
411@@ -50,14 +50,14 @@
412 applicationManager.focusApplication("app");
413
414 Q_EMIT session->suspended();
415- EXPECT_FALSE(sharedWakelock.wakelockHeld());
416+ EXPECT_FALSE(sharedWakelock.enabled());
417 }
418
419 TEST_F(ApplicationTests, checkResumeAcquiresWakeLock)
420 {
421 using namespace ::testing;
422
423- EXPECT_CALL(sharedWakelock, createWakelock()).Times(1);
424+ EXPECT_CALL(sharedWakelock, acquire(_)).Times(1);
425
426 auto app = startApplication(123, "app");
427 auto session = app->session();
428@@ -69,7 +69,7 @@
429 {
430 using namespace ::testing;
431
432- EXPECT_CALL(sharedWakelock, createWakelock()).Times(1);
433+ EXPECT_CALL(sharedWakelock, acquire(_)).Times(1);
434 const QString appId = "app";
435
436 auto app = startApplication(123, "app");
437@@ -89,7 +89,7 @@
438 {
439 using namespace ::testing;
440
441- EXPECT_CALL(sharedWakelock, createWakelock()).Times(0);
442+ EXPECT_CALL(sharedWakelock, acquire(_)).Times(0);
443
444 startApplication(123, "unity8-dash");
445 applicationManager.focusApplication("unity8-dash");
446@@ -105,14 +105,14 @@
447 applicationManager.focusApplication("unity8-dash");
448
449 Q_EMIT session->suspended();
450- EXPECT_FALSE(sharedWakelock.wakelockHeld());
451+ EXPECT_FALSE(sharedWakelock.enabled());
452 }
453
454 TEST_F(ApplicationTests, checkDashResumeDoesNotAcquireWakeLock)
455 {
456 using namespace ::testing;
457
458- EXPECT_CALL(sharedWakelock, createWakelock()).Times(0);
459+ EXPECT_CALL(sharedWakelock, acquire(_)).Times(0);
460
461 auto app = startApplication(123, "unity8-dash");
462 auto session = app->session();
463
464=== modified file 'tests/modules/ApplicationManager/application_manager_test.cpp'
465--- tests/modules/ApplicationManager/application_manager_test.cpp 2015-01-27 12:13:48 +0000
466+++ tests/modules/ApplicationManager/application_manager_test.cpp 2015-03-06 14:17:46 +0000
467@@ -2232,3 +2232,81 @@
468 .Times(0);
469 applicationManager.focusApplication(webbrowserAppId);
470 }
471+
472+/*
473+ * Test lifecycle exempt applications have their wakelocks released when shell tries to suspend them
474+ */
475+TEST_F(ApplicationManagerTests,lifecycleExemptAppsHaveWakelockReleasedOnAttemptedSuspend)
476+{
477+ using namespace ::testing;
478+
479+ const QString appId("com.ubuntu.music"); // member of lifecycle exemption list
480+ const quint64 procId = 12345;
481+
482+ // Set up Mocks & signal watcher
483+ auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
484+ ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
485+ ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
486+
487+ ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
488+
489+ EXPECT_CALL(appController, startApplicationWithAppIdAndArgs(appId, _))
490+ .Times(1)
491+ .WillOnce(Return(true));
492+
493+ auto application = applicationManager.startApplication(appId, ApplicationManager::NoFlag);
494+ applicationManager.onProcessStarting(appId);
495+ std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
496+ bool authed = true;
497+ applicationManager.authorizeSession(procId, authed);
498+ onSessionStarting(session);
499+
500+ // App creates surface, focuses it so state is running
501+ std::shared_ptr<mir::scene::Surface> surface(nullptr);
502+ applicationManager.onSessionCreatedSurface(session.get(), surface);
503+ applicationManager.focusApplication(appId);
504+
505+ applicationManager.unfocusCurrentApplication();
506+
507+ EXPECT_FALSE(sharedWakelock.enabled());
508+ EXPECT_EQ(application->state(), Application::Running);
509+}
510+
511+/*
512+ * Test lifecycle exempt applications have their wakelocks released on suspend
513+ */
514+TEST_F(ApplicationManagerTests,lifecycleExemptAppsHaveWakelockReleasedOnUnSuspend)
515+{
516+ using namespace ::testing;
517+
518+ const QString appId("com.ubuntu.music"); // member of lifecycle exemption list
519+ const quint64 procId = 12345;
520+
521+ // Set up Mocks & signal watcher
522+ auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
523+ ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
524+ ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
525+
526+ ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
527+
528+ EXPECT_CALL(appController, startApplicationWithAppIdAndArgs(appId, _))
529+ .Times(1)
530+ .WillOnce(Return(true));
531+
532+ auto application = applicationManager.startApplication(appId, ApplicationManager::NoFlag);
533+ applicationManager.onProcessStarting(appId);
534+ std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
535+ bool authed = true;
536+ applicationManager.authorizeSession(procId, authed);
537+ onSessionStarting(session);
538+
539+ // App creates surface, focuses it so state is running
540+ std::shared_ptr<mir::scene::Surface> surface(nullptr);
541+ applicationManager.onSessionCreatedSurface(session.get(), surface);
542+ applicationManager.focusApplication(appId);
543+
544+ applicationManager.setSuspended(true);
545+
546+ EXPECT_FALSE(sharedWakelock.enabled());
547+ EXPECT_EQ(application->state(), Application::Running);
548+}
549
550=== modified file 'tests/modules/SharedWakelock/CMakeLists.txt'
551--- tests/modules/SharedWakelock/CMakeLists.txt 2015-01-09 11:24:44 +0000
552+++ tests/modules/SharedWakelock/CMakeLists.txt 2015-03-06 14:17:46 +0000
553@@ -6,6 +6,8 @@
554 include_directories(
555 ${CMAKE_SOURCE_DIR}/src/modules
556 ${CMAKE_SOURCE_DIR}/tests/modules/common
557+ ${QTDBUSTEST_INCLUDE_DIRS}
558+ ${QTDBUSMOCK_INCLUDE_DIRS}
559 )
560
561 add_executable(sharedwakelock_test ${SHARED_WAKELOCK_TEST_SOURCES})
562@@ -15,8 +17,12 @@
563
564 unityapplicationplugin
565
566+ Qt5::Test
567+
568 ${GTEST_BOTH_LIBRARIES}
569 ${GMOCK_LIBRARIES}
570+ ${QTDBUSTEST_LIBRARIES}
571+ ${QTDBUSMOCK_LIBRARIES}
572 )
573
574 add_test(SharedWakelock, sharedwakelock_test)
575
576=== modified file 'tests/modules/SharedWakelock/sharedwakelock_test.cpp'
577--- tests/modules/SharedWakelock/sharedwakelock_test.cpp 2015-01-09 11:24:44 +0000
578+++ tests/modules/SharedWakelock/sharedwakelock_test.cpp 2015-03-06 14:17:46 +0000
579@@ -17,154 +17,373 @@
580
581 #include <Unity/Application/sharedwakelock.h>
582
583-#include "mock_shared_wakelock.h"
584+#include <libqtdbusmock/DBusMock.h>
585
586-#include <gmock/gmock.h>
587+#include <QCoreApplication>
588+#include <QSignalSpy>
589 #include <gtest/gtest.h>
590
591 using namespace qtmir;
592-using testing::MockSharedWakelock;
593-
594-TEST(SharedWakelock, acquireCreatesAWakelock)
595-{
596- using namespace ::testing;
597-
598- testing::NiceMock<MockSharedWakelock> sharedWakelock;
599- QScopedPointer<QObject> app1(new QObject);
600-
601- EXPECT_CALL(sharedWakelock, createWakelock()).Times(1);
602- sharedWakelock.acquire(app1.data());
603-}
604-
605-TEST(SharedWakelock, acquireThenReleaseDestroysTheWakelock)
606-{
607- using namespace ::testing;
608-
609- testing::NiceMock<MockSharedWakelock> sharedWakelock;
610- QScopedPointer<QObject> app1(new QObject);
611-
612- sharedWakelock.acquire(app1.data());
613- sharedWakelock.release(app1.data());
614- EXPECT_FALSE(sharedWakelock.wakelockHeld());
615-}
616-
617-TEST(SharedWakelock, doubleAcquireBySameOwnerOnlyCreatesASingleWakelock)
618-{
619- using namespace ::testing;
620-
621- testing::NiceMock<MockSharedWakelock> sharedWakelock;
622- QScopedPointer<QObject> app1(new QObject);
623-
624- EXPECT_CALL(sharedWakelock, createWakelock()).Times(1).WillOnce(Return(new QObject));
625- sharedWakelock.acquire(app1.data());
626- sharedWakelock.acquire(app1.data());
627-}
628-
629-TEST(SharedWakelock, doubleAcquireThenReleaseBySameOwnerDestroysWakelock)
630-{
631- using namespace ::testing;
632-
633- testing::NiceMock<MockSharedWakelock> sharedWakelock;
634- QScopedPointer<QObject> app1(new QObject);
635-
636- EXPECT_CALL(sharedWakelock, createWakelock()).Times(1).WillOnce(Return(new QObject));
637- sharedWakelock.acquire(app1.data());
638- sharedWakelock.acquire(app1.data());
639- sharedWakelock.release(app1.data());
640- EXPECT_FALSE(sharedWakelock.wakelockHeld());
641-}
642-
643-TEST(SharedWakelock, acquireByDifferentOwnerOnlyCreatesASingleWakelock)
644-{
645- using namespace ::testing;
646-
647- testing::NiceMock<MockSharedWakelock> sharedWakelock;
648- QScopedPointer<QObject> app1(new QObject);
649- QScopedPointer<QObject> app2(new QObject);
650-
651- EXPECT_CALL(sharedWakelock, createWakelock()).Times(1).WillOnce(Return(new QObject));
652- sharedWakelock.acquire(app1.data());
653- sharedWakelock.acquire(app2.data());
654-}
655-
656-TEST(SharedWakelock, twoOwnersWhenOneReleasesStillHoldWakelock)
657-{
658- using namespace ::testing;
659-
660- testing::NiceMock<MockSharedWakelock> sharedWakelock;
661- QScopedPointer<QObject> app1(new QObject);
662- QScopedPointer<QObject> app2(new QObject);
663-
664- EXPECT_CALL(sharedWakelock, createWakelock()).Times(1).WillOnce(Return(new QObject));
665- sharedWakelock.acquire(app1.data());
666- sharedWakelock.acquire(app2.data());
667- sharedWakelock.release(app1.data());
668- EXPECT_TRUE(sharedWakelock.wakelockHeld());
669-}
670-
671-TEST(SharedWakelock, twoOwnersWhenBothReleaseWakelockReleased)
672-{
673- using namespace ::testing;
674-
675- testing::NiceMock<MockSharedWakelock> sharedWakelock;
676- QScopedPointer<QObject> app1(new QObject);
677- QScopedPointer<QObject> app2(new QObject);
678-
679- EXPECT_CALL(sharedWakelock, createWakelock()).Times(1).WillOnce(Return(new QObject));
680- sharedWakelock.acquire(app1.data());
681- sharedWakelock.acquire(app2.data());
682- sharedWakelock.release(app2.data());
683- sharedWakelock.release(app1.data());
684- EXPECT_FALSE(sharedWakelock.wakelockHeld());
685-}
686-
687-TEST(SharedWakelock, doubleReleaseOfSingleOwnerIgnored)
688-{
689- using namespace ::testing;
690-
691- testing::NiceMock<MockSharedWakelock> sharedWakelock;
692- QScopedPointer<QObject> app1(new QObject);
693- QScopedPointer<QObject> app2(new QObject);
694-
695- EXPECT_CALL(sharedWakelock, createWakelock()).Times(1).WillOnce(Return(new QObject));
696- sharedWakelock.acquire(app1.data());
697- sharedWakelock.acquire(app2.data());
698- sharedWakelock.release(app1.data());
699- EXPECT_TRUE(sharedWakelock.wakelockHeld());
700-
701- sharedWakelock.release(app1.data());
702- EXPECT_TRUE(sharedWakelock.wakelockHeld());
703-}
704-
705-TEST(SharedWakelock, nullOwnerAcquireIgnored)
706-{
707- using namespace ::testing;
708-
709- testing::NiceMock<MockSharedWakelock> sharedWakelock;
710-
711- EXPECT_CALL(sharedWakelock, createWakelock()).Times(0);
712- sharedWakelock.acquire(nullptr);
713-}
714-
715-TEST(SharedWakelock, nullOwnerReleaseIgnored)
716-{
717- using namespace ::testing;
718-
719- testing::NiceMock<MockSharedWakelock> sharedWakelock;
720-
721- EXPECT_CALL(sharedWakelock, createWakelock()).Times(0);
722- sharedWakelock.release(nullptr);
723-}
724-
725-TEST(SharedWakelock, ifOwnerDestroyedWakelockReleased)
726-{
727- using namespace ::testing;
728-
729- testing::NiceMock<MockSharedWakelock> sharedWakelock;
730- QScopedPointer<QObject> app1(new QObject);
731-
732- EXPECT_CALL(sharedWakelock, createWakelock()).Times(1).WillOnce(Return(new QObject));
733- sharedWakelock.acquire(app1.data());
734- app1.reset();
735- EXPECT_FALSE(sharedWakelock.wakelockHeld());
736+using namespace testing;
737+using namespace QtDBusTest;
738+using namespace QtDBusMock;
739+
740+const char POWERD_NAME[] = "com.canonical.powerd";
741+const char POWERD_PATH[] = "/com/canonical/powerd";
742+const char POWERD_INTERFACE[] = "com.canonical.powerd";
743+
744+class SharedWakelockTest: public Test
745+{
746+protected:
747+ SharedWakelockTest()
748+ : mock(dbus)
749+ {
750+ mock.registerCustomMock(POWERD_NAME,
751+ POWERD_PATH,
752+ POWERD_INTERFACE,
753+ QDBusConnection::SystemBus);
754+
755+ dbus.startServices();
756+ }
757+
758+ virtual ~SharedWakelockTest()
759+ {}
760+
761+ virtual OrgFreedesktopDBusMockInterface& powerdMockInterface()
762+ {
763+ return mock.mockInterface(POWERD_NAME,
764+ POWERD_PATH,
765+ POWERD_INTERFACE,
766+ QDBusConnection::SystemBus);
767+ }
768+
769+ void implementRequestSysState() {
770+ // Defines the mock impementation of this DBus method
771+ powerdMockInterface().AddMethod("com.canonical.powerd",
772+ "requestSysState", "si", "s", "ret = 'cookie'").waitForFinished();
773+ }
774+
775+ void implementClearSysState() {
776+ powerdMockInterface().AddMethod("com.canonical.powerd",
777+ "clearSysState", "s", "", "").waitForFinished();
778+ }
779+
780+ void EXPECT_CALL(const QList<QVariantList> &spy, int index,
781+ const QString &name, const QVariantList &args)
782+ {
783+ QVariant args2(QVariant::fromValue(args));
784+ ASSERT_LT(index, spy.size());
785+ const QVariantList &call(spy.at(index));
786+ EXPECT_EQ(name, call.at(0).toString());
787+ EXPECT_EQ(args2.toString().toStdString(), call.at(1).toString().toStdString());
788+ }
789+
790+ DBusTestRunner dbus;
791+ DBusMock mock;
792+};
793+
794+TEST_F(SharedWakelockTest, acquireCreatesAWakelock)
795+{
796+ implementRequestSysState();
797+
798+ /* Note: we pass the DBusTestRunner constructed system DBus connection instead of letting
799+ * Qt create one. This is done as Qt has a non-testing friendly way of handling the built-in
800+ * system and session connections, and as DBusTestRunner restarts the DBus daemon for each
801+ * testcase, it unfortunately means Qt would end up pointing to a dead bus after one testcase. */
802+ SharedWakelock wakelock(dbus.systemConnection());
803+
804+ // Verify the DBus method is called & wakelock
805+ QSignalSpy wakelockDBusMethodSpy(&powerdMockInterface(), SIGNAL(MethodCalled(const QString &, const QVariantList &)));
806+ QSignalSpy wakelockEnabledSpy(&wakelock, SIGNAL( enabledChanged(bool) ));
807+
808+ QScopedPointer<QObject> object(new QObject);
809+ wakelock.acquire(object.data());
810+ wakelockDBusMethodSpy.wait();
811+
812+ EXPECT_FALSE(wakelockDBusMethodSpy.empty());
813+ EXPECT_CALL(wakelockDBusMethodSpy, 0, "requestSysState",
814+ QVariantList() << QString("active") << 1);
815+
816+ // Ensure a wakelock created
817+ wakelockEnabledSpy.wait();
818+ EXPECT_FALSE(wakelockEnabledSpy.empty());
819+ EXPECT_TRUE(wakelock.enabled());
820+}
821+
822+TEST_F(SharedWakelockTest, acquireThenReleaseDestroysTheWakelock)
823+{
824+ implementRequestSysState();
825+ implementClearSysState();
826+
827+ SharedWakelock wakelock(dbus.systemConnection());
828+
829+ QSignalSpy wakelockEnabledSpy(&wakelock, SIGNAL( enabledChanged(bool) ));
830+
831+ QScopedPointer<QObject> object(new QObject);
832+ wakelock.acquire(object.data());
833+ wakelockEnabledSpy.wait();
834+
835+ // Verify the DBus method is called
836+ QSignalSpy wakelockDBusMethodSpy(&powerdMockInterface(), SIGNAL(MethodCalled(const QString &, const QVariantList &)));
837+ wakelock.release(object.data());
838+ wakelockDBusMethodSpy.wait();
839+
840+ EXPECT_FALSE(wakelockDBusMethodSpy.empty());
841+ EXPECT_CALL(wakelockDBusMethodSpy, 0, "clearSysState",
842+ QVariantList() << QString("cookie"));
843+
844+ EXPECT_FALSE(wakelock.enabled());
845+}
846+
847+TEST_F(SharedWakelockTest, doubleAcquireBySameOwnerOnlyCreatesASingleWakelock)
848+{
849+ implementRequestSysState();
850+
851+ SharedWakelock wakelock(dbus.systemConnection());
852+
853+ // Verify the DBus method is called & wakelock
854+ QSignalSpy wakelockDBusMethodSpy(&powerdMockInterface(), SIGNAL(MethodCalled(const QString &, const QVariantList &)));
855+
856+ QScopedPointer<QObject> object(new QObject);
857+ wakelock.acquire(object.data());
858+ wakelock.acquire(object.data());
859+ wakelockDBusMethodSpy.wait();
860+
861+ EXPECT_EQ(wakelockDBusMethodSpy.count(), 1);
862+}
863+
864+TEST_F(SharedWakelockTest, doubleAcquireThenReleaseBySameOwnerDestroysWakelock)
865+{
866+ implementRequestSysState();
867+ implementClearSysState();
868+
869+ SharedWakelock wakelock(dbus.systemConnection());
870+
871+ QSignalSpy wakelockEnabledSpy(&wakelock, SIGNAL( enabledChanged(bool) ));
872+ QScopedPointer<QObject> object(new QObject);
873+
874+ wakelock.acquire(object.data());
875+ wakelock.acquire(object.data());
876+ wakelock.release(object.data());
877+ wakelockEnabledSpy.wait();
878+ EXPECT_FALSE(wakelock.enabled());
879+}
880+
881+TEST_F(SharedWakelockTest, acquireByDifferentOwnerOnlyCreatesASingleWakelock)
882+{
883+ implementRequestSysState();
884+
885+ SharedWakelock wakelock(dbus.systemConnection());
886+
887+ // Verify the DBus method is called & wakelock
888+ QSignalSpy wakelockDBusMethodSpy(&powerdMockInterface(), SIGNAL(MethodCalled(const QString &, const QVariantList &)));
889+
890+ QScopedPointer<QObject> object1(new QObject);
891+ QScopedPointer<QObject> object2(new QObject);
892+ wakelock.acquire(object1.data());
893+ wakelock.acquire(object2.data());
894+
895+ wakelockDBusMethodSpy.wait();
896+ EXPECT_EQ(wakelockDBusMethodSpy.count(), 1);
897+}
898+
899+TEST_F(SharedWakelockTest, twoOwnersWhenBothReleaseWakelockReleased)
900+{
901+ implementRequestSysState();
902+
903+ SharedWakelock wakelock(dbus.systemConnection());
904+
905+ QScopedPointer<QObject> object1(new QObject);
906+ QScopedPointer<QObject> object2(new QObject);
907+ wakelock.acquire(object1.data());
908+ wakelock.acquire(object2.data());
909+
910+ QSignalSpy wakelockEnabledSpy(&wakelock, SIGNAL( enabledChanged(bool) ));
911+
912+ wakelock.release(object1.data());
913+ wakelock.release(object2.data());
914+
915+ wakelockEnabledSpy.wait();
916+ EXPECT_FALSE(wakelock.enabled());
917+}
918+
919+TEST_F(SharedWakelockTest, doubleReleaseOfSingleOwnerIgnored)
920+{
921+ implementRequestSysState();
922+
923+ SharedWakelock wakelock(dbus.systemConnection());
924+
925+ QSignalSpy wakelockEnabledSpy(&wakelock, SIGNAL( enabledChanged(bool) ));
926+
927+ QScopedPointer<QObject> object1(new QObject);
928+ QScopedPointer<QObject> object2(new QObject);
929+ wakelock.acquire(object1.data());
930+ wakelock.acquire(object2.data());
931+ wakelock.release(object1.data());
932+
933+ wakelockEnabledSpy.wait();
934+
935+ wakelock.release(object1.data());
936+
937+ EXPECT_TRUE(wakelock.enabled());
938+}
939+
940+TEST_F(SharedWakelockTest, wakelockAcquireReleaseFlood)
941+{
942+ implementRequestSysState();
943+ implementClearSysState();
944+
945+ SharedWakelock wakelock(dbus.systemConnection());
946+
947+ QSignalSpy wakelockEnabledSpy(&wakelock, SIGNAL( enabledChanged(bool) ));
948+
949+ QScopedPointer<QObject> object(new QObject);
950+ wakelock.acquire(object.data());
951+ wakelock.release(object.data());
952+ wakelock.acquire(object.data());
953+ wakelock.release(object.data());
954+ wakelock.acquire(object.data());
955+ wakelock.release(object.data());
956+ while (wakelockEnabledSpy.wait(100)) {}
957+ EXPECT_FALSE(wakelock.enabled());
958+}
959+
960+TEST_F(SharedWakelockTest, wakelockAcquireReleaseAcquireWithDelays)
961+{
962+ powerdMockInterface().AddMethod("com.canonical.powerd",
963+ "requestSysState", "si", "s",
964+ "i=100000\n" // delay the response from the mock dbus instance
965+ "while i>0:\n"
966+ " i-=1\n"
967+ "ret = 'cookie'").waitForFinished();
968+
969+ implementClearSysState();
970+
971+ SharedWakelock wakelock(dbus.systemConnection());
972+
973+ QSignalSpy wakelockDBusMethodSpy(&powerdMockInterface(), SIGNAL(MethodCalled(const QString &, const QVariantList &)));
974+
975+ QScopedPointer<QObject> object(new QObject);
976+ wakelock.acquire(object.data());
977+ wakelock.release(object.data());
978+ wakelock.acquire(object.data());
979+
980+ while (wakelockDBusMethodSpy.wait()) {}
981+ EXPECT_TRUE(wakelock.enabled());
982+
983+ // there must be at least one clearSysState call, but is not necessarily the second call
984+ // should the dbus response be slow enough, it may be the third call. Is hard to reliably
985+ // reproduce the racey condition for all platforms.
986+ bool found = false;
987+ for (int i=0; i<wakelockDBusMethodSpy.count(); i++) {
988+ const QVariantList &call(wakelockDBusMethodSpy.at(i));
989+
990+ if (call.at(0).toString() == "clearSysState") {
991+ found = true;
992+ }
993+ }
994+ EXPECT_TRUE(found);
995+}
996+
997+TEST_F(SharedWakelockTest, nullOwnerAcquireIgnored)
998+{
999+ implementRequestSysState();
1000+
1001+ SharedWakelock wakelock(dbus.systemConnection());
1002+
1003+ QSignalSpy wakelockEnabledSpy(&wakelock, SIGNAL( enabledChanged(bool) ));
1004+
1005+ wakelock.acquire(nullptr);
1006+
1007+ wakelockEnabledSpy.wait(200); // have to wait to see if anything happens
1008+
1009+ EXPECT_FALSE(wakelock.enabled());
1010+}
1011+
1012+TEST_F(SharedWakelockTest, nullReleaseAcquireIgnored)
1013+{
1014+ implementRequestSysState();
1015+
1016+ SharedWakelock wakelock(dbus.systemConnection());
1017+
1018+ wakelock.release(nullptr);
1019+
1020+ EXPECT_FALSE(wakelock.enabled());
1021+}
1022+
1023+TEST_F(SharedWakelockTest, ifOwnerDestroyedWakelockReleased)
1024+{
1025+ implementRequestSysState();
1026+ implementClearSysState();
1027+
1028+ SharedWakelock wakelock(dbus.systemConnection());
1029+
1030+ QSignalSpy wakelockEnabledSpy(&wakelock, SIGNAL( enabledChanged(bool) ));
1031+
1032+ QScopedPointer<QObject> object(new QObject);
1033+ wakelock.acquire(object.data());
1034+ wakelockEnabledSpy.wait();
1035+
1036+ // Verify the DBus method is called
1037+ QSignalSpy wakelockDBusMethodSpy(&powerdMockInterface(), SIGNAL(MethodCalled(const QString &, const QVariantList &)));
1038+
1039+ object.reset();
1040+ wakelockDBusMethodSpy.wait();
1041+
1042+ EXPECT_FALSE(wakelockDBusMethodSpy.empty());
1043+ EXPECT_CALL(wakelockDBusMethodSpy, 0, "clearSysState",
1044+ QVariantList() << QString("cookie"));
1045+
1046+ EXPECT_FALSE(wakelock.enabled());
1047+}
1048+
1049+TEST_F(SharedWakelockTest, reloadCachedWakelockCookie)
1050+{
1051+ const char cookieFile[] = "/tmp/qtmir_powerd_cookie";
1052+
1053+ // Custom Deleter for QScopedPointer to delete the qtmir cookie file no matter what
1054+ struct ScopedQFileCustomDeleter
1055+ {
1056+ static inline void cleanup(QFile *file)
1057+ {
1058+ file->remove();
1059+ delete file;
1060+ }
1061+ };
1062+ QScopedPointer<QFile, ScopedQFileCustomDeleter> cookieCache(new QFile(cookieFile));
1063+ cookieCache->open(QFile::WriteOnly | QFile::Text);
1064+ cookieCache->write("myCookie");
1065+
1066+ SharedWakelock wakelock(dbus.systemConnection());
1067+ EXPECT_TRUE(wakelock.enabled());
1068+}
1069+
1070+TEST_F(SharedWakelockTest, wakelockReleasedOnSharedWakelockDestroyed)
1071+{
1072+ implementRequestSysState();
1073+ implementClearSysState();
1074+
1075+ auto wakelock = new SharedWakelock(dbus.systemConnection());
1076+
1077+ QSignalSpy wakelockEnabledSpy(wakelock, SIGNAL( enabledChanged(bool) ));
1078+
1079+ QScopedPointer<QObject> object(new QObject);
1080+ wakelock->acquire(object.data());
1081+ wakelockEnabledSpy.wait(); // wait for wakelock to be enabled
1082+
1083+ QSignalSpy wakelockDBusMethodSpy(&powerdMockInterface(), SIGNAL(MethodCalled(const QString &, const QVariantList &)));
1084+ delete wakelock;
1085+ wakelockDBusMethodSpy.wait();
1086+
1087+ EXPECT_FALSE(wakelockDBusMethodSpy.empty());
1088+ EXPECT_CALL(wakelockDBusMethodSpy, 0, "clearSysState",
1089+ QVariantList() << QString("cookie"));
1090+}
1091+
1092+
1093+// Define custom main() as these tests rely on a QEventLoop
1094+int main(int argc, char** argv) {
1095+ QCoreApplication app(argc, argv);
1096+ ::testing::InitGoogleTest(&argc, argv);
1097+ return RUN_ALL_TESTS();
1098 }
1099
1100=== modified file 'tests/modules/common/mock_shared_wakelock.h'
1101--- tests/modules/common/mock_shared_wakelock.h 2015-01-09 11:24:44 +0000
1102+++ tests/modules/common/mock_shared_wakelock.h 2015-03-06 14:17:46 +0000
1103@@ -23,21 +23,48 @@
1104
1105 namespace testing
1106 {
1107-struct MockSharedWakelock : public qtmir::SharedWakelock
1108+class MockSharedWakelock : public qtmir::SharedWakelock
1109 {
1110- MockSharedWakelock()
1111- {
1112- ON_CALL(*this, createWakelock()).WillByDefault(Invoke(this, &MockSharedWakelock::doCreateWakelock));
1113- }
1114-
1115- MOCK_METHOD0(createWakelock, QObject*());
1116- bool wakelockHeld() { return m_wakelock; }
1117-
1118-
1119- QObject* doCreateWakelock() const
1120- {
1121- return new QObject;
1122- }
1123+public:
1124+ MockSharedWakelock(const QDBusConnection& /*connection*/= QDBusConnection::systemBus())
1125+ {
1126+ ON_CALL(*this, enabled()).WillByDefault(Invoke(this, &MockSharedWakelock::doEnabled));
1127+ ON_CALL(*this, acquire(_)).WillByDefault(Invoke(this, &MockSharedWakelock::doAcquire));
1128+ ON_CALL(*this, release(_)).WillByDefault(Invoke(this, &MockSharedWakelock::doRelease));
1129+ }
1130+
1131+ MOCK_CONST_METHOD0(enabled, bool());
1132+ MOCK_METHOD1(acquire, void(const QObject *));
1133+ MOCK_METHOD1(release, void(const QObject *));
1134+
1135+ bool doEnabled()
1136+ {
1137+ return !m_owners.isEmpty();
1138+ }
1139+
1140+ void doAcquire(const QObject *object)
1141+ {
1142+ if (m_owners.contains(object)) {
1143+ return;
1144+ }
1145+ m_owners.insert(object);
1146+ if (m_owners.size() == 1) {
1147+ Q_EMIT enabledChanged(true);
1148+ }
1149+ }
1150+
1151+ void doRelease(const QObject *object)
1152+ {
1153+ if (!m_owners.remove(object)) {
1154+ return;
1155+ }
1156+ if (m_owners.isEmpty()) {
1157+ Q_EMIT enabledChanged(false);
1158+ }
1159+ }
1160+
1161+private:
1162+ QSet<const QObject *> m_owners;
1163 };
1164
1165 } // namespace testing

Subscribers

People subscribed via source and target branches