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 |