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 |