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