cloud_print_proxy_process_browsertest.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
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// Create a service process that uses a Mock to respond to the browser in order
6// to test launching the browser using the cloud print policy check command
7// line switch.
8
9#include "base/bind.h"
10#include "base/command_line.h"
11#include "base/message_loop/message_loop.h"
12#include "base/process/kill.h"
13#include "base/rand_util.h"
14#include "base/synchronization/waitable_event.h"
15#include "base/test/multiprocess_test.h"
16#include "base/test/test_timeouts.h"
17#include "base/time/default_tick_clock.h"
18#include "base/time/time.h"
19#include "chrome/browser/prefs/browser_prefs.h"
20#include "chrome/browser/printing/cloud_print/cloud_print_proxy_service.h"
21#include "chrome/browser/printing/cloud_print/cloud_print_proxy_service_factory.h"
22#include "chrome/browser/service_process/service_process_control.h"
23#include "chrome/browser/ui/startup/startup_browser_creator.h"
24#include "chrome/common/chrome_switches.h"
25#include "chrome/common/pref_names.h"
26#include "chrome/common/service_messages.h"
27#include "chrome/common/service_process_util.h"
28#include "chrome/service/service_ipc_server.h"
29#include "chrome/service/service_process.h"
30#include "chrome/test/base/test_launcher_utils.h"
31#include "chrome/test/base/testing_browser_process.h"
32#include "chrome/test/base/testing_io_thread_state.h"
33#include "chrome/test/base/testing_pref_service_syncable.h"
34#include "chrome/test/base/testing_profile.h"
35#include "chrome/test/base/testing_profile_manager.h"
36#include "chrome/test/base/ui_test_utils.h"
37#include "components/keyed_service/core/keyed_service.h"
38#include "content/public/browser/notification_service.h"
39#include "content/public/test/test_browser_thread_bundle.h"
40#include "ipc/ipc_descriptors.h"
41#include "ipc/ipc_multiprocess_test.h"
42#include "ipc/ipc_switches.h"
43#include "testing/gmock/include/gmock/gmock.h"
44#include "testing/gtest/include/gtest/gtest.h"
45#include "testing/multiprocess_func_list.h"
46
47#if defined(OS_MACOSX)
48#include "chrome/common/mac/mock_launchd.h"
49#endif
50#if defined(OS_POSIX)
51#include "base/posix/global_descriptors.h"
52#endif
53
54using ::testing::AnyNumber;
55using ::testing::Assign;
56using ::testing::AtLeast;
57using ::testing::DoAll;
58using ::testing::Invoke;
59using ::testing::Mock;
60using ::testing::Property;
61using ::testing::Return;
62using ::testing::WithoutArgs;
63using ::testing::_;
64using content::BrowserThread;
65
66namespace {
67
68enum MockServiceProcessExitCodes {
69  kMissingSwitch = 1,
70  kInitializationFailure,
71  kExpectationsNotMet,
72  kShutdownNotGood
73};
74
75#if defined(OS_MACOSX)
76const char kTestExecutablePath[] = "test-executable-path";
77#endif
78
79bool g_good_shutdown = false;
80
81void ShutdownTask() {
82  g_good_shutdown = true;
83  g_service_process->Shutdown();
84}
85
86class TestStartupClientChannelListener : public IPC::Listener {
87 public:
88  virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE {
89    return false;
90  }
91};
92
93}  // namespace
94
95class TestServiceProcess : public ServiceProcess {
96 public:
97  TestServiceProcess() { }
98  virtual ~TestServiceProcess() { }
99
100  bool Initialize(base::MessageLoopForUI* message_loop,
101                  ServiceProcessState* state);
102
103  base::MessageLoopProxy* IOMessageLoopProxy() {
104    return io_thread_->message_loop_proxy().get();
105  }
106};
107
108bool TestServiceProcess::Initialize(base::MessageLoopForUI* message_loop,
109                                    ServiceProcessState* state) {
110  main_message_loop_ = message_loop;
111
112  service_process_state_.reset(state);
113
114  base::Thread::Options options(base::MessageLoop::TYPE_IO, 0);
115  io_thread_.reset(new base::Thread("TestServiceProcess_IO"));
116  return io_thread_->StartWithOptions(options);
117}
118
119// This mocks the service side IPC message handler, allowing us to have a
120// minimal service process.
121class MockServiceIPCServer : public ServiceIPCServer {
122 public:
123  static std::string EnabledUserId();
124
125  explicit MockServiceIPCServer(const IPC::ChannelHandle& handle)
126      : ServiceIPCServer(handle),
127        enabled_(true) { }
128
129  MOCK_METHOD1(OnMessageReceived, bool(const IPC::Message& message));
130  MOCK_METHOD1(OnChannelConnected, void(int32 peer_pid));
131  MOCK_METHOD0(OnChannelError, void());
132
133  void SetServiceEnabledExpectations();
134  void SetWillBeDisabledExpectations();
135
136  void CallServiceOnChannelConnected(int32 peer_pid) {
137    ServiceIPCServer::OnChannelConnected(peer_pid);
138  }
139
140  bool SendInfo();
141
142 private:
143  cloud_print::CloudPrintProxyInfo info_;
144  bool enabled_;
145};
146
147// static
148std::string MockServiceIPCServer::EnabledUserId() {
149  return std::string("kitteh@canhazcheezburger.cat");
150}
151
152void MockServiceIPCServer::SetServiceEnabledExpectations() {
153  EXPECT_CALL(*this, OnChannelConnected(_)).Times(1)
154      .WillRepeatedly(
155          Invoke(this, &MockServiceIPCServer::CallServiceOnChannelConnected));
156
157  EXPECT_CALL(*this, OnChannelError()).Times(0);
158  EXPECT_CALL(*this, OnMessageReceived(_)).Times(0);
159
160  EXPECT_CALL(*this,
161      OnMessageReceived(
162          Property(&IPC::Message::type,
163                   static_cast<int32>(ServiceMsg_GetCloudPrintProxyInfo::ID))))
164      .Times(AnyNumber()).WillRepeatedly(
165          WithoutArgs(Invoke(this, &MockServiceIPCServer::SendInfo)));
166
167  EXPECT_CALL(*this,
168              OnMessageReceived(
169                  Property(&IPC::Message::type,
170                           static_cast<int32>(ServiceMsg_Shutdown::ID))))
171      .Times(1)
172      .WillOnce(
173          DoAll(Assign(&g_good_shutdown, true),
174                WithoutArgs(
175                    Invoke(g_service_process, &ServiceProcess::Shutdown)),
176                Return(true)));
177}
178
179void MockServiceIPCServer::SetWillBeDisabledExpectations() {
180  SetServiceEnabledExpectations();
181
182  EXPECT_CALL(*this,
183              OnMessageReceived(
184                  Property(&IPC::Message::type,
185                           static_cast<int32>(
186                               ServiceMsg_DisableCloudPrintProxy::ID))))
187      .Times(AtLeast(1))
188      .WillRepeatedly(DoAll(Assign(&enabled_, false), Return(true)));
189}
190
191bool MockServiceIPCServer::SendInfo() {
192  if (enabled_) {
193    info_.enabled = true;
194    info_.email = EnabledUserId();
195    EXPECT_TRUE(Send(new ServiceHostMsg_CloudPrintProxy_Info(info_)));
196  } else {
197    info_.enabled = false;
198    info_.email = std::string();
199    EXPECT_TRUE(Send(new ServiceHostMsg_CloudPrintProxy_Info(info_)));
200  }
201  return true;
202}
203
204typedef base::Callback<void(MockServiceIPCServer* server)>
205    SetExpectationsCallback;
206
207// The return value from this routine is used as the exit code for the mock
208// service process. Any non-zero return value will be printed out and can help
209// determine the failure.
210int CloudPrintMockService_Main(SetExpectationsCallback set_expectations) {
211  base::MessageLoopForUI main_message_loop;
212  main_message_loop.set_thread_name("Main Thread");
213  CommandLine* command_line = CommandLine::ForCurrentProcess();
214
215#if defined(OS_MACOSX)
216  if (!command_line->HasSwitch(kTestExecutablePath))
217    return kMissingSwitch;
218  base::FilePath executable_path =
219      command_line->GetSwitchValuePath(kTestExecutablePath);
220  EXPECT_FALSE(executable_path.empty());
221  MockLaunchd mock_launchd(executable_path, &main_message_loop, true, true);
222  Launchd::ScopedInstance use_mock(&mock_launchd);
223#endif
224
225  base::FilePath user_data_dir =
226      command_line->GetSwitchValuePath(switches::kUserDataDir);
227  CHECK(!user_data_dir.empty());
228  CHECK(test_launcher_utils::OverrideUserDataDir(user_data_dir));
229
230  ServiceProcessState* state(new ServiceProcessState);
231  bool service_process_state_initialized = state->Initialize();
232  EXPECT_TRUE(service_process_state_initialized);
233  if (!service_process_state_initialized)
234    return kInitializationFailure;
235
236  TestServiceProcess service_process;
237  EXPECT_EQ(&service_process, g_service_process);
238
239  // Takes ownership of the pointer, but we can use it since we have the same
240  // lifetime.
241  EXPECT_TRUE(service_process.Initialize(&main_message_loop, state));
242
243  MockServiceIPCServer server(state->GetServiceProcessChannel());
244
245  // Here is where the expectations/mock responses need to be set up.
246  set_expectations.Run(&server);
247
248  EXPECT_TRUE(server.Init());
249  EXPECT_TRUE(state->SignalReady(service_process.IOMessageLoopProxy(),
250                                 base::Bind(&ShutdownTask)));
251#if defined(OS_MACOSX)
252  mock_launchd.SignalReady();
253#endif
254
255  // Connect up the parent/child IPC channel to signal that the test can
256  // continue.
257  TestStartupClientChannelListener listener;
258  EXPECT_TRUE(CommandLine::ForCurrentProcess()->HasSwitch(
259      switches::kProcessChannelID));
260  std::string startup_channel_name =
261      CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
262          switches::kProcessChannelID);
263  scoped_ptr<IPC::ChannelProxy> startup_channel;
264  startup_channel.reset(
265      new IPC::ChannelProxy(startup_channel_name,
266                            IPC::Channel::MODE_CLIENT,
267                            &listener,
268                            service_process.IOMessageLoopProxy()));
269
270  main_message_loop.Run();
271  if (!Mock::VerifyAndClearExpectations(&server))
272    return kExpectationsNotMet;
273  if (!g_good_shutdown)
274    return kShutdownNotGood;
275  return 0;
276}
277
278void SetServiceEnabledExpectations(MockServiceIPCServer* server) {
279  server->SetServiceEnabledExpectations();
280}
281
282MULTIPROCESS_IPC_TEST_MAIN(CloudPrintMockService_StartEnabledWaitForQuit) {
283  return CloudPrintMockService_Main(
284      base::Bind(&SetServiceEnabledExpectations));
285}
286
287void SetServiceWillBeDisabledExpectations(MockServiceIPCServer* server) {
288  server->SetWillBeDisabledExpectations();
289}
290
291MULTIPROCESS_IPC_TEST_MAIN(CloudPrintMockService_StartEnabledExpectDisabled) {
292  return CloudPrintMockService_Main(
293      base::Bind(&SetServiceWillBeDisabledExpectations));
294}
295
296class CloudPrintProxyPolicyStartupTest : public base::MultiProcessTest,
297                                         public IPC::Listener {
298 public:
299  CloudPrintProxyPolicyStartupTest();
300  virtual ~CloudPrintProxyPolicyStartupTest();
301
302  virtual void SetUp() OVERRIDE;
303  virtual void TearDown() OVERRIDE;
304
305  scoped_refptr<base::MessageLoopProxy> IOMessageLoopProxy() {
306    return BrowserThread::GetMessageLoopProxyForThread(BrowserThread::IO);
307  }
308  base::ProcessHandle Launch(const std::string& name);
309  void WaitForConnect();
310  bool Send(IPC::Message* message);
311  void ShutdownAndWaitForExitWithTimeout(base::ProcessHandle handle);
312
313  // IPC::Listener implementation
314  virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE {
315    return false;
316  }
317  virtual void OnChannelConnected(int32 peer_pid) OVERRIDE;
318
319  // MultiProcessTest implementation.
320  virtual CommandLine MakeCmdLine(const std::string& procname) OVERRIDE;
321
322  bool LaunchBrowser(const CommandLine& command_line, Profile* profile) {
323    int return_code = 0;
324    StartupBrowserCreator browser_creator;
325    return StartupBrowserCreator::ProcessCmdLineImpl(
326        command_line, base::FilePath(), false, profile,
327        StartupBrowserCreator::Profiles(), &return_code, &browser_creator);
328  }
329
330 protected:
331  content::TestBrowserThreadBundle thread_bundle_;
332  base::ScopedTempDir temp_user_data_dir_;
333
334  std::string startup_channel_id_;
335  scoped_ptr<IPC::ChannelProxy> startup_channel_;
336
337#if defined(OS_MACOSX)
338  base::ScopedTempDir temp_dir_;
339  base::FilePath executable_path_, bundle_path_;
340  scoped_ptr<MockLaunchd> mock_launchd_;
341  scoped_ptr<Launchd::ScopedInstance> scoped_launchd_instance_;
342#endif
343
344 private:
345  class WindowedChannelConnectionObserver {
346   public:
347    WindowedChannelConnectionObserver()
348        : seen_(false),
349          running_(false) { }
350
351    void Wait() {
352      if (seen_)
353        return;
354      running_ = true;
355      content::RunMessageLoop();
356    }
357
358    void Notify() {
359      seen_ = true;
360      if (running_)
361        base::MessageLoopForUI::current()->Quit();
362    }
363
364   private:
365    bool seen_;
366    bool running_;
367  };
368
369  WindowedChannelConnectionObserver observer_;
370};
371
372CloudPrintProxyPolicyStartupTest::CloudPrintProxyPolicyStartupTest()
373    : thread_bundle_(content::TestBrowserThreadBundle::REAL_IO_THREAD) {
374}
375
376CloudPrintProxyPolicyStartupTest::~CloudPrintProxyPolicyStartupTest() {
377}
378
379void CloudPrintProxyPolicyStartupTest::SetUp() {
380  TestingBrowserProcess::CreateInstance();
381#if defined(OS_MACOSX)
382  EXPECT_TRUE(temp_dir_.CreateUniqueTempDir());
383  EXPECT_TRUE(MockLaunchd::MakeABundle(temp_dir_.path(),
384                                       "CloudPrintProxyTest",
385                                       &bundle_path_,
386                                       &executable_path_));
387  mock_launchd_.reset(new MockLaunchd(executable_path_,
388                                      base::MessageLoopForUI::current(),
389                                      true, false));
390  scoped_launchd_instance_.reset(
391      new Launchd::ScopedInstance(mock_launchd_.get()));
392#endif
393
394  // Ensure test does not use the standard profile directory. This is copied
395  // from InProcessBrowserTest::SetUp(). These tests require a more complex
396  // process startup so they are unable to just inherit from
397  // InProcessBrowserTest.
398  CommandLine* command_line = CommandLine::ForCurrentProcess();
399  base::FilePath user_data_dir =
400      command_line->GetSwitchValuePath(switches::kUserDataDir);
401  if (user_data_dir.empty()) {
402    ASSERT_TRUE(temp_user_data_dir_.CreateUniqueTempDir() &&
403                temp_user_data_dir_.IsValid())
404        << "Could not create temporary user data directory \""
405        << temp_user_data_dir_.path().value() << "\".";
406
407    user_data_dir = temp_user_data_dir_.path();
408    command_line->AppendSwitchPath(switches::kUserDataDir, user_data_dir);
409  }
410  ASSERT_TRUE(test_launcher_utils::OverrideUserDataDir(user_data_dir));
411}
412
413void CloudPrintProxyPolicyStartupTest::TearDown() {
414  TestingBrowserProcess::DeleteInstance();
415}
416
417base::ProcessHandle CloudPrintProxyPolicyStartupTest::Launch(
418    const std::string& name) {
419  EXPECT_FALSE(CheckServiceProcessReady());
420
421  startup_channel_id_ =
422      base::StringPrintf("%d.%p.%d",
423                         base::GetCurrentProcId(), this,
424                         base::RandInt(0, std::numeric_limits<int>::max()));
425  startup_channel_.reset(new IPC::ChannelProxy(
426      startup_channel_id_, IPC::Channel::MODE_SERVER,
427      this, IOMessageLoopProxy()));
428
429#if defined(OS_POSIX)
430  base::FileHandleMappingVector ipc_file_list;
431  ipc_file_list.push_back(std::make_pair(
432      startup_channel_->TakeClientFileDescriptor(),
433      kPrimaryIPCChannel + base::GlobalDescriptors::kBaseDescriptor));
434  base::LaunchOptions options;
435  options.fds_to_remap = &ipc_file_list;
436  base::ProcessHandle handle = SpawnChildWithOptions(name, options);
437#else
438  base::ProcessHandle handle = SpawnChild(name);
439#endif
440  EXPECT_TRUE(handle);
441  return handle;
442}
443
444void CloudPrintProxyPolicyStartupTest::WaitForConnect() {
445  observer_.Wait();
446  EXPECT_TRUE(CheckServiceProcessReady());
447  EXPECT_TRUE(base::MessageLoopProxy::current().get());
448  ServiceProcessControl::GetInstance()->SetChannel(
449      new IPC::ChannelProxy(GetServiceProcessChannel(),
450                            IPC::Channel::MODE_NAMED_CLIENT,
451                            ServiceProcessControl::GetInstance(),
452                            IOMessageLoopProxy()));
453}
454
455bool CloudPrintProxyPolicyStartupTest::Send(IPC::Message* message) {
456  return ServiceProcessControl::GetInstance()->Send(message);
457}
458
459void CloudPrintProxyPolicyStartupTest::ShutdownAndWaitForExitWithTimeout(
460    base::ProcessHandle handle) {
461  ASSERT_TRUE(Send(new ServiceMsg_Shutdown()));
462
463  int exit_code = -100;
464  bool exited =
465      base::WaitForExitCodeWithTimeout(handle, &exit_code,
466                                       TestTimeouts::action_timeout());
467  EXPECT_TRUE(exited);
468  EXPECT_EQ(exit_code, 0);
469  base::CloseProcessHandle(handle);
470}
471
472void CloudPrintProxyPolicyStartupTest::OnChannelConnected(int32 peer_pid) {
473  observer_.Notify();
474}
475
476CommandLine CloudPrintProxyPolicyStartupTest::MakeCmdLine(
477    const std::string& procname) {
478  CommandLine cl = MultiProcessTest::MakeCmdLine(procname);
479  cl.AppendSwitchASCII(switches::kProcessChannelID, startup_channel_id_);
480#if defined(OS_MACOSX)
481  cl.AppendSwitchASCII(kTestExecutablePath, executable_path_.value());
482#endif
483  return cl;
484}
485
486TEST_F(CloudPrintProxyPolicyStartupTest, StartAndShutdown) {
487  TestingBrowserProcess* browser_process =
488      TestingBrowserProcess::GetGlobal();
489  TestingProfileManager profile_manager(browser_process);
490  ASSERT_TRUE(profile_manager.SetUp());
491
492  // Must be created after the TestingProfileManager since that creates the
493  // LocalState for the BrowserProcess.  Must be created before profiles are
494  // constructed.
495  chrome::TestingIOThreadState testing_io_thread_state;
496
497  base::ProcessHandle handle =
498      Launch("CloudPrintMockService_StartEnabledWaitForQuit");
499  WaitForConnect();
500  ShutdownAndWaitForExitWithTimeout(handle);
501  content::RunAllPendingInMessageLoop();
502}
503
504KeyedService* CloudPrintProxyServiceFactoryForPolicyTest(
505    content::BrowserContext* profile) {
506  CloudPrintProxyService* service =
507      new CloudPrintProxyService(static_cast<Profile*>(profile));
508  service->Initialize();
509  return service;
510}
511
512TEST_F(CloudPrintProxyPolicyStartupTest, StartBrowserWithoutPolicy) {
513  base::ProcessHandle handle =
514      Launch("CloudPrintMockService_StartEnabledWaitForQuit");
515
516  // Setup the Browser Process with a full IOThread::Globals.
517  TestingBrowserProcess* browser_process =
518      TestingBrowserProcess::GetGlobal();
519
520  TestingProfileManager profile_manager(browser_process);
521  ASSERT_TRUE(profile_manager.SetUp());
522
523  // Must be created after the TestingProfileManager since that creates the
524  // LocalState for the BrowserProcess.  Must be created before profiles are
525  // constructed.
526  chrome::TestingIOThreadState testing_io_thread_state;
527
528  TestingProfile* profile =
529      profile_manager.CreateTestingProfile("StartBrowserWithoutPolicy");
530  CloudPrintProxyServiceFactory::GetInstance()->
531      SetTestingFactory(profile, CloudPrintProxyServiceFactoryForPolicyTest);
532
533  TestingPrefServiceSyncable* prefs = profile->GetTestingPrefService();
534  prefs->SetUserPref(prefs::kCloudPrintEmail,
535                     base::Value::CreateStringValue(
536                         MockServiceIPCServer::EnabledUserId()));
537
538  CommandLine command_line(CommandLine::NO_PROGRAM);
539  command_line.AppendSwitch(switches::kCheckCloudPrintConnectorPolicy);
540  test_launcher_utils::PrepareBrowserCommandLineForTests(&command_line);
541
542  WaitForConnect();
543  base::RunLoop run_loop;
544  base::MessageLoop::current()->PostDelayedTask(
545      FROM_HERE,
546      run_loop.QuitClosure(),
547      TestTimeouts::action_timeout());
548
549  bool should_run_loop = LaunchBrowser(command_line, profile);
550  EXPECT_FALSE(should_run_loop);
551  if (should_run_loop)
552    run_loop.Run();
553
554  EXPECT_EQ(MockServiceIPCServer::EnabledUserId(),
555            prefs->GetString(prefs::kCloudPrintEmail));
556
557  ShutdownAndWaitForExitWithTimeout(handle);
558  content::RunAllPendingInMessageLoop();
559  profile_manager.DeleteTestingProfile("StartBrowserWithoutPolicy");
560}
561
562TEST_F(CloudPrintProxyPolicyStartupTest, StartBrowserWithPolicy) {
563  base::ProcessHandle handle =
564      Launch("CloudPrintMockService_StartEnabledExpectDisabled");
565
566  TestingBrowserProcess* browser_process =
567      TestingBrowserProcess::GetGlobal();
568  TestingProfileManager profile_manager(browser_process);
569  ASSERT_TRUE(profile_manager.SetUp());
570
571  // Must be created after the TestingProfileManager since that creates the
572  // LocalState for the BrowserProcess.  Must be created before profiles are
573  // constructed.
574  chrome::TestingIOThreadState testing_io_thread_state;
575
576  TestingProfile* profile =
577      profile_manager.CreateTestingProfile("StartBrowserWithPolicy");
578  CloudPrintProxyServiceFactory::GetInstance()->
579      SetTestingFactory(profile, CloudPrintProxyServiceFactoryForPolicyTest);
580
581  TestingPrefServiceSyncable* prefs = profile->GetTestingPrefService();
582  prefs->SetUserPref(prefs::kCloudPrintEmail,
583                     base::Value::CreateStringValue(
584                         MockServiceIPCServer::EnabledUserId()));
585  prefs->SetManagedPref(prefs::kCloudPrintProxyEnabled,
586                        base::Value::CreateBooleanValue(false));
587
588  CommandLine command_line(CommandLine::NO_PROGRAM);
589  command_line.AppendSwitch(switches::kCheckCloudPrintConnectorPolicy);
590  test_launcher_utils::PrepareBrowserCommandLineForTests(&command_line);
591
592  WaitForConnect();
593  base::RunLoop run_loop;
594  base::MessageLoop::current()->PostDelayedTask(
595      FROM_HERE,
596      run_loop.QuitClosure(),
597      TestTimeouts::action_timeout());
598
599  bool should_run_loop = LaunchBrowser(command_line, profile);
600
601  // No expectations on run_loop being true here; that would be a race
602  // condition.
603  if (should_run_loop)
604    run_loop.Run();
605
606  EXPECT_EQ("", prefs->GetString(prefs::kCloudPrintEmail));
607
608  ShutdownAndWaitForExitWithTimeout(handle);
609  content::RunAllPendingInMessageLoop();
610  profile_manager.DeleteTestingProfile("StartBrowserWithPolicy");
611}
612