Merge lp:~gerboland/unity-mir/fix-upstart-closed-apps2 into lp:unity-mir/devel-mir-next

Proposed by Gerry Boland
Status: Merged
Approved by: Michał Sawicz
Approved revision: 249
Merged at revision: 222
Proposed branch: lp:~gerboland/unity-mir/fix-upstart-closed-apps2
Merge into: lp:unity-mir/devel-mir-next
Diff against target: 1881 lines (+1429/-138) (has conflicts)
9 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 (+90/-84)
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)
tests/CMakeLists.txt (+13/-0)
tests/application_manager_test.cpp (+1296/-16)
tests/mock_session.h (+3/-3)
Text conflict in src/modules/Unity/Application/application_manager.cpp
To merge this branch: bzr merge lp:~gerboland/unity-mir/fix-upstart-closed-apps2
Reviewer Review Type Date Requested Status
PS Jenkins bot (community) continuous-integration Approve
Michał Sawicz Approve
Review via email: mp+218721@code.launchpad.net

Commit message

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.

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

Things to test:
1. start/stop apps (definitely include webbrowser and web apps)
2. Launch app with --desktop_file_hint and close it (a) with Ctrl+C, and (b) by stopping it in the UI
3. start/stop apps with upstart-app-launch/stop
4. start app from UI, then switch to another app/go to dash, then kill the process with "pkill -9 something" - app should remain in the running apps list. tapping it will eventually resume the app

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

Steps:
1. launch a webapp
2. go to dash
3. pkill -9 webapp
4. go to dash
5. pkill -9 webapp
6. app goes away

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

I'm not able to reproduce your bug. I've done the following:
1. launch gmail
2. return to dash
3. run kill -9 `pidof webapp-container`
Gmail still visible in dash
4. tap gmail running app preview
5. gmail eventually re-appears
6. GOTO 2.

Are you killing a child process of webapp-container maybe?

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

Update: gmail was a bad choice, as it is not a typical web app. Trying with other apps, can repro. Will fix

238. By Gerry Boland

Fix webapps by disabling user-owned process shutdown detection

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

Weird behaviours:

$ webbrowser-app --desktop_file_hint=/usr/share/applications/webbrowser-app.desktop
→ go to dash
→ Ctrl+C
  → app remains in stack :|
→ tap on the app
→ gets restarted (outside of the console of course), with "webbrowser-app" as url?

$ webbrowser-app --desktop_file_hint=/usr/share/applications/
→ go to dash
→ Ctrl+C
  → app remains in stack :|
→ tap on the app
→ settings flashes, probably due to "webbrowser-app" as url?

