OLD | NEW |
| (Empty) |
1 // Copyright 2014 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 "mojo/shell/application_manager.h" | |
6 | |
7 #include <utility> | |
8 | |
9 #include "base/at_exit.h" | |
10 #include "base/bind.h" | |
11 #include "base/macros.h" | |
12 #include "base/memory/scoped_vector.h" | |
13 #include "base/message_loop/message_loop.h" | |
14 #include "base/run_loop.h" | |
15 #include "mojo/public/cpp/bindings/strong_binding.h" | |
16 #include "mojo/shell/application_loader.h" | |
17 #include "mojo/shell/connect_util.h" | |
18 #include "mojo/shell/public/cpp/interface_factory.h" | |
19 #include "mojo/shell/public/cpp/shell_client.h" | |
20 #include "mojo/shell/public/cpp/shell_connection.h" | |
21 #include "mojo/shell/public/interfaces/interface_provider.mojom.h" | |
22 #include "mojo/shell/test.mojom.h" | |
23 #include "testing/gtest/include/gtest/gtest.h" | |
24 | |
25 namespace mojo { | |
26 namespace shell { | |
27 namespace test { | |
28 | |
29 const char kTestURLString[] = "test:testService"; | |
30 const char kTestAURLString[] = "test:TestA"; | |
31 const char kTestBURLString[] = "test:TestB"; | |
32 | |
33 struct TestContext { | |
34 TestContext() : num_impls(0), num_loader_deletes(0) {} | |
35 std::string last_test_string; | |
36 int num_impls; | |
37 int num_loader_deletes; | |
38 }; | |
39 | |
40 void QuitClosure(bool* value) { | |
41 *value = true; | |
42 base::MessageLoop::current()->QuitWhenIdle(); | |
43 } | |
44 | |
45 class TestServiceImpl : public TestService { | |
46 public: | |
47 TestServiceImpl(TestContext* context, InterfaceRequest<TestService> request) | |
48 : context_(context), binding_(this, std::move(request)) { | |
49 ++context_->num_impls; | |
50 } | |
51 | |
52 ~TestServiceImpl() override { | |
53 --context_->num_impls; | |
54 if (!base::MessageLoop::current()->is_running()) | |
55 return; | |
56 base::MessageLoop::current()->QuitWhenIdle(); | |
57 } | |
58 | |
59 // TestService implementation: | |
60 void Test(const String& test_string, | |
61 const Callback<void()>& callback) override { | |
62 context_->last_test_string = test_string; | |
63 callback.Run(); | |
64 } | |
65 | |
66 private: | |
67 TestContext* context_; | |
68 StrongBinding<TestService> binding_; | |
69 }; | |
70 | |
71 class TestClient { | |
72 public: | |
73 explicit TestClient(TestServicePtr service) | |
74 : service_(std::move(service)), quit_after_ack_(false) {} | |
75 | |
76 void AckTest() { | |
77 if (quit_after_ack_) | |
78 base::MessageLoop::current()->QuitWhenIdle(); | |
79 } | |
80 | |
81 void Test(const std::string& test_string) { | |
82 quit_after_ack_ = true; | |
83 service_->Test(test_string, | |
84 base::Bind(&TestClient::AckTest, base::Unretained(this))); | |
85 } | |
86 | |
87 private: | |
88 TestServicePtr service_; | |
89 bool quit_after_ack_; | |
90 DISALLOW_COPY_AND_ASSIGN(TestClient); | |
91 }; | |
92 | |
93 class TestApplicationLoader : public ApplicationLoader, | |
94 public ShellClient, | |
95 public InterfaceFactory<TestService> { | |
96 public: | |
97 TestApplicationLoader() | |
98 : context_(nullptr), num_loads_(0) {} | |
99 | |
100 ~TestApplicationLoader() override { | |
101 if (context_) | |
102 ++context_->num_loader_deletes; | |
103 shell_connection_.reset(); | |
104 } | |
105 | |
106 void set_context(TestContext* context) { context_ = context; } | |
107 int num_loads() const { return num_loads_; } | |
108 const GURL& last_requestor_url() const { return last_requestor_url_; } | |
109 | |
110 private: | |
111 // ApplicationLoader implementation. | |
112 void Load(const GURL& url, | |
113 InterfaceRequest<mojom::ShellClient> request) override { | |
114 ++num_loads_; | |
115 shell_connection_.reset(new ShellConnection(this, std::move(request))); | |
116 } | |
117 | |
118 // mojo::ShellClient implementation. | |
119 bool AcceptConnection(Connection* connection) override { | |
120 connection->AddInterface<TestService>(this); | |
121 last_requestor_url_ = GURL(connection->GetRemoteApplicationURL()); | |
122 return true; | |
123 } | |
124 | |
125 // InterfaceFactory<TestService> implementation. | |
126 void Create(Connection* connection, | |
127 InterfaceRequest<TestService> request) override { | |
128 new TestServiceImpl(context_, std::move(request)); | |
129 } | |
130 | |
131 scoped_ptr<ShellConnection> shell_connection_; | |
132 TestContext* context_; | |
133 int num_loads_; | |
134 GURL last_requestor_url_; | |
135 | |
136 DISALLOW_COPY_AND_ASSIGN(TestApplicationLoader); | |
137 }; | |
138 | |
139 class ClosingApplicationLoader : public ApplicationLoader { | |
140 private: | |
141 // ApplicationLoader implementation. | |
142 void Load(const GURL& url, | |
143 InterfaceRequest<mojom::ShellClient> request) override { | |
144 } | |
145 }; | |
146 | |
147 class TesterContext { | |
148 public: | |
149 explicit TesterContext(base::MessageLoop* loop) | |
150 : num_b_calls_(0), | |
151 num_c_calls_(0), | |
152 num_a_deletes_(0), | |
153 num_b_deletes_(0), | |
154 num_c_deletes_(0), | |
155 tester_called_quit_(false), | |
156 a_called_quit_(false), | |
157 loop_(loop) {} | |
158 | |
159 void IncrementNumBCalls() { | |
160 base::AutoLock lock(lock_); | |
161 num_b_calls_++; | |
162 } | |
163 | |
164 void IncrementNumCCalls() { | |
165 base::AutoLock lock(lock_); | |
166 num_c_calls_++; | |
167 } | |
168 | |
169 void IncrementNumADeletes() { | |
170 base::AutoLock lock(lock_); | |
171 num_a_deletes_++; | |
172 } | |
173 | |
174 void IncrementNumBDeletes() { | |
175 base::AutoLock lock(lock_); | |
176 num_b_deletes_++; | |
177 } | |
178 | |
179 void IncrementNumCDeletes() { | |
180 base::AutoLock lock(lock_); | |
181 num_c_deletes_++; | |
182 } | |
183 | |
184 void set_tester_called_quit() { | |
185 base::AutoLock lock(lock_); | |
186 tester_called_quit_ = true; | |
187 } | |
188 | |
189 void set_a_called_quit() { | |
190 base::AutoLock lock(lock_); | |
191 a_called_quit_ = true; | |
192 } | |
193 | |
194 int num_b_calls() { | |
195 base::AutoLock lock(lock_); | |
196 return num_b_calls_; | |
197 } | |
198 int num_c_calls() { | |
199 base::AutoLock lock(lock_); | |
200 return num_c_calls_; | |
201 } | |
202 int num_a_deletes() { | |
203 base::AutoLock lock(lock_); | |
204 return num_a_deletes_; | |
205 } | |
206 int num_b_deletes() { | |
207 base::AutoLock lock(lock_); | |
208 return num_b_deletes_; | |
209 } | |
210 int num_c_deletes() { | |
211 base::AutoLock lock(lock_); | |
212 return num_c_deletes_; | |
213 } | |
214 bool tester_called_quit() { | |
215 base::AutoLock lock(lock_); | |
216 return tester_called_quit_; | |
217 } | |
218 bool a_called_quit() { | |
219 base::AutoLock lock(lock_); | |
220 return a_called_quit_; | |
221 } | |
222 | |
223 void QuitSoon() { | |
224 loop_->PostTask(FROM_HERE, base::MessageLoop::QuitWhenIdleClosure()); | |
225 } | |
226 | |
227 private: | |
228 // lock_ protects all members except for loop_ which must be unchanged for the | |
229 // lifetime of this class. | |
230 base::Lock lock_; | |
231 int num_b_calls_; | |
232 int num_c_calls_; | |
233 int num_a_deletes_; | |
234 int num_b_deletes_; | |
235 int num_c_deletes_; | |
236 bool tester_called_quit_; | |
237 bool a_called_quit_; | |
238 | |
239 base::MessageLoop* loop_; | |
240 }; | |
241 | |
242 // Used to test that the requestor url will be correctly passed. | |
243 class TestAImpl : public TestA { | |
244 public: | |
245 TestAImpl(Shell* shell, | |
246 TesterContext* test_context, | |
247 InterfaceRequest<TestA> request, | |
248 InterfaceFactory<TestC>* factory) | |
249 : test_context_(test_context), binding_(this, std::move(request)) { | |
250 connection_ = shell->Connect(kTestBURLString); | |
251 connection_->AddInterface<TestC>(factory); | |
252 connection_->GetInterface(&b_); | |
253 } | |
254 | |
255 ~TestAImpl() override { | |
256 test_context_->IncrementNumADeletes(); | |
257 if (base::MessageLoop::current()->is_running()) | |
258 Quit(); | |
259 } | |
260 | |
261 private: | |
262 void CallB() override { | |
263 b_->B(base::Bind(&TestAImpl::Quit, base::Unretained(this))); | |
264 } | |
265 | |
266 void CallCFromB() override { | |
267 b_->CallC(base::Bind(&TestAImpl::Quit, base::Unretained(this))); | |
268 } | |
269 | |
270 void Quit() { | |
271 base::MessageLoop::current()->QuitWhenIdle(); | |
272 test_context_->set_a_called_quit(); | |
273 test_context_->QuitSoon(); | |
274 } | |
275 | |
276 scoped_ptr<Connection> connection_; | |
277 TesterContext* test_context_; | |
278 TestBPtr b_; | |
279 StrongBinding<TestA> binding_; | |
280 }; | |
281 | |
282 class TestBImpl : public TestB { | |
283 public: | |
284 TestBImpl(Connection* connection, | |
285 TesterContext* test_context, | |
286 InterfaceRequest<TestB> request) | |
287 : test_context_(test_context), binding_(this, std::move(request)) { | |
288 connection->GetInterface(&c_); | |
289 } | |
290 | |
291 ~TestBImpl() override { | |
292 test_context_->IncrementNumBDeletes(); | |
293 if (base::MessageLoop::current()->is_running()) | |
294 base::MessageLoop::current()->QuitWhenIdle(); | |
295 test_context_->QuitSoon(); | |
296 } | |
297 | |
298 private: | |
299 void B(const Callback<void()>& callback) override { | |
300 test_context_->IncrementNumBCalls(); | |
301 callback.Run(); | |
302 } | |
303 | |
304 void CallC(const Callback<void()>& callback) override { | |
305 test_context_->IncrementNumBCalls(); | |
306 c_->C(callback); | |
307 } | |
308 | |
309 TesterContext* test_context_; | |
310 TestCPtr c_; | |
311 StrongBinding<TestB> binding_; | |
312 }; | |
313 | |
314 class TestCImpl : public TestC { | |
315 public: | |
316 TestCImpl(Connection* connection, | |
317 TesterContext* test_context, | |
318 InterfaceRequest<TestC> request) | |
319 : test_context_(test_context), binding_(this, std::move(request)) {} | |
320 | |
321 ~TestCImpl() override { test_context_->IncrementNumCDeletes(); } | |
322 | |
323 private: | |
324 void C(const Callback<void()>& callback) override { | |
325 test_context_->IncrementNumCCalls(); | |
326 callback.Run(); | |
327 } | |
328 | |
329 TesterContext* test_context_; | |
330 StrongBinding<TestC> binding_; | |
331 }; | |
332 | |
333 class Tester : public ShellClient, | |
334 public ApplicationLoader, | |
335 public InterfaceFactory<TestA>, | |
336 public InterfaceFactory<TestB>, | |
337 public InterfaceFactory<TestC> { | |
338 public: | |
339 Tester(TesterContext* context, const std::string& requestor_url) | |
340 : context_(context), requestor_url_(requestor_url) {} | |
341 ~Tester() override {} | |
342 | |
343 private: | |
344 void Load(const GURL& url, | |
345 InterfaceRequest<mojom::ShellClient> request) override { | |
346 app_.reset(new ShellConnection(this, std::move(request))); | |
347 } | |
348 | |
349 bool AcceptConnection(Connection* connection) override { | |
350 if (!requestor_url_.empty() && | |
351 requestor_url_ != connection->GetRemoteApplicationURL()) { | |
352 context_->set_tester_called_quit(); | |
353 context_->QuitSoon(); | |
354 base::MessageLoop::current()->QuitWhenIdle(); | |
355 return false; | |
356 } | |
357 // If we're coming from A, then add B, otherwise A. | |
358 if (connection->GetRemoteApplicationURL() == kTestAURLString) | |
359 connection->AddInterface<TestB>(this); | |
360 else | |
361 connection->AddInterface<TestA>(this); | |
362 return true; | |
363 } | |
364 | |
365 void Create(Connection* connection, | |
366 InterfaceRequest<TestA> request) override { | |
367 a_bindings_.push_back( | |
368 new TestAImpl(app_.get(), context_, std::move(request), this)); | |
369 } | |
370 | |
371 void Create(Connection* connection, | |
372 InterfaceRequest<TestB> request) override { | |
373 new TestBImpl(connection, context_, std::move(request)); | |
374 } | |
375 | |
376 void Create(Connection* connection, | |
377 InterfaceRequest<TestC> request) override { | |
378 new TestCImpl(connection, context_, std::move(request)); | |
379 } | |
380 | |
381 TesterContext* context_; | |
382 scoped_ptr<ShellConnection> app_; | |
383 std::string requestor_url_; | |
384 ScopedVector<TestAImpl> a_bindings_; | |
385 }; | |
386 | |
387 class ApplicationManagerTest : public testing::Test { | |
388 public: | |
389 ApplicationManagerTest() : tester_context_(&loop_) {} | |
390 | |
391 ~ApplicationManagerTest() override {} | |
392 | |
393 void SetUp() override { | |
394 application_manager_.reset(new ApplicationManager(true)); | |
395 test_loader_ = new TestApplicationLoader; | |
396 test_loader_->set_context(&context_); | |
397 application_manager_->set_default_loader( | |
398 scoped_ptr<ApplicationLoader>(test_loader_)); | |
399 | |
400 TestServicePtr service_proxy; | |
401 ConnectToInterface(application_manager_.get(), CreateShellIdentity(), | |
402 GURL(kTestURLString), &service_proxy); | |
403 test_client_.reset(new TestClient(std::move(service_proxy))); | |
404 } | |
405 | |
406 void TearDown() override { | |
407 test_client_.reset(); | |
408 application_manager_.reset(); | |
409 } | |
410 | |
411 void AddLoaderForURL(const GURL& url, const std::string& requestor_url) { | |
412 application_manager_->SetLoaderForURL( | |
413 make_scoped_ptr(new Tester(&tester_context_, requestor_url)), url); | |
414 } | |
415 | |
416 bool HasRunningInstanceForURL(const GURL& url) { | |
417 ApplicationManager::TestAPI manager_test_api(application_manager_.get()); | |
418 return manager_test_api.HasRunningInstanceForURL(url); | |
419 } | |
420 | |
421 protected: | |
422 base::ShadowingAtExitManager at_exit_; | |
423 TestApplicationLoader* test_loader_; | |
424 TesterContext tester_context_; | |
425 TestContext context_; | |
426 base::MessageLoop loop_; | |
427 scoped_ptr<TestClient> test_client_; | |
428 scoped_ptr<ApplicationManager> application_manager_; | |
429 DISALLOW_COPY_AND_ASSIGN(ApplicationManagerTest); | |
430 }; | |
431 | |
432 TEST_F(ApplicationManagerTest, Basic) { | |
433 test_client_->Test("test"); | |
434 loop_.Run(); | |
435 EXPECT_EQ(std::string("test"), context_.last_test_string); | |
436 } | |
437 | |
438 TEST_F(ApplicationManagerTest, ClientError) { | |
439 test_client_->Test("test"); | |
440 EXPECT_TRUE(HasRunningInstanceForURL(GURL(kTestURLString))); | |
441 loop_.Run(); | |
442 EXPECT_EQ(1, context_.num_impls); | |
443 test_client_.reset(); | |
444 loop_.Run(); | |
445 EXPECT_EQ(0, context_.num_impls); | |
446 EXPECT_TRUE(HasRunningInstanceForURL(GURL(kTestURLString))); | |
447 } | |
448 | |
449 TEST_F(ApplicationManagerTest, Deletes) { | |
450 { | |
451 ApplicationManager am(true); | |
452 TestApplicationLoader* default_loader = new TestApplicationLoader; | |
453 default_loader->set_context(&context_); | |
454 TestApplicationLoader* url_loader1 = new TestApplicationLoader; | |
455 TestApplicationLoader* url_loader2 = new TestApplicationLoader; | |
456 url_loader1->set_context(&context_); | |
457 url_loader2->set_context(&context_); | |
458 am.set_default_loader(scoped_ptr<ApplicationLoader>(default_loader)); | |
459 am.SetLoaderForURL(scoped_ptr<ApplicationLoader>(url_loader1), | |
460 GURL("test:test1")); | |
461 am.SetLoaderForURL(scoped_ptr<ApplicationLoader>(url_loader2), | |
462 GURL("test:test1")); | |
463 } | |
464 EXPECT_EQ(3, context_.num_loader_deletes); | |
465 } | |
466 | |
467 // Test for SetLoaderForURL() & set_default_loader(). | |
468 TEST_F(ApplicationManagerTest, SetLoaders) { | |
469 TestApplicationLoader* default_loader = new TestApplicationLoader; | |
470 TestApplicationLoader* url_loader = new TestApplicationLoader; | |
471 application_manager_->set_default_loader( | |
472 scoped_ptr<ApplicationLoader>(default_loader)); | |
473 application_manager_->SetLoaderForURL( | |
474 scoped_ptr<ApplicationLoader>(url_loader), GURL("test:test1")); | |
475 | |
476 // test::test1 should go to url_loader. | |
477 TestServicePtr test_service; | |
478 ConnectToInterface(application_manager_.get(), CreateShellIdentity(), | |
479 GURL("test:test1"), &test_service); | |
480 EXPECT_EQ(1, url_loader->num_loads()); | |
481 EXPECT_EQ(0, default_loader->num_loads()); | |
482 | |
483 // http::test1 should go to default loader. | |
484 ConnectToInterface(application_manager_.get(), CreateShellIdentity(), | |
485 GURL("http:test1"), &test_service); | |
486 EXPECT_EQ(1, url_loader->num_loads()); | |
487 EXPECT_EQ(1, default_loader->num_loads()); | |
488 } | |
489 | |
490 // Confirm that the url of a service is correctly passed to another service that | |
491 // it loads. | |
492 TEST_F(ApplicationManagerTest, ACallB) { | |
493 // Any url can load a. | |
494 AddLoaderForURL(GURL(kTestAURLString), std::string()); | |
495 | |
496 // Only a can load b. | |
497 AddLoaderForURL(GURL(kTestBURLString), kTestAURLString); | |
498 | |
499 TestAPtr a; | |
500 ConnectToInterface(application_manager_.get(), CreateShellIdentity(), | |
501 GURL(kTestAURLString), &a); | |
502 a->CallB(); | |
503 loop_.Run(); | |
504 EXPECT_EQ(1, tester_context_.num_b_calls()); | |
505 EXPECT_TRUE(tester_context_.a_called_quit()); | |
506 } | |
507 | |
508 // A calls B which calls C. | |
509 TEST_F(ApplicationManagerTest, BCallC) { | |
510 // Any url can load a. | |
511 AddLoaderForURL(GURL(kTestAURLString), std::string()); | |
512 | |
513 // Only a can load b. | |
514 AddLoaderForURL(GURL(kTestBURLString), kTestAURLString); | |
515 | |
516 TestAPtr a; | |
517 ConnectToInterface(application_manager_.get(), CreateShellIdentity(), | |
518 GURL(kTestAURLString), &a); | |
519 a->CallCFromB(); | |
520 loop_.Run(); | |
521 | |
522 EXPECT_EQ(1, tester_context_.num_b_calls()); | |
523 EXPECT_EQ(1, tester_context_.num_c_calls()); | |
524 EXPECT_TRUE(tester_context_.a_called_quit()); | |
525 } | |
526 | |
527 // Confirm that a service impl will be deleted if the app that connected to | |
528 // it goes away. | |
529 TEST_F(ApplicationManagerTest, BDeleted) { | |
530 AddLoaderForURL(GURL(kTestAURLString), std::string()); | |
531 AddLoaderForURL(GURL(kTestBURLString), std::string()); | |
532 | |
533 TestAPtr a; | |
534 ConnectToInterface(application_manager_.get(), CreateShellIdentity(), | |
535 GURL(kTestAURLString), &a); | |
536 | |
537 a->CallB(); | |
538 loop_.Run(); | |
539 | |
540 // Kills the a app. | |
541 application_manager_->SetLoaderForURL(scoped_ptr<ApplicationLoader>(), | |
542 GURL(kTestAURLString)); | |
543 loop_.Run(); | |
544 | |
545 EXPECT_EQ(1, tester_context_.num_b_deletes()); | |
546 } | |
547 | |
548 // Confirm that the url of a service is correctly passed to another service that | |
549 // it loads, and that it can be rejected. | |
550 TEST_F(ApplicationManagerTest, ANoLoadB) { | |
551 // Any url can load a. | |
552 AddLoaderForURL(GURL(kTestAURLString), std::string()); | |
553 | |
554 // Only c can load b, so this will fail. | |
555 AddLoaderForURL(GURL(kTestBURLString), "test:TestC"); | |
556 | |
557 TestAPtr a; | |
558 ConnectToInterface(application_manager_.get(), CreateShellIdentity(), | |
559 GURL(kTestAURLString), &a); | |
560 a->CallB(); | |
561 loop_.Run(); | |
562 EXPECT_EQ(0, tester_context_.num_b_calls()); | |
563 | |
564 EXPECT_FALSE(tester_context_.a_called_quit()); | |
565 EXPECT_TRUE(tester_context_.tester_called_quit()); | |
566 } | |
567 | |
568 TEST_F(ApplicationManagerTest, NoServiceNoLoad) { | |
569 AddLoaderForURL(GURL(kTestAURLString), std::string()); | |
570 | |
571 // There is no TestC service implementation registered with | |
572 // ApplicationManager, so this cannot succeed (but also shouldn't crash). | |
573 TestCPtr c; | |
574 ConnectToInterface(application_manager_.get(), CreateShellIdentity(), | |
575 GURL(kTestAURLString), &c); | |
576 c.set_connection_error_handler( | |
577 []() { base::MessageLoop::current()->QuitWhenIdle(); }); | |
578 | |
579 loop_.Run(); | |
580 EXPECT_TRUE(c.encountered_error()); | |
581 } | |
582 | |
583 TEST_F(ApplicationManagerTest, TestEndApplicationClosure) { | |
584 ClosingApplicationLoader* loader = new ClosingApplicationLoader(); | |
585 application_manager_->SetLoaderForURL( | |
586 scoped_ptr<ApplicationLoader>(loader), GURL("test:test")); | |
587 | |
588 bool called = false; | |
589 scoped_ptr<ConnectToApplicationParams> params(new ConnectToApplicationParams); | |
590 params->SetTargetURL(GURL("test:test")); | |
591 params->set_on_application_end( | |
592 base::Bind(&QuitClosure, base::Unretained(&called))); | |
593 application_manager_->ConnectToApplication(std::move(params)); | |
594 loop_.Run(); | |
595 EXPECT_TRUE(called); | |
596 } | |
597 | |
598 TEST_F(ApplicationManagerTest, SameIdentityShouldNotCauseDuplicateLoad) { | |
599 // 1 because ApplicationManagerTest connects once at startup. | |
600 EXPECT_EQ(1, test_loader_->num_loads()); | |
601 | |
602 TestServicePtr test_service; | |
603 ConnectToInterface(application_manager_.get(), CreateShellIdentity(), | |
604 GURL("mojo:foo"), &test_service); | |
605 EXPECT_EQ(2, test_loader_->num_loads()); | |
606 | |
607 // Exactly the same URL as above. | |
608 ConnectToInterface(application_manager_.get(), CreateShellIdentity(), | |
609 GURL("mojo:foo"), &test_service); | |
610 EXPECT_EQ(2, test_loader_->num_loads()); | |
611 | |
612 // A different identity because the domain is different. | |
613 ConnectToInterface(application_manager_.get(), CreateShellIdentity(), | |
614 GURL("mojo:bar"), &test_service); | |
615 EXPECT_EQ(3, test_loader_->num_loads()); | |
616 } | |
617 | |
618 } // namespace test | |
619 } // namespace shell | |
620 } // namespace mojo | |
OLD | NEW |