Merge lp:~gerboland/qtmir/RTM-fix-lifecycle-exempt-keeps-wakelock into lp:qtmir/rtm-14.09
- RTM-fix-lifecycle-exempt-keeps-wakelock
- Merge into rtm-14.09
Status: | Merged |
---|---|
Approved by: | Albert Astals Cid |
Approved revision: | 301 |
Merged at revision: | 295 |
Proposed branch: | lp:~gerboland/qtmir/RTM-fix-lifecycle-exempt-keeps-wakelock |
Merge into: | lp:qtmir/rtm-14.09 |
Diff against target: |
1184 lines (+653/-249) 15 files modified
debian/control (+2/-0) src/common/abstractdbusservicemonitor.cpp (+2/-3) src/common/abstractdbusservicemonitor.h (+1/-7) src/modules/Unity/Application/Application.pro (+1/-1) 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/SharedWakelock.pro (+11/-0) tests/modules/SharedWakelock/sharedwakelock_test.cpp (+365/-146) tests/modules/common/common.pri (+2/-1) tests/modules/common/mock_shared_wakelock.h (+41/-14) tests/modules/modules.pro (+1/-1) |
To merge this branch: | bzr merge lp:~gerboland/qtmir/RTM-fix-lifecycle-exempt-keeps-wakelock |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Albert Astals Cid (community) | Approve | ||
Gerry Boland (community) | Abstain | ||
Review via email:
|
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
Description of the change
- 296. By Gerry Boland
-
Add test
- 297. By Gerry Boland
-
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
- 298. By Gerry Boland
-
Fix acquire(), release(), acquire() being called in quick succession, and only after do 2 cookies get sent from dbus
- 299. By Gerry Boland
-
Fix and add test for SharedWakelock acq/rel/acq not loosing a cookie
- 300. By Gerry Boland
-
Delete unnecesary line
- 301. By Gerry Boland
-
Racey test not so reliable, this helps
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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?
No CI, but i compiled and ran the tests on a krillin
Preview Diff
1 | === modified file 'debian/control' |
2 | --- debian/control 2015-01-12 12:51:39 +0000 |
3 | +++ debian/control 2015-03-06 14:19:39 +0000 |
4 | @@ -16,6 +16,8 @@ |
5 | libmirserver-dev (>= 0.8.1), |
6 | libmtdev-dev, |
7 | libprocess-cpp-dev, |
8 | + libqtdbusmock1-dev (>= 0.2), |
9 | + libqtdbustest1-dev (>= 0.2), |
10 | libqt5sensors5-dev, |
11 | libubuntu-app-launch2-dev, |
12 | libubuntu-application-api-dev (>= 2.1.0), |
13 | |
14 | === modified file 'src/common/abstractdbusservicemonitor.cpp' |
15 | --- src/common/abstractdbusservicemonitor.cpp 2015-01-16 09:48:21 +0000 |
16 | +++ src/common/abstractdbusservicemonitor.cpp 2015-03-06 14:19:39 +0000 |
17 | @@ -41,14 +41,13 @@ |
18 | }; |
19 | |
20 | AbstractDBusServiceMonitor::AbstractDBusServiceMonitor(const QString &service, const QString &path, |
21 | - const QString &interface, const Bus bus, |
22 | + const QString &interface, const QDBusConnection connection, |
23 | QObject *parent) |
24 | : QObject(parent) |
25 | , m_service(service) |
26 | , m_path(path) |
27 | , m_interface(interface) |
28 | - , m_busConnection((bus == SystemBus) ? QDBusConnection::systemBus() |
29 | - : QDBusConnection::sessionBus()) |
30 | + , m_busConnection(connection) |
31 | , m_watcher(new QDBusServiceWatcher(service, m_busConnection)) |
32 | , m_dbusInterface(nullptr) |
33 | { |
34 | |
35 | === modified file 'src/common/abstractdbusservicemonitor.h' |
36 | --- src/common/abstractdbusservicemonitor.h 2015-01-16 09:48:21 +0000 |
37 | +++ src/common/abstractdbusservicemonitor.h 2015-03-06 14:19:39 +0000 |
38 | @@ -31,17 +31,11 @@ |
39 | class Q_DECL_EXPORT AbstractDBusServiceMonitor : public QObject |
40 | { |
41 | Q_OBJECT |
42 | - Q_ENUMS(Bus) |
43 | Q_PROPERTY(bool serviceAvailable READ serviceAvailable NOTIFY serviceAvailableChanged) |
44 | |
45 | public: |
46 | - enum Bus { |
47 | - SessionBus, |
48 | - SystemBus, |
49 | - }; |
50 | - |
51 | explicit AbstractDBusServiceMonitor(const QString &service, const QString &path, const QString &interface, |
52 | - const Bus bus = SessionBus, |
53 | + const QDBusConnection connection = QDBusConnection::sessionBus(), |
54 | QObject *parent = 0); |
55 | ~AbstractDBusServiceMonitor(); |
56 | |
57 | |
58 | === modified file 'src/modules/Unity/Application/Application.pro' |
59 | --- src/modules/Unity/Application/Application.pro 2015-01-16 09:48:21 +0000 |
60 | +++ src/modules/Unity/Application/Application.pro 2015-03-06 14:19:39 +0000 |
61 | @@ -12,7 +12,7 @@ |
62 | QMAKE_CXXFLAGS = -std=c++11 -Werror -Wall |
63 | QMAKE_LFLAGS = -std=c++11 -Wl,-no-undefined |
64 | |
65 | -PKGCONFIG += mirserver glib-2.0 gio-unix-2.0 process-cpp ubuntu-app-launch-2 |
66 | +PKGCONFIG += mirserver glib-2.0 gio-unix-2.0 process-cpp ubuntu-app-launch-2 libqtdbusmock-1 libqtdbustest-1 |
67 | |
68 | INCLUDEPATH += ../../../platforms/mirserver ../../../common |
69 | LIBS += -L../../../platforms/mirserver -lqpa-mirserver |
70 | |
71 | === modified file 'src/modules/Unity/Application/application.cpp' |
72 | --- src/modules/Unity/Application/application.cpp 2015-01-16 09:48:21 +0000 |
73 | +++ src/modules/Unity/Application/application.cpp 2015-03-06 14:19:39 +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-16 09:48:21 +0000 |
88 | +++ src/modules/Unity/Application/application_manager.cpp 2015-03-06 14:19:39 +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-16 09:48:21 +0000 |
107 | +++ src/modules/Unity/Application/sharedwakelock.cpp 2015-03-06 14:19:39 +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-16 09:48:21 +0000 |
356 | +++ src/modules/Unity/Application/sharedwakelock.h 2015-03-06 14:19:39 +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-16 09:48:21 +0000 |
401 | +++ tests/modules/Application/application_test.cpp 2015-03-06 14:19:39 +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 2014-10-20 18:48:53 +0000 |
466 | +++ tests/modules/ApplicationManager/application_manager_test.cpp 2015-03-06 14:19:39 +0000 |
467 | @@ -2135,3 +2135,81 @@ |
468 | cv.wait(lk, [&] { return done; } ); |
469 | } |
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 | === added file 'tests/modules/SharedWakelock/SharedWakelock.pro' |
551 | --- tests/modules/SharedWakelock/SharedWakelock.pro 1970-01-01 00:00:00 +0000 |
552 | +++ tests/modules/SharedWakelock/SharedWakelock.pro 2015-03-06 14:19:39 +0000 |
553 | @@ -0,0 +1,11 @@ |
554 | +include(../../test-includes.pri) |
555 | +include(../common/common.pri) |
556 | + |
557 | +TARGET = sharedwakelock_test |
558 | +PKGCONFIG += libqtdbusmock-1 libqtdbustest-1 |
559 | + |
560 | +INCLUDEPATH += \ |
561 | + ../../../src/modules/Unity/Application |
562 | + |
563 | +SOURCES += \ |
564 | + sharedwakelock_test.cpp |
565 | |
566 | === modified file 'tests/modules/SharedWakelock/sharedwakelock_test.cpp' |
567 | --- tests/modules/SharedWakelock/sharedwakelock_test.cpp 2015-01-16 09:48:21 +0000 |
568 | +++ tests/modules/SharedWakelock/sharedwakelock_test.cpp 2015-03-06 14:19:39 +0000 |
569 | @@ -17,154 +17,373 @@ |
570 | |
571 | #include <Unity/Application/sharedwakelock.h> |
572 | |
573 | -#include "mock_shared_wakelock.h" |
574 | +#include <libqtdbusmock/DBusMock.h> |
575 | |
576 | -#include <gmock/gmock.h> |
577 | +#include <QCoreApplication> |
578 | +#include <QSignalSpy> |
579 | #include <gtest/gtest.h> |
580 | |
581 | using namespace qtmir; |
582 | -using testing::MockSharedWakelock; |
583 | - |
584 | -TEST(SharedWakelock, acquireCreatesAWakelock) |
585 | -{ |
586 | - using namespace ::testing; |
587 | - |
588 | - testing::NiceMock<MockSharedWakelock> sharedWakelock; |
589 | - QScopedPointer<QObject> app1(new QObject); |
590 | - |
591 | - EXPECT_CALL(sharedWakelock, createWakelock()).Times(1); |
592 | - sharedWakelock.acquire(app1.data()); |
593 | -} |
594 | - |
595 | -TEST(SharedWakelock, acquireThenReleaseDestroysTheWakelock) |
596 | -{ |
597 | - using namespace ::testing; |
598 | - |
599 | - testing::NiceMock<MockSharedWakelock> sharedWakelock; |
600 | - QScopedPointer<QObject> app1(new QObject); |
601 | - |
602 | - sharedWakelock.acquire(app1.data()); |
603 | - sharedWakelock.release(app1.data()); |
604 | - EXPECT_FALSE(sharedWakelock.wakelockHeld()); |
605 | -} |
606 | - |
607 | -TEST(SharedWakelock, doubleAcquireBySameOwnerOnlyCreatesASingleWakelock) |
608 | -{ |
609 | - using namespace ::testing; |
610 | - |
611 | - testing::NiceMock<MockSharedWakelock> sharedWakelock; |
612 | - QScopedPointer<QObject> app1(new QObject); |
613 | - |
614 | - EXPECT_CALL(sharedWakelock, createWakelock()).Times(1).WillOnce(Return(new QObject)); |
615 | - sharedWakelock.acquire(app1.data()); |
616 | - sharedWakelock.acquire(app1.data()); |
617 | -} |
618 | - |
619 | -TEST(SharedWakelock, doubleAcquireThenReleaseBySameOwnerDestroysWakelock) |
620 | -{ |
621 | - using namespace ::testing; |
622 | - |
623 | - testing::NiceMock<MockSharedWakelock> sharedWakelock; |
624 | - QScopedPointer<QObject> app1(new QObject); |
625 | - |
626 | - EXPECT_CALL(sharedWakelock, createWakelock()).Times(1).WillOnce(Return(new QObject)); |
627 | - sharedWakelock.acquire(app1.data()); |
628 | - sharedWakelock.acquire(app1.data()); |
629 | - sharedWakelock.release(app1.data()); |
630 | - EXPECT_FALSE(sharedWakelock.wakelockHeld()); |
631 | -} |
632 | - |
633 | -TEST(SharedWakelock, acquireByDifferentOwnerOnlyCreatesASingleWakelock) |
634 | -{ |
635 | - using namespace ::testing; |
636 | - |
637 | - testing::NiceMock<MockSharedWakelock> sharedWakelock; |
638 | - QScopedPointer<QObject> app1(new QObject); |
639 | - QScopedPointer<QObject> app2(new QObject); |
640 | - |
641 | - EXPECT_CALL(sharedWakelock, createWakelock()).Times(1).WillOnce(Return(new QObject)); |
642 | - sharedWakelock.acquire(app1.data()); |
643 | - sharedWakelock.acquire(app2.data()); |
644 | -} |
645 | - |
646 | -TEST(SharedWakelock, twoOwnersWhenOneReleasesStillHoldWakelock) |
647 | -{ |
648 | - using namespace ::testing; |
649 | - |
650 | - testing::NiceMock<MockSharedWakelock> sharedWakelock; |
651 | - QScopedPointer<QObject> app1(new QObject); |
652 | - QScopedPointer<QObject> app2(new QObject); |
653 | - |
654 | - EXPECT_CALL(sharedWakelock, createWakelock()).Times(1).WillOnce(Return(new QObject)); |
655 | - sharedWakelock.acquire(app1.data()); |
656 | - sharedWakelock.acquire(app2.data()); |
657 | - sharedWakelock.release(app1.data()); |
658 | - EXPECT_TRUE(sharedWakelock.wakelockHeld()); |
659 | -} |
660 | - |
661 | -TEST(SharedWakelock, twoOwnersWhenBothReleaseWakelockReleased) |
662 | -{ |
663 | - using namespace ::testing; |
664 | - |
665 | - testing::NiceMock<MockSharedWakelock> sharedWakelock; |
666 | - QScopedPointer<QObject> app1(new QObject); |
667 | - QScopedPointer<QObject> app2(new QObject); |
668 | - |
669 | - EXPECT_CALL(sharedWakelock, createWakelock()).Times(1).WillOnce(Return(new QObject)); |
670 | - sharedWakelock.acquire(app1.data()); |
671 | - sharedWakelock.acquire(app2.data()); |
672 | - sharedWakelock.release(app2.data()); |
673 | - sharedWakelock.release(app1.data()); |
674 | - EXPECT_FALSE(sharedWakelock.wakelockHeld()); |
675 | -} |
676 | - |
677 | -TEST(SharedWakelock, doubleReleaseOfSingleOwnerIgnored) |
678 | -{ |
679 | - using namespace ::testing; |
680 | - |
681 | - testing::NiceMock<MockSharedWakelock> sharedWakelock; |
682 | - QScopedPointer<QObject> app1(new QObject); |
683 | - QScopedPointer<QObject> app2(new QObject); |
684 | - |
685 | - EXPECT_CALL(sharedWakelock, createWakelock()).Times(1).WillOnce(Return(new QObject)); |
686 | - sharedWakelock.acquire(app1.data()); |
687 | - sharedWakelock.acquire(app2.data()); |
688 | - sharedWakelock.release(app1.data()); |
689 | - EXPECT_TRUE(sharedWakelock.wakelockHeld()); |
690 | - |
691 | - sharedWakelock.release(app1.data()); |
692 | - EXPECT_TRUE(sharedWakelock.wakelockHeld()); |
693 | -} |
694 | - |
695 | -TEST(SharedWakelock, nullOwnerAcquireIgnored) |
696 | -{ |
697 | - using namespace ::testing; |
698 | - |
699 | - testing::NiceMock<MockSharedWakelock> sharedWakelock; |
700 | - |
701 | - EXPECT_CALL(sharedWakelock, createWakelock()).Times(0); |
702 | - sharedWakelock.acquire(nullptr); |
703 | -} |
704 | - |
705 | -TEST(SharedWakelock, nullOwnerReleaseIgnored) |
706 | -{ |
707 | - using namespace ::testing; |
708 | - |
709 | - testing::NiceMock<MockSharedWakelock> sharedWakelock; |
710 | - |
711 | - EXPECT_CALL(sharedWakelock, createWakelock()).Times(0); |
712 | - sharedWakelock.release(nullptr); |
713 | -} |
714 | - |
715 | -TEST(SharedWakelock, ifOwnerDestroyedWakelockReleased) |
716 | -{ |
717 | - using namespace ::testing; |
718 | - |
719 | - testing::NiceMock<MockSharedWakelock> sharedWakelock; |
720 | - QScopedPointer<QObject> app1(new QObject); |
721 | - |
722 | - EXPECT_CALL(sharedWakelock, createWakelock()).Times(1).WillOnce(Return(new QObject)); |
723 | - sharedWakelock.acquire(app1.data()); |
724 | - app1.reset(); |
725 | - EXPECT_FALSE(sharedWakelock.wakelockHeld()); |
726 | +using namespace testing; |
727 | +using namespace QtDBusTest; |
728 | +using namespace QtDBusMock; |
729 | + |
730 | +const char POWERD_NAME[] = "com.canonical.powerd"; |
731 | +const char POWERD_PATH[] = "/com/canonical/powerd"; |
732 | +const char POWERD_INTERFACE[] = "com.canonical.powerd"; |
733 | + |
734 | +class SharedWakelockTest: public Test |
735 | +{ |
736 | +protected: |
737 | + SharedWakelockTest() |
738 | + : mock(dbus) |
739 | + { |
740 | + mock.registerCustomMock(POWERD_NAME, |
741 | + POWERD_PATH, |
742 | + POWERD_INTERFACE, |
743 | + QDBusConnection::SystemBus); |
744 | + |
745 | + dbus.startServices(); |
746 | + } |
747 | + |
748 | + virtual ~SharedWakelockTest() |
749 | + {} |
750 | + |
751 | + virtual OrgFreedesktopDBusMockInterface& powerdMockInterface() |
752 | + { |
753 | + return mock.mockInterface(POWERD_NAME, |
754 | + POWERD_PATH, |
755 | + POWERD_INTERFACE, |
756 | + QDBusConnection::SystemBus); |
757 | + } |
758 | + |
759 | + void implementRequestSysState() { |
760 | + // Defines the mock impementation of this DBus method |
761 | + powerdMockInterface().AddMethod("com.canonical.powerd", |
762 | + "requestSysState", "si", "s", "ret = 'cookie'").waitForFinished(); |
763 | + } |
764 | + |
765 | + void implementClearSysState() { |
766 | + powerdMockInterface().AddMethod("com.canonical.powerd", |
767 | + "clearSysState", "s", "", "").waitForFinished(); |
768 | + } |
769 | + |
770 | + void EXPECT_CALL(const QList<QVariantList> &spy, int index, |
771 | + const QString &name, const QVariantList &args) |
772 | + { |
773 | + QVariant args2(QVariant::fromValue(args)); |
774 | + ASSERT_LT(index, spy.size()); |
775 | + const QVariantList &call(spy.at(index)); |
776 | + EXPECT_EQ(name, call.at(0).toString()); |
777 | + EXPECT_EQ(args2.toString().toStdString(), call.at(1).toString().toStdString()); |
778 | + } |
779 | + |
780 | + DBusTestRunner dbus; |
781 | + DBusMock mock; |
782 | +}; |
783 | + |
784 | +TEST_F(SharedWakelockTest, acquireCreatesAWakelock) |
785 | +{ |
786 | + implementRequestSysState(); |
787 | + |
788 | + /* Note: we pass the DBusTestRunner constructed system DBus connection instead of letting |
789 | + * Qt create one. This is done as Qt has a non-testing friendly way of handling the built-in |
790 | + * system and session connections, and as DBusTestRunner restarts the DBus daemon for each |
791 | + * testcase, it unfortunately means Qt would end up pointing to a dead bus after one testcase. */ |
792 | + SharedWakelock wakelock(dbus.systemConnection()); |
793 | + |
794 | + // Verify the DBus method is called & wakelock |
795 | + QSignalSpy wakelockDBusMethodSpy(&powerdMockInterface(), SIGNAL(MethodCalled(const QString &, const QVariantList &))); |
796 | + QSignalSpy wakelockEnabledSpy(&wakelock, SIGNAL( enabledChanged(bool) )); |
797 | + |
798 | + QScopedPointer<QObject> object(new QObject); |
799 | + wakelock.acquire(object.data()); |
800 | + wakelockDBusMethodSpy.wait(); |
801 | + |
802 | + EXPECT_FALSE(wakelockDBusMethodSpy.empty()); |
803 | + EXPECT_CALL(wakelockDBusMethodSpy, 0, "requestSysState", |
804 | + QVariantList() << QString("active") << 1); |
805 | + |
806 | + // Ensure a wakelock created |
807 | + wakelockEnabledSpy.wait(); |
808 | + EXPECT_FALSE(wakelockEnabledSpy.empty()); |
809 | + EXPECT_TRUE(wakelock.enabled()); |
810 | +} |
811 | + |
812 | +TEST_F(SharedWakelockTest, acquireThenReleaseDestroysTheWakelock) |
813 | +{ |
814 | + implementRequestSysState(); |
815 | + implementClearSysState(); |
816 | + |
817 | + SharedWakelock wakelock(dbus.systemConnection()); |
818 | + |
819 | + QSignalSpy wakelockEnabledSpy(&wakelock, SIGNAL( enabledChanged(bool) )); |
820 | + |
821 | + QScopedPointer<QObject> object(new QObject); |
822 | + wakelock.acquire(object.data()); |
823 | + wakelockEnabledSpy.wait(); |
824 | + |
825 | + // Verify the DBus method is called |
826 | + QSignalSpy wakelockDBusMethodSpy(&powerdMockInterface(), SIGNAL(MethodCalled(const QString &, const QVariantList &))); |
827 | + wakelock.release(object.data()); |
828 | + wakelockDBusMethodSpy.wait(); |
829 | + |
830 | + EXPECT_FALSE(wakelockDBusMethodSpy.empty()); |
831 | + EXPECT_CALL(wakelockDBusMethodSpy, 0, "clearSysState", |
832 | + QVariantList() << QString("cookie")); |
833 | + |
834 | + EXPECT_FALSE(wakelock.enabled()); |
835 | +} |
836 | + |
837 | +TEST_F(SharedWakelockTest, doubleAcquireBySameOwnerOnlyCreatesASingleWakelock) |
838 | +{ |
839 | + implementRequestSysState(); |
840 | + |
841 | + SharedWakelock wakelock(dbus.systemConnection()); |
842 | + |
843 | + // Verify the DBus method is called & wakelock |
844 | + QSignalSpy wakelockDBusMethodSpy(&powerdMockInterface(), SIGNAL(MethodCalled(const QString &, const QVariantList &))); |
845 | + |
846 | + QScopedPointer<QObject> object(new QObject); |
847 | + wakelock.acquire(object.data()); |
848 | + wakelock.acquire(object.data()); |
849 | + wakelockDBusMethodSpy.wait(); |
850 | + |
851 | + EXPECT_EQ(wakelockDBusMethodSpy.count(), 1); |
852 | +} |
853 | + |
854 | +TEST_F(SharedWakelockTest, doubleAcquireThenReleaseBySameOwnerDestroysWakelock) |
855 | +{ |
856 | + implementRequestSysState(); |
857 | + implementClearSysState(); |
858 | + |
859 | + SharedWakelock wakelock(dbus.systemConnection()); |
860 | + |
861 | + QSignalSpy wakelockEnabledSpy(&wakelock, SIGNAL( enabledChanged(bool) )); |
862 | + QScopedPointer<QObject> object(new QObject); |
863 | + |
864 | + wakelock.acquire(object.data()); |
865 | + wakelock.acquire(object.data()); |
866 | + wakelock.release(object.data()); |
867 | + wakelockEnabledSpy.wait(); |
868 | + EXPECT_FALSE(wakelock.enabled()); |
869 | +} |
870 | + |
871 | +TEST_F(SharedWakelockTest, acquireByDifferentOwnerOnlyCreatesASingleWakelock) |
872 | +{ |
873 | + implementRequestSysState(); |
874 | + |
875 | + SharedWakelock wakelock(dbus.systemConnection()); |
876 | + |
877 | + // Verify the DBus method is called & wakelock |
878 | + QSignalSpy wakelockDBusMethodSpy(&powerdMockInterface(), SIGNAL(MethodCalled(const QString &, const QVariantList &))); |
879 | + |
880 | + QScopedPointer<QObject> object1(new QObject); |
881 | + QScopedPointer<QObject> object2(new QObject); |
882 | + wakelock.acquire(object1.data()); |
883 | + wakelock.acquire(object2.data()); |
884 | + |
885 | + wakelockDBusMethodSpy.wait(); |
886 | + EXPECT_EQ(wakelockDBusMethodSpy.count(), 1); |
887 | +} |
888 | + |
889 | +TEST_F(SharedWakelockTest, twoOwnersWhenBothReleaseWakelockReleased) |
890 | +{ |
891 | + implementRequestSysState(); |
892 | + |
893 | + SharedWakelock wakelock(dbus.systemConnection()); |
894 | + |
895 | + QScopedPointer<QObject> object1(new QObject); |
896 | + QScopedPointer<QObject> object2(new QObject); |
897 | + wakelock.acquire(object1.data()); |
898 | + wakelock.acquire(object2.data()); |
899 | + |
900 | + QSignalSpy wakelockEnabledSpy(&wakelock, SIGNAL( enabledChanged(bool) )); |
901 | + |
902 | + wakelock.release(object1.data()); |
903 | + wakelock.release(object2.data()); |
904 | + |
905 | + wakelockEnabledSpy.wait(); |
906 | + EXPECT_FALSE(wakelock.enabled()); |
907 | +} |
908 | + |
909 | +TEST_F(SharedWakelockTest, doubleReleaseOfSingleOwnerIgnored) |
910 | +{ |
911 | + implementRequestSysState(); |
912 | + |
913 | + SharedWakelock wakelock(dbus.systemConnection()); |
914 | + |
915 | + QSignalSpy wakelockEnabledSpy(&wakelock, SIGNAL( enabledChanged(bool) )); |
916 | + |
917 | + QScopedPointer<QObject> object1(new QObject); |
918 | + QScopedPointer<QObject> object2(new QObject); |
919 | + wakelock.acquire(object1.data()); |
920 | + wakelock.acquire(object2.data()); |
921 | + wakelock.release(object1.data()); |
922 | + |
923 | + wakelockEnabledSpy.wait(); |
924 | + |
925 | + wakelock.release(object1.data()); |
926 | + |
927 | + EXPECT_TRUE(wakelock.enabled()); |
928 | +} |
929 | + |
930 | +TEST_F(SharedWakelockTest, wakelockAcquireReleaseFlood) |
931 | +{ |
932 | + implementRequestSysState(); |
933 | + implementClearSysState(); |
934 | + |
935 | + SharedWakelock wakelock(dbus.systemConnection()); |
936 | + |
937 | + QSignalSpy wakelockEnabledSpy(&wakelock, SIGNAL( enabledChanged(bool) )); |
938 | + |
939 | + QScopedPointer<QObject> object(new QObject); |
940 | + wakelock.acquire(object.data()); |
941 | + wakelock.release(object.data()); |
942 | + wakelock.acquire(object.data()); |
943 | + wakelock.release(object.data()); |
944 | + wakelock.acquire(object.data()); |
945 | + wakelock.release(object.data()); |
946 | + while (wakelockEnabledSpy.wait(100)) {} |
947 | + EXPECT_FALSE(wakelock.enabled()); |
948 | +} |
949 | + |
950 | +TEST_F(SharedWakelockTest, wakelockAcquireReleaseAcquireWithDelays) |
951 | +{ |
952 | + powerdMockInterface().AddMethod("com.canonical.powerd", |
953 | + "requestSysState", "si", "s", |
954 | + "i=100000\n" // delay the response from the mock dbus instance |
955 | + "while i>0:\n" |
956 | + " i-=1\n" |
957 | + "ret = 'cookie'").waitForFinished(); |
958 | + |
959 | + implementClearSysState(); |
960 | + |
961 | + SharedWakelock wakelock(dbus.systemConnection()); |
962 | + |
963 | + QSignalSpy wakelockDBusMethodSpy(&powerdMockInterface(), SIGNAL(MethodCalled(const QString &, const QVariantList &))); |
964 | + |
965 | + QScopedPointer<QObject> object(new QObject); |
966 | + wakelock.acquire(object.data()); |
967 | + wakelock.release(object.data()); |
968 | + wakelock.acquire(object.data()); |
969 | + |
970 | + while (wakelockDBusMethodSpy.wait(100)) {} |
971 | + EXPECT_TRUE(wakelock.enabled()); |
972 | + |
973 | + // there must be at least one clearSysState call, but is not necessarily the second call |
974 | + // should the dbus response be slow enough, it may be the third call. Is hard to reliably |
975 | + // reproduce the racey condition for all platforms. |
976 | + bool found = false; |
977 | + for (int i=0; i<wakelockDBusMethodSpy.count(); i++) { |
978 | + const QVariantList &call(wakelockDBusMethodSpy.at(i)); |
979 | + |
980 | + if (call.at(0).toString() == "clearSysState") { |
981 | + found = true; |
982 | + } |
983 | + } |
984 | + EXPECT_TRUE(found); |
985 | +} |
986 | + |
987 | +TEST_F(SharedWakelockTest, nullOwnerAcquireIgnored) |
988 | +{ |
989 | + implementRequestSysState(); |
990 | + |
991 | + SharedWakelock wakelock(dbus.systemConnection()); |
992 | + |
993 | + QSignalSpy wakelockEnabledSpy(&wakelock, SIGNAL( enabledChanged(bool) )); |
994 | + |
995 | + wakelock.acquire(nullptr); |
996 | + |
997 | + wakelockEnabledSpy.wait(200); // have to wait to see if anything happens |
998 | + |
999 | + EXPECT_FALSE(wakelock.enabled()); |
1000 | +} |
1001 | + |
1002 | +TEST_F(SharedWakelockTest, nullReleaseAcquireIgnored) |
1003 | +{ |
1004 | + implementRequestSysState(); |
1005 | + |
1006 | + SharedWakelock wakelock(dbus.systemConnection()); |
1007 | + |
1008 | + wakelock.release(nullptr); |
1009 | + |
1010 | + EXPECT_FALSE(wakelock.enabled()); |
1011 | +} |
1012 | + |
1013 | +TEST_F(SharedWakelockTest, ifOwnerDestroyedWakelockReleased) |
1014 | +{ |
1015 | + implementRequestSysState(); |
1016 | + implementClearSysState(); |
1017 | + |
1018 | + SharedWakelock wakelock(dbus.systemConnection()); |
1019 | + |
1020 | + QSignalSpy wakelockEnabledSpy(&wakelock, SIGNAL( enabledChanged(bool) )); |
1021 | + |
1022 | + QScopedPointer<QObject> object(new QObject); |
1023 | + wakelock.acquire(object.data()); |
1024 | + wakelockEnabledSpy.wait(); |
1025 | + |
1026 | + // Verify the DBus method is called |
1027 | + QSignalSpy wakelockDBusMethodSpy(&powerdMockInterface(), SIGNAL(MethodCalled(const QString &, const QVariantList &))); |
1028 | + |
1029 | + object.reset(); |
1030 | + wakelockDBusMethodSpy.wait(); |
1031 | + |
1032 | + EXPECT_FALSE(wakelockDBusMethodSpy.empty()); |
1033 | + EXPECT_CALL(wakelockDBusMethodSpy, 0, "clearSysState", |
1034 | + QVariantList() << QString("cookie")); |
1035 | + |
1036 | + EXPECT_FALSE(wakelock.enabled()); |
1037 | +} |
1038 | + |
1039 | +TEST_F(SharedWakelockTest, reloadCachedWakelockCookie) |
1040 | +{ |
1041 | + const char cookieFile[] = "/tmp/qtmir_powerd_cookie"; |
1042 | + |
1043 | + // Custom Deleter for QScopedPointer to delete the qtmir cookie file no matter what |
1044 | + struct ScopedQFileCustomDeleter |
1045 | + { |
1046 | + static inline void cleanup(QFile *file) |
1047 | + { |
1048 | + file->remove(); |
1049 | + delete file; |
1050 | + } |
1051 | + }; |
1052 | + QScopedPointer<QFile, ScopedQFileCustomDeleter> cookieCache(new QFile(cookieFile)); |
1053 | + cookieCache->open(QFile::WriteOnly | QFile::Text); |
1054 | + cookieCache->write("myCookie"); |
1055 | + |
1056 | + SharedWakelock wakelock(dbus.systemConnection()); |
1057 | + EXPECT_TRUE(wakelock.enabled()); |
1058 | +} |
1059 | + |
1060 | +TEST_F(SharedWakelockTest, wakelockReleasedOnSharedWakelockDestroyed) |
1061 | +{ |
1062 | + implementRequestSysState(); |
1063 | + implementClearSysState(); |
1064 | + |
1065 | + auto wakelock = new SharedWakelock(dbus.systemConnection()); |
1066 | + |
1067 | + QSignalSpy wakelockEnabledSpy(wakelock, SIGNAL( enabledChanged(bool) )); |
1068 | + |
1069 | + QScopedPointer<QObject> object(new QObject); |
1070 | + wakelock->acquire(object.data()); |
1071 | + wakelockEnabledSpy.wait(); // wait for wakelock to be enabled |
1072 | + |
1073 | + QSignalSpy wakelockDBusMethodSpy(&powerdMockInterface(), SIGNAL(MethodCalled(const QString &, const QVariantList &))); |
1074 | + delete wakelock; |
1075 | + wakelockDBusMethodSpy.wait(); |
1076 | + |
1077 | + EXPECT_FALSE(wakelockDBusMethodSpy.empty()); |
1078 | + EXPECT_CALL(wakelockDBusMethodSpy, 0, "clearSysState", |
1079 | + QVariantList() << QString("cookie")); |
1080 | +} |
1081 | + |
1082 | + |
1083 | +// Define custom main() as these tests rely on a QEventLoop |
1084 | +int main(int argc, char** argv) { |
1085 | + QCoreApplication app(argc, argv); |
1086 | + ::testing::InitGoogleTest(&argc, argv); |
1087 | + return RUN_ALL_TESTS(); |
1088 | } |
1089 | |
1090 | === modified file 'tests/modules/common/common.pri' |
1091 | --- tests/modules/common/common.pri 2014-10-20 18:48:53 +0000 |
1092 | +++ tests/modules/common/common.pri 2015-03-06 14:19:39 +0000 |
1093 | @@ -1,7 +1,7 @@ |
1094 | CONFIG += no_keywords # keywords clash with ProcessC++ |
1095 | PKGCONFIG += ubuntu-app-launch-2 process-cpp |
1096 | |
1097 | -QT += quick |
1098 | +QT += quick dbus |
1099 | |
1100 | HEADERS += ../common/mock_application_controller.h \ |
1101 | ../common/mock_desktop_file_reader.h \ |
1102 | @@ -11,6 +11,7 @@ |
1103 | ../common/mock_prompt_session.h \ |
1104 | ../common/mock_prompt_session_manager.h \ |
1105 | ../common/mock_renderable.h \ |
1106 | + ../common/mock_shared_wakelock.h \ |
1107 | ../common/mock_session.h \ |
1108 | ../common/mock_surface.h \ |
1109 | ../common/qtmir_test.h \ |
1110 | |
1111 | === modified file 'tests/modules/common/mock_shared_wakelock.h' |
1112 | --- tests/modules/common/mock_shared_wakelock.h 2015-01-16 09:48:21 +0000 |
1113 | +++ tests/modules/common/mock_shared_wakelock.h 2015-03-06 14:19:39 +0000 |
1114 | @@ -23,21 +23,48 @@ |
1115 | |
1116 | namespace testing |
1117 | { |
1118 | -struct MockSharedWakelock : public qtmir::SharedWakelock |
1119 | +class MockSharedWakelock : public qtmir::SharedWakelock |
1120 | { |
1121 | - MockSharedWakelock() |
1122 | - { |
1123 | - ON_CALL(*this, createWakelock()).WillByDefault(Invoke(this, &MockSharedWakelock::doCreateWakelock)); |
1124 | - } |
1125 | - |
1126 | - MOCK_METHOD0(createWakelock, QObject*()); |
1127 | - bool wakelockHeld() { return m_wakelock; } |
1128 | - |
1129 | - |
1130 | - QObject* doCreateWakelock() const |
1131 | - { |
1132 | - return new QObject; |
1133 | - } |
1134 | +public: |
1135 | + MockSharedWakelock(const QDBusConnection& /*connection*/= QDBusConnection::systemBus()) |
1136 | + { |
1137 | + ON_CALL(*this, enabled()).WillByDefault(Invoke(this, &MockSharedWakelock::doEnabled)); |
1138 | + ON_CALL(*this, acquire(_)).WillByDefault(Invoke(this, &MockSharedWakelock::doAcquire)); |
1139 | + ON_CALL(*this, release(_)).WillByDefault(Invoke(this, &MockSharedWakelock::doRelease)); |
1140 | + } |
1141 | + |
1142 | + MOCK_CONST_METHOD0(enabled, bool()); |
1143 | + MOCK_METHOD1(acquire, void(const QObject *)); |
1144 | + MOCK_METHOD1(release, void(const QObject *)); |
1145 | + |
1146 | + bool doEnabled() |
1147 | + { |
1148 | + return !m_owners.isEmpty(); |
1149 | + } |
1150 | + |
1151 | + void doAcquire(const QObject *object) |
1152 | + { |
1153 | + if (m_owners.contains(object)) { |
1154 | + return; |
1155 | + } |
1156 | + m_owners.insert(object); |
1157 | + if (m_owners.size() == 1) { |
1158 | + Q_EMIT enabledChanged(true); |
1159 | + } |
1160 | + } |
1161 | + |
1162 | + void doRelease(const QObject *object) |
1163 | + { |
1164 | + if (!m_owners.remove(object)) { |
1165 | + return; |
1166 | + } |
1167 | + if (m_owners.isEmpty()) { |
1168 | + Q_EMIT enabledChanged(false); |
1169 | + } |
1170 | + } |
1171 | + |
1172 | +private: |
1173 | + QSet<const QObject *> m_owners; |
1174 | }; |
1175 | |
1176 | } // namespace testing |
1177 | |
1178 | === modified file 'tests/modules/modules.pro' |
1179 | --- tests/modules/modules.pro 2015-01-16 09:48:21 +0000 |
1180 | +++ tests/modules/modules.pro 2015-03-06 14:19:39 +0000 |
1181 | @@ -1,2 +1,2 @@ |
1182 | TEMPLATE = subdirs |
1183 | -SUBDIRS = Application ApplicationManager General MirSurfaceItem SessionManager TaskController DesktopFileReader |
1184 | +SUBDIRS = Application ApplicationManager General MirSurfaceItem SessionManager SharedWakelock TaskController DesktopFileReader |
2 wakelocks somehow acquired with this by the Music app, something wrong