Merge lp:unity-mir/devel-mir-next into lp:unity-mir

Proposed by Gerry Boland
Status: Merged
Approved by: Michał Sawicz
Approved revision: no longer in the source branch.
Merged at revision: 222
Proposed branch: lp:unity-mir/devel-mir-next
Merge into: lp:unity-mir
Diff against target: 1928 lines (+1441/-142)
10 files modified
src/modules/Unity/Application/application.cpp (+11/-0)
src/modules/Unity/Application/application.h (+4/-1)
src/modules/Unity/Application/application_manager.cpp (+80/-85)
src/modules/Unity/Application/application_manager.h (+5/-5)
src/modules/Unity/Application/taskcontroller.cpp (+4/-25)
src/modules/Unity/Application/taskcontroller.h (+3/-4)
src/unity-mir/qmirserver.cpp (+22/-3)
tests/CMakeLists.txt (+13/-0)
tests/application_manager_test.cpp (+1296/-16)
tests/mock_session.h (+3/-3)
To merge this branch: bzr merge lp:unity-mir/devel-mir-next
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
Michał Sawicz Approve
Review via email: mp+220249@code.launchpad.net

Commit message

Merge devel at rev 222. (Fix crash on shell shutdown; Refactor app shutdown)

2014-05-06 Gerry Boland <email address hidden>

        [220] Fix crash on Mir-initiated shutdown, where stop() was being
        called on an already shutting-down server.

        Mir initiates shutdown on SIGINT & SIGTERM, need to distinguish that
        shutdown from a client-initiated shutdown. Do this by installing a
        custom signal handler that is run after Mir's initiate-shutdown
        handler is called, so that we only call server.stop() on a client-
        initiated shutdown. Fixes: https://bugs.launchpad.net/bugs/1315251.

        Approved by Michał Sawicz, PS Jenkins bot.

2014-05-20 Gerry Boland <email address hidden>

        [222] Refactoring to have app shutdown handled correctly.

        Involves adding a canBeResumed flag to Application and using it to
        determine if AppMan should remove the application from the lists.
        Also add a big bunch of unit tests for AppMan. Fixes:
        https://bugs.launchpad.net/bugs/1305128.

        Approved by PS Jenkins bot, Michał Sawicz.

Description of the change

Refactoring to have app shutdown handled correctly.

Involves adding a canBeResumed flag to Application and using it to determine if AppMan should remove the application from the lists. Also add a big bunch of unit tests for AppMan. Fixes: https://bugs.launchpad.net/bugs/1305128.

Checklist:
 * Are there any related MPs required for this MP to build/function as expected? Please list.
N
 * Did you perform an exploratory manual test run of your code change and any related functionality?
Y
 * If you changed the packaging (debian), did you subscribe the ubuntu-unity team to this MP?
N/A

To post a comment you must log in.
Revision history for this message
Michał Sawicz (saviq) wrote :

Yup.

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
lp:unity-mir/devel-mir-next updated
222. By Tarmac

Merge devel at rev 222. (Fix crash on shell shutdown; Refactor app shutdown)

2014-05-06 Gerry Boland <email address hidden>

        [220] Fix crash on Mir-initiated shutdown, where stop() was being
        called on an already shutting-down server.

        Mir initiates shutdown on SIGINT & SIGTERM, need to distinguish that
        shutdown from a client-initiated shutdown. Do this by installing a
        custom signal handler that is run after Mir's initiate-shutdown
        handler is called, so that we only call server.stop() on a client-
        initiated shutdown. Fixes: https://bugs.launchpad.net/bugs/1315251.

        Approved by Michał Sawicz, PS Jenkins bot.

2014-05-20 Gerry Boland <email address hidden>

        [222] Refactoring to have app shutdown handled correctly.

        Involves adding a canBeResumed flag to Application and using it to
        determine if AppMan should remove the application from the lists.
        Also add a big bunch of unit tests for AppMan. Fixes:
        https://bugs.launchpad.net/bugs/1305128.

        Approved by PS Jenkins bot, Michał Sawicz.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/modules/Unity/Application/application.cpp'
