// Copyright (c) 2012 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 "chrome_frame/test/automation_client_mock.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "chrome/common/automation_messages.h"
#include "chrome_frame/custom_sync_call_context.h"
#include "chrome_frame/navigation_constraints.h"
#include "chrome_frame/test/chrome_frame_test_utils.h"
#include "chrome_frame/test/test_scrubber.h"
#include "net/base/net_errors.h"
#define GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING
#include "testing/gmock_mutant.h"
using testing::_;
using testing::CreateFunctor;
using testing::Return;
namespace {
#ifndef NDEBUG
const base::TimeDelta kChromeLaunchTimeout = base::TimeDelta::FromSeconds(15);
#else
const base::TimeDelta kChromeLaunchTimeout = base::TimeDelta::FromSeconds(10);
#endif
const int kSaneAutomationTimeoutMs = 10 * 1000;
} // namespace
MATCHER_P(LaunchParamProfileEq, profile_name, "Check for profile name") {
return arg->profile_name().compare(profile_name) == 0;
}
void MockProxyFactory::GetServerImpl(ChromeFrameAutomationProxy* pxy,
void* proxy_id,
AutomationLaunchResult result,
LaunchDelegate* d,
ChromeFrameLaunchParams* params,
void** automation_server_id) {
*automation_server_id = proxy_id;
loop_->PostDelayedTask(FROM_HERE,
base::Bind(&LaunchDelegate::LaunchComplete,
base::Unretained(d), pxy, result),
base::TimeDelta::FromMilliseconds(params->launch_timeout()) / 2);
}
void CFACMockTest::SetAutomationServerOk(int times) {
EXPECT_CALL(factory_, GetAutomationServer(testing::NotNull(),
LaunchParamProfileEq(profile_path_.BaseName().value()),
testing::NotNull()))
.Times(times)
.WillRepeatedly(testing::Invoke(CreateFunctor(&factory_,
&MockProxyFactory::GetServerImpl, get_proxy(), id_,
AUTOMATION_SUCCESS)));
EXPECT_CALL(factory_,
ReleaseAutomationServer(testing::Eq(id_), testing::NotNull()))
.Times(times);
}
void CFACMockTest::Set_CFD_LaunchFailed(AutomationLaunchResult result) {
EXPECT_CALL(cfd_, OnAutomationServerLaunchFailed(testing::Eq(result),
testing::_))
.Times(1)
.WillOnce(QUIT_LOOP(loop_));
}
MATCHER_P(MsgType, msg_type, "IPC::Message::type()") {
const IPC::Message& m = arg;
return (m.type() == msg_type);
}
MATCHER_P(EqNavigationInfoUrl, url, "IPC::NavigationInfo matcher") {
if (url.is_valid() && url != arg.url)
return false;
// TODO(stevet): other members
return true;
}
// Could be implemented as MockAutomationProxy member (we have WithArgs<>!)
ACTION_P4(HandleCreateTab, tab_handle, external_tab_container, tab_wnd,
session_id) {
// arg0 - message
// arg1 - callback
// arg2 - key
CreateExternalTabContext::output_type input_args(tab_wnd,
external_tab_container,
tab_handle,
session_id);
CreateExternalTabContext* context =
reinterpret_cast<CreateExternalTabContext*>(arg1);
DispatchToMethod(context, &CreateExternalTabContext::Completed, input_args);
delete context;
}
ACTION_P4(InitiateNavigation, client, url, referrer, constraints) {
client->InitiateNavigation(url, referrer, constraints);
}
// ChromeFrameAutomationClient tests that launch Chrome.
class CFACWithChrome : public testing::Test {
protected:
static void SetUpTestCase();
static void TearDownTestCase();
virtual void SetUp() OVERRIDE;
virtual void TearDown() OVERRIDE;
static base::FilePath profile_path_;
MockCFDelegate cfd_;
scoped_refptr<ChromeFrameAutomationClient> client_;
scoped_refptr<ChromeFrameLaunchParams> launch_params_;
chrome_frame_test::TimedMsgLoop loop_;
};
// static
base::FilePath CFACWithChrome::profile_path_;
// static
void CFACWithChrome::SetUpTestCase() {
GetChromeFrameProfilePath(L"Adam.N.Epilinter", &profile_path_);
}
// static
void CFACWithChrome::TearDownTestCase() {
profile_path_.clear();
}
void CFACWithChrome::SetUp() {
chrome_frame_test::OverrideDataDirectoryForThisTest(profile_path_.value());
client_ = new ChromeFrameAutomationClient();
GURL empty;
launch_params_ = new ChromeFrameLaunchParams(
empty, empty, profile_path_, profile_path_.BaseName().value(), L"",
false, false, false);
launch_params_->set_version_check(false);
launch_params_->set_launch_timeout(kSaneAutomationTimeoutMs);
}
void CFACWithChrome::TearDown() {
client_->Uninitialize();
}
// We mock ChromeFrameDelegate only. The rest is with real AutomationProxy
TEST_F(CFACWithChrome, CreateTooFast) {
int timeout = 0; // Chrome cannot send Hello message so fast.
EXPECT_CALL(cfd_, OnAutomationServerLaunchFailed(AUTOMATION_TIMEOUT, _))
.WillOnce(QUIT_LOOP(loop_));
launch_params_->set_launch_timeout(timeout);
EXPECT_TRUE(client_->Initialize(&cfd_, launch_params_));
loop_.RunFor(kChromeLaunchTimeout);
}
// This test may fail if Chrome take more that 10 seconds (timeout var) to
// launch. In this case GMock shall print something like "unexpected call to
// OnAutomationServerLaunchFailed". I'm yet to find out how to specify
// that this is an unexpected call, and still to execute an action.
TEST_F(CFACWithChrome, CreateNotSoFast) {
EXPECT_CALL(cfd_, OnAutomationServerReady())
.WillOnce(QUIT_LOOP(loop_));
EXPECT_CALL(cfd_, OnAutomationServerLaunchFailed(_, _))
.Times(0);
EXPECT_TRUE(client_->Initialize(&cfd_, launch_params_));
loop_.RunFor(kChromeLaunchTimeout);
}
TEST_F(CFACWithChrome, NavigateOk) {
NavigationConstraintsImpl navigation_constraints;
const std::string url = "about:version";
EXPECT_CALL(cfd_, OnAutomationServerReady())
.WillOnce(InitiateNavigation(client_.get(), url, std::string(),
&navigation_constraints));
EXPECT_CALL(cfd_, GetBounds(_)).Times(testing::AnyNumber());
EXPECT_CALL(cfd_, OnNavigationStateChanged(_))
.Times(testing::AnyNumber());
{
testing::InSequence s;
EXPECT_CALL(cfd_, OnDidNavigate(EqNavigationInfoUrl(GURL())))
.Times(1);
EXPECT_CALL(cfd_, OnUpdateTargetUrl(_)).Times(testing::AtMost(1));
EXPECT_CALL(cfd_, OnLoad(_))
.WillOnce(QUIT_LOOP(loop_));
}
EXPECT_TRUE(client_->Initialize(&cfd_, launch_params_));
loop_.RunFor(kChromeLaunchTimeout);
}
TEST_F(CFACWithChrome, NavigateFailed) {
NavigationConstraintsImpl navigation_constraints;
const std::string url = "http://127.0.0.3:65412/";
const net::URLRequestStatus connection_failed(net::URLRequestStatus::FAILED,
net::ERR_INVALID_URL);
cfd_.SetRequestDelegate(client_);
EXPECT_CALL(cfd_, OnAutomationServerReady())
.WillOnce(testing::IgnoreResult(testing::InvokeWithoutArgs(CreateFunctor(
client_.get(), &ChromeFrameAutomationClient::InitiateNavigation,
url, std::string(), &navigation_constraints))));
EXPECT_CALL(cfd_, GetBounds(_)).Times(testing::AnyNumber());
EXPECT_CALL(cfd_, OnNavigationStateChanged(_)).Times(testing::AnyNumber());
EXPECT_CALL(cfd_, OnRequestStart(_, _))
// Often there's another request for the error page
.Times(testing::Between(1, 2))
.WillRepeatedly(testing::WithArgs<0>(testing::Invoke(CreateFunctor(&cfd_,
&MockCFDelegate::Reply, connection_failed))));
EXPECT_CALL(cfd_, OnUpdateTargetUrl(_)).Times(testing::AnyNumber());
EXPECT_CALL(cfd_, OnLoad(_)).Times(testing::AtMost(1));
EXPECT_CALL(cfd_, OnNavigationFailed(_, GURL(url)))
.Times(1)
.WillOnce(QUIT_LOOP_SOON(loop_, base::TimeDelta::FromSeconds(2)));
EXPECT_TRUE(client_->Initialize(&cfd_, launch_params_));
loop_.RunFor(kChromeLaunchTimeout);
}
TEST_F(CFACMockTest, MockedCreateTabOk) {
int timeout = 500;
CreateTab();
SetAutomationServerOk(1);
EXPECT_CALL(mock_proxy_, server_version()).Times(testing::AnyNumber())
.WillRepeatedly(Return(""));
// We need some valid HWNDs, when responding to CreateExternalTab
HWND h1 = ::GetDesktopWindow();
HWND h2 = ::GetDesktopWindow();
EXPECT_CALL(mock_proxy_, SendAsAsync(testing::Property(
&IPC::SyncMessage::type, AutomationMsg_CreateExternalTab::ID),
testing::NotNull(), _))
.Times(1).WillOnce(HandleCreateTab(tab_handle_, h1, h2, 99));
EXPECT_CALL(mock_proxy_, CreateTabProxy(testing::Eq(tab_handle_)))
.WillOnce(Return(tab_));
EXPECT_CALL(cfd_, OnAutomationServerReady())
.WillOnce(QUIT_LOOP(loop_));
EXPECT_CALL(mock_proxy_, CancelAsync(_)).Times(testing::AnyNumber());
// Here we go!
GURL empty;
scoped_refptr<ChromeFrameLaunchParams> clp(new ChromeFrameLaunchParams(
empty, empty, profile_path_, profile_path_.BaseName().value(), L"",
false, false, false));
clp->set_launch_timeout(timeout);
clp->set_version_check(false);
EXPECT_TRUE(client_->Initialize(&cfd_, clp));
loop_.RunFor(base::TimeDelta::FromSeconds(10));
EXPECT_CALL(mock_proxy_, ReleaseTabProxy(testing::Eq(tab_handle_))).Times(1);
client_->Uninitialize();
}
TEST_F(CFACMockTest, MockedCreateTabFailed) {
HWND null_wnd = NULL;
SetAutomationServerOk(1);
EXPECT_CALL(mock_proxy_, server_version()).Times(testing::AnyNumber())
.WillRepeatedly(Return(""));
EXPECT_CALL(mock_proxy_, SendAsAsync(testing::Property(
&IPC::SyncMessage::type, AutomationMsg_CreateExternalTab::ID),
testing::NotNull(), _))
.Times(1).WillOnce(HandleCreateTab(tab_handle_, null_wnd, null_wnd,
99));
EXPECT_CALL(mock_proxy_, CreateTabProxy(_)).Times(0);
EXPECT_CALL(mock_proxy_, CancelAsync(_)).Times(testing::AnyNumber());
Set_CFD_LaunchFailed(AUTOMATION_CREATE_TAB_FAILED);
// Here we go!
GURL empty;
scoped_refptr<ChromeFrameLaunchParams> clp(new ChromeFrameLaunchParams(
empty, empty, profile_path_, profile_path_.BaseName().value(), L"",
false, false, false));
clp->set_launch_timeout(timeout_);
clp->set_version_check(false);
EXPECT_TRUE(client_->Initialize(&cfd_, clp));
loop_.RunFor(base::TimeDelta::FromSeconds(4));
client_->Uninitialize();
}
class TestChromeFrameAutomationProxyImpl
: public ChromeFrameAutomationProxyImpl {
public:
TestChromeFrameAutomationProxyImpl()
// 1 is an unneeded timeout.
: ChromeFrameAutomationProxyImpl(
NULL,
AutomationProxy::GenerateChannelID(),
base::TimeDelta::FromMilliseconds(1)) {
}
MOCK_METHOD3(
SendAsAsync,
void(IPC::SyncMessage* msg,
SyncMessageReplyDispatcher::SyncMessageCallContext* context,
void* key));
void FakeChannelError() {
reinterpret_cast<IPC::ChannelProxy::MessageFilter*>(message_filter_.get())->
OnChannelError();
}
};
TEST_F(CFACMockTest, OnChannelErrorEmpty) {
TestChromeFrameAutomationProxyImpl proxy;
// No tabs should do nothing yet still not fail either.
proxy.FakeChannelError();
}
TEST_F(CFACMockTest, OnChannelError) {
const base::TimeDelta loop_duration = base::TimeDelta::FromSeconds(11);
TestChromeFrameAutomationProxyImpl proxy;
returned_proxy_ = &proxy;
GURL empty;
scoped_refptr<ChromeFrameLaunchParams> clp(new ChromeFrameLaunchParams(
empty, empty, profile_path_, profile_path_.BaseName().value(), L"",
false, false, false));
clp->set_launch_timeout(1); // Unneeded timeout, but can't be 0.
clp->set_version_check(false);
HWND h1 = ::GetDesktopWindow();
HWND h2 = ::GetDesktopWindow();
EXPECT_CALL(proxy, SendAsAsync(testing::Property(
&IPC::SyncMessage::type, AutomationMsg_CreateExternalTab::ID),
testing::NotNull(), _)).Times(3)
.WillOnce(HandleCreateTab(tab_handle_, h1, h2, 99))
.WillOnce(HandleCreateTab(tab_handle_ * 2, h1, h2, 100))
.WillOnce(HandleCreateTab(tab_handle_ * 3, h1, h2, 101));
SetAutomationServerOk(3);
// First, try a single tab and make sure the notification find its way to the
// Chrome Frame Delegate.
StrictMock<MockCFDelegate> cfd1;
scoped_refptr<ChromeFrameAutomationClient> client1;
client1 = new ChromeFrameAutomationClient;
client1->set_proxy_factory(&factory_);
EXPECT_CALL(cfd1, OnAutomationServerReady()).WillOnce(QUIT_LOOP(loop_));
EXPECT_TRUE(client1->Initialize(&cfd1, clp));
// Wait for OnAutomationServerReady to be called in the UI thread.
loop_.RunFor(loop_duration);
proxy.FakeChannelError();
EXPECT_CALL(cfd1, OnChannelError()).WillOnce(QUIT_LOOP(loop_));
// Wait for OnChannelError to be propagated to delegate from the UI thread.
loop_.RunFor(loop_duration);
// Add a second tab using a different delegate.
StrictMock<MockCFDelegate> cfd2;
scoped_refptr<ChromeFrameAutomationClient> client2;
client2 = new ChromeFrameAutomationClient;
client2->set_proxy_factory(&factory_);
EXPECT_CALL(cfd2, OnAutomationServerReady()).WillOnce(QUIT_LOOP(loop_));
EXPECT_TRUE(client2->Initialize(&cfd2, clp));
// Wait for OnAutomationServerReady to be called in the UI thread.
loop_.RunFor(loop_duration);
EXPECT_CALL(cfd1, OnChannelError()).Times(1);
EXPECT_CALL(cfd2, OnChannelError()).WillOnce(QUIT_LOOP(loop_));
proxy.FakeChannelError();
// Wait for OnChannelError to be propagated to delegate from the UI thread.
loop_.RunFor(loop_duration);
// And now a 3rd tab using the first delegate.
scoped_refptr<ChromeFrameAutomationClient> client3;
client3 = new ChromeFrameAutomationClient;
client3->set_proxy_factory(&factory_);
EXPECT_CALL(cfd1, OnAutomationServerReady()).WillOnce(QUIT_LOOP(loop_));
EXPECT_TRUE(client3->Initialize(&cfd1, clp));
// Wait for OnAutomationServerReady to be called in the UI thread.
loop_.RunFor(loop_duration);
EXPECT_CALL(cfd2, OnChannelError()).Times(1);
EXPECT_CALL(cfd1, OnChannelError()).Times(2).WillOnce(Return())
.WillOnce(QUIT_LOOP(loop_));
proxy.FakeChannelError();
// Wait for OnChannelError to be propagated to delegate from the UI thread.
loop_.RunFor(loop_duration);
// Cleanup.
client1->Uninitialize();
client2->Uninitialize();
client3->Uninitialize();
client1 = NULL;
client2 = NULL;
client3 = NULL;
}
TEST_F(CFACMockTest, NavigateTwiceAfterInitToSameUrl) {
int timeout = 500;
NavigationConstraintsImpl navigation_constraints;
CreateTab();
SetAutomationServerOk(1);
EXPECT_CALL(mock_proxy_, server_version()).Times(testing::AnyNumber())
.WillRepeatedly(Return(""));
// We need some valid HWNDs, when responding to CreateExternalTab
HWND h1 = ::GetDesktopWindow();
HWND h2 = ::GetDesktopWindow();
EXPECT_CALL(mock_proxy_, SendAsAsync(testing::Property(
&IPC::SyncMessage::type, AutomationMsg_CreateExternalTab::ID),
testing::NotNull(), _))
.Times(1).WillOnce(HandleCreateTab(tab_handle_, h1, h2, 99));
EXPECT_CALL(mock_proxy_, CreateTabProxy(testing::Eq(tab_handle_)))
.WillOnce(Return(tab_));
EXPECT_CALL(cfd_, OnAutomationServerReady())
.WillOnce(InitiateNavigation(client_.get(),
std::string("http://www.nonexistent.com"),
std::string(), &navigation_constraints));
EXPECT_CALL(mock_proxy_, SendAsAsync(testing::Property(
&IPC::SyncMessage::type, AutomationMsg_NavigateInExternalTab::ID),
testing::NotNull(), _))
.Times(1).WillOnce(QUIT_LOOP(loop_));
EXPECT_CALL(mock_proxy_, CancelAsync(_)).Times(testing::AnyNumber());
EXPECT_CALL(mock_proxy_, Send(
testing::Property(&IPC::Message::type, AutomationMsg_TabReposition::ID)))
.Times(1)
.WillOnce(Return(true));
EXPECT_CALL(cfd_, GetBounds(_)).Times(1);
// Here we go!
GURL empty;
scoped_refptr<ChromeFrameLaunchParams> launch_params(
new ChromeFrameLaunchParams(
GURL("http://www.nonexistent.com"), empty, profile_path_,
profile_path_.BaseName().value(), L"", false, false, false));
launch_params->set_launch_timeout(timeout);
launch_params->set_version_check(false);
EXPECT_TRUE(client_->Initialize(&cfd_, launch_params));
loop_.RunFor(base::TimeDelta::FromSeconds(10));
EXPECT_CALL(mock_proxy_, ReleaseTabProxy(testing::Eq(tab_handle_))).Times(1);
client_->Uninitialize();
}