blob: db21715ba6a18a9a321047b7d1b98056afaccd5d [file] [log] [blame]
// Copyright 2018 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 <lib/fidl/cpp/binding.h>
#include <lib/zx/channel.h>
#include "base/fuchsia/component_context.h"
#include "base/fuchsia/fuchsia_logging.h"
#include "base/fuchsia/service_directory.h"
#include "base/message_loop/message_loop.h"
#include "base/strings/stringprintf.h"
#include "base/test/test_timeouts.h"
#include "fuchsia/common/test/test_common.h"
#include "fuchsia/fidl/chromium/web/cpp/fidl.h"
#include "fuchsia/runners/cast/cast_runner.h"
#include "fuchsia/runners/cast/fake_application_config_manager.h"
#include "fuchsia/runners/cast/test_common.h"
#include "fuchsia/runners/common/web_component.h"
#include "fuchsia/runners/common/web_content_runner.h"
#include "fuchsia/test/promise.h"
#include "net/test/embedded_test_server/default_handlers.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace castrunner {
namespace {
const char kTestServerRoot[] =
FILE_PATH_LITERAL("fuchsia/runners/cast/testdata");
void ComponentErrorHandler(zx_status_t status) {
ZX_LOG(ERROR, status) << "Component launch failed.";
ADD_FAILURE();
}
} // namespace
class CastRunnerIntegrationTest : public testing::Test,
public chromium::cast::CastChannel {
public:
CastRunnerIntegrationTest()
: run_timeout_(TestTimeouts::action_timeout()),
cast_channel_binding_(this) {
// Create a new test ServiceDirectory, and ServiceDirectoryClient connected
// to it, for tests to use to drive the CastRunner.
fidl::InterfaceHandle<fuchsia::io::Directory> directory;
test_services_ = std::make_unique<base::fuchsia::ServiceDirectory>(
directory.NewRequest());
test_services_client_ =
std::make_unique<base::fuchsia::ServiceDirectoryClient>(
std::move(directory));
// Create the AppConfigManager.
app_config_binding_ = std::make_unique<
fidl::Binding<chromium::cast::ApplicationConfigManager>>(
&app_config_manager_);
chromium::cast::ApplicationConfigManagerPtr app_config_manager_interface;
app_config_binding_->Bind(app_config_manager_interface.NewRequest());
// Create the CastRunner, published into |test_services_|.
cast_runner_ = std::make_unique<CastRunner>(
test_services_.get(), WebContentRunner::CreateDefaultWebContext(),
std::move(app_config_manager_interface),
cast_runner_run_loop_.QuitClosure());
// Connect to the CastRunner's fuchsia.sys.Runner interface.
cast_runner_ptr_ =
test_services_client_->ConnectToService<fuchsia::sys::Runner>();
cast_runner_ptr_.set_error_handler([this](zx_status_t status) {
ZX_LOG(ERROR, status) << "CastRunner closed channel.";
ADD_FAILURE();
cast_runner_run_loop_.Quit();
});
}
void SetUp() override {
test_server_.ServeFilesFromSourceDirectory(kTestServerRoot);
net::test_server::RegisterDefaultHandlers(&test_server_);
ASSERT_TRUE(test_server_.Start());
}
void TearDown() override {
// Disconnect the CastRunner.
cast_runner_ptr_.Unbind();
cast_runner_run_loop_.Run();
}
void WaitUntilCastChannelOpened() {
if (connected_channel_)
return;
base::RunLoop run_loop;
on_channel_connected_cb_ = run_loop.QuitClosure();
run_loop.Run();
}
// chromium::web::CastChannel implementation.
void OnOpened(fidl::InterfaceHandle<chromium::web::MessagePort> channel,
OnOpenedCallback callback_ignored) override {
// |callback_ignored| is dropped because these tests don't exercise multiple
// channel lifetimes.
connected_channel_ = channel.Bind();
if (on_channel_connected_cb_)
std::move(on_channel_connected_cb_).Run();
}
protected:
const base::RunLoop::ScopedRunTimeoutForTest run_timeout_;
base::MessageLoopForIO message_loop_;
net::EmbeddedTestServer test_server_;
// Returns fake Cast application information to the CastRunner.
FakeApplicationConfigManager app_config_manager_;
std::unique_ptr<fidl::Binding<chromium::cast::ApplicationConfigManager>>
app_config_binding_;
// The connected Cast Channel.
chromium::web::MessagePortPtr connected_channel_;
// A pending on-connect callback, to be invoked once a Cast Channel is
// received.
base::OnceClosure on_channel_connected_cb_;
// Receives connected channels from the CastRunner.
fidl::Binding<chromium::cast::CastChannel> cast_channel_binding_;
// ServiceDirectory into which the CastRunner will publish itself.
std::unique_ptr<base::fuchsia::ServiceDirectory> test_services_;
std::unique_ptr<base::fuchsia::ComponentContext> test_services_client_;
std::unique_ptr<CastRunner> cast_runner_;
fuchsia::sys::RunnerPtr cast_runner_ptr_;
base::RunLoop cast_runner_run_loop_;
DISALLOW_COPY_AND_ASSIGN(CastRunnerIntegrationTest);
};
// A basic integration test ensuring a basic cast request launches the right
// URL in the Chromium service.
TEST_F(CastRunnerIntegrationTest, BasicRequest) {
const char kBlankAppId[] = "00000000";
const char kBlankAppPath[] = "/defaultresponse";
app_config_manager_.AddAppMapping(kBlankAppId,
test_server_.GetURL(kBlankAppPath));
// Launch the test-app component.
fuchsia::sys::ComponentControllerPtr component_controller_ptr;
base::fuchsia::ServiceDirectoryClient services_client(StartCastComponent(
base::StringPrintf("cast:%s", kBlankAppId), &cast_runner_ptr_,
component_controller_ptr.NewRequest(), &cast_channel_binding_));
component_controller_ptr.set_error_handler(&ComponentErrorHandler);
// Access the NavigationController from the WebComponent. The test will hang
// here if no WebComponent was created.
chromium::web::NavigationControllerPtr nav_controller;
{
base::RunLoop run_loop;
webrunner::Promise<WebComponent*> web_component(run_loop.QuitClosure());
cast_runner_->GetWebComponentForTest(web_component.GetReceiveCallback());
run_loop.Run();
ASSERT_NE(*web_component, nullptr);
(*web_component)
->frame()
->GetNavigationController(nav_controller.NewRequest());
}
// Ensure the NavigationEntry has the expected URL.
{
base::RunLoop run_loop;
webrunner::Promise<std::unique_ptr<chromium::web::NavigationEntry>>
nav_entry(run_loop.QuitClosure());
nav_controller->GetVisibleEntry(
webrunner::ConvertToFitFunction(nav_entry.GetReceiveCallback()));
run_loop.Run();
EXPECT_EQ(nav_entry->get()->url, test_server_.GetURL(kBlankAppPath).spec());
}
// Verify that the component is torn down when |component_controller_ptr| is
// unbound, by observing the destruction of its child Interfaces.
base::RunLoop destruction_run_loop;
nav_controller.set_error_handler(
[&destruction_run_loop](zx_status_t) { destruction_run_loop.Quit(); });
component_controller_ptr.Unbind();
destruction_run_loop.Run();
}
TEST_F(CastRunnerIntegrationTest, IncorrectCastAppId) {
// Launch the test-app component.
fuchsia::sys::ComponentControllerPtr component_controller_ptr;
base::fuchsia::ServiceDirectoryClient services_client(StartCastComponent(
"cast:99999999", &cast_runner_ptr_, component_controller_ptr.NewRequest(),
&cast_channel_binding_));
component_controller_ptr.set_error_handler(&ComponentErrorHandler);
// Ensure no WebComponent was created.
base::RunLoop run_loop;
webrunner::Promise<WebComponent*> web_component(run_loop.QuitClosure());
cast_runner_->GetWebComponentForTest(web_component.GetReceiveCallback());
run_loop.Run();
EXPECT_EQ(*web_component, nullptr);
}
TEST_F(CastRunnerIntegrationTest, CastChannel) {
const char kCastChannelAppId[] = "00000001";
const char kCastChannelAppPath[] = "/cast_channel.html";
app_config_manager_.AddAppMapping(kCastChannelAppId,
test_server_.GetURL(kCastChannelAppPath));
// Launch the test-app component.
fuchsia::sys::ComponentControllerPtr component_controller_ptr;
base::fuchsia::ServiceDirectoryClient services_client(StartCastComponent(
base::StringPrintf("cast:%s", kCastChannelAppId), &cast_runner_ptr_,
component_controller_ptr.NewRequest(), &cast_channel_binding_));
component_controller_ptr.set_error_handler(&ComponentErrorHandler);
// Access the NavigationController from the WebComponent. The test will hang
// here if no WebComponent was created.
chromium::web::NavigationControllerPtr nav_controller;
{
base::RunLoop run_loop;
webrunner::Promise<WebComponent*> web_component(run_loop.QuitClosure());
cast_runner_->GetWebComponentForTest(web_component.GetReceiveCallback());
run_loop.Run();
ASSERT_NE(*web_component, nullptr);
(*web_component)
->frame()
->GetNavigationController(nav_controller.NewRequest());
}
// Ensure the NavigationEntry has the expected URL.
{
base::RunLoop run_loop;
webrunner::Promise<std::unique_ptr<chromium::web::NavigationEntry>>
nav_entry(run_loop.QuitClosure());
nav_controller->GetVisibleEntry(
webrunner::ConvertToFitFunction(nav_entry.GetReceiveCallback()));
run_loop.Run();
EXPECT_EQ(nav_entry->get()->url,
test_server_.GetURL(kCastChannelAppPath).spec());
}
WaitUntilCastChannelOpened();
auto expected_list = {"this", "is", "a", "test"};
for (const std::string& expected : expected_list) {
base::RunLoop run_loop;
webrunner::Promise<chromium::web::WebMessage> message(
run_loop.QuitClosure());
connected_channel_->ReceiveMessage(
webrunner::ConvertToFitFunction(message.GetReceiveCallback()));
run_loop.Run();
EXPECT_EQ(webrunner::StringFromMemBufferOrDie(message->data), expected);
}
component_controller_ptr.Unbind();
base::RunLoop run_loop;
cast_channel_binding_.set_error_handler(
[&run_loop](zx_status_t) { run_loop.Quit(); });
run_loop.Run();
}
TEST_F(CastRunnerIntegrationTest, CastChannelConsumerDropped) {
const char kCastChannelAppId[] = "00000001";
const char kCastChannelAppPath[] = "/cast_channel.html";
app_config_manager_.AddAppMapping(kCastChannelAppId,
test_server_.GetURL(kCastChannelAppPath));
// Launch the test-app component.
fuchsia::sys::ComponentControllerPtr component_controller_ptr;
base::fuchsia::ServiceDirectoryClient services_client(StartCastComponent(
base::StringPrintf("cast:%s", kCastChannelAppId), &cast_runner_ptr_,
component_controller_ptr.NewRequest(), &cast_channel_binding_));
// Expect that disconnecting the Cast Channel consumer service will trigger
// the destruction of the Cast Component.
cast_channel_binding_.Unbind();
base::RunLoop run_loop;
component_controller_ptr.set_error_handler(
[&run_loop](zx_status_t) { run_loop.Quit(); });
run_loop.Run();
EXPECT_FALSE(component_controller_ptr.is_bound());
}
TEST_F(CastRunnerIntegrationTest, CastChannelComponentControllerDropped) {
const char kCastChannelAppId[] = "00000001";
const char kCastChannelAppPath[] = "/cast_channel.html";
app_config_manager_.AddAppMapping(kCastChannelAppId,
test_server_.GetURL(kCastChannelAppPath));
// Launch the test-app component.
fuchsia::sys::ComponentControllerPtr component_controller_ptr;
base::fuchsia::ServiceDirectoryClient services_client(StartCastComponent(
base::StringPrintf("cast:%s", kCastChannelAppId), &cast_runner_ptr_,
component_controller_ptr.NewRequest(), &cast_channel_binding_));
// Expect that disconnecting the ComponentController will kill the Cast
// Component, which we verify indirectly by listening for the disconnection
// of one of its CastChannel FIDL client.
component_controller_ptr.Unbind();
base::RunLoop run_loop;
cast_channel_binding_.set_error_handler(
[&run_loop](zx_status_t) { run_loop.Quit(); });
run_loop.Run();
}
} // namespace castrunner