→ launch settings
→ go into Accounts
→ press back and swipe to dash
→ empty "online accounts" in dash, disappears when tapped (probably can't be restarted)

I think if we special-case the if(false) for browser... only? We'd be good.

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

Ugh, the middle example is missing "ubuntu-system-settings.desktop" at the end of the command

239. By Gerry Boland

Merge trunk

240. By Gerry Boland

Cmake tweak, mention mock header files as app dependency, so files appear in QtCreator

241. By Gerry Boland

Update tests against trunk

242. By Gerry Boland

Add "canBeResumed" state into Application, use to specify if AppMan must manage non-upstart started applications differently

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

Ok, think I've fixed those issues, please re-test

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

Problem:
* $ upstart-app-launch webbrowser-app
* go to dash
* $ upstart app-stop webbrowser-app
* app remains in dash, where as you say it should go away

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

Hit meh

243. By Gerry Boland

Logic refactoring to handle graceful app shutdown via upstart-app-stop

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

Two last issues I saw, probably not to fix in this branch anyway:
* when resuming apps, old app is focused until the new one launches
1. launch browser and another app
2. go to dash
3. $ pkill -9 webbrowser
4. tap on browser
5. the other app is focused before browser comes back in again

* when upstart-launching a focused app, animation is played
1. launch browser
2. $ upstart-app-launch webbrowser-app
3. browser animates on screen

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

> Two last issues I saw, probably not to fix in this branch anyway:
> * when resuming apps, old app is focused until the new one launches
> 1. launch browser and another app
> 2. go to dash
> 3. $ pkill -9 webbrowser
> 4. tap on browser
> 5. the other app is focused before browser comes back in again

Unable to fix with current architecture. Not a regression either I don't think.

> * when upstart-launching a focused app, animation is played
> 1. launch browser
> 2. $ upstart-app-launch webbrowser-app
> 3. browser animates on screen

unity8 bug I suspect, you see it also when flipping from gallery to camera using the in-app buttons. Also not regression

Revision history for this message
Michał Sawicz (saviq) wrote :
Download full text (3.4 KiB)

We should probably start thinking about generating coverage reports.

=====

 601 + // check application data
 602 + EXPECT_EQ(Application::Starting, theApp->state());
 603 + EXPECT_EQ(appId, theApp->appId());
 604 + EXPECT_EQ(name, theApp->name());
 605 + EXPECT_EQ(true, theApp->canBeResumed());
 606 +
 607 + // check signals were emitted
 608 + EXPECT_EQ(countSpy.count(), 2); //FIXME(greyback)
 609 + EXPECT_EQ(applicationManager.count(), 1);
 610 + EXPECT_EQ(addedSpy.count(), 1);
 611 + EXPECT_EQ(addedSpy.takeFirst().at(0).toString(), appId);

Is there a reason why in the first set the expected value is the first arg while in the second set it's the second arg? I know this doesn't matter much, but is confusing TBH... You did this in most of the tests.

=====

 689 + bool authed = true;
 690 + applicationManager.authorizeSession(procId, authed);
 691 + EXPECT_EQ(authed, true);

Does Mir authorize sessions by default? I.e. is the bool true when passed to authorizeSession? Shouldn't we make it false to start with? Question valid for all the other places where you test this. Ah you actually do use = false in other places. Feels like we should consistently mimic what Mir does here. Or, alternatively, consistently require the call to change the value.

=====

All the FIXMEs for countSpy are about countChanged() emitted twice?

=====

 670 + QByteArray cmdLine("/usr/bin/testApp --desktop_file_hint=");
 671 + cmdLine = cmdLine.append(appId);

I'd probably flatten this, since you don't actually care about the value.

=====

 714 + const QString appId("testAppId");
 715 + const QString name("Test App");

Unused.

=====

 749 + const QString appId("testAppId");
 750 + const QString badDesktopFile = QString("%1.desktop").arg(appId);
 751 + quint64 procId = 5551;
 752 + QByteArray cmdLine("/usr/bin/testApp --desktop_file_hint=");
 753 + cmdLine = cmdLine.append(badDesktopFile);

Can be simplified, too - the value of badDesktopFile isn't actually important.

=====

 761 + ON_CALL(*mockDesktopFileReader, loaded()).WillByDefault(Return(false));
 762 + ON_CALL(*mockDesktopFileReader, appId()).WillByDefault(Return(appId));

Will this ever be called when loaded() returns false?

=====

You alternate between ON_CALL and EXPECT_CALL, WillByDefault and WillOnce, is there a reason why for the desktop reader you ignore whether / how many times it was called, while for procInfo you want them to be test fails when the unexpected happens?

=====

I wonder if procId randomness will mislead that the value has significance?

=====

 867 + * Test that an application in the Starting state is reacts correctly to the Mir sessionStarted

 911 + * Test that an application in the Starting state is reacts correctly to the Mir surfaceCreated

s/is reacts/reacts/

=====

 893 + std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("OO", procId);

 937 + std::shared_ptr<mir::scene::Session> session = std::make_shared<MockSession>("OO", procId);

Does "OO" have significance?

=====

 896 + bool authed = true;
 897 + applicationManager.authorizeSession(procId, authed);

Do we n...

Read more...

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

1215 + * Test that if a background application is stopped by upstart, AppMan removes it from the app list
1216 + * as event result of direct user interaction

"as the result"?

=====

1317 + * Test that if a background application is reported to have stopped on startup by upstart
1318 + * This may be an upstart bug, or AppMan mis-understanging upstart's intentions. But need to check
1319 + * we're doing the right thing in either case. FIXME!

"by upstart", then?

review: Needs Information
244. By Gerry Boland

Consistent arguments order for EXPET_EQ

245. By Gerry Boland

Surface name "OO" unnecessary, might confuse so removed

246. By Gerry Boland

"is reacts" -> "reacts"

247. By Gerry Boland

"as event result" -> "as the event is a result"

248. By Gerry Boland

Expand the explanation of the upstart "stopping while starting" workaround

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
Gerry Boland (gerboland) wrote :
Download full text (4.3 KiB)

> We should probably start thinking about generating coverage reports.
True, l8r

> =====
> 601 + // check application data
> Is there a reason why in the first set the expected value is the first arg
> while in the second set it's the second arg? I know this doesn't matter much,
> but is confusing TBH... You did this in most of the tests.

Fixed

> =====

> 689 + bool authed = true;
> 690 + applicationManager.authorizeSession(procId, authed);
> 691 + EXPECT_EQ(authed, true);
>
> Does Mir authorize sessions by default? I.e. is the bool true when passed to
> authorizeSession? Shouldn't we make it false to start with? Question valid for
> all the other places where you test this. Ah you actually do use = false in
> other places. Feels like we should consistently mimic what Mir does here. Or,
> alternatively, consistently require the call to change the value.

It's a confusing API: "authed" is passed by reference, and set by authorizeSession. So it doesn't really matter what it is initialized to, what matters the decision authorizeSession makes.

We are emulating what Mir does here - for a client connecting to Mir, Mir calls this method, and if the returned value of authed is false, it rejects the client connection.

authorizeSession does set some internal state, so the step cannot be ignored.

> =====
> All the FIXMEs for countSpy are about countChanged() emitted twice?
Yep, I don't know why it's happening, would like to fix it.

> =====
>
> 670 + QByteArray cmdLine("/usr/bin/testApp --desktop_file_hint=");
> 671 + cmdLine = cmdLine.append(appId);
>
> I'd probably flatten this, since you don't actually care about the value.
It does, authorizeSession reads this line and parses the appId from the desktop, and then calls createInstance(appId,_).
>
> =====
>
> 714 + const QString appId("testAppId");
> 715 + const QString name("Test App");
> Unused.

They are used. The compiler would complain if they weren't.

> =====
>
> 749 + const QString appId("testAppId");
> 750 + const QString badDesktopFile = QString("%1.desktop").arg(appId);
> 751 + quint64 procId = 5551;
> 752 + QByteArray cmdLine("/usr/bin/testApp --desktop_file_hint=");
> 753 + cmdLine = cmdLine.append(badDesktopFile);
>
> Can be simplified, too - the value of badDesktopFile isn't actually important.
I'd prefer to keep this, as it's more realistically testing authorizeSession's CLI parsing.

> =====
> 761 + ON_CALL(*mockDesktopFileReader,
> loaded()).WillByDefault(Return(false));
> 762 + ON_CALL(*mockDesktopFileReader,
> appId()).WillByDefault(Return(appId));
>
> Will this ever be called when loaded() returns false?
Good catch, removed

> =====
> You alternate between ON_CALL and EXPECT_CALL, WillByDefault and WillOnce, is
> there a reason why for the desktop reader you ignore whether / how many times
> it was called, while for procInfo you want them to be test fails when the
> unexpected happens?

Because I don't really care how often the desktop file methods are called, only that they supply the correct info to AppManager do its thing. But things like procInfo do tie in closely with how AppManager works, so I wa...

Read more...

249. By Gerry Boland

Remove unneeded mock DesktopFileReader::appId() definition

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

 * Did you perform an exploratory manual test run of the code change and any related functionality?
Yes, tests pass and everything behaves as we want.

 * Did CI run pass? If not, please explain why.
Yes.

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

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

Subscribers

People subscribed via source and target branches