Index: services/shell/tests/loader_unittest.cc |
diff --git a/services/shell/tests/loader_unittest.cc b/services/shell/tests/loader_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..757514a2932f5f09c40543b5acee0bdd180c7e1d |
--- /dev/null |
+++ b/services/shell/tests/loader_unittest.cc |
@@ -0,0 +1,560 @@ |
+// Copyright 2014 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include <memory> |
+#include <utility> |
+ |
+#include "base/at_exit.h" |
+#include "base/bind.h" |
+#include "base/macros.h" |
+#include "base/memory/ptr_util.h" |
+#include "base/memory/scoped_vector.h" |
+#include "base/message_loop/message_loop.h" |
+#include "base/run_loop.h" |
+#include "mojo/public/cpp/bindings/strong_binding.h" |
+#include "services/catalog/catalog.h" |
+#include "services/catalog/store.h" |
+#include "services/shell/connect_util.h" |
+#include "services/shell/loader.h" |
+#include "services/shell/public/cpp/connector.h" |
+#include "services/shell/public/cpp/interface_factory.h" |
+#include "services/shell/public/cpp/shell_client.h" |
+#include "services/shell/public/cpp/shell_connection.h" |
+#include "services/shell/public/interfaces/interface_provider.mojom.h" |
+#include "services/shell/shell.h" |
+#include "services/shell/tests/test.mojom.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+namespace shell { |
+namespace test { |
+ |
+const char kTestURLString[] = "test:testService"; |
+const char kTestAURLString[] = "test:TestA"; |
+const char kTestBURLString[] = "test:TestB"; |
+ |
+struct TestContext { |
+ TestContext() : num_impls(0), num_loader_deletes(0) {} |
+ std::string last_test_string; |
+ int num_impls; |
+ int num_loader_deletes; |
+}; |
+ |
+void QuitClosure(const Identity& expected, |
+ bool* value, |
+ const Identity& actual) { |
+ if (expected == actual) { |
+ *value = true; |
+ base::MessageLoop::current()->QuitWhenIdle(); |
+ } |
+} |
+ |
+class TestServiceImpl : public TestService { |
+ public: |
+ TestServiceImpl(TestContext* context, TestServiceRequest request) |
+ : context_(context), binding_(this, std::move(request)) { |
+ ++context_->num_impls; |
+ } |
+ |
+ ~TestServiceImpl() override { |
+ --context_->num_impls; |
+ if (!base::MessageLoop::current()->is_running()) |
+ return; |
+ base::MessageLoop::current()->QuitWhenIdle(); |
+ } |
+ |
+ // TestService implementation: |
+ void Test(const mojo::String& test_string, |
+ const mojo::Closure& callback) override { |
+ context_->last_test_string = test_string; |
+ callback.Run(); |
+ } |
+ |
+ private: |
+ TestContext* context_; |
+ mojo::StrongBinding<TestService> binding_; |
+}; |
+ |
+class TestClient { |
+ public: |
+ explicit TestClient(TestServicePtr service) |
+ : service_(std::move(service)), quit_after_ack_(false) {} |
+ |
+ void AckTest() { |
+ if (quit_after_ack_) |
+ base::MessageLoop::current()->QuitWhenIdle(); |
+ } |
+ |
+ void Test(const std::string& test_string) { |
+ quit_after_ack_ = true; |
+ service_->Test(test_string, |
+ base::Bind(&TestClient::AckTest, base::Unretained(this))); |
+ } |
+ |
+ private: |
+ TestServicePtr service_; |
+ bool quit_after_ack_; |
+ DISALLOW_COPY_AND_ASSIGN(TestClient); |
+}; |
+ |
+class TestLoader : public Loader, |
+ public ShellClient, |
+ public InterfaceFactory<TestService> { |
+ public: |
+ explicit TestLoader(TestContext* context) |
+ : context_(context), num_loads_(0) {} |
+ |
+ ~TestLoader() override { |
+ ++context_->num_loader_deletes; |
+ shell_connection_.reset(); |
+ } |
+ |
+ int num_loads() const { return num_loads_; } |
+ const std::string& last_requestor_name() const { |
+ return last_requestor_name_; |
+ } |
+ |
+ private: |
+ // Loader implementation. |
+ void Load(const std::string& name, |
+ mojom::ShellClientRequest request) override { |
+ ++num_loads_; |
+ shell_connection_.reset(new ShellConnection(this, std::move(request))); |
+ } |
+ |
+ // ShellClient implementation. |
+ bool AcceptConnection(Connection* connection) override { |
+ connection->AddInterface<TestService>(this); |
+ last_requestor_name_ = connection->GetRemoteIdentity().name(); |
+ return true; |
+ } |
+ |
+ // InterfaceFactory<TestService> implementation. |
+ void Create(Connection* connection, TestServiceRequest request) override { |
+ new TestServiceImpl(context_, std::move(request)); |
+ } |
+ |
+ std::unique_ptr<ShellConnection> shell_connection_; |
+ TestContext* context_; |
+ int num_loads_; |
+ std::string last_requestor_name_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(TestLoader); |
+}; |
+ |
+class ClosingLoader : public Loader { |
+ private: |
+ // Loader implementation. |
+ void Load(const std::string& name, |
+ mojom::ShellClientRequest request) override { |
+ } |
+}; |
+ |
+class TesterContext { |
+ public: |
+ explicit TesterContext(base::MessageLoop* loop) |
+ : num_b_calls_(0), |
+ num_c_calls_(0), |
+ num_a_deletes_(0), |
+ num_b_deletes_(0), |
+ num_c_deletes_(0), |
+ tester_called_quit_(false), |
+ a_called_quit_(false), |
+ loop_(loop) {} |
+ |
+ void IncrementNumBCalls() { |
+ base::AutoLock lock(lock_); |
+ num_b_calls_++; |
+ } |
+ |
+ void IncrementNumCCalls() { |
+ base::AutoLock lock(lock_); |
+ num_c_calls_++; |
+ } |
+ |
+ void IncrementNumADeletes() { |
+ base::AutoLock lock(lock_); |
+ num_a_deletes_++; |
+ } |
+ |
+ void IncrementNumBDeletes() { |
+ base::AutoLock lock(lock_); |
+ num_b_deletes_++; |
+ } |
+ |
+ void IncrementNumCDeletes() { |
+ base::AutoLock lock(lock_); |
+ num_c_deletes_++; |
+ } |
+ |
+ void set_tester_called_quit() { |
+ base::AutoLock lock(lock_); |
+ tester_called_quit_ = true; |
+ } |
+ |
+ void set_a_called_quit() { |
+ base::AutoLock lock(lock_); |
+ a_called_quit_ = true; |
+ } |
+ |
+ int num_b_calls() { |
+ base::AutoLock lock(lock_); |
+ return num_b_calls_; |
+ } |
+ int num_c_calls() { |
+ base::AutoLock lock(lock_); |
+ return num_c_calls_; |
+ } |
+ int num_a_deletes() { |
+ base::AutoLock lock(lock_); |
+ return num_a_deletes_; |
+ } |
+ int num_b_deletes() { |
+ base::AutoLock lock(lock_); |
+ return num_b_deletes_; |
+ } |
+ int num_c_deletes() { |
+ base::AutoLock lock(lock_); |
+ return num_c_deletes_; |
+ } |
+ bool tester_called_quit() { |
+ base::AutoLock lock(lock_); |
+ return tester_called_quit_; |
+ } |
+ bool a_called_quit() { |
+ base::AutoLock lock(lock_); |
+ return a_called_quit_; |
+ } |
+ |
+ void QuitSoon() { |
+ loop_->PostTask(FROM_HERE, base::MessageLoop::QuitWhenIdleClosure()); |
+ } |
+ |
+ private: |
+ // lock_ protects all members except for loop_ which must be unchanged for the |
+ // lifetime of this class. |
+ base::Lock lock_; |
+ int num_b_calls_; |
+ int num_c_calls_; |
+ int num_a_deletes_; |
+ int num_b_deletes_; |
+ int num_c_deletes_; |
+ bool tester_called_quit_; |
+ bool a_called_quit_; |
+ |
+ base::MessageLoop* loop_; |
+}; |
+ |
+// Used to test that the requestor name will be correctly passed. |
+class TestAImpl : public TestA { |
+ public: |
+ TestAImpl(Connector* connector, |
+ TesterContext* test_context, |
+ TestARequest request, |
+ InterfaceFactory<TestC>* factory) |
+ : test_context_(test_context), binding_(this, std::move(request)) { |
+ connection_ = connector->Connect(kTestBURLString); |
+ connection_->AddInterface<TestC>(factory); |
+ connection_->GetInterface(&b_); |
+ } |
+ |
+ ~TestAImpl() override { |
+ test_context_->IncrementNumADeletes(); |
+ if (base::MessageLoop::current()->is_running()) |
+ Quit(); |
+ } |
+ |
+ private: |
+ void CallB() override { |
+ b_->B(base::Bind(&TestAImpl::Quit, base::Unretained(this))); |
+ } |
+ |
+ void CallCFromB() override { |
+ b_->CallC(base::Bind(&TestAImpl::Quit, base::Unretained(this))); |
+ } |
+ |
+ void Quit() { |
+ base::MessageLoop::current()->QuitWhenIdle(); |
+ test_context_->set_a_called_quit(); |
+ test_context_->QuitSoon(); |
+ } |
+ |
+ std::unique_ptr<Connection> connection_; |
+ TesterContext* test_context_; |
+ TestBPtr b_; |
+ mojo::StrongBinding<TestA> binding_; |
+}; |
+ |
+class TestBImpl : public TestB { |
+ public: |
+ TestBImpl(Connection* connection, |
+ TesterContext* test_context, |
+ TestBRequest request) |
+ : test_context_(test_context), binding_(this, std::move(request)) { |
+ connection->GetInterface(&c_); |
+ } |
+ |
+ ~TestBImpl() override { |
+ test_context_->IncrementNumBDeletes(); |
+ if (base::MessageLoop::current()->is_running()) |
+ base::MessageLoop::current()->QuitWhenIdle(); |
+ test_context_->QuitSoon(); |
+ } |
+ |
+ private: |
+ void B(const mojo::Closure& callback) override { |
+ test_context_->IncrementNumBCalls(); |
+ callback.Run(); |
+ } |
+ |
+ void CallC(const mojo::Closure& callback) override { |
+ test_context_->IncrementNumBCalls(); |
+ c_->C(callback); |
+ } |
+ |
+ TesterContext* test_context_; |
+ TestCPtr c_; |
+ mojo::StrongBinding<TestB> binding_; |
+}; |
+ |
+class TestCImpl : public TestC { |
+ public: |
+ TestCImpl(Connection* connection, |
+ TesterContext* test_context, |
+ TestCRequest request) |
+ : test_context_(test_context), binding_(this, std::move(request)) {} |
+ |
+ ~TestCImpl() override { test_context_->IncrementNumCDeletes(); } |
+ |
+ private: |
+ void C(const mojo::Closure& callback) override { |
+ test_context_->IncrementNumCCalls(); |
+ callback.Run(); |
+ } |
+ |
+ TesterContext* test_context_; |
+ mojo::StrongBinding<TestC> binding_; |
+}; |
+ |
+class Tester : public ShellClient, |
+ public Loader, |
+ public InterfaceFactory<TestA>, |
+ public InterfaceFactory<TestB>, |
+ public InterfaceFactory<TestC> { |
+ public: |
+ Tester(TesterContext* context, const std::string& requestor_name) |
+ : context_(context), requestor_name_(requestor_name) {} |
+ ~Tester() override {} |
+ |
+ private: |
+ void Load(const std::string& name, |
+ mojom::ShellClientRequest request) override { |
+ app_.reset(new ShellConnection(this, std::move(request))); |
+ } |
+ |
+ bool AcceptConnection(Connection* connection) override { |
+ if (!requestor_name_.empty() && |
+ requestor_name_ != connection->GetRemoteIdentity().name()) { |
+ context_->set_tester_called_quit(); |
+ context_->QuitSoon(); |
+ base::MessageLoop::current()->QuitWhenIdle(); |
+ return false; |
+ } |
+ // If we're coming from A, then add B, otherwise A. |
+ if (connection->GetRemoteIdentity().name() == kTestAURLString) |
+ connection->AddInterface<TestB>(this); |
+ else |
+ connection->AddInterface<TestA>(this); |
+ return true; |
+ } |
+ |
+ void Create(Connection* connection, TestARequest request) override { |
+ a_bindings_.push_back( |
+ new TestAImpl(app_->connector(), context_, std::move(request), this)); |
+ } |
+ |
+ void Create(Connection* connection, TestBRequest request) override { |
+ new TestBImpl(connection, context_, std::move(request)); |
+ } |
+ |
+ void Create(Connection* connection, TestCRequest request) override { |
+ new TestCImpl(connection, context_, std::move(request)); |
+ } |
+ |
+ TesterContext* context_; |
+ std::unique_ptr<ShellConnection> app_; |
+ std::string requestor_name_; |
+ ScopedVector<TestAImpl> a_bindings_; |
+}; |
+ |
+void OnConnect(base::RunLoop* loop, |
+ mojom::ConnectResult result, |
+ const mojo::String& user_id, |
+ uint32_t instance_id) { |
+ loop->Quit(); |
+} |
+ |
+class LoaderTest : public testing::Test { |
+ public: |
+ LoaderTest() : tester_context_(&loop_) {} |
+ ~LoaderTest() override {} |
+ |
+ void SetUp() override { |
+ blocking_pool_ = new base::SequencedWorkerPool(3, "blocking_pool"); |
+ catalog_.reset( |
+ new catalog::Catalog(blocking_pool_.get(), nullptr, nullptr)); |
+ shell_.reset(new Shell(nullptr, catalog_->TakeShellClient())); |
+ test_loader_ = new TestLoader(&context_); |
+ shell_->set_default_loader(std::unique_ptr<Loader>(test_loader_)); |
+ |
+ TestServicePtr service_proxy; |
+ ConnectToInterface(kTestURLString, &service_proxy); |
+ test_client_.reset(new TestClient(std::move(service_proxy))); |
+ } |
+ |
+ void TearDown() override { |
+ test_client_.reset(); |
+ shell_.reset(); |
+ blocking_pool_->Shutdown(); |
+ } |
+ |
+ void AddLoaderForName(const std::string& name, |
+ const std::string& requestor_name) { |
+ shell_->SetLoaderForName( |
+ base::WrapUnique(new Tester(&tester_context_, requestor_name)), name); |
+ } |
+ |
+ bool HasRunningInstanceForName(const std::string& name) { |
+ Shell::TestAPI test_api(shell_.get()); |
+ return test_api.HasRunningInstanceForName(name); |
+ } |
+ |
+ protected: |
+ template <typename Interface> |
+ void ConnectToInterface(const std::string& name, |
+ mojo::InterfacePtr<Interface>* ptr) { |
+ base::RunLoop loop; |
+ mojom::InterfaceProviderPtr remote_interfaces; |
+ std::unique_ptr<ConnectParams> params(new ConnectParams); |
+ params->set_source(CreateShellIdentity()); |
+ params->set_target(Identity(name, mojom::kRootUserID)); |
+ params->set_remote_interfaces(mojo::GetProxy(&remote_interfaces)); |
+ params->set_connect_callback( |
+ base::Bind(&OnConnect, base::Unretained(&loop))); |
+ shell_->Connect(std::move(params)); |
+ loop.Run(); |
+ |
+ GetInterface(remote_interfaces.get(), ptr); |
+ } |
+ |
+ base::ShadowingAtExitManager at_exit_; |
+ TestLoader* test_loader_; |
+ TesterContext tester_context_; |
+ TestContext context_; |
+ base::MessageLoop loop_; |
+ std::unique_ptr<TestClient> test_client_; |
+ std::unique_ptr<catalog::Catalog> catalog_; |
+ scoped_refptr<base::SequencedWorkerPool> blocking_pool_; |
+ std::unique_ptr<Shell> shell_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(LoaderTest); |
+}; |
+ |
+TEST_F(LoaderTest, Basic) { |
+ test_client_->Test("test"); |
+ loop_.Run(); |
+ EXPECT_EQ(std::string("test"), context_.last_test_string); |
+} |
+ |
+TEST_F(LoaderTest, ClientError) { |
+ test_client_->Test("test"); |
+ EXPECT_TRUE(HasRunningInstanceForName(kTestURLString)); |
+ loop_.Run(); |
+ EXPECT_EQ(1, context_.num_impls); |
+ test_client_.reset(); |
+ loop_.Run(); |
+ EXPECT_EQ(0, context_.num_impls); |
+ EXPECT_TRUE(HasRunningInstanceForName(kTestURLString)); |
+} |
+ |
+TEST_F(LoaderTest, Deletes) { |
+ { |
+ catalog::Catalog catalog(blocking_pool_.get(), nullptr, nullptr); |
+ Shell shell(nullptr, catalog.TakeShellClient()); |
+ TestLoader* default_loader = new TestLoader(&context_); |
+ TestLoader* name_loader1 = new TestLoader(&context_); |
+ TestLoader* name_loader2 = new TestLoader(&context_); |
+ shell.set_default_loader(std::unique_ptr<Loader>(default_loader)); |
+ shell.SetLoaderForName(std::unique_ptr<Loader>(name_loader1), "test:test1"); |
+ shell.SetLoaderForName(std::unique_ptr<Loader>(name_loader2), "test:test1"); |
+ } |
+ EXPECT_EQ(3, context_.num_loader_deletes); |
+} |
+ |
+// Test for SetLoaderForName() & set_default_loader(). |
+TEST_F(LoaderTest, SetLoaders) { |
+ TestLoader* default_loader = new TestLoader(&context_); |
+ TestLoader* name_loader = new TestLoader(&context_); |
+ shell_->set_default_loader(std::unique_ptr<Loader>(default_loader)); |
+ shell_->SetLoaderForName(std::unique_ptr<Loader>(name_loader), "test:test1"); |
+ |
+ // test::test1 should go to name_loader. |
+ TestServicePtr test_service; |
+ ConnectToInterface("test:test1", &test_service); |
+ EXPECT_EQ(1, name_loader->num_loads()); |
+ EXPECT_EQ(0, default_loader->num_loads()); |
+ |
+ // http::test1 should go to default loader. |
+ ConnectToInterface("http:test1", &test_service); |
+ EXPECT_EQ(1, name_loader->num_loads()); |
+ EXPECT_EQ(1, default_loader->num_loads()); |
+} |
+ |
+TEST_F(LoaderTest, NoServiceNoLoad) { |
+ AddLoaderForName(kTestAURLString, std::string()); |
+ |
+ // There is no TestC service implementation registered with the Shell, so this |
+ // cannot succeed (but also shouldn't crash). |
+ TestCPtr c; |
+ ConnectToInterface(kTestAURLString, &c); |
+ c.set_connection_error_handler( |
+ []() { base::MessageLoop::current()->QuitWhenIdle(); }); |
+ |
+ loop_.Run(); |
+ EXPECT_TRUE(c.encountered_error()); |
+} |
+ |
+TEST_F(LoaderTest, TestEndApplicationClosure) { |
+ ClosingLoader* loader = new ClosingLoader(); |
+ shell_->SetLoaderForName(std::unique_ptr<Loader>(loader), "test:test"); |
+ |
+ bool called = false; |
+ std::unique_ptr<ConnectParams> params(new ConnectParams); |
+ params->set_source(CreateShellIdentity()); |
+ params->set_target(Identity("test:test", mojom::kRootUserID)); |
+ shell_->SetInstanceQuitCallback( |
+ base::Bind(&QuitClosure, params->target(), &called)); |
+ shell_->Connect(std::move(params)); |
+ loop_.Run(); |
+ EXPECT_TRUE(called); |
+} |
+ |
+TEST_F(LoaderTest, SameIdentityShouldNotCauseDuplicateLoad) { |
+ // 1 because LoaderTest connects once at startup. |
+ EXPECT_EQ(1, test_loader_->num_loads()); |
+ |
+ TestServicePtr test_service; |
+ ConnectToInterface("test:foo", &test_service); |
+ EXPECT_EQ(2, test_loader_->num_loads()); |
+ |
+ // Exactly the same name as above. |
+ ConnectToInterface("test:foo", &test_service); |
+ EXPECT_EQ(2, test_loader_->num_loads()); |
+ |
+ // A different identity because the domain is different. |
+ ConnectToInterface("test:bar", &test_service); |
+ EXPECT_EQ(3, test_loader_->num_loads()); |
+} |
+ |
+} // namespace test |
+} // namespace shell |