blob: 6225dfd018329182fadba3df51fb21653209aee3 [file] [log] [blame]
// 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 "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/threading/thread.h"
#include "mojo/application_manager/application_manager.h"
#include "mojo/common/common_type_converters.h"
#include "mojo/public/interfaces/application/application.mojom.h"
#include "mojo/public/interfaces/application/service_provider.mojom.h"
#include "mojo/public/interfaces/application/shell.mojom.h"
#include "mojo/shell/external_application_listener_posix.h"
#include "mojo/shell/external_application_registrar.mojom.h"
#include "mojo/shell/external_application_registrar_connection.h"
#include "net/base/net_errors.h"
#include "net/base/test_completion_callback.h"
#include "net/socket/unix_domain_client_socket_posix.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
namespace mojo {
namespace shell {
class ExternalApplicationListenerTest : public testing::Test {
public:
ExternalApplicationListenerTest() : io_thread_("io thread") {}
virtual ~ExternalApplicationListenerTest() {}
virtual void SetUp() override {
base::Thread::Options options;
options.message_loop_type = base::MessageLoop::TYPE_IO;
io_thread_.StartWithOptions(options);
listener_.reset(new ExternalApplicationListenerPosix(
loop_.task_runner(), io_thread_.task_runner()));
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
socket_path_ = temp_dir_.path().Append(FILE_PATH_LITERAL("socket"));
}
protected:
base::MessageLoop loop_;
base::RunLoop run_loop_;
base::Thread io_thread_;
base::ScopedTempDir temp_dir_;
ApplicationManager application_manager_;
base::FilePath socket_path_;
scoped_ptr<ExternalApplicationListener> listener_;
};
namespace {
class StubShellImpl : public InterfaceImpl<Shell> {
private:
virtual void ConnectToApplication(
const String& requestor_url,
InterfaceRequest<ServiceProvider> in_service_provider) override {
ServiceProviderPtr out_service_provider;
out_service_provider.Bind(in_service_provider.PassMessagePipe());
client()->AcceptConnection(requestor_url, out_service_provider.Pass());
}
};
void DoLocalRegister(const GURL& app_url, ScopedMessagePipeHandle shell) {
BindToPipe(new StubShellImpl, shell.Pass());
}
void QuitLoopOnConnect(scoped_refptr<base::TaskRunner> loop,
base::Closure quit_callback,
int rv) {
EXPECT_EQ(net::OK, rv);
loop->PostTask(FROM_HERE, quit_callback);
}
void ConnectOnIOThread(const base::FilePath& socket_path,
scoped_refptr<base::TaskRunner> to_quit,
base::Closure quit_callback) {
ExternalApplicationRegistrarConnection connection(socket_path);
connection.Connect(base::Bind(&QuitLoopOnConnect, to_quit, quit_callback));
}
} // namespace
TEST_F(ExternalApplicationListenerTest, ConnectConnection) {
listener_->ListenInBackground(socket_path_, base::Bind(&DoLocalRegister));
listener_->WaitForListening();
io_thread_.task_runner()->PostTask(FROM_HERE,
base::Bind(&ConnectOnIOThread,
socket_path_,
loop_.task_runner(),
run_loop_.QuitClosure()));
run_loop_.Run();
}
namespace {
class QuitLoopOnConnectApplicationImpl : public InterfaceImpl<Application> {
public:
QuitLoopOnConnectApplicationImpl(const std::string& url,
scoped_refptr<base::TaskRunner> loop,
base::Closure quit_callback)
: url_(url), to_quit_(loop), quit_callback_(quit_callback) {}
private:
virtual void Initialize(Array<String> args) override {}
virtual void AcceptConnection(const String& requestor_url,
ServiceProviderPtr p) override {
DVLOG(1) << url_ << " accepting connection from " << requestor_url;
to_quit_->PostTask(FROM_HERE, quit_callback_);
}
const std::string url_;
scoped_refptr<base::TaskRunner> to_quit_;
base::Closure quit_callback_;
};
class FakeExternalApplication {
public:
FakeExternalApplication(const std::string& url) : url_(url) {}
void ConnectSynchronously(const base::FilePath& socket_path) {
connection_.reset(new ExternalApplicationRegistrarConnection(socket_path));
net::TestCompletionCallback connect_callback;
connection_->Connect(connect_callback.callback());
connect_callback.WaitForResult();
}
// application_impl is the the actual implementation to be registered.
void Register(scoped_ptr<InterfaceImpl<Application>> application_impl,
base::Closure register_complete_callback) {
connection_->Register(GURL(url_), &ptr_, register_complete_callback);
application_impl_ = application_impl.Pass();
ptr_.set_client(application_impl_.get());
}
void ConnectToAppByUrl(std::string app_url) {
ServiceProviderPtr sp;
ptr_->ConnectToApplication(app_url, GetProxy(&sp));
}
const std::string& url() { return url_; }
private:
const std::string url_;
scoped_ptr<InterfaceImpl<Application>> application_impl_;
ShellPtr ptr_;
scoped_ptr<ExternalApplicationRegistrarConnection> connection_;
};
void ConnectToApp(FakeExternalApplication* connector,
FakeExternalApplication* connectee) {
connector->ConnectToAppByUrl(connectee->url());
}
void NoOp() {
}
void ConnectAndRegisterOnIOThread(const base::FilePath& socket_path,
scoped_refptr<base::TaskRunner> loop,
base::Closure quit_callback,
FakeExternalApplication* connector,
FakeExternalApplication* connectee) {
// Connect the first app to the registrar.
connector->ConnectSynchronously(socket_path);
// connector will use this implementation of the Mojo Application interface
// once registration complete.
scoped_ptr<QuitLoopOnConnectApplicationImpl> connector_app_impl(
new QuitLoopOnConnectApplicationImpl(
connector->url(), loop, quit_callback));
// Since connectee won't be ready when connector is done registering, pass
// in a do-nothing callback.
connector->Register(connector_app_impl.PassAs<InterfaceImpl<Application>>(),
base::Bind(&NoOp));
// Connect the second app to the registrar.
connectee->ConnectSynchronously(socket_path);
scoped_ptr<QuitLoopOnConnectApplicationImpl> connectee_app_impl(
new QuitLoopOnConnectApplicationImpl(
connectee->url(), loop, quit_callback));
// After connectee is successfully registered, connector should be
// able to connect to is by URL. Pass in a callback to attempt the
// app -> app connection.
connectee->Register(connectee_app_impl.PassAs<InterfaceImpl<Application>>(),
base::Bind(&ConnectToApp, connector, connectee));
}
void DestroyOnIOThread(scoped_ptr<FakeExternalApplication> doomed1,
scoped_ptr<FakeExternalApplication> doomed2) {
}
} // namespace
// Create two external applications, have them discover and connect to
// the registrar, and then have one app connect to the other by URL.
TEST_F(ExternalApplicationListenerTest, ConnectTwoExternalApplications) {
listener_->ListenInBackground(
socket_path_,
base::Bind(&ApplicationManager::RegisterExternalApplication,
base::Unretained(&application_manager_)));
listener_->WaitForListening();
// Create two external apps.
scoped_ptr<FakeExternalApplication> supersweet_app(
new FakeExternalApplication("http://my.supersweet.app"));
scoped_ptr<FakeExternalApplication> awesome_app(
new FakeExternalApplication("http://my.awesome.app"));
// Connecting and talking to the registrar has to happen on the IO thread.
io_thread_.task_runner()->PostTask(FROM_HERE,
base::Bind(&ConnectAndRegisterOnIOThread,
socket_path_,
loop_.task_runner(),
run_loop_.QuitClosure(),
supersweet_app.get(),
awesome_app.get()));
run_loop_.Run();
// The apps need to be destroyed on the thread where they did socket stuff.
io_thread_.task_runner()->PostTask(FROM_HERE,
base::Bind(&DestroyOnIOThread,
base::Passed(&supersweet_app),
base::Passed(&awesome_app)));
}
} // namespace shell
} // namespace mojo