Merge lp:~gerboland/qtmir/fix-lifecycle-exempt-keeps-wakelock into lp:qtmir
- fix-lifecycle-exempt-keeps-wakelock
- Merge into trunk
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 |
Related bugs: |
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:
|
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
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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)
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:322
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Michał Sawicz (saviq) wrote : | # |
Should we have a test for setFocused(false)?
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:323
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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-
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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:
Wakelock object created!
SharedWakelock:
Wakelock object destroyed!
qtmir.sessions: Wakelock released
SharedWakelock:
Wakelock object created!
qtmir.sessions: Wakelock acquired "8d92eec1-
SharedWakelock:
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
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:327
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:328
http://
Executed test runs:
FAILURE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:328
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:332
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Gerry Boland (gerboland) : | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:333
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Michał Sawicz (saviq) : | # |
![](/+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?
Yes
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:334
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:334
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:335
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Albert Astals Cid (aacid) : | # |
Preview Diff
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 |
PASSED: Continuous integration, rev:320 jenkins. qa.ubuntu. com/job/ qtmir-ci/ 217/ jenkins. qa.ubuntu. com/job/ qtmir-vivid- amd64-ci/ 68 jenkins. qa.ubuntu. com/job/ qtmir-vivid- armhf-ci/ 68 jenkins. qa.ubuntu. com/job/ qtmir-vivid- armhf-ci/ 68/artifact/ work/output/ *zip*/output. zip
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/qtmir- ci/217/ rebuild
http://