1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome_frame/test/automation_client_mock.h"
6
7#include "base/bind.h"
8#include "base/bind_helpers.h"
9#include "chrome/common/automation_messages.h"
10#include "chrome_frame/custom_sync_call_context.h"
11#include "chrome_frame/navigation_constraints.h"
12#include "chrome_frame/test/chrome_frame_test_utils.h"
13#include "chrome_frame/test/test_scrubber.h"
14#include "net/base/net_errors.h"
15
16#define GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING
17#include "testing/gmock_mutant.h"
18
19using testing::_;
20using testing::CreateFunctor;
21using testing::Return;
22
23namespace {
24
25#ifndef NDEBUG
26const base::TimeDelta kChromeLaunchTimeout = base::TimeDelta::FromSeconds(15);
27#else
28const base::TimeDelta kChromeLaunchTimeout = base::TimeDelta::FromSeconds(10);
29#endif
30
31const int kSaneAutomationTimeoutMs = 10 * 1000;
32
33}  // namespace
34
35MATCHER_P(LaunchParamProfileEq, profile_name, "Check for profile name") {
36  return arg->profile_name().compare(profile_name) == 0;
37}
38
39void MockProxyFactory::GetServerImpl(ChromeFrameAutomationProxy* pxy,
40                                     void* proxy_id,
41                                     AutomationLaunchResult result,
42                                     LaunchDelegate* d,
43                                     ChromeFrameLaunchParams* params,
44                                     void** automation_server_id) {
45  *automation_server_id = proxy_id;
46  loop_->PostDelayedTask(FROM_HERE,
47      base::Bind(&LaunchDelegate::LaunchComplete,
48                 base::Unretained(d), pxy, result),
49      base::TimeDelta::FromMilliseconds(params->launch_timeout()) / 2);
50}
51
52void CFACMockTest::SetAutomationServerOk(int times) {
53  EXPECT_CALL(factory_, GetAutomationServer(testing::NotNull(),
54        LaunchParamProfileEq(profile_path_.BaseName().value()),
55        testing::NotNull()))
56    .Times(times)
57    .WillRepeatedly(testing::Invoke(CreateFunctor(&factory_,
58        &MockProxyFactory::GetServerImpl, get_proxy(), id_,
59        AUTOMATION_SUCCESS)));
60
61  EXPECT_CALL(factory_,
62      ReleaseAutomationServer(testing::Eq(id_), testing::NotNull()))
63          .Times(times);
64}
65
66void CFACMockTest::Set_CFD_LaunchFailed(AutomationLaunchResult result) {
67  EXPECT_CALL(cfd_, OnAutomationServerLaunchFailed(testing::Eq(result),
68                                                   testing::_))
69      .Times(1)
70      .WillOnce(QUIT_LOOP(loop_));
71}
72
73MATCHER_P(MsgType, msg_type, "IPC::Message::type()") {
74  const IPC::Message& m = arg;
75  return (m.type() == msg_type);
76}
77
78MATCHER_P(EqNavigationInfoUrl, url, "IPC::NavigationInfo matcher") {
79  if (url.is_valid() && url != arg.url)
80    return false;
81  // TODO(stevet): other members
82  return true;
83}
84
85// Could be implemented as MockAutomationProxy member (we have WithArgs<>!)
86ACTION_P4(HandleCreateTab, tab_handle, external_tab_container, tab_wnd,
87          session_id) {
88  // arg0 - message
89  // arg1 - callback
90  // arg2 - key
91  CreateExternalTabContext::output_type input_args(tab_wnd,
92                                                   external_tab_container,
93                                                   tab_handle,
94                                                   session_id);
95  CreateExternalTabContext* context =
96      reinterpret_cast<CreateExternalTabContext*>(arg1);
97  DispatchToMethod(context, &CreateExternalTabContext::Completed, input_args);
98  delete context;
99}
100
101ACTION_P4(InitiateNavigation, client, url, referrer, constraints) {
102  client->InitiateNavigation(url, referrer, constraints);
103}
104
105// ChromeFrameAutomationClient tests that launch Chrome.
106class CFACWithChrome : public testing::Test {
107 protected:
108  static void SetUpTestCase();
109  static void TearDownTestCase();
110
111  virtual void SetUp() OVERRIDE;
112  virtual void TearDown() OVERRIDE;
113
114  static base::FilePath profile_path_;
115  MockCFDelegate cfd_;
116  scoped_refptr<ChromeFrameAutomationClient> client_;
117  scoped_refptr<ChromeFrameLaunchParams> launch_params_;
118  chrome_frame_test::TimedMsgLoop loop_;
119};
120
121// static
122base::FilePath CFACWithChrome::profile_path_;
123
124// static
125void CFACWithChrome::SetUpTestCase() {
126  GetChromeFrameProfilePath(L"Adam.N.Epilinter", &profile_path_);
127}
128
129// static
130void CFACWithChrome::TearDownTestCase() {
131  profile_path_.clear();
132}
133
134void CFACWithChrome::SetUp() {
135  chrome_frame_test::OverrideDataDirectoryForThisTest(profile_path_.value());
136  client_ = new ChromeFrameAutomationClient();
137  GURL empty;
138  launch_params_ = new ChromeFrameLaunchParams(
139      empty, empty, profile_path_, profile_path_.BaseName().value(), L"",
140      false, false, false);
141  launch_params_->set_version_check(false);
142  launch_params_->set_launch_timeout(kSaneAutomationTimeoutMs);
143}
144
145void CFACWithChrome::TearDown() {
146  client_->Uninitialize();
147}
148
149// We mock ChromeFrameDelegate only. The rest is with real AutomationProxy
150TEST_F(CFACWithChrome, CreateTooFast) {
151  int timeout = 0;  // Chrome cannot send Hello message so fast.
152
153  EXPECT_CALL(cfd_, OnAutomationServerLaunchFailed(AUTOMATION_TIMEOUT, _))
154      .WillOnce(QUIT_LOOP(loop_));
155
156  launch_params_->set_launch_timeout(timeout);
157  EXPECT_TRUE(client_->Initialize(&cfd_, launch_params_));
158  loop_.RunFor(kChromeLaunchTimeout);
159}
160
161// This test may fail if Chrome take more that 10 seconds (timeout var) to
162// launch. In this case GMock shall print something like "unexpected call to
163// OnAutomationServerLaunchFailed". I'm yet to find out how to specify
164// that this is an unexpected call, and still to execute an action.
165TEST_F(CFACWithChrome, CreateNotSoFast) {
166  EXPECT_CALL(cfd_, OnAutomationServerReady())
167      .WillOnce(QUIT_LOOP(loop_));
168
169  EXPECT_CALL(cfd_, OnAutomationServerLaunchFailed(_, _))
170      .Times(0);
171
172  EXPECT_TRUE(client_->Initialize(&cfd_, launch_params_));
173
174  loop_.RunFor(kChromeLaunchTimeout);
175}
176
177TEST_F(CFACWithChrome, NavigateOk) {
178  NavigationConstraintsImpl navigation_constraints;
179
180  const std::string url = "about:version";
181
182  EXPECT_CALL(cfd_, OnAutomationServerReady())
183      .WillOnce(InitiateNavigation(client_.get(), url, std::string(),
184                                   &navigation_constraints));
185
186  EXPECT_CALL(cfd_, GetBounds(_)).Times(testing::AnyNumber());
187
188  EXPECT_CALL(cfd_, OnNavigationStateChanged(_))
189      .Times(testing::AnyNumber());
190
191  {
192    testing::InSequence s;
193
194    EXPECT_CALL(cfd_, OnDidNavigate(EqNavigationInfoUrl(GURL())))
195        .Times(1);
196
197    EXPECT_CALL(cfd_, OnUpdateTargetUrl(_)).Times(testing::AtMost(1));
198
199    EXPECT_CALL(cfd_, OnLoad(_))
200        .WillOnce(QUIT_LOOP(loop_));
201  }
202
203  EXPECT_TRUE(client_->Initialize(&cfd_, launch_params_));
204  loop_.RunFor(kChromeLaunchTimeout);
205}
206
207TEST_F(CFACWithChrome, NavigateFailed) {
208  NavigationConstraintsImpl navigation_constraints;
209  const std::string url = "http://127.0.0.3:65412/";
210  const net::URLRequestStatus connection_failed(net::URLRequestStatus::FAILED,
211                                                net::ERR_INVALID_URL);
212
213  cfd_.SetRequestDelegate(client_);
214
215  EXPECT_CALL(cfd_, OnAutomationServerReady())
216      .WillOnce(testing::IgnoreResult(testing::InvokeWithoutArgs(CreateFunctor(
217          client_.get(), &ChromeFrameAutomationClient::InitiateNavigation,
218          url, std::string(), &navigation_constraints))));
219
220  EXPECT_CALL(cfd_, GetBounds(_)).Times(testing::AnyNumber());
221  EXPECT_CALL(cfd_, OnNavigationStateChanged(_)).Times(testing::AnyNumber());
222
223  EXPECT_CALL(cfd_, OnRequestStart(_, _))
224      // Often there's another request for the error page
225      .Times(testing::Between(1, 2))
226      .WillRepeatedly(testing::WithArgs<0>(testing::Invoke(CreateFunctor(&cfd_,
227          &MockCFDelegate::Reply, connection_failed))));
228
229  EXPECT_CALL(cfd_, OnUpdateTargetUrl(_)).Times(testing::AnyNumber());
230  EXPECT_CALL(cfd_, OnLoad(_)).Times(testing::AtMost(1));
231
232  EXPECT_CALL(cfd_, OnNavigationFailed(_, GURL(url)))
233      .Times(1)
234      .WillOnce(QUIT_LOOP_SOON(loop_, base::TimeDelta::FromSeconds(2)));
235
236  EXPECT_TRUE(client_->Initialize(&cfd_, launch_params_));
237
238  loop_.RunFor(kChromeLaunchTimeout);
239}
240
241TEST_F(CFACMockTest, MockedCreateTabOk) {
242  int timeout = 500;
243  CreateTab();
244  SetAutomationServerOk(1);
245
246  EXPECT_CALL(mock_proxy_, server_version()).Times(testing::AnyNumber())
247      .WillRepeatedly(Return(""));
248
249  // We need some valid HWNDs, when responding to CreateExternalTab
250  HWND h1 = ::GetDesktopWindow();
251  HWND h2 = ::GetDesktopWindow();
252  EXPECT_CALL(mock_proxy_, SendAsAsync(testing::Property(
253      &IPC::SyncMessage::type, AutomationMsg_CreateExternalTab::ID),
254      testing::NotNull(), _))
255          .Times(1).WillOnce(HandleCreateTab(tab_handle_, h1, h2, 99));
256
257  EXPECT_CALL(mock_proxy_, CreateTabProxy(testing::Eq(tab_handle_)))
258      .WillOnce(Return(tab_));
259
260  EXPECT_CALL(cfd_, OnAutomationServerReady())
261      .WillOnce(QUIT_LOOP(loop_));
262
263  EXPECT_CALL(mock_proxy_, CancelAsync(_)).Times(testing::AnyNumber());
264
265  // Here we go!
266  GURL empty;
267  scoped_refptr<ChromeFrameLaunchParams> clp(new ChromeFrameLaunchParams(
268      empty, empty, profile_path_, profile_path_.BaseName().value(), L"",
269      false, false, false));
270  clp->set_launch_timeout(timeout);
271  clp->set_version_check(false);
272  EXPECT_TRUE(client_->Initialize(&cfd_, clp));
273  loop_.RunFor(base::TimeDelta::FromSeconds(10));
274
275  EXPECT_CALL(mock_proxy_, ReleaseTabProxy(testing::Eq(tab_handle_))).Times(1);
276  client_->Uninitialize();
277}
278
279TEST_F(CFACMockTest, MockedCreateTabFailed) {
280  HWND null_wnd = NULL;
281  SetAutomationServerOk(1);
282
283  EXPECT_CALL(mock_proxy_, server_version()).Times(testing::AnyNumber())
284      .WillRepeatedly(Return(""));
285
286  EXPECT_CALL(mock_proxy_, SendAsAsync(testing::Property(
287      &IPC::SyncMessage::type, AutomationMsg_CreateExternalTab::ID),
288      testing::NotNull(), _))
289          .Times(1).WillOnce(HandleCreateTab(tab_handle_, null_wnd, null_wnd,
290                                             99));
291
292  EXPECT_CALL(mock_proxy_, CreateTabProxy(_)).Times(0);
293
294  EXPECT_CALL(mock_proxy_, CancelAsync(_)).Times(testing::AnyNumber());
295
296  Set_CFD_LaunchFailed(AUTOMATION_CREATE_TAB_FAILED);
297
298  // Here we go!
299  GURL empty;
300  scoped_refptr<ChromeFrameLaunchParams> clp(new ChromeFrameLaunchParams(
301      empty, empty, profile_path_, profile_path_.BaseName().value(), L"",
302      false, false, false));
303  clp->set_launch_timeout(timeout_);
304  clp->set_version_check(false);
305  EXPECT_TRUE(client_->Initialize(&cfd_, clp));
306  loop_.RunFor(base::TimeDelta::FromSeconds(4));
307  client_->Uninitialize();
308}
309
310class TestChromeFrameAutomationProxyImpl
311    : public ChromeFrameAutomationProxyImpl {
312 public:
313  TestChromeFrameAutomationProxyImpl()
314        // 1 is an unneeded timeout.
315      : ChromeFrameAutomationProxyImpl(
316          NULL,
317          AutomationProxy::GenerateChannelID(),
318          base::TimeDelta::FromMilliseconds(1)) {
319  }
320  MOCK_METHOD3(
321      SendAsAsync,
322      void(IPC::SyncMessage* msg,
323           SyncMessageReplyDispatcher::SyncMessageCallContext* context,
324           void* key));
325  void FakeChannelError() {
326    reinterpret_cast<IPC::ChannelProxy::MessageFilter*>(message_filter_.get())->
327        OnChannelError();
328  }
329};
330
331TEST_F(CFACMockTest, OnChannelErrorEmpty) {
332  TestChromeFrameAutomationProxyImpl proxy;
333
334  // No tabs should do nothing yet still not fail either.
335  proxy.FakeChannelError();
336}
337
338TEST_F(CFACMockTest, OnChannelError) {
339  const base::TimeDelta loop_duration = base::TimeDelta::FromSeconds(11);
340  TestChromeFrameAutomationProxyImpl proxy;
341  returned_proxy_ = &proxy;
342
343  GURL empty;
344  scoped_refptr<ChromeFrameLaunchParams> clp(new ChromeFrameLaunchParams(
345      empty, empty, profile_path_, profile_path_.BaseName().value(), L"",
346      false, false, false));
347  clp->set_launch_timeout(1);  // Unneeded timeout, but can't be 0.
348  clp->set_version_check(false);
349
350  HWND h1 = ::GetDesktopWindow();
351  HWND h2 = ::GetDesktopWindow();
352  EXPECT_CALL(proxy, SendAsAsync(testing::Property(
353    &IPC::SyncMessage::type, AutomationMsg_CreateExternalTab::ID),
354    testing::NotNull(), _)).Times(3)
355        .WillOnce(HandleCreateTab(tab_handle_, h1, h2, 99))
356        .WillOnce(HandleCreateTab(tab_handle_ * 2, h1, h2, 100))
357        .WillOnce(HandleCreateTab(tab_handle_ * 3, h1, h2, 101));
358
359  SetAutomationServerOk(3);
360
361  // First, try a single tab and make sure the notification find its way to the
362  // Chrome Frame Delegate.
363  StrictMock<MockCFDelegate> cfd1;
364  scoped_refptr<ChromeFrameAutomationClient> client1;
365  client1 = new ChromeFrameAutomationClient;
366  client1->set_proxy_factory(&factory_);
367
368  EXPECT_CALL(cfd1, OnAutomationServerReady()).WillOnce(QUIT_LOOP(loop_));
369  EXPECT_TRUE(client1->Initialize(&cfd1, clp));
370  // Wait for OnAutomationServerReady to be called in the UI thread.
371  loop_.RunFor(loop_duration);
372
373  proxy.FakeChannelError();
374  EXPECT_CALL(cfd1, OnChannelError()).WillOnce(QUIT_LOOP(loop_));
375  // Wait for OnChannelError to be propagated to delegate from the UI thread.
376  loop_.RunFor(loop_duration);
377
378  // Add a second tab using a different delegate.
379  StrictMock<MockCFDelegate> cfd2;
380  scoped_refptr<ChromeFrameAutomationClient> client2;
381  client2 = new ChromeFrameAutomationClient;
382  client2->set_proxy_factory(&factory_);
383
384  EXPECT_CALL(cfd2, OnAutomationServerReady()).WillOnce(QUIT_LOOP(loop_));
385  EXPECT_TRUE(client2->Initialize(&cfd2, clp));
386  // Wait for OnAutomationServerReady to be called in the UI thread.
387  loop_.RunFor(loop_duration);
388
389  EXPECT_CALL(cfd1, OnChannelError()).Times(1);
390  EXPECT_CALL(cfd2, OnChannelError()).WillOnce(QUIT_LOOP(loop_));
391  proxy.FakeChannelError();
392  // Wait for OnChannelError to be propagated to delegate from the UI thread.
393  loop_.RunFor(loop_duration);
394
395  // And now a 3rd tab using the first delegate.
396  scoped_refptr<ChromeFrameAutomationClient> client3;
397  client3 = new ChromeFrameAutomationClient;
398  client3->set_proxy_factory(&factory_);
399
400  EXPECT_CALL(cfd1, OnAutomationServerReady()).WillOnce(QUIT_LOOP(loop_));
401  EXPECT_TRUE(client3->Initialize(&cfd1, clp));
402  // Wait for OnAutomationServerReady to be called in the UI thread.
403  loop_.RunFor(loop_duration);
404
405  EXPECT_CALL(cfd2, OnChannelError()).Times(1);
406  EXPECT_CALL(cfd1, OnChannelError()).Times(2).WillOnce(Return())
407      .WillOnce(QUIT_LOOP(loop_));
408  proxy.FakeChannelError();
409  // Wait for OnChannelError to be propagated to delegate from the UI thread.
410  loop_.RunFor(loop_duration);
411
412  // Cleanup.
413  client1->Uninitialize();
414  client2->Uninitialize();
415  client3->Uninitialize();
416  client1 = NULL;
417  client2 = NULL;
418  client3 = NULL;
419}
420
421TEST_F(CFACMockTest, NavigateTwiceAfterInitToSameUrl) {
422  int timeout = 500;
423  NavigationConstraintsImpl navigation_constraints;
424
425  CreateTab();
426  SetAutomationServerOk(1);
427
428  EXPECT_CALL(mock_proxy_, server_version()).Times(testing::AnyNumber())
429      .WillRepeatedly(Return(""));
430
431  // We need some valid HWNDs, when responding to CreateExternalTab
432  HWND h1 = ::GetDesktopWindow();
433  HWND h2 = ::GetDesktopWindow();
434  EXPECT_CALL(mock_proxy_, SendAsAsync(testing::Property(
435      &IPC::SyncMessage::type, AutomationMsg_CreateExternalTab::ID),
436      testing::NotNull(), _))
437          .Times(1).WillOnce(HandleCreateTab(tab_handle_, h1, h2, 99));
438
439  EXPECT_CALL(mock_proxy_, CreateTabProxy(testing::Eq(tab_handle_)))
440      .WillOnce(Return(tab_));
441
442  EXPECT_CALL(cfd_, OnAutomationServerReady())
443      .WillOnce(InitiateNavigation(client_.get(),
444                                   std::string("http://www.nonexistent.com"),
445                                   std::string(), &navigation_constraints));
446
447  EXPECT_CALL(mock_proxy_, SendAsAsync(testing::Property(
448      &IPC::SyncMessage::type, AutomationMsg_NavigateInExternalTab::ID),
449      testing::NotNull(), _))
450          .Times(1).WillOnce(QUIT_LOOP(loop_));
451
452  EXPECT_CALL(mock_proxy_, CancelAsync(_)).Times(testing::AnyNumber());
453
454  EXPECT_CALL(mock_proxy_, Send(
455      testing::Property(&IPC::Message::type, AutomationMsg_TabReposition::ID)))
456          .Times(1)
457          .WillOnce(Return(true));
458
459  EXPECT_CALL(cfd_, GetBounds(_)).Times(1);
460
461  // Here we go!
462  GURL empty;
463  scoped_refptr<ChromeFrameLaunchParams> launch_params(
464      new ChromeFrameLaunchParams(
465          GURL("http://www.nonexistent.com"), empty, profile_path_,
466          profile_path_.BaseName().value(), L"", false, false, false));
467  launch_params->set_launch_timeout(timeout);
468  launch_params->set_version_check(false);
469  EXPECT_TRUE(client_->Initialize(&cfd_, launch_params));
470  loop_.RunFor(base::TimeDelta::FromSeconds(10));
471
472  EXPECT_CALL(mock_proxy_, ReleaseTabProxy(testing::Eq(tab_handle_))).Times(1);
473  client_->Uninitialize();
474}
475