Merge lp:~gerboland/unity-mir/fix-upstart-closed-apps2 into lp:unity-mir/devel-mir-next
- fix-upstart-closed-apps2
- Merge into devel-mir-next
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 |
Related bugs: |
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.
Description of the change
PS Jenkins bot (ps-jenkins) wrote : | # |
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-
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
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
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?
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
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:238
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Michał Sawicz (saviq) wrote : | # |
Weird behaviours:
$ 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_
→ 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.
Michał Sawicz (saviq) wrote : | # |
Ugh, the middle example is missing "ubuntu-
- 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
Gerry Boland (gerboland) wrote : | # |
Ok, think I've fixed those issues, please re-test
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:242
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
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
Gerry Boland (gerboland) wrote : | # |
Hit meh
- 243. By Gerry Boland
-
Logic refactoring to handle graceful app shutdown via upstart-app-stop
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:243
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
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
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
Michał Sawicz (saviq) wrote : | # |
We should probably start thinking about generating coverage reports.
=====
601 + // check application data
602 + EXPECT_
603 + EXPECT_EQ(appId, theApp->appId());
604 + EXPECT_EQ(name, theApp->name());
605 + EXPECT_EQ(true, theApp-
606 +
607 + // check signals were emitted
608 + EXPECT_
609 + EXPECT_
610 + EXPECT_
611 + EXPECT_
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 + applicationMana
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(
671 + cmdLine = cmdLine.
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(
751 + quint64 procId = 5551;
752 + QByteArray cmdLine(
753 + cmdLine = cmdLine.
Can be simplified, too - the value of badDesktopFile isn't actually important.
=====
761 + ON_CALL(
762 + ON_CALL(
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_
937 + std::shared_
Does "OO" have significance?
=====
896 + bool authed = true;
897 + applicationMana
Do we n...
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?
- 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
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:244
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Gerry Boland (gerboland) wrote : | # |
> 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 + applicationMana
> 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(
> 671 + cmdLine = cmdLine.
>
> 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(
>
> =====
>
> 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(
> 751 + quint64 procId = 5551;
> 752 + QByteArray cmdLine(
> 753 + cmdLine = cmdLine.
>
> 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(
> loaded(
> 762 + ON_CALL(
> 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...
- 249. By Gerry Boland
-
Remove unneeded mock DesktopFileRead
er::appId( ) definition
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.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:249
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Preview Diff
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 |
PASSED: Continuous integration, rev:237 jenkins. qa.ubuntu. com/job/ unity-mir- devel-ci/ 5/ jenkins. qa.ubuntu. com/job/ unity-mir- devel-utopic- amd64-ci/ 5 jenkins. qa.ubuntu. com/job/ unity-mir- devel-utopic- armhf-ci/ 5 jenkins. qa.ubuntu. com/job/ unity-mir- devel-utopic- armhf-ci/ 5/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ unity-mir- devel-utopic- i386-ci/ 5
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/unity- mir-devel- ci/5/rebuild
http://