2
* Copyright (C) 2015-2016 Canonical, Ltd.
4
* This program is free software: you can redistribute it and/or modify it under
5
* the terms of the GNU Lesser General Public License version 3, as published by
6
* the Free Software Foundation.
8
* This program is distributed in the hope that it will be useful, but WITHOUT
9
* ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10
* SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11
* Lesser General Public License for more details.
13
* You should have received a copy of the GNU Lesser General Public License
14
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17
#include <gmock/gmock.h>
18
#include <gtest/gtest.h>
20
#include "qtmir_test.h"
22
#include <fake_mirsurface.h>
23
#include <fake_application_info.h>
24
#include <fake_session.h>
25
#include <mock_application_info.h>
26
#include <mock_session.h>
28
#include <QScopedPointer>
31
using namespace qtmir;
33
class ApplicationTests : public ::testing::QtMirTest
37
: fakeTimeSource(new FakeTimeSource)
41
inline void suspend(QScopedPointer<Application> &application)
43
suspend(application.data());
46
inline void suspend(Application *application)
48
application->setRequestedState(Application::RequestedSuspended);
49
auto session = dynamic_cast<Session*>(application->session());
51
ASSERT_EQ(Application::InternalState::SuspendingWaitSession, application->internalState());
52
ASSERT_EQ(Session::Suspending, session->state());
54
QSignalSpy suspendProcessRequestedSpy(application, &Application::suspendProcessRequested);
56
passTimeUntilTimerTimesOut(session->suspendTimer());
58
ASSERT_EQ(Session::Suspended, session->state());
59
ASSERT_EQ(Application::InternalState::SuspendingWaitProcess, application->internalState());
60
ASSERT_EQ(1, suspendProcessRequestedSpy.count());
62
application->setProcessState(Application::ProcessSuspended);
64
ASSERT_EQ(Application::InternalState::Suspended, application->internalState());
67
inline Application *createApplicationWithFakes()
69
Application *application = new Application(
70
QSharedPointer<MockSharedWakelock>(&sharedWakelock, [](MockSharedWakelock *){}),
71
QSharedPointer<FakeApplicationInfo>::create());
73
application->setStopTimer(new FakeTimer(fakeTimeSource));
78
inline Session *createSessionWithFakes()
80
using namespace ::testing;
81
const QString appId("test-app");
82
const pid_t procId = 1234;
84
auto mirSession = std::make_shared<NiceMock<mir::scene::MockSession>>(appId.toStdString(), procId);
85
Session* session = new Session(mirSession, promptSessionManager);
87
FakeTimer *fakeTimer(new FakeTimer(fakeTimeSource));
88
session->setSuspendTimer(fakeTimer);
93
inline void passTimeUntilTimerTimesOut(AbstractTimer *timer)
95
FakeTimer *fakeTimer = dynamic_cast<FakeTimer*>(timer);
96
if (fakeTimer->isRunning()) {
97
fakeTimeSource->m_msecsSinceReference = fakeTimer->nextTimeoutTime() + 1;
102
QSharedPointer<FakeTimeSource> fakeTimeSource;
105
TEST_F(ApplicationTests, acquiresWakelockWhenRunningAndReleasesWhenSuspended)
107
using namespace ::testing;
109
QScopedPointer<Application> application(createApplicationWithFakes());
111
application->setProcessState(Application::ProcessRunning);
113
FakeSession *session = new FakeSession;
115
application->setSession(session);
117
ASSERT_EQ(Application::InternalState::Starting, application->internalState());
119
session->setState(SessionInterface::Running);
121
EXPECT_TRUE(sharedWakelock.enabled());
123
ASSERT_EQ(Application::InternalState::Running, application->internalState());
125
application->setRequestedState(Application::RequestedSuspended);
127
ASSERT_EQ(SessionInterface::Suspending, session->state());
128
ASSERT_EQ(Application::InternalState::SuspendingWaitSession, application->internalState());
130
session->setState(SessionInterface::Suspended);
132
ASSERT_EQ(Application::InternalState::SuspendingWaitProcess, application->internalState());
134
application->setProcessState(Application::ProcessSuspended);
136
ASSERT_EQ(Application::InternalState::Suspended, application->internalState());
138
EXPECT_FALSE(sharedWakelock.enabled());
141
TEST_F(ApplicationTests, checkResumeAcquiresWakeLock)
143
using namespace ::testing;
145
QScopedPointer<Application> application(createApplicationWithFakes());
146
FakeSession *session = new FakeSession;
148
// Get it running and then suspend it
149
application->setProcessState(Application::ProcessRunning);
150
application->setSession(session);
151
session->setState(SessionInterface::Running);
152
application->setRequestedState(Application::RequestedSuspended);
153
session->setState(SessionInterface::Suspended);
154
application->setProcessState(Application::ProcessSuspended);
155
ASSERT_EQ(Application::InternalState::Suspended, application->internalState());
157
EXPECT_FALSE(sharedWakelock.enabled());
159
application->setRequestedState(Application::RequestedRunning);
161
ASSERT_EQ(Application::InternalState::Running, application->internalState());
163
EXPECT_TRUE(sharedWakelock.enabled());
166
TEST_F(ApplicationTests, checkRespawnAcquiresWakeLock)
168
using namespace ::testing;
170
QScopedPointer<Application> application(createApplicationWithFakes());
171
FakeSession *session = new FakeSession;
173
// Get it running, suspend it, and finally stop it
174
application->setProcessState(Application::ProcessRunning);
175
application->setSession(session);
176
session->setState(SessionInterface::Running);
177
application->setRequestedState(Application::RequestedSuspended);
178
session->setState(SessionInterface::Suspended);
179
application->setProcessState(Application::ProcessSuspended);
180
ASSERT_EQ(Application::InternalState::Suspended, application->internalState());
181
session->setState(SessionInterface::Stopped);
182
application->setProcessState(Application::ProcessFailed);
183
ASSERT_EQ(Application::InternalState::StoppedResumable, application->internalState());
185
EXPECT_FALSE(sharedWakelock.enabled());
187
QSignalSpy spyStartProcess(application.data(), SIGNAL(startProcessRequested()));
188
application->setRequestedState(Application::RequestedRunning);
189
ASSERT_EQ(1, spyStartProcess.count());
190
application->setProcessState(Application::ProcessRunning);
192
ASSERT_EQ(Application::InternalState::Starting, application->internalState());
194
EXPECT_TRUE(sharedWakelock.enabled());
197
TEST_F(ApplicationTests, checkDashDoesNotImpactWakeLock)
199
using namespace ::testing;
201
EXPECT_CALL(sharedWakelock, acquire(_)).Times(0);
202
EXPECT_CALL(sharedWakelock, release(_)).Times(0);
204
auto applicationInfo = QSharedPointer<FakeApplicationInfo>::create();
205
applicationInfo->m_appId = QString("unity8-dash");
207
QScopedPointer<Application> application(new Application(
208
QSharedPointer<MockSharedWakelock>(&sharedWakelock, [](MockSharedWakelock *){}),
211
application->setProcessState(Application::ProcessRunning);
213
FakeSession *session = new FakeSession;
215
application->setSession(session);
217
ASSERT_EQ(Application::InternalState::Starting, application->internalState());
219
session->setState(SessionInterface::Running);
221
ASSERT_EQ(Application::InternalState::Running, application->internalState());
223
application->setRequestedState(Application::RequestedSuspended);
225
ASSERT_EQ(SessionInterface::Suspending, session->state());
226
ASSERT_EQ(Application::InternalState::SuspendingWaitSession, application->internalState());
228
session->setState(SessionInterface::Suspended);
230
ASSERT_EQ(Application::InternalState::SuspendingWaitProcess, application->internalState());
232
application->setProcessState(Application::ProcessSuspended);
234
ASSERT_EQ(Application::InternalState::Suspended, application->internalState());
236
application->setRequestedState(Application::RequestedRunning);
238
ASSERT_EQ(Application::InternalState::Running, application->internalState());
241
TEST_F(ApplicationTests, emitsStoppedWhenRunningAppStops)
243
using namespace ::testing;
245
QScopedPointer<Application> application(createApplicationWithFakes());
247
application->setProcessState(Application::ProcessRunning);
249
FakeSession *session = new FakeSession;
250
application->setSession(session);
252
QSignalSpy spyAppStopped(application.data(), SIGNAL(stopped()));
254
session->setState(SessionInterface::Running);
256
ASSERT_EQ(Application::InternalState::Running, application->internalState());
259
// Simulate a running application closing itself (ie, ending its own process)
261
session->setState(SessionInterface::Stopped);
263
ASSERT_EQ(Application::InternalState::Stopped, application->internalState());
265
application->setProcessState(Application::ProcessStopped);
267
ASSERT_EQ(1, spyAppStopped.count());
271
* Regression test for https://bugs.launchpad.net/qtmir/+bug/1485608
272
* In that case, the camera-app closes itself right after unity8 unfocus it (and thus requests it to be suspended).
274
TEST_F(ApplicationTests, emitsStoppedWhenAppStopsWhileSuspending)
276
using namespace ::testing;
278
QScopedPointer<Application> application(createApplicationWithFakes());
280
application->setProcessState(Application::ProcessRunning);
282
Session *session = createSessionWithFakes();
283
application->setSession(session);
285
QSignalSpy spyAppStopped(application.data(), SIGNAL(stopped()));
287
FakeMirSurface *surface = new FakeMirSurface;
288
session->registerSurface(surface);
289
surface->drawFirstFrame();
291
ASSERT_EQ(Application::InternalState::Running, application->internalState());
293
application->setRequestedState(Application::RequestedSuspended);
295
ASSERT_EQ(Application::InternalState::SuspendingWaitSession, application->internalState());
297
// Now the application closes itself (ie, ending its own process)
298
// Session always responds before the process state.
299
session->setLive(false);
300
application->setProcessState(Application::ProcessStopped);
302
ASSERT_EQ(Application::InternalState::Stopped, application->internalState());
303
ASSERT_EQ(1, spyAppStopped.count());
309
TEST_F(ApplicationTests, doesNotEmitStoppedWhenKilledWhileSuspended)
311
using namespace ::testing;
313
QScopedPointer<Application> application(createApplicationWithFakes());
315
application->setProcessState(Application::ProcessRunning);
317
FakeSession *session = new FakeSession;
318
application->setSession(session);
320
QSignalSpy spyAppStopped(application.data(), SIGNAL(stopped()));
322
session->setState(SessionInterface::Running);
324
ASSERT_EQ(Application::InternalState::Running, application->internalState());
326
application->setRequestedState(Application::RequestedSuspended);
328
ASSERT_EQ(SessionInterface::Suspending, session->state());
329
ASSERT_EQ(Application::InternalState::SuspendingWaitSession, application->internalState());
331
session->setState(SessionInterface::Suspended);
333
ASSERT_EQ(Application::InternalState::SuspendingWaitProcess, application->internalState());
335
application->setProcessState(Application::ProcessSuspended);
338
// Now simulate the process getting killed. Mir session always ends before
339
// we get notified about the process state
341
session->setState(SessionInterface::Stopped);
343
application->setProcessState(Application::ProcessFailed);
345
ASSERT_EQ(Application::InternalState::StoppedResumable, application->internalState());
347
ASSERT_EQ(0, spyAppStopped.count());
351
TEST_F(ApplicationTests, passesIsTouchAppThrough)
353
using namespace ::testing;
355
auto mockApplicationInfo = QSharedPointer<MockApplicationInfo>(new NiceMock<MockApplicationInfo>("foo-app"));
356
QScopedPointer<Application> application(new Application(
357
QSharedPointer<MockSharedWakelock>(&sharedWakelock, [](MockSharedWakelock *){}),
358
mockApplicationInfo, QStringList(), nullptr));
360
ON_CALL(*mockApplicationInfo, isTouchApp()).WillByDefault(Return(true));
361
ASSERT_TRUE(application->isTouchApp());
363
ON_CALL(*mockApplicationInfo, isTouchApp()).WillByDefault(Return(false));
364
ASSERT_FALSE(application->isTouchApp());
368
* A suspended application resumes itself while any of its surfaces is being closed.
370
TEST_F(ApplicationTests, suspendedApplicationResumesWhileSurfaceBeingClosed)
372
using namespace ::testing;
374
QScopedPointer<Application> application(createApplicationWithFakes());
376
application->setProcessState(Application::ProcessRunning);
378
Session *session = createSessionWithFakes();
380
application->setSession(session);
382
FakeMirSurface *surface = new FakeMirSurface;
383
session->registerSurface(surface);
384
surface->drawFirstFrame();
386
ASSERT_EQ(Application::InternalState::Running, application->internalState());
388
// Add a second surface to ensure the application doesn't kill itself after it loses
390
FakeMirSurface *secondSurface = new FakeMirSurface;
391
session->registerSurface(secondSurface);
392
secondSurface->drawFirstFrame();
394
suspend(application.data());
396
QSignalSpy resumeProcessRequestedSpy(application.data(), &Application::resumeProcessRequested);
400
ASSERT_EQ(Application::InternalState::Running, application->internalState());
401
ASSERT_EQ(1, resumeProcessRequestedSpy.count());
402
ASSERT_EQ(Session::Running, session->state());
404
// And goes back to sleep after the closing surface is gone
406
QSignalSpy suspendProcessRequestedSpy(application.data(), &Application::suspendProcessRequested);
410
ASSERT_EQ(Application::InternalState::SuspendingWaitSession, application->internalState());
411
ASSERT_EQ(Session::Suspending, session->state());
413
passTimeUntilTimerTimesOut(session->suspendTimer());
415
ASSERT_EQ(Session::Suspended, session->state());
416
ASSERT_EQ(Application::InternalState::SuspendingWaitProcess, application->internalState());
417
ASSERT_EQ(1, suspendProcessRequestedSpy.count());
419
application->setProcessState(Application::ProcessSuspended);
421
ASSERT_EQ(Application::InternalState::Suspended, application->internalState());
424
delete secondSurface;
427
TEST_F(ApplicationTests, quitsAfterLastSurfaceIsClosed)
429
using namespace ::testing;
431
QScopedPointer<Application> application(createApplicationWithFakes());
433
application->setProcessState(Application::ProcessRunning);
435
Session *session = createSessionWithFakes();
437
application->setSession(session);
439
FakeMirSurface *surface = new FakeMirSurface;
440
session->registerSurface(surface);
441
surface->drawFirstFrame();
443
ASSERT_EQ(Application::InternalState::Running, application->internalState());
444
ASSERT_EQ(Session::Running, session->state());
446
FakeMirSurface *secondSurface = new FakeMirSurface;
447
session->registerSurface(secondSurface);
448
secondSurface->drawFirstFrame();
452
passTimeUntilTimerTimesOut(application->stopTimer());
454
// All fine as there's still one surface left
455
ASSERT_EQ(Application::InternalState::Running, application->internalState());
456
ASSERT_EQ(Session::Running, session->state());
458
delete secondSurface;
460
QSignalSpy stopProcessRequestedSpy(application.data(), &Application::stopProcessRequested);
462
passTimeUntilTimerTimesOut(application->stopTimer());
464
// Ok, now the application should go way
465
ASSERT_EQ(1, stopProcessRequestedSpy.count());
469
* Test that an application that is suspended after its session is stopped is closed
471
* Regression test for bug LP#1536133
472
* (https://bugs.launchpad.net/canonical-devices-system-image/+bug/1536133)
474
TEST_F(ApplicationTests, sessionStopsWhileBeingSuspended)
476
using namespace ::testing;
480
QCoreApplication qtApp(argc, argv); // app for deleteLater event
482
QScopedPointer<Application> application(createApplicationWithFakes());
484
application->setProcessState(Application::ProcessRunning);
486
QPointer<Session> session(createSessionWithFakes());
488
application->setSession(session);
490
FakeMirSurface *surface = new FakeMirSurface;
491
session->registerSurface(surface);
492
surface->drawFirstFrame();
493
ASSERT_EQ(Application::InternalState::Running, application->internalState());
495
application->setRequestedState(Application::RequestedSuspended);
497
ASSERT_EQ(Application::InternalState::SuspendingWaitSession, application->internalState());
499
passTimeUntilTimerTimesOut(session->suspendTimer());
501
ASSERT_EQ(Application::InternalState::SuspendingWaitProcess, application->internalState());
502
QSignalSpy stopProcessRequestedSpy(application.data(), &Application::stopProcessRequested);
504
// surface dies, followed by session
505
surface->setLive(false);
508
session->setLive(false);
510
// Session should have called deleteLater() on itself, as it's zombie and doesn't hold any surface
511
// But DeferredDelete is special: likes to be called out specifically or it won't come out
512
qtApp.sendPostedEvents(session.data(), QEvent::DeferredDelete);
513
qtApp.sendPostedEvents();
515
EXPECT_EQ(true, session.isNull());
516
EXPECT_EQ(1, stopProcessRequestedSpy.count());
517
EXPECT_EQ(Application::InternalState::Stopped, application->internalState());
521
* Test that an application that fails while suspended will stop on close request
523
TEST_F(ApplicationTests, closeWhenSuspendedProcessFailed)
525
using namespace ::testing;
527
QScopedPointer<Application> application(createApplicationWithFakes());
529
application->setProcessState(Application::ProcessRunning);
531
QPointer<Session> session(createSessionWithFakes());
532
application->setSession(session);
534
FakeMirSurface *surface = new FakeMirSurface;
535
session->registerSurface(surface);
536
surface->drawFirstFrame();
537
ASSERT_EQ(Application::InternalState::Running, application->internalState());
539
suspend(application.data());
542
surface->setLive(false);
543
session->setLive(false);
544
application->setProcessState(Application::ProcessFailed);
546
ASSERT_EQ(Application::InternalState::StoppedResumable, application->internalState());
548
application->close();
550
ASSERT_EQ(Application::InternalState::Stopped, application->internalState());
557
If an application gets stopped (ie, loses its surfaces, session and process) while it's in Suspended
558
state, it goes into StoppedResumable state.
560
TEST_F(ApplicationTests, stoppedWhileSuspendedTurnsIntoStoppeResumable)
562
using namespace ::testing;
564
QScopedPointer<Application> application(createApplicationWithFakes());
566
application->setProcessState(Application::ProcessRunning);
568
Session *session = createSessionWithFakes();
570
application->setSession(session);
572
FakeMirSurface *surface = new FakeMirSurface;
573
session->registerSurface(surface);
574
surface->drawFirstFrame();
576
ASSERT_EQ(Application::InternalState::Running, application->internalState());
578
suspend(application);
580
// Process gets killed. Mir objects respond first
581
surface->setLive(false);
582
session->setLive(false);
583
delete surface; surface = nullptr;
585
EXPECT_EQ(Application::InternalState::StoppedResumable, application->internalState());
587
// And later comes upstart telling us about it
588
application->setProcessState(Application::ProcessFailed);
590
EXPECT_EQ(Application::InternalState::StoppedResumable, application->internalState());
594
Regression test for bug "App respawns if manually closed while it's launching"
595
https://bugs.launchpad.net/ubuntu/+source/qtmir/+bug/1575577
597
TEST_F(ApplicationTests, dontRespawnIfClosedWhileStillStartingUp)
599
using namespace ::testing;
601
QScopedPointer<Application> application(createApplicationWithFakes());
603
application->setProcessState(Application::ProcessRunning);
605
FakeSession *session = new FakeSession;
607
application->setSession(session);
609
QSignalSpy spyStartProcess(application.data(), SIGNAL(startProcessRequested()));
611
// Close the application before it even gets a surface (it's still in "starting" state)
612
application->close();
614
session->setState(SessionInterface::Stopped);
616
EXPECT_EQ(Application::InternalState::Stopped, application->internalState());
617
EXPECT_EQ(0, spyStartProcess.count());