2--- src/modules/Unity/Application/application.cpp 2014-05-14 16:44:55 +0000
3+++ src/modules/Unity/Application/application.cpp 2014-05-20 12:42:58 +0000
4@@ -42,6 +42,7 @@
5 , m_stage((m_desktopData->stageHint() == "SideStage") ? Application::SideStage : Application::MainStage)
6 , m_state(state)
7 , m_focused(false)
8+ , m_canBeResumed(true)
9 , m_fullscreen(false)
10 , m_visible(false)
11 , m_arguments(arguments)
12@@ -142,6 +143,16 @@
13 return m_visible;
14 }
15
16+bool Application::canBeResumed() const
17+{
18+ return m_canBeResumed;
19+}
20+
21+void Application::setCanBeResumed(const bool resume)
22+{
23+ m_canBeResumed = resume;
24+}
25+
26 pid_t Application::pid() const
27 {
28 return m_pid;
29
30=== modified file 'src/modules/Unity/Application/application.h'
31--- src/modules/Unity/Application/application.h 2014-05-14 16:44:55 +0000
32+++ src/modules/Unity/Application/application.h 2014-05-20 12:42:58 +0000
33@@ -71,6 +71,9 @@
34 QImage screenshotImage() const;
35 void updateScreenshot();
36
37+ bool canBeResumed() const;
38+ void setCanBeResumed(const bool);
39+
40 bool isValid() const;
41 QString desktopFile() const;
42 QString exec() const;
43@@ -106,6 +109,7 @@
44 bool m_focused;
45 QUrl m_screenshot;
46 QImage m_screenshotImage;
47+ bool m_canBeResumed;
48 bool m_fullscreen;
49 bool m_visible; // duplicating internal Mir data :(
50 std::shared_ptr<::mir::scene::Session> m_session;
51@@ -114,7 +118,6 @@
52 QTimer* m_suspendTimer;
53
54 friend class ApplicationManager;
55- friend class ApplicationListModel;
56 friend class MirSurfaceManager;
57 };
58
59
60=== modified file 'src/modules/Unity/Application/application_manager.cpp'
61--- src/modules/Unity/Application/application_manager.cpp 2014-05-16 12:19:52 +0000
62+++ src/modules/Unity/Application/application_manager.cpp 2014-05-20 12:42:58 +0000
63@@ -113,22 +113,22 @@
64 {
65 QObject::connect(strategy, &InitialSurfacePlacementStrategy::requestPlacementForSession,
66 manager, &ApplicationManager::placeSession, Qt::DirectConnection);
67-
68 }
69
70 void connectToTaskController(ApplicationManager * manager, TaskController * controller)
71 {
72- QObject::connect(controller, &TaskController::processStartReport,
73- manager, &ApplicationManager::onProcessStartReportReceived);
74+ QObject::connect(controller, &TaskController::processStarting,
75+ manager, &ApplicationManager::onProcessStarting);
76 QObject::connect(controller, &TaskController::processStopped,
77 manager, &ApplicationManager::onProcessStopped);
78+ QObject::connect(controller, &TaskController::processFailed,
79+ manager, &ApplicationManager::onProcessFailed);
80 QObject::connect(controller, &TaskController::requestFocus,
81 manager, &ApplicationManager::onFocusRequested);
82 QObject::connect(controller, &TaskController::requestResume,
83 manager, &ApplicationManager::onResumeRequested);
84-
85-}
86-}
87+}
88+} // namespace
89
90 ApplicationManager* ApplicationManager::Factory::Factory::create()
91 {
92@@ -485,20 +485,12 @@
93 return application;
94 }
95
96-void ApplicationManager::onProcessStartReportReceived(const QString &appId, const bool failure)
97+void ApplicationManager::onProcessStarting(const QString &appId)
98 {
99- DLOG("ApplicationManager::onProcessStartReportReceived (this=%p, appId=%s, failure=%c)",
100- this, qPrintable(appId), (failure) ? 'Y' : 'N');
101-
102- if (failure) {
103- DLOG("ApplicationManager::onProcessStartReportReceived handling failure:");
104- stopStartingApplication(appId);
105- return;
106- }
107-
108+ DLOG("ApplicationManager::onProcessStarting (this=%p, appId=%s)", this, qPrintable(appId));
109 Application *application = findApplication(appId);
110
111- if (!application) { // if shell did not start this application, but upstart did
112+ if (!application) { // then shell did not start this application, so upstart must have - add to list
113 application = new Application(
114 m_taskController,
115 m_desktopFileReaderFactory->createInstance(appId, m_taskController->findDesktopFileForAppId(appId)),
116@@ -519,7 +511,37 @@
117 Q_EMIT focusRequested(appId);
118 }
119 else {
120- DLOG("ApplicationManager::onProcessStartReportReceived application already found: (app=%p, appId=%s)", application, qPrintable(appId));
121+ DLOG("ApplicationManager::onProcessStarting application already found: (app=%p, appId=%s)", application, qPrintable(appId));
122+ }
123+}
124+
125+void ApplicationManager::onProcessFailed(const QString &appId, const bool duringStartup)
126+{
127+ /* Applications fail if they fail to launch, crash or are killed. If failed to start, must
128+ * immediately remove from list of applications. If crash or kill, instead we set flag on the
129+ * Application to indicate it can be resumed.
130+ */
131+
132+ DLOG("ApplicationManager::onProcessFailed (this=%p, appId=%s, duringStartup=%c)",
133+ this, qPrintable(appId), (duringStartup) ? 'Y' : 'N');
134+
135+ Application *application = findApplication(appId);
136+ if (!application) {
137+ LOG("ApplicationManager::onProcessFailed - upstart reports failure of application AppManager is not managing");
138+ return;
139+ }
140+
141+ Q_UNUSED(duringStartup); // FIXME(greyback) upstart reports app that fully started up & crashes as failing during startup??
142+ if (application->state() == Application::Starting) {
143+ checkFocusOnRemovedApplication(application);
144+ remove(application);
145+ m_dbusWindowStack->WindowDestroyed(0, application->appId());
146+ delete application;
147+ } else {
148+ // We need to set flags on the Application to say the app can be resumed, and thus should not be removed
149+ // from the list by onProcessStopped.
150+ application->setCanBeResumed(true);
151+ application->setPid(0);
152 }
153 }
154
155@@ -574,55 +596,31 @@
156 return true;
157 }
158
159-void ApplicationManager::stopStartingApplication(const QString &appId)
160-{
161- Application *application = findApplication(appId);
162-
163- if (application && application->state() == Application::Starting) {
164- shutdownApplication(application);
165- }
166- else if (application) {
167- DLOG("ApplicationManager::stopStartingApplication start failure report received - but application=%p, appId=%s is not in Starting state",application, qPrintable(appId));
168- }
169-}
170-
171-void ApplicationManager::onProcessStopped(const QString &appId, const bool unexpected)
172-{
173- Application *application = findApplication(appId);
174+void ApplicationManager::onProcessStopped(const QString &appId)
175+{
176+ Application *application = findApplication(appId);
177+ if (!application) {
178+ DLOG("ApplicationManager::onProcessStopped reports stop of appId='%s' which AppMan is not managing, ignoring the event");
179+ return;
180+ }
181 DLOG("ApplicationManager::onProcessStopped (this=%p, application=%p, appId=%s)", this, application, qPrintable(appId));
182
183- // if shell did not stop the application, but upstart says it died, we assume the process has been
184- // killed, so it can be respawned later. Only exception is if that application is focused or running
185- // as then it most likely crashed. Update this logic when upstart gives some failure info.
186- if (application) {
187- shutdownApplication(application);
188- }
189-
190- if (unexpected) {
191- // TODO: pop up a message box/notification?
192- LOG("ApplicationManager: application '%s' died unexpectedly!", qPrintable(appId));
193- }
194-}
195-
196-void ApplicationManager::shutdownApplication(Application* application)
197-{
198- bool removeApplication = checkFocusOnRemovedApplication(application);
199-
200- if (application->state() == Application::Running || application->state() == Application::Starting) {
201- // Application probably crashed, else OOM killer struck. Either way state wasn't saved
202- // so just remove application
203- removeApplication = true;
204- } else if (application->state() == Application::Suspended) {
205- application->setState(Application::Stopped);
206- application->setSession(nullptr);
207+ bool removeApplication = true;
208+
209+ // The following scenario is the only time that we do NOT remove the application from the app list:
210+ if ((application->state() == Application::Suspended || application->state() == Application::Stopped)
211+ && application->pid() == 0 // i.e. onProcessFailed was called, which resets the PID of this application
212+ && application->canBeResumed()) {
213+ removeApplication = false;
214 }
215
216 if (removeApplication) {
217+ DLOG("ApplicationManager::onProcessStopped - removing appId='%s' from the application list", qPrintable(appId));
218+ checkFocusOnRemovedApplication(application);
219 remove(application);
220 m_dbusWindowStack->WindowDestroyed(0, application->appId());
221 delete application;
222 }
223-
224 }
225
226 void ApplicationManager::onFocusRequested(const QString& appId)
227@@ -644,7 +642,7 @@
228 }
229
230 // If app Stopped, trust that upstart-app-launch respawns it itself, and AppManager will
231- // be notified of that through the onProcessStartReportReceived slot. Else resume.
232+ // be notified of that through the onProcessStarting slot. Else resume.
233 if (application->state() == Application::Suspended) {
234 application->setState(Application::Running);
235 }
236@@ -670,8 +668,6 @@
237 }
238 }
239
240-/************************************* Mir-side methods *************************************/
241-
242 void ApplicationManager::authorizeSession(const quint64 pid, bool &authorized)
243 {
244 authorized = false; //to be proven wrong
245@@ -761,6 +757,7 @@
246 application = new Application(m_taskController, desktopData, Application::Starting, arguments, this);
247 application->setPid(pid);
248 application->setStage(stage);
249+ application->setCanBeResumed(false);
250 add(application);
251 authorized = true;
252 }
253@@ -768,8 +765,8 @@
254 void ApplicationManager::placeSession(ms::Session const* session, uint32_t &x, uint32_t &y)
255 {
256 Application* application = findApplicationWithSession(session);
257- DLOG("ApplicationManager::placeSession (this=%p, application=%p, session=%p, name=%s)", this, application, session, session?(session->name().c_str()):"null");
258-
259+ DLOG("ApplicationManager::placeSession (this=%p, application=%p, session=%p, name=%s)", this, application, session,
260+ session?(session->name().c_str()):"null");
261
262 // Application defaults
263 x = 0;
264@@ -818,32 +815,29 @@
265 // in case application closed not by hand of shell, check again here:
266 Application* application = findApplicationWithSession(session);
267
268- DLOG("ApplicationManager::onSessionStopping (this=%p, application=%p, appId=%s, session name=%s)", this, application, application?qPrintable(application->appId()):"null", session?session->name().c_str():"null");
269+ DLOG("ApplicationManager::onSessionStopping (this=%p, application=%p, appId=%s, session name=%s)", this, application,
270+ application?qPrintable(application->appId()):"null", session?session->name().c_str():"null");
271
272 if (application) {
273- bool removeApplication = true;
274-
275- if (application->state() != Application::Starting) {
276- application->setState(Application::Stopped);
277- application->setSession(nullptr);
278+ /* Can remove the application from the running apps list immediately in these curcumstances:
279+ * 1. application is not managed by upstart (this message from Mir is only notice the app has stopped, must do
280+ * it here)
281+ * 2. application is managed by upstart, but has stopped before it managed to create a surface, we can assume
282+ * it crashed on startup, and thus cannot be resumed - so remove it.
283+ * 3. application is managed by upstart and is in foreground (i.e. has Running state), if Mir reports the
284+ * application disconnects, it either crashed or stopped itself. Either case, remove it.
285+ */
286+ if (!application->canBeResumed()
287+ || application->state() == Application::Starting
288+ || application->state() == Application::Running) {
289+ checkFocusOnRemovedApplication(application);
290 m_dbusWindowStack->WindowDestroyed(0, application->appId());
291- if (application != m_focusedApplication) {
292- removeApplication = false;
293- }
294- }
295-
296- if (m_mainStageApplication == application)
297- m_mainStageApplication = nullptr;
298-
299- if (m_sideStageApplication == application)
300- m_sideStageApplication = nullptr;
301-
302- if (removeApplication) {
303- // TODO(greyback) What to do?? Focus next app, or unfocus everything??
304- m_focusedApplication = nullptr;
305 remove(application);
306 delete application;
307- Q_EMIT focusedApplicationIdChanged();
308+ } else {
309+ // otherwise, we do not have enough information to make any changes to the model, so await events from
310+ // upstart to go further, but set the app state
311+ application->setState(Application::Stopped);
312 }
313 }
314 }
315@@ -851,7 +845,8 @@
316 void ApplicationManager::onSessionFocused(const std::shared_ptr<ms::Session>& session)
317 {
318 Application* application = findApplicationWithSession(session);
319- DLOG("ApplicationManager::onSessionFocused (this=%p, application=%p, appId=%s, session name=%s)", this, application, application?qPrintable(application->appId()):"null", session?session->name().c_str():"null");
320+ DLOG("ApplicationManager::onSessionFocused (this=%p, application=%p, appId=%s, session name=%s)", this, application,
321+ application?qPrintable(application->appId()):"null", session?session->name().c_str():"null");
322
323 // Don't give application focus until it has created it's surface, when it is set as state "Running"
324 // and only notify shell of focus changes that it actually expects
325
326=== modified file 'src/modules/Unity/Application/application_manager.h'
327--- src/modules/Unity/Application/application_manager.h 2014-04-15 14:31:02 +0000
328+++ src/modules/Unity/Application/application_manager.h 2014-05-20 12:42:58 +0000
329@@ -125,8 +125,9 @@
330
331 void onSessionCreatedSurface(::mir::scene::Session const*, std::shared_ptr<::mir::scene::Surface> const&);
332
333- void onProcessStartReportReceived(const QString& appId, const bool failure);
334- void onProcessStopped(const QString& appId, const bool unexpected);
335+ void onProcessFailed(const QString& appId, const bool duringStartup);
336+ void onProcessStarting(const QString& appId);
337+ void onProcessStopped(const QString& appId);
338 void onFocusRequested(const QString& appId);
339 void onResumeRequested(const QString& appId);
340
341@@ -144,9 +145,8 @@
342 Application* findApplicationWithSession(const ::mir::scene::Session *session);
343 Application* applicationForStage(Application::Stage stage);
344 QModelIndex findIndex(Application* application);
345- bool checkFocusOnRemovedApplication(Application* application);
346- void shutdownApplication(Application* application);
347- void stopStartingApplication(const QString &appId);
348+ bool isFocused(Application* application);
349+ bool checkFocusOnRemovedApplication(Application *application);
350
351 QList<Application*> m_applications;
352 Application* m_focusedApplication; // remove as Mir has API for this
353
354=== modified file 'src/modules/Unity/Application/taskcontroller.cpp'
355--- src/modules/Unity/Application/taskcontroller.cpp 2014-05-12 11:09:42 +0000
356+++ src/modules/Unity/Application/taskcontroller.cpp 2014-05-20 12:42:58 +0000
357@@ -34,11 +34,8 @@
358 // STL
359 #include <mutex>
360
361-// glib
362-#include <glib.h>
363-
364 // std
365-#include <signal.h>
366+#include <csignal>
367 #include <unistd.h>
368
369 namespace unitymir
370@@ -55,7 +52,7 @@
371 connect(m_appController.data(),
372 &ApplicationController::applicationAboutToBeStarted,
373 this,
374- &TaskController::onApplicationAboutToBeStarted);
375+ &TaskController::processStarting);
376
377 connect(m_appController.data(),
378 &ApplicationController::applicationStarted,
379@@ -65,7 +62,7 @@
380 connect(m_appController.data(),
381 &ApplicationController::applicationStopped,
382 this,
383- &TaskController::onApplicationStopped);
384+ &TaskController::processStopped);
385
386 connect(m_appController.data(),
387 &ApplicationController::applicationFocusRequest,
388@@ -146,22 +143,12 @@
389 }
390 }
391
392-void TaskController::onApplicationAboutToBeStarted(const QString& id)
393-{
394- Q_EMIT processStartReport(id, false);
395-}
396-
397 void TaskController::onApplicationStarted(const QString& id)
398 {
399 pid_t pid = m_appController->primaryPidForAppId(id);
400 m_processController->oomController()->ensureProcessUnlikelyToBeKilled(pid);
401 }
402
403-void TaskController::onApplicationStopped(const QString& id)
404-{
405- Q_EMIT processStopped(id, false);
406-}
407-
408 void TaskController::onApplicationFocusRequest(const QString& id)
409 {
410 pid_t pid = m_appController->primaryPidForAppId(id);
411@@ -176,15 +163,7 @@
412
413 void TaskController::onApplicationError(const QString& id, ApplicationController::Error error)
414 {
415- switch(error)
416- {
417- case ApplicationController::Error::APPLICATION_CRASHED:
418- Q_EMIT processStopped(id, true);
419- break;
420- case ApplicationController::Error::APPLICATION_FAILED_TO_START:
421- Q_EMIT processStartReport(id, true);
422- break;
423- }
424+ Q_EMIT processFailed(id, (error == ApplicationController::Error::APPLICATION_FAILED_TO_START) );
425
426 // Is this really the signal we want to emit?
427 Q_EMIT requestResume(id);
428
429=== modified file 'src/modules/Unity/Application/taskcontroller.h'
430--- src/modules/Unity/Application/taskcontroller.h 2014-05-12 11:09:42 +0000
431+++ src/modules/Unity/Application/taskcontroller.h 2014-05-20 12:42:58 +0000
432@@ -48,15 +48,14 @@
433 QFileInfo findDesktopFileForAppId(const QString &appId) const;
434
435 Q_SIGNALS:
436- void processStartReport(const QString& appId, const bool failure);
437- void processStopped(const QString& appId, const bool unexpectedly);
438+ void processStarting(const QString& appId);
439+ void processStopped(const QString& appId);
440+ void processFailed(const QString& appId, const bool duringStartup);
441 void requestFocus(const QString& appId);
442 void requestResume(const QString& appId);
443
444 private Q_SLOTS:
445- void onApplicationAboutToBeStarted(const QString& id);
446 void onApplicationStarted(const QString& id);
447- void onApplicationStopped(const QString& id);
448 void onApplicationFocusRequest(const QString& id);
449 void onApplicationResumeRequest(const QString& id);
450
451
452=== modified file 'src/unity-mir/qmirserver.cpp'
453--- src/unity-mir/qmirserver.cpp 2014-04-09 15:47:05 +0000
454+++ src/unity-mir/qmirserver.cpp 2014-05-20 12:42:58 +0000
455@@ -19,6 +19,7 @@
456 #include <mir/abnormal_exit.h>
457 #include <mir/default_server_configuration.h>
458 #include <mir/display_server.h>
459+#include <mir/main_loop.h>
460
461 // Platform API
462 #include <application/ubuntu_application_api_mirserver_priv.h>
463@@ -30,9 +31,11 @@
464
465 // Std
466 #include <chrono>
467+#include <csignal>
468 #include <thread>
469
470 // local
471+#include "logging.h"
472 #include "qmirserver.h"
473
474 QMirServer::QMirServer(int argc, const char* argv[], QObject *parent)
475@@ -53,19 +56,35 @@
476 auto argv = m_argv;
477 auto config = new ShellServerConfiguration(m_argc, m_argv);
478 std::thread *t;
479+ bool mirServerShutDown = false;
480
481- mir::run_mir(*config, [config, &client, &argc, &argv, &t](mir::DisplayServer& server) {
482+ mir::run_mir(*config, [&](mir::DisplayServer &server) {
483 ua_ui_mirserver_init(*config);
484
485+ // Mir initiates shutdown on SIGINT & SIGTERM, need to distinguish that shutdown from a
486+ // client-initiated shutdown. Do this by installing a custom signal handler that is run
487+ // after Mir's initiate-shutdown handler is called.
488+ config->the_main_loop()->register_signal_handler(
489+ {SIGINT, SIGTERM},
490+ [&](int)
491+ {
492+ DLOG("Mir-initiated shut down in progress");
493+ mirServerShutDown = true;
494+ });
495+
496 try {
497 t = new std::thread([&]() {
498 client (argc, argv, config);
499- server.stop();
500+ if (!mirServerShutDown) {
501+ DLOG("Qt-initiated shut down in progress");
502+ server.stop();
503+ }
504 });
505 } catch (...) {
506- qDebug() << "Exception caught, quitting";
507+ LOG("Exception caught, quitting");
508 }
509 });
510+
511 if (QCoreApplication::instance()) {
512 bool aboutToQuitSignaled = false;
513 QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, [&]() {
514
515=== modified file 'tests/CMakeLists.txt'
516--- tests/CMakeLists.txt 2014-03-25 11:05:42 +0000
517+++ tests/CMakeLists.txt 2014-05-20 12:42:58 +0000
518@@ -24,14 +24,27 @@
519 ${QT_TEST}
520 )
521
522+set(
523+ MOCK_HEADERS
524+ mock_application_controller.h
525+ mock_desktop_file_reader.h
526+ mock_focus_controller.h
527+ mock_oom_controller.h
528+ mock_process_controller.h
529+ mock_proc_info.h
530+ mock_session.h
531+)
532+
533 add_executable(
534 application_manager_test
535 application_manager_test.cpp
536+ ${MOCK_HEADERS}
537 )
538
539 add_executable(
540 taskcontroller_test
541 taskcontroller_test.cpp
542+ ${MOCK_HEADERS}
543 )
544
545 # We should not need this line according to the Qt5/CMake docs.
546
547=== modified file 'tests/application_manager_test.cpp'
548--- tests/application_manager_test.cpp 2014-05-13 17:23:22 +0000
549+++ tests/application_manager_test.cpp 2014-05-20 12:42:58 +0000
550@@ -45,18 +45,22 @@
551 QSharedPointer<ProcessController::OomController> (
552 &oomController,
553 [](ProcessController::OomController*){})
554- },
555- applicationManager{
556- QSharedPointer<TaskController>{
557- new TaskController(
558- nullptr,
559- QSharedPointer<ApplicationController>(
560- &appController,
561- [](ApplicationController*){}),
562- QSharedPointer<ProcessController>(
563- &processController,
564- [](ProcessController*){})
565- )},
566+ }
567+ , taskController{
568+ QSharedPointer<TaskController> (
569+ new TaskController(
570+ nullptr,
571+ QSharedPointer<ApplicationController>(
572+ &appController,
573+ [](ApplicationController*){}),
574+ QSharedPointer<ProcessController>(
575+ &processController,
576+ [](ProcessController*){})
577+ )
578+ )
579+ }
580+ , applicationManager{
581+ taskController,
582 QSharedPointer<DesktopFileReader::Factory>(
583 &desktopFileReaderFactory,
584 [](DesktopFileReader::Factory*){}),
585@@ -72,6 +76,7 @@
586 testing::NiceMock<testing::MockProcInfo> procInfo;
587 testing::NiceMock<testing::MockDesktopFileReaderFactory> desktopFileReaderFactory;
588 testing::NiceMock<testing::MockFocusController> focusController;
589+ QSharedPointer<TaskController> taskController;
590 ApplicationManager applicationManager;
591 };
592
593@@ -167,7 +172,7 @@
594
595 // now a second session without desktop file is launched:
596 applicationManager.authorizeSession(secondProcId, authed);
597- applicationManager.onProcessStartReportReceived(dialer_app_id, true);
598+ applicationManager.onProcessStarting(dialer_app_id);
599
600 EXPECT_EQ(false,authed);
601 EXPECT_EQ(app,applicationManager.findApplication(dialer_app_id));
602@@ -191,7 +196,8 @@
603 applicationManager.authorizeSession(procId, authed);
604 applicationManager.onSessionStarting(mirSession);
605 Application * beforeFailure = applicationManager.findApplication(app_id);
606- applicationManager.onProcessStartReportReceived(app_id,true);
607+ applicationManager.onProcessStarting(app_id);
608+ applicationManager.onProcessFailed(app_id, true);
609 Application * afterFailure = applicationManager.findApplication(app_id);
610
611 EXPECT_EQ(true, authed);
612@@ -218,7 +224,8 @@
613 applicationManager.onSessionStarting(mirSession);
614 Application * beforeFailure = applicationManager.findApplication(app_id);
615 applicationManager.onSessionCreatedSurface(mirSession.get(), aSurface);
616- applicationManager.onProcessStartReportReceived(app_id, true);
617+ applicationManager.onProcessStarting(app_id);
618+ applicationManager.onProcessFailed(app_id, false);
619 Application * afterFailure = applicationManager.findApplication(app_id);
620
621 EXPECT_EQ(true, authed);
622@@ -394,7 +401,7 @@
623 ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
624
625 // mock upstart launching an app which reports itself as sidestage, but we're on phone
626- applicationManager.onProcessStartReportReceived(appId, false);
627+ applicationManager.onProcessStarting(appId);
628
629 // ensure the app stage is overridden to be main stage
630 Application* theApp = applicationManager.findApplication(appId);
631@@ -513,3 +520,1276 @@
632 QList<QVariant> arguments = spy.takeFirst(); // take the first signal
633 EXPECT_EQ(arguments.at(0).toString(), "app3");
634 }
635+
636+/*
637+ * Test that an application launched by shell itself creates the correct Application instance and
638+ * emits signals indicating the model updated
639+ */
640+TEST_F(ApplicationManagerTests,appStartedByShell)
641+{
642+ using namespace ::testing;
643+ const QString appId("testAppId");
644+ const QString name("Test App");
645+
646+ // Set up Mocks & signal watcher
647+ auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
648+ ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
649+ ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
650+ ON_CALL(*mockDesktopFileReader, name()).WillByDefault(Return(name));
651+
652+ ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
653+
654+ EXPECT_CALL(appController, startApplicationWithAppIdAndArgs(appId, _))
655+ .Times(1)
656+ .WillOnce(Return(true));
657+
658+ QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
659+ QSignalSpy addedSpy(&applicationManager, SIGNAL(applicationAdded(const QString &)));
660+
661+ // start the application
662+ Application *theApp = applicationManager.startApplication(appId, ApplicationManager::NoFlag);
663+
664+ // check application data
665+ EXPECT_EQ(theApp->state(), Application::Starting);
666+ EXPECT_EQ(theApp->appId(), appId);
667+ EXPECT_EQ(theApp->name(), name);
668+ EXPECT_EQ(theApp->canBeResumed(), true);
669+
670+ // check signals were emitted
671+ EXPECT_EQ(countSpy.count(), 2); //FIXME(greyback)
672+ EXPECT_EQ(applicationManager.count(), 1);
673+ EXPECT_EQ(addedSpy.count(), 1);
674+ EXPECT_EQ(addedSpy.takeFirst().at(0).toString(), appId);
675+
676+ // check application in list of apps
677+ Application *theAppAgain = applicationManager.findApplication(appId);
678+ EXPECT_EQ(theAppAgain, theApp);
679+}
680+
681+/*
682+ * Test that an application launched upstart (i.e. not by shell itself) creates the correct Application
683+ * instance and emits signals indicating the model updated
684+ */
685+TEST_F(ApplicationManagerTests,appStartedByUpstart)
686+{
687+ using namespace ::testing;
688+ const QString appId("testAppId");
689+ const QString name("Test App");
690+
691+ // Set up Mocks & signal watcher
692+ auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
693+ ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
694+ ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
695+ ON_CALL(*mockDesktopFileReader, name()).WillByDefault(Return(name));
696+
697+ ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
698+
699+ QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
700+ QSignalSpy addedSpy(&applicationManager, SIGNAL(applicationAdded(const QString &)));
701+ QSignalSpy focusSpy(&applicationManager, SIGNAL(focusRequested(const QString &)));
702+
703+ // upstart sends notification that the application was started
704+ applicationManager.onProcessStarting(appId);
705+
706+ Application *theApp = applicationManager.findApplication(appId);
707+
708+ // check application data
709+ EXPECT_EQ(theApp->state(), Application::Starting);
710+ EXPECT_EQ(theApp->appId(), appId);
711+ EXPECT_EQ(theApp->name(), name);
712+ EXPECT_EQ(theApp->canBeResumed(), true);
713+
714+ // check signals were emitted
715+ EXPECT_EQ(countSpy.count(), 2); //FIXME(greyback)
716+ EXPECT_EQ(applicationManager.count(), 1);
717+ EXPECT_EQ(addedSpy.count(), 1);
718+ EXPECT_EQ(addedSpy.takeFirst().at(0).toString(), appId);
719+ EXPECT_EQ(focusSpy.count(), 1);
720+ EXPECT_EQ(focusSpy.takeFirst().at(0).toString(), appId);
721+}
722+
723+/*
724+ * Test that an application launched via the command line with a correct --desktop_file_hint is accepted,
725+ * creates the correct Application instance and emits signals indicating the model updated
726+ */
727+TEST_F(ApplicationManagerTests,appStartedUsingCorrectDesktopFileHintSwitch)
728+{
729+ using namespace ::testing;
730+ const QString appId("testAppId");
731+ const QString name("Test App");
732+ quint64 procId = 5551;
733+ QByteArray cmdLine("/usr/bin/testApp --desktop_file_hint=");
734+ cmdLine = cmdLine.append(appId);
735+
736+ // Set up Mocks & signal watcher
737+ EXPECT_CALL(procInfo,command_line(procId))
738+ .Times(1)
739+ .WillOnce(Return(cmdLine));
740+
741+ auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
742+ ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
743+ ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
744+ ON_CALL(*mockDesktopFileReader, name()).WillByDefault(Return(name));
745+
746+ ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
747+
748+ QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
749+ QSignalSpy addedSpy(&applicationManager, SIGNAL(applicationAdded(const QString &)));
750+
751+ // Mir requests authentication for an application that was started
752+ bool authed = false;
753+ applicationManager.authorizeSession(procId, authed);
754+ EXPECT_EQ(authed, true);
755+
756+ Application *theApp = applicationManager.findApplication(appId);
757+
758+ // check application data
759+ EXPECT_EQ(theApp->state(), Application::Starting);
760+ EXPECT_EQ(theApp->appId(), appId);
761+ EXPECT_EQ(theApp->name(), name);
762+ EXPECT_EQ(theApp->canBeResumed(), false);
763+
764+ // check signals were emitted
765+ EXPECT_EQ(countSpy.count(), 2); //FIXME(greyback)
766+ EXPECT_EQ(applicationManager.count(), 1);
767+ EXPECT_EQ(addedSpy.count(), 1);
768+ EXPECT_EQ(addedSpy.takeFirst().at(0).toString(), appId);
769+}
770+
771+/*
772+ * Test that an application launched via the command line without the correct --desktop_file_hint is rejected
773+ */
774+TEST_F(ApplicationManagerTests,appDoesNotStartWhenUsingBadDesktopFileHintSwitch)
775+{
776+ using namespace ::testing;
777+ const QString appId("testAppId");
778+ const QString name("Test App");
779+ quint64 procId = 5551;
780+ QByteArray cmdLine("/usr/bin/testApp");
781+
782+ // Set up Mocks & signal watcher
783+ EXPECT_CALL(procInfo,command_line(procId))
784+ .Times(1)
785+ .WillOnce(Return(cmdLine));
786+
787+ QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
788+ QSignalSpy addedSpy(&applicationManager, SIGNAL(applicationAdded(const QString &)));
789+
790+ // Mir requests authentication for an application that was started
791+ bool authed = true;
792+ applicationManager.authorizeSession(procId, authed);
793+ EXPECT_EQ(authed, false);
794+
795+ Application *theApp = applicationManager.findApplication(appId);
796+
797+ EXPECT_EQ(theApp, nullptr);
798+
799+ // check no new signals were emitted
800+ EXPECT_EQ(countSpy.count(), 0);
801+ EXPECT_EQ(applicationManager.count(), 0);
802+ EXPECT_EQ(addedSpy.count(), 0);
803+}
804+
805+/*
806+ * Test that an application launched via the command line with the --desktop_file_hint but an incorrect
807+ * desktop file specified is rejected
808+ */
809+TEST_F(ApplicationManagerTests,appDoesNotStartWhenUsingBadDesktopFileHintFile)
810+{
811+ using namespace ::testing;
812+ const QString appId("testAppId");
813+ const QString badDesktopFile = QString("%1.desktop").arg(appId);
814+ quint64 procId = 5551;
815+ QByteArray cmdLine("/usr/bin/testApp --desktop_file_hint=");
816+ cmdLine = cmdLine.append(badDesktopFile);
817+
818+ // Set up Mocks & signal watcher
819+ EXPECT_CALL(procInfo,command_line(procId))
820+ .Times(1)
821+ .WillOnce(Return(cmdLine));
822+
823+ auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
824+ ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(false));
825+
826+ ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
827+
828+ // Mir requests authentication for an application that was started, should fail
829+ bool authed = true;
830+ applicationManager.authorizeSession(procId, authed);
831+ EXPECT_EQ(authed, false);
832+}
833+
834+/*
835+ * Test that the child sessions of a webapp process are accepted
836+ */
837+TEST_F(ApplicationManagerTests,webAppSecondarySessionsAccepted)
838+{
839+ using namespace ::testing;
840+ quint64 procId = 5551;
841+ QByteArray cmdLine("/usr/bin/qt5/libexec/QtWebProcess");
842+
843+ // Set up Mocks & signal watcher
844+ EXPECT_CALL(procInfo,command_line(procId))
845+ .Times(1)
846+ .WillOnce(Return(cmdLine));
847+
848+ bool authed = false;
849+ applicationManager.authorizeSession(procId, authed);
850+ EXPECT_EQ(authed, true);
851+}
852+
853+/*
854+ * Test that signon-ui sessions are accepted
855+ */
856+TEST_F(ApplicationManagerTests,signonUiSessionsAccepted)
857+{
858+ using namespace ::testing;
859+ quint64 procId = 5151;
860+ QByteArray cmdLine("/usr/bin/signon-ui --blah=http://signon-ui");
861+
862+ // Set up Mocks & signal watcher
863+ EXPECT_CALL(procInfo,command_line(procId))
864+ .Times(1)
865+ .WillOnce(Return(cmdLine));
866+
867+ bool authed = false;
868+ applicationManager.authorizeSession(procId, authed);
869+ EXPECT_EQ(authed, true);
870+}
871+
872+/*
873+ * Test that maliit sessions are accepted
874+ */
875+TEST_F(ApplicationManagerTests,maliitSessionsAccepted)
876+{
877+ using namespace ::testing;
878+ quint64 procId = 151;
879+ QByteArray cmdLine("maliit-server --blah");
880+
881+ // Set up Mocks & signal watcher
882+ EXPECT_CALL(procInfo,command_line(procId))
883+ .Times(1)
884+ .WillOnce(Return(cmdLine));
885+
886+ bool authed = false;
887+ applicationManager.authorizeSession(procId, authed);
888+ EXPECT_EQ(authed, true);
889+}
890+
891+/*
892+ * Test that an application in the Starting state is not impacted by the upstart "Starting" message
893+ * for that application (i.e. the upstart message is effectively useless)
894+ */
895+TEST_F(ApplicationManagerTests,onceAppAddedToApplicationLists_upstartStartingEventIgnored)
896+{
897+ using namespace ::testing;
898+ const QString appId("testAppId");
899+
900+ // Set up Mocks & signal watcher
901+ auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
902+ ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
903+ ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
904+
905+ ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
906+
907+ EXPECT_CALL(appController, startApplicationWithAppIdAndArgs(appId, _))
908+ .Times(1)
909+ .WillOnce(Return(true));
910+
911+ applicationManager.startApplication(appId, ApplicationManager::NoFlag);
912+
913+ QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
914+ QSignalSpy addedSpy(&applicationManager, SIGNAL(applicationAdded(const QString &)));
915+
916+ // upstart sends notification that the application was started
917+ applicationManager.onProcessStarting(appId);
918+
919+ // check no new signals were emitted and application state unchanged
920+ EXPECT_EQ(countSpy.count(), 0);
921+ EXPECT_EQ(applicationManager.count(), 1);
922+ EXPECT_EQ(addedSpy.count(), 0);
923+
924+ Application *theApp = applicationManager.findApplication(appId);
925+ EXPECT_EQ(Application::Starting, theApp->state());
926+}
927+
928+/*
929+ * Test that an application in the Starting state reacts correctly to the Mir sessionStarted
930+ * event for that application (i.e. the Session is associated)
931+ */
932+TEST_F(ApplicationManagerTests,onceAppAddedToApplicationLists_mirSessionStartingEventHandled)
933+{
934+ using namespace ::testing;
935+ const QString appId("testAppId");
936+ quint64 procId = 5551;
937+
938+ // Set up Mocks & signal watcher
939+ auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
940+ ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
941+ ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
942+
943+ ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
944+
945+ EXPECT_CALL(appController, startApplicationWithAppIdAndArgs(appId, _))
946+ .Times(1)
947+ .WillOnce(Return(true));
948+
949+ applicationManager.startApplication(appId, ApplicationManager::NoFlag);
950+ applicationManager.onProcessStarting(appId);
951+
952+ QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
953+ QSignalSpy addedSpy(&applicationManager, SIGNAL(applicationAdded(const QString &)));
954+
955+ std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
956+
957+ // Authorize session and emit Mir sessionStarting event
958+ bool authed = true;
959+ applicationManager.authorizeSession(procId, authed);
960+ applicationManager.onSessionStarting(session);
961+
962+ EXPECT_EQ(countSpy.count(), 0);
963+ EXPECT_EQ(applicationManager.count(), 1);
964+ EXPECT_EQ(addedSpy.count(), 0);
965+
966+ // Check application state and session are correctly set
967+ Application *theApp = applicationManager.findApplication(appId);
968+ EXPECT_EQ(theApp->session(), session);
969+ EXPECT_EQ(theApp->focused(), false);
970+}
971+
972+/*
973+ * Test that an application in the Starting state reacts correctly to the Mir surfaceCreated
974+ * event for that application (i.e. the Surface is associated and state set to Running)
975+ */
976+TEST_F(ApplicationManagerTests,onceAppAddedToApplicationLists_mirSurfaceCreatedEventHandled)
977+{
978+ using namespace ::testing;
979+ const QString appId("testAppId");
980+ quint64 procId = 5551;
981+
982+ // Set up Mocks & signal watcher
983+ auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
984+ ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
985+ ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
986+
987+ ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
988+
989+ EXPECT_CALL(appController, startApplicationWithAppIdAndArgs(appId, _))
990+ .Times(1)
991+ .WillOnce(Return(true));
992+
993+ applicationManager.startApplication(appId, ApplicationManager::NoFlag);
994+ applicationManager.onProcessStarting(appId);
995+
996+ QSignalSpy dataSpy(&applicationManager, SIGNAL(dataChanged(QModelIndex,QModelIndex)));
997+ QSignalSpy focusSpy(&applicationManager, SIGNAL(focusedApplicationIdChanged()));
998+
999+ std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
1000+
1001+ bool authed = true;
1002+ applicationManager.authorizeSession(procId, authed);
1003+ applicationManager.onSessionStarting(session);
1004+
1005+ std::shared_ptr<mir::scene::Surface> surface(nullptr);
1006+
1007+ applicationManager.onSessionCreatedSurface(session.get(), surface);
1008+
1009+ EXPECT_EQ(dataSpy.count(), 1);
1010+ EXPECT_EQ(focusSpy.count(), 1);
1011+
1012+ // Check application state is correctly set
1013+ Application *theApp = applicationManager.findApplication(appId);
1014+ EXPECT_EQ(theApp->state(), Application::Running);
1015+ EXPECT_EQ(theApp->focused(), true);
1016+
1017+ EXPECT_EQ(applicationManager.focusedApplicationId(), appId);
1018+}
1019+
1020+/*
1021+ * Test that an application is stopped correctly, if it has not yet created a surface (still in Starting state)
1022+ */
1023+TEST_F(ApplicationManagerTests,shellStopsAppCorrectlyBeforeSurfaceCreated)
1024+{
1025+ using namespace ::testing;
1026+ const QString appId("testAppId");
1027+ quint64 procId = 5551;
1028+
1029+ // Set up Mocks & signal watcher
1030+ auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
1031+ ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
1032+ ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
1033+
1034+ ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
1035+
1036+ EXPECT_CALL(appController, startApplicationWithAppIdAndArgs(appId, _))
1037+ .Times(1)
1038+ .WillOnce(Return(true));
1039+
1040+ applicationManager.startApplication(appId, ApplicationManager::NoFlag);
1041+ applicationManager.onProcessStarting(appId);
1042+ std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
1043+ bool authed = true;
1044+ applicationManager.authorizeSession(procId, authed);
1045+ applicationManager.onSessionStarting(session);
1046+
1047+ QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
1048+ QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &)));
1049+
1050+ // Stop app
1051+ applicationManager.stopApplication(appId);
1052+
1053+ EXPECT_EQ(countSpy.count(), 2); //FIXME(greyback)
1054+ EXPECT_EQ(applicationManager.count(), 0);
1055+ EXPECT_EQ(removedSpy.count(), 1);
1056+ EXPECT_EQ(removedSpy.takeFirst().at(0).toString(), appId);
1057+}
1058+
1059+/*
1060+ * Test that the foreground application is stopped correctly (is in Running state, has surface)
1061+ */
1062+TEST_F(ApplicationManagerTests,shellStopsForegroundAppCorrectly)
1063+{
1064+ using namespace ::testing;
1065+ const QString appId("testAppId");
1066+ quint64 procId = 5551;
1067+
1068+ // Set up Mocks & signal watcher
1069+ auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
1070+ ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
1071+ ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
1072+
1073+ ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
1074+
1075+ EXPECT_CALL(appController, startApplicationWithAppIdAndArgs(appId, _))
1076+ .Times(1)
1077+ .WillOnce(Return(true));
1078+
1079+ applicationManager.startApplication(appId, ApplicationManager::NoFlag);
1080+ applicationManager.onProcessStarting(appId);
1081+ std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
1082+ bool authed = true;
1083+ applicationManager.authorizeSession(procId, authed);
1084+ applicationManager.onSessionStarting(session);
1085+
1086+ std::shared_ptr<mir::scene::Surface> surface(nullptr);
1087+ applicationManager.onSessionCreatedSurface(session.get(), surface);
1088+ EXPECT_EQ(applicationManager.focusedApplicationId(), appId);
1089+
1090+ QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
1091+ QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &)));
1092+
1093+ // Stop app
1094+ applicationManager.stopApplication(appId);
1095+
1096+ EXPECT_EQ(countSpy.count(), 2); //FIXME(greyback)
1097+ EXPECT_EQ(applicationManager.count(), 0);
1098+ EXPECT_EQ(removedSpy.count(), 1);
1099+ EXPECT_EQ(removedSpy.takeFirst().at(0).toString(), appId);
1100+}
1101+
1102+/*
1103+ * Test that the background application is stopped correctly
1104+ */
1105+TEST_F(ApplicationManagerTests,shellStopsBackgroundAppCorrectly)
1106+{
1107+ using namespace ::testing;
1108+ const QString appId("testAppId");
1109+ quint64 procId = 5551;
1110+
1111+ // Set up Mocks & signal watcher
1112+ auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
1113+ ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
1114+ ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
1115+
1116+ ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
1117+
1118+ EXPECT_CALL(appController, startApplicationWithAppIdAndArgs(appId, _))
1119+ .Times(1)
1120+ .WillOnce(Return(true));
1121+
1122+ applicationManager.startApplication(appId, ApplicationManager::NoFlag);
1123+ applicationManager.onProcessStarting(appId);
1124+ std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
1125+ bool authed = true;
1126+ applicationManager.authorizeSession(procId, authed);
1127+ applicationManager.onSessionStarting(session);
1128+
1129+ std::shared_ptr<mir::scene::Surface> surface(nullptr);
1130+ applicationManager.onSessionCreatedSurface(session.get(), surface);
1131+ applicationManager.onSessionUnfocused();
1132+
1133+ EXPECT_EQ(applicationManager.focusedApplicationId(), QString());
1134+
1135+ QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
1136+ QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &)));
1137+
1138+ // Stop app
1139+ applicationManager.stopApplication(appId);
1140+
1141+ EXPECT_EQ(countSpy.count(), 2); //FIXME(greyback)
1142+ EXPECT_EQ(applicationManager.count(), 0);
1143+ EXPECT_EQ(removedSpy.count(), 1);
1144+ EXPECT_EQ(removedSpy.takeFirst().at(0).toString(), appId);
1145+}
1146+
1147+/*
1148+ * Test that if an application is stopped by upstart, before it has created a surface, AppMan cleans up after it ok
1149+ */
1150+TEST_F(ApplicationManagerTests,upstartNotificationOfStartingAppBeingStopped)
1151+{
1152+ using namespace ::testing;
1153+ const QString appId("testAppId");
1154+ quint64 procId = 5551;
1155+
1156+ // Set up Mocks & signal watcher
1157+ auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
1158+ ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
1159+ ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
1160+
1161+ ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
1162+
1163+ EXPECT_CALL(appController, startApplicationWithAppIdAndArgs(appId, _))
1164+ .Times(1)
1165+ .WillOnce(Return(true));
1166+
1167+ applicationManager.startApplication(appId, ApplicationManager::NoFlag);
1168+ applicationManager.onProcessStarting(appId);
1169+ std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
1170+ bool authed = true;
1171+ applicationManager.authorizeSession(procId, authed);
1172+ applicationManager.onSessionStarting(session);
1173+
1174+ QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
1175+ QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &)));
1176+
1177+ // Upstart notifies of stopping app
1178+ applicationManager.onProcessStopped(appId);
1179+
1180+ EXPECT_EQ(countSpy.count(), 2); //FIXME(greyback)
1181+ EXPECT_EQ(applicationManager.count(), 0);
1182+ EXPECT_EQ(removedSpy.count(), 1);
1183+ EXPECT_EQ(removedSpy.takeFirst().at(0).toString(), appId);
1184+}
1185+
1186+/*
1187+ * Test that if the foreground Running application is stopped by upstart, AppMan cleans up after it ok
1188+ */
1189+TEST_F(ApplicationManagerTests,upstartNotifiesOfStoppingForegroundApp)
1190+{
1191+ using namespace ::testing;
1192+ const QString appId("testAppId");
1193+ quint64 procId = 5551;
1194+
1195+ // Set up Mocks & signal watcher
1196+ auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
1197+ ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
1198+ ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
1199+
1200+ ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
1201+
1202+ EXPECT_CALL(appController, startApplicationWithAppIdAndArgs(appId, _))
1203+ .Times(1)
1204+ .WillOnce(Return(true));
1205+
1206+ applicationManager.startApplication(appId, ApplicationManager::NoFlag);
1207+ applicationManager.onProcessStarting(appId);
1208+ std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
1209+ bool authed = true;
1210+ applicationManager.authorizeSession(procId, authed);
1211+ applicationManager.onSessionStarting(session);
1212+
1213+ std::shared_ptr<mir::scene::Surface> surface(nullptr);
1214+ applicationManager.onSessionCreatedSurface(session.get(), surface);
1215+ EXPECT_EQ(applicationManager.focusedApplicationId(), appId);
1216+
1217+ QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
1218+ QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &)));
1219+
1220+ // Upstart notifies of stopping app
1221+ applicationManager.onProcessStopped(appId);
1222+
1223+ EXPECT_EQ(countSpy.count(), 2); //FIXME(greyback)
1224+ EXPECT_EQ(applicationManager.count(), 0);
1225+ EXPECT_EQ(removedSpy.count(), 1);
1226+ EXPECT_EQ(removedSpy.takeFirst().at(0).toString(), appId);
1227+}
1228+
1229+/*
1230+ * Test that if the foreground Running application is reported to unexpectedly stop by upstart, AppMan
1231+ * cleans up after it ok (as was not in background, had not lifecycle saved its state, so cannot be resumed)
1232+ */
1233+TEST_F(ApplicationManagerTests,upstartNotifiesOfUnexpectedStopOfForegroundApp)
1234+{
1235+ using namespace ::testing;
1236+ const QString appId("testAppId");
1237+ quint64 procId = 5551;
1238+
1239+ // Set up Mocks & signal watcher
1240+ auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
1241+ ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
1242+ ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
1243+
1244+ ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
1245+
1246+ EXPECT_CALL(appController, startApplicationWithAppIdAndArgs(appId, _))
1247+ .Times(1)
1248+ .WillOnce(Return(true));
1249+
1250+ applicationManager.startApplication(appId, ApplicationManager::NoFlag);
1251+ applicationManager.onProcessStarting(appId);
1252+ std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
1253+ bool authed = true;
1254+ applicationManager.authorizeSession(procId, authed);
1255+ applicationManager.onSessionStarting(session);
1256+
1257+ std::shared_ptr<mir::scene::Surface> surface(nullptr);
1258+ applicationManager.onSessionCreatedSurface(session.get(), surface);
1259+ EXPECT_EQ(applicationManager.focusedApplicationId(), appId);
1260+
1261+ QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
1262+ QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &)));
1263+
1264+ // Upstart notifies of crashing / OOM killed app
1265+ applicationManager.onProcessFailed(appId, false);
1266+
1267+ // Upstart finally notifies the app stopped
1268+ applicationManager.onProcessStopped(appId);
1269+
1270+ EXPECT_EQ(countSpy.count(), 2); //FIXME(greyback)
1271+ EXPECT_EQ(applicationManager.count(), 0);
1272+ EXPECT_EQ(removedSpy.count(), 1);
1273+ EXPECT_EQ(removedSpy.takeFirst().at(0).toString(), appId);
1274+}
1275+
1276+/*
1277+ * Test that if a background application is stopped by upstart, AppMan removes it from the app list
1278+ * as the event is a result of direct user interaction
1279+ */
1280+TEST_F(ApplicationManagerTests,upstartNotifiesOfStoppingBackgroundApp)
1281+{
1282+ using namespace ::testing;
1283+ const QString appId("testAppId");
1284+ quint64 procId = 5551;
1285+
1286+ // Set up Mocks & signal watcher
1287+ auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
1288+ ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
1289+ ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
1290+
1291+ ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
1292+
1293+ EXPECT_CALL(appController, startApplicationWithAppIdAndArgs(appId, _))
1294+ .Times(1)
1295+ .WillOnce(Return(true));
1296+
1297+ applicationManager.startApplication(appId, ApplicationManager::NoFlag);
1298+ applicationManager.onProcessStarting(appId);
1299+ std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
1300+ bool authed = true;
1301+ applicationManager.authorizeSession(procId, authed);
1302+ applicationManager.onSessionStarting(session);
1303+
1304+ std::shared_ptr<mir::scene::Surface> surface(nullptr);
1305+ applicationManager.onSessionCreatedSurface(session.get(), surface);
1306+ applicationManager.onSessionUnfocused();
1307+ EXPECT_EQ(applicationManager.focusedApplicationId(), QString());
1308+
1309+ QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
1310+ QSignalSpy focusSpy(&applicationManager, SIGNAL(focusedApplicationIdChanged()));
1311+ QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &)));
1312+
1313+ // Upstart notifies of stopping app
1314+ applicationManager.onProcessStopped(appId);
1315+
1316+ EXPECT_EQ(countSpy.count(), 2); //FIXME(greyback)
1317+ EXPECT_EQ(applicationManager.count(), 0);
1318+ EXPECT_EQ(focusSpy.count(), 0);
1319+ EXPECT_EQ(removedSpy.count(), 1);
1320+ EXPECT_EQ(removedSpy.takeFirst().at(0).toString(), appId);
1321+}
1322+
1323+/*
1324+ * Test that if a background application is reported to unexpectedly stop by upstart, AppMan does not remove
1325+ * it from the app lists but instead considers it Stopped, ready to be resumed. This is due to the fact the
1326+ * app should have saved its state, so can be resumed. This situation can occur due to the OOM killer, or
1327+ * a dodgy app crashing.
1328+ */
1329+TEST_F(ApplicationManagerTests,unexpectedStopOfBackgroundApp)
1330+{
1331+ using namespace ::testing;
1332+ const QString appId("testAppId");
1333+ quint64 procId = 5551;
1334+
1335+ // Set up Mocks & signal watcher
1336+ auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
1337+ ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
1338+ ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
1339+
1340+ ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
1341+
1342+ EXPECT_CALL(appController, startApplicationWithAppIdAndArgs(appId, _))
1343+ .Times(1)
1344+ .WillOnce(Return(true));
1345+
1346+ applicationManager.startApplication(appId, ApplicationManager::NoFlag);
1347+ applicationManager.onProcessStarting(appId);
1348+ std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
1349+ bool authed = true;
1350+ applicationManager.authorizeSession(procId, authed);
1351+ applicationManager.onSessionStarting(session);
1352+
1353+ std::shared_ptr<mir::scene::Surface> surface(nullptr);
1354+ applicationManager.onSessionCreatedSurface(session.get(), surface);
1355+ applicationManager.onSessionUnfocused();
1356+ EXPECT_EQ(applicationManager.focusedApplicationId(), QString());
1357+
1358+ QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
1359+ QSignalSpy focusSpy(&applicationManager, SIGNAL(focusedApplicationIdChanged()));
1360+ QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &)));
1361+
1362+ // Mir reports disconnection
1363+ applicationManager.onSessionStopping(session);
1364+
1365+ // Upstart notifies of crashing / OOM-killed app
1366+ applicationManager.onProcessFailed(appId, false);
1367+
1368+ EXPECT_EQ(focusSpy.count(), 0);
1369+
1370+ // Upstart finally notifies the app stopped
1371+ applicationManager.onProcessStopped(appId);
1372+
1373+ EXPECT_EQ(countSpy.count(), 0);
1374+ EXPECT_EQ(applicationManager.count(), 1);
1375+ EXPECT_EQ(removedSpy.count(), 0);
1376+}
1377+
1378+/*
1379+ * Test that if a background application is reported to have stopped on startup by upstart, that it
1380+ * is kept in the application model, as the app can be lifecycle resumed.
1381+ *
1382+ * Note that upstart reports this "stopped on startup" even for applications which are running.
1383+ * This may be an upstart bug, or AppMan mis-understanding upstart's intentions. But need to check
1384+ * we're doing the right thing in either case. CHECKME(greyback)!
1385+ */
1386+TEST_F(ApplicationManagerTests,unexpectedStopOfBackgroundAppCheckingUpstartBug)
1387+{
1388+ using namespace ::testing;
1389+ const QString appId("testAppId");
1390+ quint64 procId = 5551;
1391+
1392+ // Set up Mocks & signal watcher
1393+ auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
1394+ ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
1395+ ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
1396+
1397+ ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
1398+
1399+ EXPECT_CALL(appController, startApplicationWithAppIdAndArgs(appId, _))
1400+ .Times(1)
1401+ .WillOnce(Return(true));
1402+
1403+ applicationManager.startApplication(appId, ApplicationManager::NoFlag);
1404+ applicationManager.onProcessStarting(appId);
1405+ std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
1406+ bool authed = true;
1407+ applicationManager.authorizeSession(procId, authed);
1408+ applicationManager.onSessionStarting(session);
1409+
1410+ std::shared_ptr<mir::scene::Surface> surface(nullptr);
1411+ applicationManager.onSessionCreatedSurface(session.get(), surface);
1412+ applicationManager.onSessionUnfocused();
1413+ EXPECT_EQ(applicationManager.focusedApplicationId(), QString());
1414+
1415+ QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
1416+ QSignalSpy focusSpy(&applicationManager, SIGNAL(focusedApplicationIdChanged()));
1417+ QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &)));
1418+
1419+ // Mir reports disconnection
1420+ applicationManager.onSessionStopping(session);
1421+
1422+ // Upstart notifies of crashing app
1423+ applicationManager.onProcessFailed(appId, true);
1424+
1425+ EXPECT_EQ(focusSpy.count(), 0);
1426+
1427+ // Upstart finally notifies the app stopped
1428+ applicationManager.onProcessStopped(appId);
1429+
1430+ EXPECT_EQ(countSpy.count(), 0);
1431+ EXPECT_EQ(applicationManager.count(), 1);
1432+ EXPECT_EQ(removedSpy.count(), 0);
1433+}
1434+
1435+/*
1436+ * Test that if a Starting application is then reported to be stopping by Mir, AppMan cleans up after it ok
1437+ */
1438+TEST_F(ApplicationManagerTests,mirNotifiesStartingAppIsNowStopping)
1439+{
1440+ using namespace ::testing;
1441+ const QString appId("testAppId");
1442+ quint64 procId = 5551;
1443+
1444+ // Set up Mocks & signal watcher
1445+ auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
1446+ ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
1447+ ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
1448+
1449+ ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
1450+
1451+ EXPECT_CALL(appController, startApplicationWithAppIdAndArgs(appId, _))
1452+ .Times(1)
1453+ .WillOnce(Return(true));
1454+
1455+ applicationManager.startApplication(appId, ApplicationManager::NoFlag);
1456+ applicationManager.onProcessStarting(appId);
1457+ std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
1458+ bool authed = true;
1459+ applicationManager.authorizeSession(procId, authed);
1460+ applicationManager.onSessionStarting(session);
1461+
1462+ QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
1463+ QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &)));
1464+
1465+ // Mir notifies of stopping app
1466+ applicationManager.onSessionStopping(session);
1467+
1468+ EXPECT_EQ(countSpy.count(), 2); //FIXME(greyback)
1469+ EXPECT_EQ(applicationManager.count(), 0);
1470+ EXPECT_EQ(removedSpy.count(), 1);
1471+ EXPECT_EQ(removedSpy.takeFirst().at(0).toString(), appId);
1472+}
1473+
1474+/*
1475+ * Test that if a Running foreground application is reported to be stopping by Mir, AppMan cleans up after it ok
1476+ */
1477+TEST_F(ApplicationManagerTests,mirNotifiesOfStoppingForegroundApp)
1478+{
1479+ using namespace ::testing;
1480+ const QString appId("testAppId");
1481+ quint64 procId = 5551;
1482+
1483+ // Set up Mocks & signal watcher
1484+ auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
1485+ ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
1486+ ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
1487+
1488+ ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
1489+
1490+ EXPECT_CALL(appController, startApplicationWithAppIdAndArgs(appId, _))
1491+ .Times(1)
1492+ .WillOnce(Return(true));
1493+
1494+ applicationManager.startApplication(appId, ApplicationManager::NoFlag);
1495+ applicationManager.onProcessStarting(appId);
1496+ std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
1497+ bool authed = true;
1498+ applicationManager.authorizeSession(procId, authed);
1499+ applicationManager.onSessionStarting(session);
1500+
1501+ // Associate a surface so AppMan considers app Running, check focused
1502+ std::shared_ptr<mir::scene::Surface> surface(nullptr);
1503+ applicationManager.onSessionCreatedSurface(session.get(), surface);
1504+ EXPECT_EQ(applicationManager.focusedApplicationId(), appId);
1505+
1506+ QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
1507+ QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &)));
1508+
1509+ // Mir notifies of stopping app
1510+ applicationManager.onSessionStopping(session);
1511+
1512+ EXPECT_EQ(countSpy.count(), 2); //FIXME(greyback)
1513+ EXPECT_EQ(applicationManager.count(), 0);
1514+ EXPECT_EQ(removedSpy.count(), 1);
1515+ EXPECT_EQ(removedSpy.takeFirst().at(0).toString(), appId);
1516+}
1517+
1518+/*
1519+ * Test that if a foreground application (one launched via desktop_file_hint) is reported to be stopping by
1520+ * Mir, AppMan removes it from the model immediately
1521+ */
1522+TEST_F(ApplicationManagerTests,mirNotifiesOfStoppingForegroundAppLaunchedWithDesktopFileHint)
1523+{
1524+ using namespace ::testing;
1525+ const QString appId("testAppId");
1526+ const QString name("Test App");
1527+ quint64 procId = 5551;
1528+ QByteArray cmdLine("/usr/bin/testApp --desktop_file_hint=");
1529+ cmdLine = cmdLine.append(appId);
1530+
1531+ // Set up Mocks & signal watcher
1532+ EXPECT_CALL(procInfo,command_line(procId))
1533+ .Times(1)
1534+ .WillOnce(Return(cmdLine));
1535+
1536+ auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
1537+ ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
1538+ ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
1539+ ON_CALL(*mockDesktopFileReader, name()).WillByDefault(Return(name));
1540+
1541+ ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
1542+
1543+ // Mir requests authentication for an application that was started
1544+ bool authed = true;
1545+ applicationManager.authorizeSession(procId, authed);
1546+ EXPECT_EQ(authed, true);
1547+ std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
1548+ applicationManager.onSessionStarting(session);
1549+
1550+ // Associate a surface so AppMan considers app Running, check focused
1551+ std::shared_ptr<mir::scene::Surface> surface(nullptr);
1552+ applicationManager.onSessionCreatedSurface(session.get(), surface);
1553+ EXPECT_EQ(applicationManager.focusedApplicationId(), appId);
1554+
1555+ QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
1556+ QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &)));
1557+
1558+ // Mir notifies of stopping app
1559+ applicationManager.onSessionStopping(session);
1560+
1561+ EXPECT_EQ(countSpy.count(), 2); //FIXME(greyback)
1562+ EXPECT_EQ(applicationManager.count(), 0);
1563+ EXPECT_EQ(removedSpy.count(), 1);
1564+
1565+ Application *app = applicationManager.findApplication(appId);
1566+ EXPECT_EQ(nullptr, app);
1567+}
1568+
1569+/*
1570+ * Test that if a background application is reported to be stopping by Mir, AppMan sets its state to Stopped
1571+ * but does not remove it from the model
1572+ */
1573+TEST_F(ApplicationManagerTests,mirNotifiesOfStoppingBackgroundApp)
1574+{
1575+ using namespace ::testing;
1576+ const QString appId("testAppId");
1577+ quint64 procId = 5551;
1578+
1579+ // Set up Mocks & signal watcher
1580+ auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
1581+ ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
1582+ ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
1583+
1584+ ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
1585+
1586+ EXPECT_CALL(appController, startApplicationWithAppIdAndArgs(appId, _))
1587+ .Times(1)
1588+ .WillOnce(Return(true));
1589+
1590+ applicationManager.startApplication(appId, ApplicationManager::NoFlag);
1591+ applicationManager.onProcessStarting(appId);
1592+ std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
1593+ bool authed = true;
1594+ applicationManager.authorizeSession(procId, authed);
1595+ applicationManager.onSessionStarting(session);
1596+
1597+ // Associate a surface so AppMan considers app Running, check in background
1598+ std::shared_ptr<mir::scene::Surface> surface(nullptr);
1599+ applicationManager.onSessionCreatedSurface(session.get(), surface);
1600+ applicationManager.onSessionUnfocused();
1601+ EXPECT_EQ(applicationManager.focusedApplicationId(), QString());
1602+
1603+ QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
1604+ QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &)));
1605+
1606+ // Mir notifies of stopping app
1607+ applicationManager.onSessionStopping(session);
1608+
1609+ EXPECT_EQ(countSpy.count(), 0);
1610+ EXPECT_EQ(applicationManager.count(), 1);
1611+ EXPECT_EQ(removedSpy.count(), 0);
1612+
1613+ Application * app = applicationManager.findApplication(appId);
1614+ EXPECT_NE(nullptr,app);
1615+ EXPECT_EQ(app->state(), Application::Stopped);
1616+}
1617+
1618+/*
1619+ * Test that if a background application (one launched via desktop_file_hint) is reported to be stopping by
1620+ * Mir, AppMan removes it from the model immediately
1621+ */
1622+TEST_F(ApplicationManagerTests,mirNotifiesOfStoppingBackgroundAppLaunchedWithDesktopFileHint)
1623+{
1624+ using namespace ::testing;
1625+ const QString appId("testAppId");
1626+ const QString name("Test App");
1627+ quint64 procId = 5551;
1628+ QByteArray cmdLine("/usr/bin/testApp --desktop_file_hint=");
1629+ cmdLine = cmdLine.append(appId);
1630+
1631+ // Set up Mocks & signal watcher
1632+ EXPECT_CALL(procInfo,command_line(procId))
1633+ .Times(1)
1634+ .WillOnce(Return(cmdLine));
1635+
1636+ auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
1637+ ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
1638+ ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
1639+ ON_CALL(*mockDesktopFileReader, name()).WillByDefault(Return(name));
1640+
1641+ ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
1642+
1643+ // Mir requests authentication for an application that was started
1644+ bool authed = true;
1645+ applicationManager.authorizeSession(procId, authed);
1646+ EXPECT_EQ(authed, true);
1647+ std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
1648+ applicationManager.onSessionStarting(session);
1649+
1650+ // Associate a surface so AppMan considers app Running, check in background
1651+ std::shared_ptr<mir::scene::Surface> surface(nullptr);
1652+ applicationManager.onSessionCreatedSurface(session.get(), surface);
1653+ applicationManager.onSessionUnfocused();
1654+ EXPECT_EQ(applicationManager.focusedApplicationId(), QString());
1655+
1656+ QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
1657+ QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &)));
1658+
1659+ // Mir notifies of stopping app
1660+ applicationManager.onSessionStopping(session);
1661+
1662+ EXPECT_EQ(countSpy.count(), 2); //FIXME(greyback)
1663+ EXPECT_EQ(applicationManager.count(), 0);
1664+ EXPECT_EQ(removedSpy.count(), 1);
1665+
1666+ Application * app = applicationManager.findApplication(appId);
1667+ EXPECT_EQ(nullptr,app);
1668+}
1669+
1670+/*
1671+ * Test that when an application is stopped correctly by shell, the upstart stopping event is ignored
1672+ */
1673+TEST_F(ApplicationManagerTests,shellStoppedApp_upstartStoppingEventIgnored)
1674+{
1675+ using namespace ::testing;
1676+ const QString appId("testAppId");
1677+ quint64 procId = 5551;
1678+
1679+ // Set up Mocks & signal watcher
1680+ auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
1681+ ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
1682+ ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
1683+
1684+ ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
1685+
1686+ EXPECT_CALL(appController, startApplicationWithAppIdAndArgs(appId, _))
1687+ .Times(1)
1688+ .WillOnce(Return(true));
1689+
1690+ applicationManager.startApplication(appId, ApplicationManager::NoFlag);
1691+ applicationManager.onProcessStarting(appId);
1692+ std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
1693+ bool authed = true;
1694+ applicationManager.authorizeSession(procId, authed);
1695+ applicationManager.onSessionStarting(session);
1696+
1697+ applicationManager.stopApplication(appId);
1698+
1699+ QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
1700+ QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &)));
1701+
1702+ // Upstart notifies of stopping app
1703+ applicationManager.onProcessStopped(appId);
1704+
1705+ EXPECT_EQ(countSpy.count(), 0);
1706+ EXPECT_EQ(removedSpy.count(), 0);
1707+}
1708+
1709+/*
1710+ * Test that when an application is stopped correctly by shell, the Mir Session stopped event is ignored
1711+ */
1712+TEST_F(ApplicationManagerTests,shellStoppedApp_mirSessionStoppingEventIgnored)
1713+{
1714+ using namespace ::testing;
1715+ const QString appId("testAppId");
1716+ quint64 procId = 5551;
1717+
1718+ // Set up Mocks & signal watcher
1719+ auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
1720+ ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
1721+ ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
1722+
1723+ ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
1724+
1725+ EXPECT_CALL(appController, startApplicationWithAppIdAndArgs(appId, _))
1726+ .Times(1)
1727+ .WillOnce(Return(true));
1728+
1729+ applicationManager.startApplication(appId, ApplicationManager::NoFlag);
1730+ applicationManager.onProcessStarting(appId);
1731+ std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
1732+ bool authed = true;
1733+ applicationManager.authorizeSession(procId, authed);
1734+ applicationManager.onSessionStarting(session);
1735+
1736+ applicationManager.stopApplication(appId);
1737+
1738+ QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
1739+ QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &)));
1740+
1741+ // Mir notifies of stopping app/Session
1742+ applicationManager.onSessionStopping(session);
1743+
1744+ EXPECT_EQ(countSpy.count(), 0);
1745+ EXPECT_EQ(removedSpy.count(), 0);
1746+}
1747+
1748+/*
1749+ * Test that if an application is stopped by upstart, the Mir stopping event is ignored
1750+ */
1751+TEST_F(ApplicationManagerTests,appStoppedByUpstart_mirSessionStoppingEventIgnored)
1752+{
1753+ using namespace ::testing;
1754+ const QString appId("testAppId");
1755+ quint64 procId = 5551;
1756+
1757+ // Set up Mocks & signal watcher
1758+ auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
1759+ ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
1760+ ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
1761+
1762+ ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
1763+
1764+ EXPECT_CALL(appController, startApplicationWithAppIdAndArgs(appId, _))
1765+ .Times(1)
1766+ .WillOnce(Return(true));
1767+
1768+ applicationManager.startApplication(appId, ApplicationManager::NoFlag);
1769+ applicationManager.onProcessStarting(appId);
1770+ std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("", procId);
1771+ bool authed = true;
1772+ applicationManager.authorizeSession(procId, authed);
1773+ applicationManager.onSessionStarting(session);
1774+
1775+ applicationManager.onProcessStopped(appId);
1776+
1777+ QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
1778+ QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &)));
1779+
1780+ // Mir notifies of stopping app
1781+ applicationManager.onSessionStopping(session);
1782+
1783+ EXPECT_EQ(countSpy.count(), 0);
1784+ EXPECT_EQ(removedSpy.count(), 0);
1785+}
1786+
1787+/*
1788+ * Webapps have multiple sessions, but only one is linked to the application (other is considered a hidden session).
1789+ * If webapp in foreground stops unexpectedly, remove it and it alone from app list
1790+ */
1791+TEST_F(ApplicationManagerTests,unexpectedStopOfForegroundWebapp)
1792+{
1793+ using namespace ::testing;
1794+ const QString appId("webapp");
1795+ quint64 procId1 = 5551;
1796+ quint64 procId2 = 5564;
1797+ QByteArray cmdLine("/usr/bin/qt5/libexec/QtWebProcess");
1798+
1799+ // Set up Mocks & signal watcher
1800+ EXPECT_CALL(procInfo,command_line(procId2))
1801+ .Times(1)
1802+ .WillOnce(Return(cmdLine));
1803+
1804+ ON_CALL(appController,appIdHasProcessId(procId1, appId)).WillByDefault(Return(true));
1805+ ON_CALL(appController,appIdHasProcessId(procId2, _)).WillByDefault(Return(false));
1806+
1807+ // Set up Mocks & signal watcher
1808+ auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
1809+ ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
1810+ ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
1811+
1812+ ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
1813+
1814+ EXPECT_CALL(appController, startApplicationWithAppIdAndArgs(appId, _))
1815+ .Times(1)
1816+ .WillOnce(Return(true));
1817+
1818+ applicationManager.startApplication(appId, ApplicationManager::NoFlag);
1819+ applicationManager.onProcessStarting(appId);
1820+ std::shared_ptr<mir::scene::Session> session1 = std::make_shared<MockSession>("", procId1);
1821+ std::shared_ptr<mir::scene::Session> session2 = std::make_shared<MockSession>("", procId2);
1822+
1823+ bool authed = false;
1824+ applicationManager.authorizeSession(procId1, authed);
1825+ applicationManager.onSessionStarting(session1);
1826+ EXPECT_EQ(authed, true);
1827+ applicationManager.authorizeSession(procId2, authed);
1828+ applicationManager.onSessionStarting(session2);
1829+ EXPECT_EQ(authed, true);
1830+ std::shared_ptr<mir::scene::Surface> surface(nullptr);
1831+ applicationManager.onSessionCreatedSurface(session2.get(), surface);
1832+
1833+ QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
1834+ QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &)));
1835+
1836+ // Mir notifies of stopping app/Session
1837+ applicationManager.onSessionStopping(session2);
1838+ applicationManager.onSessionStopping(session1);
1839+
1840+ EXPECT_EQ(countSpy.count(), 2); //FIXME(greyback)
1841+ EXPECT_EQ(applicationManager.count(), 0);
1842+ EXPECT_EQ(removedSpy.count(), 1);
1843+ EXPECT_EQ(removedSpy.takeFirst().at(0).toString(), appId);
1844+}
1845+
1846+/*
1847+ * Webapps have multiple sessions, but only one is linked to the application (other is considered a hidden session).
1848+ * If webapp in background stops unexpectedly, do not remove it from app list
1849+ */
1850+TEST_F(ApplicationManagerTests,unexpectedStopOfBackgroundWebapp)
1851+{
1852+ using namespace ::testing;
1853+ const QString appId("webapp");
1854+ quint64 procId1 = 5551;
1855+ quint64 procId2 = 5564;
1856+ QByteArray cmdLine("/usr/bin/qt5/libexec/QtWebProcess");
1857+
1858+ // Set up Mocks & signal watcher
1859+ EXPECT_CALL(procInfo,command_line(procId2))
1860+ .Times(1)
1861+ .WillOnce(Return(cmdLine));
1862+
1863+ ON_CALL(appController,appIdHasProcessId(procId1, appId)).WillByDefault(Return(true));
1864+ ON_CALL(appController,appIdHasProcessId(procId2, _)).WillByDefault(Return(false));
1865+
1866+ // Set up Mocks & signal watcher
1867+ auto mockDesktopFileReader = new NiceMock<MockDesktopFileReader>(appId, QFileInfo());
1868+ ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(true));
1869+ ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));
1870+
1871+ ON_CALL(desktopFileReaderFactory, createInstance(appId, _)).WillByDefault(Return(mockDesktopFileReader));
1872+
1873+ EXPECT_CALL(appController, startApplicationWithAppIdAndArgs(appId, _))
1874+ .Times(1)
1875+ .WillOnce(Return(true));
1876+
1877+ applicationManager.startApplication(appId, ApplicationManager::NoFlag);
1878+ applicationManager.onProcessStarting(appId);
1879+ std::shared_ptr<mir::scene::Session> session1 = std::make_shared<MockSession>("", procId1);
1880+ std::shared_ptr<mir::scene::Session> session2 = std::make_shared<MockSession>("", procId2);
1881+
1882+ bool authed = false;
1883+ applicationManager.authorizeSession(procId1, authed);
1884+ applicationManager.onSessionStarting(session1);
1885+ EXPECT_EQ(authed, true);
1886+ applicationManager.authorizeSession(procId2, authed);
1887+ applicationManager.onSessionStarting(session2);
1888+ EXPECT_EQ(authed, true);
1889+
1890+ // both sessions create surfaces, then unfocus everything.
1891+ std::shared_ptr<mir::scene::Surface> surface1(nullptr);
1892+ applicationManager.onSessionCreatedSurface(session1.get(), surface1);
1893+ std::shared_ptr<mir::scene::Surface> surface2(nullptr);
1894+ applicationManager.onSessionCreatedSurface(session2.get(), surface2);
1895+ applicationManager.onSessionUnfocused();
1896+ EXPECT_EQ(applicationManager.focusedApplicationId(), QString());
1897+
1898+ QSignalSpy countSpy(&applicationManager, SIGNAL(countChanged()));
1899+ QSignalSpy removedSpy(&applicationManager, SIGNAL(applicationRemoved(const QString &)));
1900+
1901+ // Mir notifies of stopping app/Session
1902+ applicationManager.onSessionStopping(session2);
1903+ applicationManager.onSessionStopping(session1);
1904+
1905+ EXPECT_EQ(countSpy.count(), 0);
1906+ EXPECT_EQ(removedSpy.count(), 0);
1907+}
1908
1909=== modified file 'tests/mock_session.h'
1910--- tests/mock_session.h 2014-04-15 14:31:02 +0000
1911+++ tests/mock_session.h 2014-05-20 12:42:58 +0000
1912@@ -15,8 +15,8 @@
1913 *
1914 */
1915
1916-#ifndef MOCK_MIR_SHELL_SESSION_H
1917-#define MOCK_MIR_SHELL_SESSION_H
1918+#ifndef MOCK_MIR_SCENE_SESSION_H
1919+#define MOCK_MIR_SCENE_SESSION_H
1920
1921 #include <mir/scene/session.h>
1922 #include <mir/graphics/display_configuration.h>
1923@@ -66,4 +66,4 @@
1924 };
1925 }
1926
1927-#endif // MOCK_MIR_SHELL_SESSION_H
1928+#endif // MOCK_MIR_SCENE_SESSION_H

Subscribers

People subscribed via source and target branches