| 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/process/process.h" | |
| 9 #include "base/run_loop.h" | |
| 10 #include "mojo/shell/public/cpp/identity.h" | |
| 11 #include "mojo/shell/public/cpp/shell_test.h" | |
| 12 #include "mojo/shell/public/interfaces/shell.mojom.h" | |
| 13 #include "mojo/shell/tests/lifecycle/lifecycle_unittest.mojom.h" | |
| 14 #include "mojo/shell/tests/util.h" | |
| 15 | |
| 16 namespace mojo { | |
| 17 namespace shell { | |
| 18 namespace { | |
| 19 | |
| 20 const char kTestAppName[] = "mojo:lifecycle_unittest_app"; | |
| 21 const char kTestParentName[] = "mojo:lifecycle_unittest_parent"; | |
| 22 const char kTestExeName[] = "exe:lifecycle_unittest_exe"; | |
| 23 const char kTestPackageName[] = "mojo:lifecycle_unittest_package"; | |
| 24 const char kTestPackageAppNameA[] = "mojo:lifecycle_unittest_package_app_a"; | |
| 25 const char kTestPackageAppNameB[] = "mojo:lifecycle_unittest_package_app_b"; | |
| 26 const char kTestName[] = "mojo:lifecycle_unittest"; | |
| 27 | |
| 28 void QuitLoop(base::RunLoop* loop) { | |
| 29 loop->Quit(); | |
| 30 } | |
| 31 | |
| 32 void DecrementCountAndQuitWhenZero(base::RunLoop* loop, size_t* count) { | |
| 33 if (!--(*count)) | |
| 34 loop->Quit(); | |
| 35 } | |
| 36 | |
| 37 struct Instance { | |
| 38 Instance() : id(shell::mojom::kInvalidInstanceID), pid(0) {} | |
| 39 Instance(const Identity& identity, uint32_t id, uint32_t pid) | |
| 40 : identity(identity), id(id), pid(pid) {} | |
| 41 | |
| 42 Identity identity; | |
| 43 uint32_t id; | |
| 44 uint32_t pid; | |
| 45 }; | |
| 46 | |
| 47 class InstanceState : public mojom::InstanceListener { | |
| 48 public: | |
| 49 InstanceState(mojom::InstanceListenerRequest request, base::RunLoop* loop) | |
| 50 : binding_(this, std::move(request)), loop_(loop) {} | |
| 51 ~InstanceState() override {} | |
| 52 | |
| 53 bool HasInstanceForName(const std::string& name) const { | |
| 54 return instances_.find(name) != instances_.end(); | |
| 55 } | |
| 56 size_t GetNewInstanceCount() const { | |
| 57 return instances_.size() - initial_instances_.size(); | |
| 58 } | |
| 59 void WaitForInstanceDestruction(base::RunLoop* loop) { | |
| 60 DCHECK(!destruction_loop_); | |
| 61 destruction_loop_ = loop; | |
| 62 // First of all check to see if we should be spinning this loop at all - | |
| 63 // the app(s) we're waiting on quitting may already have quit. | |
| 64 TryToQuitDestructionLoop(); | |
| 65 } | |
| 66 | |
| 67 private: | |
| 68 // mojom::InstanceListener: | |
| 69 void SetExistingInstances(Array<mojom::InstanceInfoPtr> instances) override { | |
| 70 for (const auto& instance : instances) { | |
| 71 Instance i(instance->identity.To<Identity>(), instance->id, | |
| 72 instance->pid); | |
| 73 initial_instances_[i.identity.name()] = i; | |
| 74 instances_[i.identity.name()] = i; | |
| 75 } | |
| 76 loop_->Quit(); | |
| 77 } | |
| 78 void InstanceCreated(mojom::InstanceInfoPtr instance) override { | |
| 79 instances_[instance->identity->name] = | |
| 80 Instance(instance->identity.To<Identity>(), instance->id, | |
| 81 instance->pid); | |
| 82 } | |
| 83 void InstanceDestroyed(uint32_t id) override { | |
| 84 for (auto it = instances_.begin(); it != instances_.end(); ++it) { | |
| 85 if (it->second.id == id) { | |
| 86 instances_.erase(it); | |
| 87 break; | |
| 88 } | |
| 89 } | |
| 90 TryToQuitDestructionLoop(); | |
| 91 } | |
| 92 void InstancePIDAvailable(uint32_t id, uint32_t pid) override { | |
| 93 for (auto& instance : instances_) { | |
| 94 if (instance.second.id == id) { | |
| 95 instance.second.pid = pid; | |
| 96 break; | |
| 97 } | |
| 98 } | |
| 99 } | |
| 100 | |
| 101 void TryToQuitDestructionLoop() { | |
| 102 if (!GetNewInstanceCount() && destruction_loop_) { | |
| 103 destruction_loop_->Quit(); | |
| 104 destruction_loop_ = nullptr; | |
| 105 } | |
| 106 } | |
| 107 | |
| 108 // All currently running instances. | |
| 109 std::map<std::string, Instance> instances_; | |
| 110 // The initial set of instances. | |
| 111 std::map<std::string, Instance> initial_instances_; | |
| 112 | |
| 113 Binding<mojom::InstanceListener> binding_; | |
| 114 base::RunLoop* loop_; | |
| 115 | |
| 116 // Set when the client wants to wait for this object to track the destruction | |
| 117 // of an instance before proceeding. | |
| 118 base::RunLoop* destruction_loop_ = nullptr; | |
| 119 | |
| 120 DISALLOW_COPY_AND_ASSIGN(InstanceState); | |
| 121 }; | |
| 122 | |
| 123 } | |
| 124 | |
| 125 class LifecycleTest : public mojo::test::ShellTest { | |
| 126 public: | |
| 127 LifecycleTest() : ShellTest(kTestName) {} | |
| 128 ~LifecycleTest() override {} | |
| 129 | |
| 130 protected: | |
| 131 // mojo::test::ShellTest: | |
| 132 void SetUp() override { | |
| 133 mojo::test::ShellTest::SetUp(); | |
| 134 InitPackage(); | |
| 135 instances_ = TrackInstances(); | |
| 136 } | |
| 137 void TearDown() override { | |
| 138 instances_.reset(); | |
| 139 mojo::test::ShellTest::TearDown(); | |
| 140 } | |
| 141 | |
| 142 bool CanRunCrashTest() { | |
| 143 return !base::CommandLine::ForCurrentProcess()->HasSwitch("single-process"); | |
| 144 } | |
| 145 | |
| 146 void InitPackage() { | |
| 147 test::mojom::LifecycleControlPtr lifecycle = ConnectTo(kTestPackageName); | |
| 148 base::RunLoop loop; | |
| 149 lifecycle.set_connection_error_handler(base::Bind(&QuitLoop, &loop)); | |
| 150 lifecycle->GracefulQuit(); | |
| 151 loop.Run(); | |
| 152 } | |
| 153 | |
| 154 test::mojom::LifecycleControlPtr ConnectTo(const std::string& name) { | |
| 155 test::mojom::LifecycleControlPtr lifecycle; | |
| 156 connector()->ConnectToInterface(name, &lifecycle); | |
| 157 PingPong(lifecycle.get()); | |
| 158 return lifecycle; | |
| 159 } | |
| 160 | |
| 161 base::Process LaunchProcess() { | |
| 162 base::Process process; | |
| 163 test::LaunchAndConnectToProcess( | |
| 164 #if defined(OS_WIN) | |
| 165 "lifecycle_unittest_exe.exe", | |
| 166 #else | |
| 167 "lifecycle_unittest_exe", | |
| 168 #endif | |
| 169 Identity(kTestExeName, mojom::kInheritUserID), | |
| 170 connector(), | |
| 171 &process); | |
| 172 return process; | |
| 173 } | |
| 174 | |
| 175 void PingPong(test::mojom::LifecycleControl* lifecycle) { | |
| 176 base::RunLoop loop; | |
| 177 lifecycle->Ping(base::Bind(&QuitLoop, &loop)); | |
| 178 loop.Run(); | |
| 179 } | |
| 180 | |
| 181 InstanceState* instances() { return instances_.get(); } | |
| 182 | |
| 183 void WaitForInstanceDestruction() { | |
| 184 base::RunLoop loop; | |
| 185 instances()->WaitForInstanceDestruction(&loop); | |
| 186 loop.Run(); | |
| 187 } | |
| 188 | |
| 189 private: | |
| 190 scoped_ptr<InstanceState> TrackInstances() { | |
| 191 mojom::ShellPtr shell; | |
| 192 connector()->ConnectToInterface("mojo:shell", &shell); | |
| 193 mojom::InstanceListenerPtr listener; | |
| 194 base::RunLoop loop; | |
| 195 InstanceState* state = new InstanceState(GetProxy(&listener), &loop); | |
| 196 shell->AddInstanceListener(std::move(listener)); | |
| 197 loop.Run(); | |
| 198 return make_scoped_ptr(state); | |
| 199 } | |
| 200 | |
| 201 scoped_ptr<InstanceState> instances_; | |
| 202 | |
| 203 DISALLOW_COPY_AND_ASSIGN(LifecycleTest); | |
| 204 }; | |
| 205 | |
| 206 TEST_F(LifecycleTest, Standalone_GracefulQuit) { | |
| 207 test::mojom::LifecycleControlPtr lifecycle = ConnectTo(kTestAppName); | |
| 208 | |
| 209 EXPECT_TRUE(instances()->HasInstanceForName(kTestAppName)); | |
| 210 EXPECT_EQ(1u, instances()->GetNewInstanceCount()); | |
| 211 | |
| 212 base::RunLoop loop; | |
| 213 lifecycle.set_connection_error_handler(base::Bind(&QuitLoop, &loop)); | |
| 214 lifecycle->GracefulQuit(); | |
| 215 loop.Run(); | |
| 216 | |
| 217 WaitForInstanceDestruction(); | |
| 218 EXPECT_FALSE(instances()->HasInstanceForName(kTestAppName)); | |
| 219 EXPECT_EQ(0u, instances()->GetNewInstanceCount()); | |
| 220 } | |
| 221 | |
| 222 TEST_F(LifecycleTest, Standalone_Crash) { | |
| 223 if (!CanRunCrashTest()) { | |
| 224 LOG(INFO) << "Skipping Standalone_Crash test in --single-process mode."; | |
| 225 return; | |
| 226 } | |
| 227 | |
| 228 test::mojom::LifecycleControlPtr lifecycle = ConnectTo(kTestAppName); | |
| 229 | |
| 230 EXPECT_TRUE(instances()->HasInstanceForName(kTestAppName)); | |
| 231 EXPECT_EQ(1u, instances()->GetNewInstanceCount()); | |
| 232 | |
| 233 base::RunLoop loop; | |
| 234 lifecycle.set_connection_error_handler(base::Bind(&QuitLoop, &loop)); | |
| 235 lifecycle->Crash(); | |
| 236 loop.Run(); | |
| 237 | |
| 238 WaitForInstanceDestruction(); | |
| 239 EXPECT_FALSE(instances()->HasInstanceForName(kTestAppName)); | |
| 240 EXPECT_EQ(0u, instances()->GetNewInstanceCount()); | |
| 241 } | |
| 242 | |
| 243 TEST_F(LifecycleTest, Standalone_CloseShellConnection) { | |
| 244 test::mojom::LifecycleControlPtr lifecycle = ConnectTo(kTestAppName); | |
| 245 | |
| 246 EXPECT_TRUE(instances()->HasInstanceForName(kTestAppName)); | |
| 247 EXPECT_EQ(1u, instances()->GetNewInstanceCount()); | |
| 248 | |
| 249 base::RunLoop loop; | |
| 250 lifecycle.set_connection_error_handler(base::Bind(&QuitLoop, &loop)); | |
| 251 lifecycle->CloseShellConnection(); | |
| 252 | |
| 253 WaitForInstanceDestruction(); | |
| 254 | |
| 255 // |lifecycle| pipe should still be valid. | |
| 256 PingPong(lifecycle.get()); | |
| 257 } | |
| 258 | |
| 259 TEST_F(LifecycleTest, PackagedApp_GracefulQuit) { | |
| 260 test::mojom::LifecycleControlPtr lifecycle = ConnectTo(kTestPackageAppNameA); | |
| 261 | |
| 262 // There should be two new instances - one for the app and one for the package | |
| 263 // that vended it. | |
| 264 EXPECT_TRUE(instances()->HasInstanceForName(kTestPackageAppNameA)); | |
| 265 EXPECT_TRUE(instances()->HasInstanceForName(kTestPackageName)); | |
| 266 EXPECT_EQ(2u, instances()->GetNewInstanceCount()); | |
| 267 | |
| 268 base::RunLoop loop; | |
| 269 lifecycle.set_connection_error_handler(base::Bind(&QuitLoop, &loop)); | |
| 270 lifecycle->GracefulQuit(); | |
| 271 loop.Run(); | |
| 272 | |
| 273 WaitForInstanceDestruction(); | |
| 274 EXPECT_FALSE(instances()->HasInstanceForName(kTestPackageName)); | |
| 275 EXPECT_FALSE(instances()->HasInstanceForName(kTestAppName)); | |
| 276 EXPECT_EQ(0u, instances()->GetNewInstanceCount()); | |
| 277 } | |
| 278 | |
| 279 TEST_F(LifecycleTest, PackagedApp_Crash) { | |
| 280 if (!CanRunCrashTest()) { | |
| 281 LOG(INFO) << "Skipping Standalone_Crash test in --single-process mode."; | |
| 282 return; | |
| 283 } | |
| 284 | |
| 285 test::mojom::LifecycleControlPtr lifecycle = ConnectTo(kTestPackageAppNameA); | |
| 286 | |
| 287 // There should be two new instances - one for the app and one for the package | |
| 288 // that vended it. | |
| 289 EXPECT_TRUE(instances()->HasInstanceForName(kTestPackageAppNameA)); | |
| 290 EXPECT_TRUE(instances()->HasInstanceForName(kTestPackageName)); | |
| 291 EXPECT_EQ(2u, instances()->GetNewInstanceCount()); | |
| 292 | |
| 293 base::RunLoop loop; | |
| 294 lifecycle.set_connection_error_handler(base::Bind(&QuitLoop, &loop)); | |
| 295 lifecycle->Crash(); | |
| 296 loop.Run(); | |
| 297 | |
| 298 WaitForInstanceDestruction(); | |
| 299 EXPECT_FALSE(instances()->HasInstanceForName(kTestPackageName)); | |
| 300 EXPECT_FALSE(instances()->HasInstanceForName(kTestPackageAppNameA)); | |
| 301 EXPECT_EQ(0u, instances()->GetNewInstanceCount()); | |
| 302 } | |
| 303 | |
| 304 TEST_F(LifecycleTest, PackagedApp_CloseShellConnection) { | |
| 305 test::mojom::LifecycleControlPtr lifecycle = ConnectTo(kTestPackageAppNameA); | |
| 306 | |
| 307 EXPECT_TRUE(instances()->HasInstanceForName(kTestPackageAppNameA)); | |
| 308 EXPECT_TRUE(instances()->HasInstanceForName(kTestPackageName)); | |
| 309 EXPECT_EQ(2u, instances()->GetNewInstanceCount()); | |
| 310 | |
| 311 base::RunLoop loop; | |
| 312 lifecycle.set_connection_error_handler(base::Bind(&QuitLoop, &loop)); | |
| 313 lifecycle->CloseShellConnection(); | |
| 314 | |
| 315 WaitForInstanceDestruction(); | |
| 316 EXPECT_FALSE(instances()->HasInstanceForName(kTestPackageAppNameA)); | |
| 317 EXPECT_FALSE(instances()->HasInstanceForName(kTestPackageName)); | |
| 318 EXPECT_EQ(0u, instances()->GetNewInstanceCount()); | |
| 319 | |
| 320 // |lifecycle| pipe should still be valid. | |
| 321 PingPong(lifecycle.get()); | |
| 322 } | |
| 323 | |
| 324 | |
| 325 // When a single package provides multiple apps out of one process, crashing one | |
| 326 // app crashes all. | |
| 327 TEST_F(LifecycleTest, PackagedApp_CrashCrashesOtherProvidedApp) { | |
| 328 if (!CanRunCrashTest()) { | |
| 329 LOG(INFO) << "Skipping Standalone_Crash test in --single-process mode."; | |
| 330 return; | |
| 331 } | |
| 332 | |
| 333 test::mojom::LifecycleControlPtr lifecycle_a = | |
| 334 ConnectTo(kTestPackageAppNameA); | |
| 335 test::mojom::LifecycleControlPtr lifecycle_b = | |
| 336 ConnectTo(kTestPackageAppNameB); | |
| 337 test::mojom::LifecycleControlPtr lifecycle_package = | |
| 338 ConnectTo(kTestPackageName); | |
| 339 | |
| 340 // There should be three instances, one for each packaged app and the package | |
| 341 // itself. | |
| 342 EXPECT_TRUE(instances()->HasInstanceForName(kTestPackageAppNameA)); | |
| 343 EXPECT_TRUE(instances()->HasInstanceForName(kTestPackageAppNameB)); | |
| 344 EXPECT_TRUE(instances()->HasInstanceForName(kTestPackageName)); | |
| 345 size_t instance_count = instances()->GetNewInstanceCount(); | |
| 346 EXPECT_EQ(3u, instance_count); | |
| 347 | |
| 348 base::RunLoop loop; | |
| 349 lifecycle_a.set_connection_error_handler( | |
| 350 base::Bind(&DecrementCountAndQuitWhenZero, &loop, &instance_count)); | |
| 351 lifecycle_b.set_connection_error_handler( | |
| 352 base::Bind(&DecrementCountAndQuitWhenZero, &loop, &instance_count)); | |
| 353 lifecycle_package.set_connection_error_handler( | |
| 354 base::Bind(&DecrementCountAndQuitWhenZero, &loop, &instance_count)); | |
| 355 | |
| 356 // Now crash one of the packaged apps. | |
| 357 lifecycle_a->Crash(); | |
| 358 loop.Run(); | |
| 359 | |
| 360 WaitForInstanceDestruction(); | |
| 361 EXPECT_FALSE(instances()->HasInstanceForName(kTestPackageName)); | |
| 362 EXPECT_FALSE(instances()->HasInstanceForName(kTestPackageAppNameA)); | |
| 363 EXPECT_FALSE(instances()->HasInstanceForName(kTestPackageAppNameB)); | |
| 364 EXPECT_EQ(0u, instances()->GetNewInstanceCount()); | |
| 365 } | |
| 366 | |
| 367 // When a single package provides multiple apps out of one process, crashing one | |
| 368 // app crashes all. | |
| 369 TEST_F(LifecycleTest, PackagedApp_GracefulQuitPackageQuitsAll) { | |
| 370 test::mojom::LifecycleControlPtr lifecycle_a = | |
| 371 ConnectTo(kTestPackageAppNameA); | |
| 372 test::mojom::LifecycleControlPtr lifecycle_b = | |
| 373 ConnectTo(kTestPackageAppNameB); | |
| 374 test::mojom::LifecycleControlPtr lifecycle_package = | |
| 375 ConnectTo(kTestPackageName); | |
| 376 | |
| 377 // There should be three instances, one for each packaged app and the package | |
| 378 // itself. | |
| 379 EXPECT_TRUE(instances()->HasInstanceForName(kTestPackageAppNameA)); | |
| 380 EXPECT_TRUE(instances()->HasInstanceForName(kTestPackageAppNameB)); | |
| 381 EXPECT_TRUE(instances()->HasInstanceForName(kTestPackageName)); | |
| 382 size_t instance_count = instances()->GetNewInstanceCount(); | |
| 383 EXPECT_EQ(3u, instance_count); | |
| 384 | |
| 385 base::RunLoop loop; | |
| 386 lifecycle_a.set_connection_error_handler( | |
| 387 base::Bind(&DecrementCountAndQuitWhenZero, &loop, &instance_count)); | |
| 388 lifecycle_b.set_connection_error_handler( | |
| 389 base::Bind(&DecrementCountAndQuitWhenZero, &loop, &instance_count)); | |
| 390 lifecycle_package.set_connection_error_handler( | |
| 391 base::Bind(&DecrementCountAndQuitWhenZero, &loop, &instance_count)); | |
| 392 | |
| 393 // Now quit the package. All the packaged apps should close. | |
| 394 lifecycle_package->GracefulQuit(); | |
| 395 loop.Run(); | |
| 396 | |
| 397 WaitForInstanceDestruction(); | |
| 398 EXPECT_FALSE(instances()->HasInstanceForName(kTestPackageName)); | |
| 399 EXPECT_FALSE(instances()->HasInstanceForName(kTestPackageAppNameA)); | |
| 400 EXPECT_FALSE(instances()->HasInstanceForName(kTestPackageAppNameB)); | |
| 401 EXPECT_EQ(0u, instances()->GetNewInstanceCount()); | |
| 402 } | |
| 403 | |
| 404 TEST_F(LifecycleTest, Exe_GracefulQuit) { | |
| 405 base::Process process = LaunchProcess(); | |
| 406 | |
| 407 test::mojom::LifecycleControlPtr lifecycle = ConnectTo(kTestExeName); | |
| 408 | |
| 409 EXPECT_TRUE(instances()->HasInstanceForName(kTestExeName)); | |
| 410 EXPECT_EQ(1u, instances()->GetNewInstanceCount()); | |
| 411 | |
| 412 base::RunLoop loop; | |
| 413 lifecycle.set_connection_error_handler(base::Bind(&QuitLoop, &loop)); | |
| 414 lifecycle->GracefulQuit(); | |
| 415 loop.Run(); | |
| 416 | |
| 417 WaitForInstanceDestruction(); | |
| 418 EXPECT_FALSE(instances()->HasInstanceForName(kTestExeName)); | |
| 419 EXPECT_EQ(0u, instances()->GetNewInstanceCount()); | |
| 420 | |
| 421 process.Terminate(9, true); | |
| 422 } | |
| 423 | |
| 424 TEST_F(LifecycleTest, Exe_TerminateProcess) { | |
| 425 base::Process process = LaunchProcess(); | |
| 426 | |
| 427 test::mojom::LifecycleControlPtr lifecycle = ConnectTo(kTestExeName); | |
| 428 | |
| 429 EXPECT_TRUE(instances()->HasInstanceForName(kTestExeName)); | |
| 430 EXPECT_EQ(1u, instances()->GetNewInstanceCount()); | |
| 431 | |
| 432 base::RunLoop loop; | |
| 433 lifecycle.set_connection_error_handler(base::Bind(&QuitLoop, &loop)); | |
| 434 process.Terminate(9, true); | |
| 435 loop.Run(); | |
| 436 | |
| 437 WaitForInstanceDestruction(); | |
| 438 EXPECT_FALSE(instances()->HasInstanceForName(kTestExeName)); | |
| 439 EXPECT_EQ(0u, instances()->GetNewInstanceCount()); | |
| 440 } | |
| 441 | |
| 442 TEST_F(LifecycleTest, ShutdownTree) { | |
| 443 // Verifies that Instances are destroyed when their creator is. | |
| 444 scoped_ptr<Connection> parent_connection = | |
| 445 connector()->Connect(kTestParentName); | |
| 446 test::mojom::ParentPtr parent; | |
| 447 parent_connection->GetInterface(&parent); | |
| 448 | |
| 449 // This asks kTestParentName to open a connection to kTestAppName and blocks | |
| 450 // on a response from a Ping(). | |
| 451 { | |
| 452 base::RunLoop loop; | |
| 453 parent->ConnectToChild(base::Bind(&QuitLoop, &loop)); | |
| 454 loop.Run(); | |
| 455 } | |
| 456 | |
| 457 // Should now have two new instances (parent and child). | |
| 458 EXPECT_EQ(2u, instances()->GetNewInstanceCount()); | |
| 459 EXPECT_TRUE(instances()->HasInstanceForName(kTestParentName)); | |
| 460 EXPECT_TRUE(instances()->HasInstanceForName(kTestAppName)); | |
| 461 | |
| 462 parent->Quit(); | |
| 463 | |
| 464 // Quitting the parent should cascade-quit the child. | |
| 465 WaitForInstanceDestruction(); | |
| 466 EXPECT_EQ(0u, instances()->GetNewInstanceCount()); | |
| 467 EXPECT_FALSE(instances()->HasInstanceForName(kTestParentName)); | |
| 468 EXPECT_FALSE(instances()->HasInstanceForName(kTestAppName)); | |
| 469 } | |
| 470 | |
| 471 } // namespace shell | |
| 472 } // namespace mojo | |
| OLD | NEW |