// 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(); }