| OLD | NEW |
| (Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "base/bind.h" |
| 6 #include "base/command_line.h" |
| 7 #include "base/macros.h" |
| 8 #include "base/run_loop.h" |
| 9 #include "mojo/shell/public/cpp/shell_test.h" |
| 10 #include "mojo/shell/public/interfaces/application_manager.mojom.h" |
| 11 #include "mojo/shell/tests/lifecycle/lifecycle_unittest.mojom.h" |
| 12 |
| 13 namespace mojo { |
| 14 namespace shell { |
| 15 namespace { |
| 16 |
| 17 const char kTestAppName[] = "mojo:lifecycle_unittest_app"; |
| 18 const char kTestPackageName[] = "mojo:lifecycle_unittest_package"; |
| 19 const char kTestPackageAppNameA[] = "mojo:lifecycle_unittest_package_app_a"; |
| 20 const char kTestPackageAppNameB[] = "mojo:lifecycle_unittest_package_app_b"; |
| 21 const char kTestName[] = "mojo:lifecycle_unittest"; |
| 22 |
| 23 void QuitLoop(base::RunLoop* loop) { |
| 24 loop->Quit(); |
| 25 } |
| 26 |
| 27 void DecrementCountAndQuitWhenZero(base::RunLoop* loop, size_t* count) { |
| 28 if (!--(*count)) |
| 29 loop->Quit(); |
| 30 } |
| 31 |
| 32 struct Instance { |
| 33 Instance() : id(mojom::Connector::kInvalidApplicationID), pid(0) {} |
| 34 Instance(const std::string& name, const std::string& qualifier, uint32_t id, |
| 35 uint32_t pid) |
| 36 : name(name), qualifier(qualifier), id(id), pid(pid) {} |
| 37 |
| 38 std::string name; |
| 39 std::string qualifier; |
| 40 uint32_t id; |
| 41 uint32_t pid; |
| 42 }; |
| 43 |
| 44 class InstanceState : public mojom::InstanceListener { |
| 45 public: |
| 46 InstanceState(mojom::InstanceListenerRequest request, base::RunLoop* loop) |
| 47 : binding_(this, std::move(request)), loop_(loop) {} |
| 48 ~InstanceState() override {} |
| 49 |
| 50 bool HasInstanceForName(const std::string& name) const { |
| 51 return instances_.find(name) != instances_.end(); |
| 52 } |
| 53 size_t GetNewInstanceCount() const { |
| 54 return instances_.size() - initial_instances_.size(); |
| 55 } |
| 56 void WaitForInstanceDestruction(base::RunLoop* loop) { |
| 57 DCHECK(!destruction_loop_); |
| 58 destruction_loop_ = loop; |
| 59 // First of all check to see if we should be spinning this loop at all - |
| 60 // the app(s) we're waiting on quitting may already have quit. |
| 61 TryToQuitDestructionLoop(); |
| 62 } |
| 63 |
| 64 private: |
| 65 // mojom::InstanceListener: |
| 66 void SetExistingInstances(Array<mojom::InstanceInfoPtr> instances) override { |
| 67 for (const auto& instance : instances) { |
| 68 Instance i(instance->name, instance->qualifier, instance->id, |
| 69 instance->pid); |
| 70 initial_instances_[i.name] = i; |
| 71 instances_[i.name] = i; |
| 72 } |
| 73 loop_->Quit(); |
| 74 } |
| 75 void InstanceCreated(mojom::InstanceInfoPtr instance) override { |
| 76 instances_[instance->name] = |
| 77 Instance(instance->name, instance->qualifier, instance->id, |
| 78 instance->pid); |
| 79 } |
| 80 void InstanceDestroyed(uint32_t id) override { |
| 81 for (auto it = instances_.begin(); it != instances_.end(); ++it) { |
| 82 if (it->second.id == id) { |
| 83 instances_.erase(it); |
| 84 break; |
| 85 } |
| 86 } |
| 87 TryToQuitDestructionLoop(); |
| 88 } |
| 89 void InstancePIDAvailable(uint32_t id, uint32_t pid) override { |
| 90 for (auto& instance : instances_) { |
| 91 if (instance.second.id == id) { |
| 92 instance.second.pid = pid; |
| 93 break; |
| 94 } |
| 95 } |
| 96 } |
| 97 |
| 98 void TryToQuitDestructionLoop() { |
| 99 if (!GetNewInstanceCount() && destruction_loop_) { |
| 100 destruction_loop_->Quit(); |
| 101 destruction_loop_ = nullptr; |
| 102 } |
| 103 } |
| 104 |
| 105 // All currently running instances. |
| 106 std::map<std::string, Instance> instances_; |
| 107 // The initial set of instances. |
| 108 std::map<std::string, Instance> initial_instances_; |
| 109 |
| 110 Binding<mojom::InstanceListener> binding_; |
| 111 base::RunLoop* loop_; |
| 112 |
| 113 // Set when the client wants to wait for this object to track the destruction |
| 114 // of an instance before proceeding. |
| 115 base::RunLoop* destruction_loop_ = nullptr; |
| 116 |
| 117 DISALLOW_COPY_AND_ASSIGN(InstanceState); |
| 118 }; |
| 119 |
| 120 } |
| 121 |
| 122 class LifecycleTest : public mojo::test::ShellTest { |
| 123 public: |
| 124 LifecycleTest() : ShellTest(kTestName) {} |
| 125 ~LifecycleTest() override {} |
| 126 |
| 127 protected: |
| 128 // mojo::test::ShellTest: |
| 129 void SetUp() override { |
| 130 mojo::test::ShellTest::SetUp(); |
| 131 InitPackage(); |
| 132 instances_ = TrackInstances(); |
| 133 } |
| 134 void TearDown() override { |
| 135 instances_.reset(); |
| 136 mojo::test::ShellTest::TearDown(); |
| 137 } |
| 138 |
| 139 bool CanRunCrashTest() { |
| 140 return !base::CommandLine::ForCurrentProcess()->HasSwitch("single-process"); |
| 141 } |
| 142 |
| 143 void InitPackage() { |
| 144 test::mojom::LifecycleControlPtr lifecycle = ConnectTo(kTestPackageName); |
| 145 base::RunLoop loop; |
| 146 lifecycle.set_connection_error_handler(base::Bind(&QuitLoop, &loop)); |
| 147 lifecycle->GracefulQuit(); |
| 148 loop.Run(); |
| 149 } |
| 150 |
| 151 test::mojom::LifecycleControlPtr ConnectTo(const std::string& name) { |
| 152 test::mojom::LifecycleControlPtr lifecycle; |
| 153 connector()->ConnectToInterface(name, &lifecycle); |
| 154 PingPong(lifecycle.get()); |
| 155 return lifecycle; |
| 156 } |
| 157 |
| 158 void PingPong(test::mojom::LifecycleControl* lifecycle) { |
| 159 base::RunLoop loop; |
| 160 lifecycle->Ping(base::Bind(&QuitLoop, &loop)); |
| 161 loop.Run(); |
| 162 } |
| 163 |
| 164 InstanceState* instances() { return instances_.get(); } |
| 165 |
| 166 void WaitForInstanceDestruction() { |
| 167 base::RunLoop loop; |
| 168 instances()->WaitForInstanceDestruction(&loop); |
| 169 loop.Run(); |
| 170 } |
| 171 |
| 172 private: |
| 173 scoped_ptr<InstanceState> TrackInstances() { |
| 174 mojom::ApplicationManagerPtr am; |
| 175 connector()->ConnectToInterface("mojo:shell", &am); |
| 176 mojom::InstanceListenerPtr listener; |
| 177 base::RunLoop loop; |
| 178 InstanceState* state = new InstanceState(GetProxy(&listener), &loop); |
| 179 am->AddInstanceListener(std::move(listener)); |
| 180 loop.Run(); |
| 181 return make_scoped_ptr(state); |
| 182 } |
| 183 |
| 184 scoped_ptr<InstanceState> instances_; |
| 185 |
| 186 DISALLOW_COPY_AND_ASSIGN(LifecycleTest); |
| 187 }; |
| 188 |
| 189 TEST_F(LifecycleTest, Standalone_GracefulQuit) { |
| 190 test::mojom::LifecycleControlPtr lifecycle = ConnectTo(kTestAppName); |
| 191 |
| 192 EXPECT_TRUE(instances()->HasInstanceForName(kTestAppName)); |
| 193 EXPECT_EQ(1u, instances()->GetNewInstanceCount()); |
| 194 |
| 195 base::RunLoop loop; |
| 196 lifecycle.set_connection_error_handler(base::Bind(&QuitLoop, &loop)); |
| 197 lifecycle->GracefulQuit(); |
| 198 loop.Run(); |
| 199 |
| 200 WaitForInstanceDestruction(); |
| 201 EXPECT_FALSE(instances()->HasInstanceForName(kTestAppName)); |
| 202 EXPECT_EQ(0u, instances()->GetNewInstanceCount()); |
| 203 } |
| 204 |
| 205 TEST_F(LifecycleTest, Standalone_Crash) { |
| 206 if (!CanRunCrashTest()) { |
| 207 LOG(INFO) << "Skipping Standalone_Crash test in --single-process mode."; |
| 208 return; |
| 209 } |
| 210 |
| 211 test::mojom::LifecycleControlPtr lifecycle = ConnectTo(kTestAppName); |
| 212 |
| 213 EXPECT_TRUE(instances()->HasInstanceForName(kTestAppName)); |
| 214 EXPECT_EQ(1u, instances()->GetNewInstanceCount()); |
| 215 |
| 216 base::RunLoop loop; |
| 217 lifecycle.set_connection_error_handler(base::Bind(&QuitLoop, &loop)); |
| 218 lifecycle->Crash(); |
| 219 loop.Run(); |
| 220 |
| 221 WaitForInstanceDestruction(); |
| 222 EXPECT_FALSE(instances()->HasInstanceForName(kTestAppName)); |
| 223 EXPECT_EQ(0u, instances()->GetNewInstanceCount()); |
| 224 } |
| 225 |
| 226 TEST_F(LifecycleTest, Standalone_CloseShellConnection) { |
| 227 test::mojom::LifecycleControlPtr lifecycle = ConnectTo(kTestAppName); |
| 228 |
| 229 EXPECT_TRUE(instances()->HasInstanceForName(kTestAppName)); |
| 230 EXPECT_EQ(1u, instances()->GetNewInstanceCount()); |
| 231 |
| 232 base::RunLoop loop; |
| 233 lifecycle.set_connection_error_handler(base::Bind(&QuitLoop, &loop)); |
| 234 lifecycle->CloseShellConnection(); |
| 235 |
| 236 WaitForInstanceDestruction(); |
| 237 |
| 238 // |lifecycle| pipe should still be valid. |
| 239 PingPong(lifecycle.get()); |
| 240 } |
| 241 |
| 242 TEST_F(LifecycleTest, PackagedApp_GracefulQuit) { |
| 243 test::mojom::LifecycleControlPtr lifecycle = ConnectTo(kTestPackageAppNameA); |
| 244 |
| 245 // There should be two new instances - one for the app and one for the package |
| 246 // that vended it. |
| 247 EXPECT_TRUE(instances()->HasInstanceForName(kTestPackageAppNameA)); |
| 248 EXPECT_TRUE(instances()->HasInstanceForName(kTestPackageName)); |
| 249 EXPECT_EQ(2u, instances()->GetNewInstanceCount()); |
| 250 |
| 251 base::RunLoop loop; |
| 252 lifecycle.set_connection_error_handler(base::Bind(&QuitLoop, &loop)); |
| 253 lifecycle->GracefulQuit(); |
| 254 loop.Run(); |
| 255 |
| 256 WaitForInstanceDestruction(); |
| 257 EXPECT_FALSE(instances()->HasInstanceForName(kTestPackageName)); |
| 258 EXPECT_FALSE(instances()->HasInstanceForName(kTestAppName)); |
| 259 EXPECT_EQ(0u, instances()->GetNewInstanceCount()); |
| 260 } |
| 261 |
| 262 TEST_F(LifecycleTest, PackagedApp_Crash) { |
| 263 if (!CanRunCrashTest()) { |
| 264 LOG(INFO) << "Skipping Standalone_Crash test in --single-process mode."; |
| 265 return; |
| 266 } |
| 267 |
| 268 test::mojom::LifecycleControlPtr lifecycle = ConnectTo(kTestPackageAppNameA); |
| 269 |
| 270 // There should be two new instances - one for the app and one for the package |
| 271 // that vended it. |
| 272 EXPECT_TRUE(instances()->HasInstanceForName(kTestPackageAppNameA)); |
| 273 EXPECT_TRUE(instances()->HasInstanceForName(kTestPackageName)); |
| 274 EXPECT_EQ(2u, instances()->GetNewInstanceCount()); |
| 275 |
| 276 base::RunLoop loop; |
| 277 lifecycle.set_connection_error_handler(base::Bind(&QuitLoop, &loop)); |
| 278 lifecycle->Crash(); |
| 279 loop.Run(); |
| 280 |
| 281 WaitForInstanceDestruction(); |
| 282 EXPECT_FALSE(instances()->HasInstanceForName(kTestPackageName)); |
| 283 EXPECT_FALSE(instances()->HasInstanceForName(kTestPackageAppNameA)); |
| 284 EXPECT_EQ(0u, instances()->GetNewInstanceCount()); |
| 285 } |
| 286 |
| 287 TEST_F(LifecycleTest, PackagedApp_CloseShellConnection) { |
| 288 test::mojom::LifecycleControlPtr lifecycle = ConnectTo(kTestPackageAppNameA); |
| 289 |
| 290 EXPECT_TRUE(instances()->HasInstanceForName(kTestPackageAppNameA)); |
| 291 EXPECT_TRUE(instances()->HasInstanceForName(kTestPackageName)); |
| 292 EXPECT_EQ(2u, instances()->GetNewInstanceCount()); |
| 293 |
| 294 base::RunLoop loop; |
| 295 lifecycle.set_connection_error_handler(base::Bind(&QuitLoop, &loop)); |
| 296 lifecycle->CloseShellConnection(); |
| 297 |
| 298 WaitForInstanceDestruction(); |
| 299 EXPECT_FALSE(instances()->HasInstanceForName(kTestPackageAppNameA)); |
| 300 EXPECT_FALSE(instances()->HasInstanceForName(kTestPackageName)); |
| 301 EXPECT_EQ(0u, instances()->GetNewInstanceCount()); |
| 302 |
| 303 // |lifecycle| pipe should still be valid. |
| 304 PingPong(lifecycle.get()); |
| 305 } |
| 306 |
| 307 |
| 308 // When a single package provides multiple apps out of one process, crashing one |
| 309 // app crashes all. |
| 310 TEST_F(LifecycleTest, PackagedApp_CrashCrashesOtherProvidedApp) { |
| 311 if (!CanRunCrashTest()) { |
| 312 LOG(INFO) << "Skipping Standalone_Crash test in --single-process mode."; |
| 313 return; |
| 314 } |
| 315 |
| 316 test::mojom::LifecycleControlPtr lifecycle_a = |
| 317 ConnectTo(kTestPackageAppNameA); |
| 318 test::mojom::LifecycleControlPtr lifecycle_b = |
| 319 ConnectTo(kTestPackageAppNameB); |
| 320 test::mojom::LifecycleControlPtr lifecycle_package = |
| 321 ConnectTo(kTestPackageName); |
| 322 |
| 323 // There should be three instances, one for each packaged app and the package |
| 324 // itself. |
| 325 EXPECT_TRUE(instances()->HasInstanceForName(kTestPackageAppNameA)); |
| 326 EXPECT_TRUE(instances()->HasInstanceForName(kTestPackageAppNameB)); |
| 327 EXPECT_TRUE(instances()->HasInstanceForName(kTestPackageName)); |
| 328 size_t instance_count = instances()->GetNewInstanceCount(); |
| 329 EXPECT_EQ(3u, instance_count); |
| 330 |
| 331 base::RunLoop loop; |
| 332 lifecycle_a.set_connection_error_handler( |
| 333 base::Bind(&DecrementCountAndQuitWhenZero, &loop, &instance_count)); |
| 334 lifecycle_b.set_connection_error_handler( |
| 335 base::Bind(&DecrementCountAndQuitWhenZero, &loop, &instance_count)); |
| 336 lifecycle_package.set_connection_error_handler( |
| 337 base::Bind(&DecrementCountAndQuitWhenZero, &loop, &instance_count)); |
| 338 |
| 339 // Now crash one of the packaged apps. |
| 340 lifecycle_a->Crash(); |
| 341 loop.Run(); |
| 342 |
| 343 WaitForInstanceDestruction(); |
| 344 EXPECT_FALSE(instances()->HasInstanceForName(kTestPackageName)); |
| 345 EXPECT_FALSE(instances()->HasInstanceForName(kTestPackageAppNameA)); |
| 346 EXPECT_FALSE(instances()->HasInstanceForName(kTestPackageAppNameB)); |
| 347 EXPECT_EQ(0u, instances()->GetNewInstanceCount()); |
| 348 } |
| 349 |
| 350 // When a single package provides multiple apps out of one process, crashing one |
| 351 // app crashes all. |
| 352 TEST_F(LifecycleTest, PackagedApp_GracefulQuitPackageQuitsAll) { |
| 353 test::mojom::LifecycleControlPtr lifecycle_a = |
| 354 ConnectTo(kTestPackageAppNameA); |
| 355 test::mojom::LifecycleControlPtr lifecycle_b = |
| 356 ConnectTo(kTestPackageAppNameB); |
| 357 test::mojom::LifecycleControlPtr lifecycle_package = |
| 358 ConnectTo(kTestPackageName); |
| 359 |
| 360 // There should be three instances, one for each packaged app and the package |
| 361 // itself. |
| 362 EXPECT_TRUE(instances()->HasInstanceForName(kTestPackageAppNameA)); |
| 363 EXPECT_TRUE(instances()->HasInstanceForName(kTestPackageAppNameB)); |
| 364 EXPECT_TRUE(instances()->HasInstanceForName(kTestPackageName)); |
| 365 size_t instance_count = instances()->GetNewInstanceCount(); |
| 366 EXPECT_EQ(3u, instance_count); |
| 367 |
| 368 base::RunLoop loop; |
| 369 lifecycle_a.set_connection_error_handler( |
| 370 base::Bind(&DecrementCountAndQuitWhenZero, &loop, &instance_count)); |
| 371 lifecycle_b.set_connection_error_handler( |
| 372 base::Bind(&DecrementCountAndQuitWhenZero, &loop, &instance_count)); |
| 373 lifecycle_package.set_connection_error_handler( |
| 374 base::Bind(&DecrementCountAndQuitWhenZero, &loop, &instance_count)); |
| 375 |
| 376 // Now quit the package. All the packaged apps should close. |
| 377 lifecycle_package->GracefulQuit(); |
| 378 loop.Run(); |
| 379 |
| 380 WaitForInstanceDestruction(); |
| 381 EXPECT_FALSE(instances()->HasInstanceForName(kTestPackageName)); |
| 382 EXPECT_FALSE(instances()->HasInstanceForName(kTestPackageAppNameA)); |
| 383 EXPECT_FALSE(instances()->HasInstanceForName(kTestPackageAppNameB)); |
| 384 EXPECT_EQ(0u, instances()->GetNewInstanceCount()); |
| 385 } |
| 386 |
| 387 } // namespace shell |
| 388 } // namespace mojo |
| OLD | NEW |