diff options
24 files changed, 449 insertions, 93 deletions
diff --git a/chrome/browser/automation/automation_provider.cc b/chrome/browser/automation/automation_provider.cc index ec48e7a..65dc5a3 100644 --- a/chrome/browser/automation/automation_provider.cc +++ b/chrome/browser/automation/automation_provider.cc @@ -113,7 +113,9 @@ using base::Time; AutomationProvider::AutomationProvider(Profile* profile) : profile_(profile), - reply_message_(NULL) { + reply_message_(NULL), + is_connected_(false), + initial_loads_complete_(false) { TRACE_EVENT_BEGIN("AutomationProvider::AutomationProvider", 0, ""); browser_tracker_.reset(new AutomationBrowserTracker(this)); @@ -149,23 +151,38 @@ AutomationProvider::~AutomationProvider() { g_browser_process->ReleaseModule(); } -void AutomationProvider::ConnectToChannel(const std::string& channel_id) { - TRACE_EVENT_BEGIN("AutomationProvider::ConnectToChannel", 0, ""); +bool AutomationProvider::InitializeChannel(const std::string& channel_id) { + TRACE_EVENT_BEGIN("AutomationProvider::InitializeChannel", 0, ""); + + std::string effective_channel_id = channel_id; + + // If the channel_id starts with kNamedInterfacePrefix, create a named IPC + // server and listen on it, else connect as client to an existing IPC server + bool use_named_interface = + channel_id.find(automation::kNamedInterfacePrefix) == 0; + if (use_named_interface) { + effective_channel_id = channel_id.substr( + strlen(automation::kNamedInterfacePrefix)); + if (effective_channel_id.length() <= 0) + return false; + } if (!automation_resource_message_filter_.get()) { automation_resource_message_filter_ = new AutomationResourceMessageFilter; } - channel_.reset( - new IPC::SyncChannel(channel_id, IPC::Channel::MODE_CLIENT, this, - automation_resource_message_filter_, - g_browser_process->io_thread()->message_loop(), - true, g_browser_process->shutdown_event())); + channel_.reset(new IPC::SyncChannel( + effective_channel_id, + use_named_interface ? IPC::Channel::MODE_NAMED_SERVER + : IPC::Channel::MODE_CLIENT, + this, + automation_resource_message_filter_, + g_browser_process->io_thread()->message_loop(), + true, g_browser_process->shutdown_event())); - // Send a hello message with our current automation protocol version. - channel_->Send(new AutomationMsg_Hello(0, GetProtocolVersion().c_str())); + TRACE_EVENT_END("AutomationProvider::InitializeChannel", 0, ""); - TRACE_EVENT_END("AutomationProvider::ConnectToChannel", 0, ""); + return true; } std::string AutomationProvider::GetProtocolVersion() { @@ -174,11 +191,16 @@ std::string AutomationProvider::GetProtocolVersion() { } void AutomationProvider::SetExpectedTabCount(size_t expected_tabs) { - if (expected_tabs == 0) { - Send(new AutomationMsg_InitialLoadsComplete(0)); - } else { + if (expected_tabs == 0) + OnInitialLoadsComplete(); + else initial_load_observer_.reset(new InitialLoadObserver(expected_tabs, this)); - } +} + +void AutomationProvider::OnInitialLoadsComplete() { + initial_loads_complete_ = true; + if (is_connected_) + Send(new AutomationMsg_InitialLoadsComplete(0)); } NotificationObserver* AutomationProvider::AddNavigationStatusListener( @@ -327,6 +349,17 @@ const Extension* AutomationProvider::GetDisabledExtension( return NULL; } +void AutomationProvider::OnChannelConnected(int pid) { + is_connected_ = true; + LOG(INFO) << "Testing channel connected, sending hello message"; + + // Send a hello message with our current automation protocol version. + chrome::VersionInfo version_info; + channel_->Send(new AutomationMsg_Hello(0, version_info.Version())); + if (initial_loads_complete_) + Send(new AutomationMsg_InitialLoadsComplete(0)); +} + void AutomationProvider::OnMessageReceived(const IPC::Message& message) { IPC_BEGIN_MESSAGE_MAP(AutomationProvider, message) #if !defined(OS_MACOSX) diff --git a/chrome/browser/automation/automation_provider.h b/chrome/browser/automation/automation_provider.h index 0da77a1..c30fb97 100644 --- a/chrome/browser/automation/automation_provider.h +++ b/chrome/browser/automation/automation_provider.h @@ -17,6 +17,7 @@ #include <vector> #include "base/basictypes.h" +#include "base/compiler_specific.h" #include "base/observer_list.h" #include "base/scoped_ptr.h" #include "base/string16.h" @@ -82,15 +83,23 @@ class AutomationProvider : public base::RefCounted<AutomationProvider>, Profile* profile() const { return profile_; } - // Establishes a connection to an automation client, if present. - // An AutomationProxy should be established (probably in a different process) - // before calling this. - void ConnectToChannel(const std::string& channel_id); + // Initializes a channel for a connection to an AutomationProxy. + // If channel_id starts with kNamedInterfacePrefix, it will act + // as a server, create a named IPC socket with channel_id as its + // path, and will listen on the socket for incoming connections. + // If channel_id does not, it will act as a client and establish + // a connection on its primary IPC channel. See ipc/ipc_channel_posix.cc + // for more information about kPrimaryIPCChannel. + bool InitializeChannel(const std::string& channel_id) WARN_UNUSED_RESULT; // Sets the number of tabs that we expect; when this number of tabs has // loaded, an AutomationMsg_InitialLoadsComplete message is sent. void SetExpectedTabCount(size_t expected_tabs); + // Called when the inital set of tabs has finished loading. + // Call SetExpectedTabCount(0) to set this to true immediately. + void OnInitialLoadsComplete(); + // Add a listener for navigation status notification. Currently only // navigation completion is observed; when the |number_of_navigations| // complete, the completed_response object is sent; if the server requires @@ -138,6 +147,7 @@ class AutomationProvider : public base::RefCounted<AutomationProvider>, // IPC implementations virtual bool Send(IPC::Message* msg); + virtual void OnChannelConnected(int pid); virtual void OnMessageReceived(const IPC::Message& msg); virtual void OnChannelError(); @@ -406,6 +416,12 @@ class AutomationProvider : public base::RefCounted<AutomationProvider>, scoped_ptr<AutomationExtensionTracker> extension_tracker_; PortContainerMap port_containers_; + // True iff connected to an AutomationProxy. + bool is_connected_; + + // True iff browser finished loading initial set of tabs. + bool initial_loads_complete_; + DISALLOW_COPY_AND_ASSIGN(AutomationProvider); }; diff --git a/chrome/browser/automation/automation_provider_observers.cc b/chrome/browser/automation/automation_provider_observers.cc index 0bdb00d..779b6eb 100644 --- a/chrome/browser/automation/automation_provider_observers.cc +++ b/chrome/browser/automation/automation_provider_observers.cc @@ -140,7 +140,7 @@ DictionaryValue* InitialLoadObserver::GetTimingInformation() const { void InitialLoadObserver::ConditionMet() { registrar_.RemoveAll(); - automation_->Send(new AutomationMsg_InitialLoadsComplete(0)); + automation_->OnInitialLoadsComplete(); } NewTabUILoadObserver::NewTabUILoadObserver(AutomationProvider* automation) diff --git a/chrome/browser/ui/browser_init.cc b/chrome/browser/ui/browser_init.cc index d4b0d5d..e0a7ff5 100644 --- a/chrome/browser/ui/browser_init.cc +++ b/chrome/browser/ui/browser_init.cc @@ -973,10 +973,11 @@ bool BrowserInit::ProcessCmdLineImpl(const CommandLine& command_line, expected_tab_count = std::max(1, static_cast<int>(command_line.args().size())); } - CreateAutomationProvider<TestingAutomationProvider>( + if (!CreateAutomationProvider<TestingAutomationProvider>( testing_channel_id, profile, - static_cast<size_t>(expected_tab_count)); + static_cast<size_t>(expected_tab_count))) + return false; } } @@ -993,11 +994,13 @@ bool BrowserInit::ProcessCmdLineImpl(const CommandLine& command_line, silent_launch = true; if (command_line.HasSwitch(switches::kChromeFrame)) { - CreateAutomationProvider<ChromeFrameAutomationProvider>( - automation_channel_id, profile, expected_tabs); + if (!CreateAutomationProvider<ChromeFrameAutomationProvider>( + automation_channel_id, profile, expected_tabs)) + return false; } else { - CreateAutomationProvider<AutomationProvider>(automation_channel_id, - profile, expected_tabs); + if (!CreateAutomationProvider<AutomationProvider>( + automation_channel_id, profile, expected_tabs)) + return false; } } @@ -1056,16 +1059,20 @@ bool BrowserInit::ProcessCmdLineImpl(const CommandLine& command_line, } template <class AutomationProviderClass> -void BrowserInit::CreateAutomationProvider(const std::string& channel_id, +bool BrowserInit::CreateAutomationProvider(const std::string& channel_id, Profile* profile, size_t expected_tabs) { scoped_refptr<AutomationProviderClass> automation = new AutomationProviderClass(profile); - automation->ConnectToChannel(channel_id); + + if (!automation->InitializeChannel(channel_id)) + return false; automation->SetExpectedTabCount(expected_tabs); AutomationProviderList* list = g_browser_process->InitAutomationProviderList(); DCHECK(list); list->AddProvider(automation); + + return true; } diff --git a/chrome/browser/ui/browser_init.h b/chrome/browser/ui/browser_init.h index 07e99e9..746320c 100644 --- a/chrome/browser/ui/browser_init.h +++ b/chrome/browser/ui/browser_init.h @@ -53,7 +53,7 @@ class BrowserInit { } template <class AutomationProviderClass> - static void CreateAutomationProvider(const std::string& channel_id, + static bool CreateAutomationProvider(const std::string& channel_id, Profile* profile, size_t expected_tabs); diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi index bdd161a..03347f5 100644 --- a/chrome/chrome_tests.gypi +++ b/chrome/chrome_tests.gypi @@ -168,6 +168,8 @@ 'sources': [ 'test/automated_ui_tests/automated_ui_test_base.cc', 'test/automated_ui_tests/automated_ui_test_base.h', + 'test/automation/proxy_launcher.cc', + 'test/automation/proxy_launcher.h', 'test/testing_browser_process.h', 'test/ui/javascript_test_util.cc', 'test/ui/npapi_test_helper.cc', @@ -515,6 +517,7 @@ 'test/ui/dromaeo_benchmark_uitest.cc', 'test/ui/history_uitest.cc', 'test/ui/layout_plugin_uitest.cc', + 'test/ui/named_interface_uitest.cc', 'test/ui/npapi_uitest.cc', 'test/ui/omnibox_uitest.cc', 'test/ui/pepper_uitest.cc', @@ -600,6 +603,10 @@ }, }, }, + 'sources!': [ + # TODO(dtu): port to windows http://crosbug.com/8515 + 'test/ui/named_interface_uitest.cc', + ], }, { # else: OS != "win" 'sources!': [ # TODO(port): http://crbug.com/45770 @@ -3109,6 +3116,8 @@ '-Wno-uninitialized', ], 'sources': [ + 'test/automation/proxy_launcher.cc', + 'test/automation/proxy_launcher.h', 'test/pyautolib/pyautolib.cc', 'test/pyautolib/pyautolib.h', 'test/ui/ui_test.cc', diff --git a/chrome/common/automation_constants.cc b/chrome/common/automation_constants.cc index c253871..9480254 100644 --- a/chrome/common/automation_constants.cc +++ b/chrome/common/automation_constants.cc @@ -5,6 +5,7 @@ #include "chrome/common/automation_constants.h" namespace automation { + // JSON value labels for proxy settings that are passed in via // AutomationMsg_SetProxyConfig. const char kJSONProxyAutoconfig[] = "proxy.autoconfig"; @@ -12,4 +13,10 @@ const char kJSONProxyNoProxy[] = "proxy.no_proxy"; const char kJSONProxyPacUrl[] = "proxy.pac_url"; const char kJSONProxyBypassList[] = "proxy.bypass_list"; const char kJSONProxyServer[] = "proxy.server"; -} + +// Named testing interface is used when you want to connect an +// AutomationProxy to an already-running browser instance. +const char kNamedInterfacePrefix[] = "NamedTestingInterface:"; + +} // namespace automation + diff --git a/chrome/common/automation_constants.h b/chrome/common/automation_constants.h index 13b120e..6579776 100644 --- a/chrome/common/automation_constants.h +++ b/chrome/common/automation_constants.h @@ -7,6 +7,7 @@ #pragma once namespace automation { + // JSON value labels for proxy settings that are passed in via // AutomationMsg_SetProxyConfig. These are here since they are used by both // AutomationProvider and AutomationProxy. @@ -16,9 +17,16 @@ extern const char kJSONProxyPacUrl[]; extern const char kJSONProxyBypassList[]; extern const char kJSONProxyServer[]; +// When passing the kTestingChannelID switch to the browser, prepend +// this prefix to the channel id to enable the named testing interface. +// Named testing interface is used when you want to connect an +// AutomationProxy to an already-running browser instance. +extern const char kNamedInterfacePrefix[]; + // Amount of time to wait before querying the browser. static const int kSleepTime = 250; -} + +} // namespace automation // Used by AutomationProxy, declared here so that other headers don't need // to include automation_proxy.h. diff --git a/chrome/test/automation/automation_proxy.cc b/chrome/test/automation/automation_proxy.cc index fbdde25..a33f095 100644 --- a/chrome/test/automation/automation_proxy.cc +++ b/chrome/test/automation/automation_proxy.cc @@ -107,10 +107,8 @@ AutomationProxy::AutomationProxy(int command_execution_timeout_ms, // least it is legal... ;-) DCHECK_GE(command_execution_timeout_ms, 0); listener_thread_id_ = PlatformThread::CurrentId(); - InitializeChannelID(); InitializeHandleTracker(); InitializeThread(); - InitializeChannel(); } AutomationProxy::~AutomationProxy() { @@ -122,7 +120,7 @@ AutomationProxy::~AutomationProxy() { tracker_.reset(); } -void AutomationProxy::InitializeChannelID() { +std::string AutomationProxy::GenerateChannelID() { // The channel counter keeps us out of trouble if we create and destroy // several AutomationProxies sequentially over the course of a test run. // (Creating the channel sometimes failed before when running a lot of @@ -133,7 +131,7 @@ void AutomationProxy::InitializeChannelID() { std::ostringstream buf; buf << "ChromeTestingInterface:" << base::GetCurrentProcId() << "." << ++channel_counter; - channel_id_ = buf.str(); + return buf.str(); } void AutomationProxy::InitializeThread() { @@ -146,7 +144,8 @@ void AutomationProxy::InitializeThread() { thread_.swap(thread); } -void AutomationProxy::InitializeChannel() { +void AutomationProxy::InitializeChannel(const std::string& channel_id, + bool use_named_interface) { DCHECK(shutdown_event_.get() != NULL); // TODO(iyengar) @@ -154,8 +153,9 @@ void AutomationProxy::InitializeChannel() { // provider, where we use the shutdown event provided by the chrome browser // process. channel_.reset(new IPC::SyncChannel( - channel_id_, - IPC::Channel::MODE_SERVER, + channel_id, + use_named_interface ? IPC::Channel::MODE_NAMED_CLIENT + : IPC::Channel::MODE_SERVER, this, // we are the listener new AutomationMessageFilter(this), thread_->message_loop(), diff --git a/chrome/test/automation/automation_proxy.h b/chrome/test/automation/automation_proxy.h index 853a6d3..7902ef0 100644 --- a/chrome/test/automation/automation_proxy.h +++ b/chrome/test/automation/automation_proxy.h @@ -61,6 +61,17 @@ class AutomationProxy : public IPC::Channel::Listener, AutomationProxy(int command_execution_timeout_ms, bool disconnect_on_failure); virtual ~AutomationProxy(); + // Creates a previously unused channel id. + static std::string GenerateChannelID(); + + // Initializes a channel for a connection to an AutomationProvider. + // If use_named_interface is false, it will act as a client + // and connect to the named IPC socket with channel_id as its path. + // If use_named_interface is true, it will act as a server and + // use an anonymous socketpair instead. + void InitializeChannel(const std::string& channel_id, + bool use_named_interface); + // IPC callback virtual void OnMessageReceived(const IPC::Message& msg); virtual void OnChannelError(); @@ -208,10 +219,6 @@ class AutomationProxy : public IPC::Channel::Listener, const std::string& password) WARN_UNUSED_RESULT; #endif - // Returns the ID of the automation IPC channel, so that it can be - // passed to the app as a launch parameter. - const std::string& channel_id() const { return channel_id_; } - #if defined(OS_POSIX) base::file_handle_mapping_vector fds_to_map() const; #endif @@ -263,12 +270,9 @@ class AutomationProxy : public IPC::Channel::Listener, protected: template <class T> scoped_refptr<T> ProxyObjectFromHandle(int handle); - void InitializeChannelID(); void InitializeThread(); - void InitializeChannel(); void InitializeHandleTracker(); - std::string channel_id_; scoped_ptr<base::Thread> thread_; scoped_ptr<IPC::SyncChannel> channel_; scoped_ptr<AutomationHandleTracker> tracker_; diff --git a/chrome/test/automation/automation_proxy_uitest.cc b/chrome/test/automation/automation_proxy_uitest.cc index db6a18e4..f96de59 100644 --- a/chrome/test/automation/automation_proxy_uitest.cc +++ b/chrome/test/automation/automation_proxy_uitest.cc @@ -29,6 +29,7 @@ #include "chrome/test/automation/autocomplete_edit_proxy.h" #include "chrome/test/automation/automation_proxy_uitest.h" #include "chrome/test/automation/browser_proxy.h" +#include "chrome/test/automation/proxy_launcher.h" #include "chrome/test/automation/tab_proxy.h" #include "chrome/test/automation/window_proxy.h" #include "chrome/test/ui_test_utils.h" @@ -47,6 +48,34 @@ using testing::CreateFunctor; using testing::StrEq; using testing::_; + +// Replace the default automation proxy with our mock client. +class ExternalTabUITestMockLauncher : public ProxyLauncher { + public: + explicit ExternalTabUITestMockLauncher(ExternalTabUITestMockClient **mock) + : mock_(mock) { + channel_id_ = AutomationProxy::GenerateChannelID(); + } + + AutomationProxy* CreateAutomationProxy(int execution_timeout) { + *mock_ = new ExternalTabUITestMockClient(execution_timeout); + (*mock_)->InitializeChannel(channel_id_, false); + return *mock_; + } + + void InitializeConnection(UITestBase* ui_test_base) const { + ui_test_base->LaunchBrowserAndServer(); + } + + std::string PrefixedChannelID() const { + return channel_id_; + } + + private: + ExternalTabUITestMockClient **mock_; + std::string channel_id_; // Channel id of automation proxy. +}; + class AutomationProxyTest : public UITest { protected: AutomationProxyTest() { @@ -841,9 +870,9 @@ template <typename T> T** ReceivePointer(scoped_refptr<T>& p) { // NOLINT return reinterpret_cast<T**>(&p); } -AutomationProxy* ExternalTabUITest::CreateAutomationProxy(int exec_timeout) { - mock_ = new ExternalTabUITestMockClient(exec_timeout); - return mock_; +// Replace the default automation proxy with our mock client. +ProxyLauncher* ExternalTabUITest::CreateProxyLauncher() { + return new ExternalTabUITestMockLauncher(&mock_); } // Create with specifying a url diff --git a/chrome/test/automation/automation_proxy_uitest.h b/chrome/test/automation/automation_proxy_uitest.h index 55da8cc..4e6d095 100644 --- a/chrome/test/automation/automation_proxy_uitest.h +++ b/chrome/test/automation/automation_proxy_uitest.h @@ -112,16 +112,17 @@ class ExternalTabUITestMockClient : public AutomationProxy { class ExternalTabUITest : public UITest { public: ExternalTabUITest() : UITest(MessageLoop::TYPE_UI) {} - // Override UITest's CreateAutomationProxy to provide the unit test + // Override UITest's CreateProxyLauncher to provide the unit test // with our special implementation of AutomationProxy. - // This function is called from within UITest::LaunchBrowserAndServer. - virtual AutomationProxy* CreateAutomationProxy(int execution_timeout); + // This function is called from within UITest::SetUp(). + virtual ProxyLauncher* CreateProxyLauncher(); protected: // Filtered Inet will override automation callbacks for network resources. virtual bool ShouldFilterInet() { return false; } ExternalTabUITestMockClient* mock_; + std::string channel_id_; // Channel id of automation proxy. }; #endif // CHROME_TEST_AUTOMATION_AUTOMATION_PROXY_UITEST_H_ diff --git a/chrome/test/automation/proxy_launcher.cc b/chrome/test/automation/proxy_launcher.cc new file mode 100644 index 0000000..2325958 --- /dev/null +++ b/chrome/test/automation/proxy_launcher.cc @@ -0,0 +1,75 @@ +// Copyright (c) 2010 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/test/automation/proxy_launcher.h" + +#include "chrome/common/automation_constants.h" +#include "chrome/common/logging_chrome.h" +#include "chrome/test/automation/automation_proxy.h" +#include "chrome/test/ui/ui_test.h" + +// Default path of named testing interface. +static const char kInterfacePath[] = "/var/tmp/ChromeTestingInterface"; + +// NamedProxyLauncher functions + +NamedProxyLauncher::NamedProxyLauncher(bool launch_browser, + bool disconnect_on_failure) + : launch_browser_(launch_browser), + disconnect_on_failure_(disconnect_on_failure) { + channel_id_ = kInterfacePath; +} + +AutomationProxy* NamedProxyLauncher::CreateAutomationProxy( + int execution_timeout) { + AutomationProxy* proxy = new AutomationProxy(execution_timeout, + disconnect_on_failure_); + proxy->InitializeChannel(channel_id_, true); + return proxy; +} + +void NamedProxyLauncher::InitializeConnection(UITestBase* ui_test_base) const { + if (launch_browser_) { + // Set up IPC testing interface as a client. + ui_test_base->LaunchBrowser(); + + // Wait for browser to be ready for connections. + struct stat file_info; + while (stat(kInterfacePath, &file_info)) + PlatformThread::Sleep(automation::kSleepTime); + } + + ui_test_base->ConnectToRunningBrowser(); +} + +std::string NamedProxyLauncher::PrefixedChannelID() const { + std::string channel_id; + channel_id.append(automation::kNamedInterfacePrefix).append(channel_id_); + return channel_id; +} + +// AnonymousProxyLauncher functions + +AnonymousProxyLauncher::AnonymousProxyLauncher(bool disconnect_on_failure) + : disconnect_on_failure_(disconnect_on_failure) { + channel_id_ = AutomationProxy::GenerateChannelID(); +} + +AutomationProxy* AnonymousProxyLauncher::CreateAutomationProxy( + int execution_timeout) { + AutomationProxy* proxy = new AutomationProxy(execution_timeout, + disconnect_on_failure_); + proxy->InitializeChannel(channel_id_, false); + return proxy; +} + +void AnonymousProxyLauncher::InitializeConnection( + UITestBase* ui_test_base) const { + ui_test_base->LaunchBrowserAndServer(); +} + +std::string AnonymousProxyLauncher::PrefixedChannelID() const { + return channel_id_; +} + diff --git a/chrome/test/automation/proxy_launcher.h b/chrome/test/automation/proxy_launcher.h new file mode 100644 index 0000000..0f4d04d --- /dev/null +++ b/chrome/test/automation/proxy_launcher.h @@ -0,0 +1,78 @@ +// Copyright (c) 2010 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. + +#ifndef CHROME_TEST_AUTOMATION_PROXY_LAUNCHER_H_ +#define CHROME_TEST_AUTOMATION_PROXY_LAUNCHER_H_ + +#include <string> + +#include "base/basictypes.h" + +class AutomationProxy; +class UITestBase; + +// Subclass from this class to use a different implementation of AutomationProxy +// or to use different channel IDs inside a class that derives from UITest. +class ProxyLauncher { + public: + ProxyLauncher() {} + virtual ~ProxyLauncher() {} + + // Creates an automation proxy. + virtual AutomationProxy* CreateAutomationProxy( + int execution_timeout) = 0; + + // Launches the browser if needed and establishes a connection + // connection with it using the specified UITestBase. + virtual void InitializeConnection(UITestBase* ui_test_base) const = 0; + + // Returns the automation proxy's channel with any prefixes prepended, + // for passing as a command line parameter over to the browser. + virtual std::string PrefixedChannelID() const = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(ProxyLauncher); +}; + +// Uses an automation proxy that communicates over a named socket. +// This is used if you want to connect an AutomationProxy +// to a browser process that is already running. +// The channel id of the proxy is a constant specified by kInterfacePath. +class NamedProxyLauncher : public ProxyLauncher { + public: + // If launch_browser is true, launches Chrome with named interface enabled. + // Otherwise, there should be an existing instance the proxy can connect to. + NamedProxyLauncher(bool launch_browser, bool disconnect_on_failure); + + virtual AutomationProxy* CreateAutomationProxy(int execution_timeout); + virtual void InitializeConnection(UITestBase* ui_test_base) const; + virtual std::string PrefixedChannelID() const; + + protected: + std::string channel_id_; // Channel id of automation proxy. + bool launch_browser_; // True if we should launch the browser too. + bool disconnect_on_failure_; // True if we disconnect on IPC channel failure. + + private: + DISALLOW_COPY_AND_ASSIGN(NamedProxyLauncher); +}; + +// Uses an automation proxy that communicates over an anonymous socket. +class AnonymousProxyLauncher : public ProxyLauncher { + public: + explicit AnonymousProxyLauncher(bool disconnect_on_failure); + virtual AutomationProxy* CreateAutomationProxy(int execution_timeout); + virtual void InitializeConnection(UITestBase* ui_test_base) const; + virtual std::string PrefixedChannelID() const; + + protected: + std::string channel_id_; // Channel id of automation proxy. + bool disconnect_on_failure_; // True if we disconnect on IPC channel failure. + + private: + DISALLOW_COPY_AND_ASSIGN(AnonymousProxyLauncher); +}; + +#endif // CHROME_TEST_AUTOMATION_PROXY_LAUNCHER_H_ + diff --git a/chrome/test/ui/named_interface_uitest.cc b/chrome/test/ui/named_interface_uitest.cc new file mode 100644 index 0000000..ee44820 --- /dev/null +++ b/chrome/test/ui/named_interface_uitest.cc @@ -0,0 +1,42 @@ +// Copyright (c) 2010 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/test/ui/ui_test.h" + +#include "chrome/common/url_constants.h" +#include "chrome/test/automation/proxy_launcher.h" + +// The named testing interface enables the use of a named socket for controlling +// the browser. This eliminates the dependency that the browser must be forked +// from the controlling process. +namespace { + +class NamedInterfaceTest : public UITest { + public: + NamedInterfaceTest() { + show_window_ = true; + } + + virtual ProxyLauncher *CreateProxyLauncher() { + return new NamedProxyLauncher(true, true); + } +}; + +// Basic sanity test for named testing interface which +// launches a browser instance that uses a named socket, then +// sends it some commands to open some tabs over that socket. +TEST_F(NamedInterfaceTest, BasicNamedInterface) { + scoped_refptr<BrowserProxy> browser_proxy( + automation()->GetBrowserWindow(0)); + ASSERT_TRUE(browser_proxy.get()); + + for (int i = 0; i < 10; ++i) + ASSERT_TRUE(browser_proxy->AppendTab(GURL(chrome::kAboutBlankURL))); +} + +// TODO(dtu): crosbug.com/8514: Write a test that makes sure you can disconnect, +// then reconnect with a new connection and continue automation. + +} // namespace + diff --git a/chrome/test/ui/ui_test.cc b/chrome/test/ui/ui_test.cc index 9fbb857..e6aff60 100644 --- a/chrome/test/ui/ui_test.cc +++ b/chrome/test/ui/ui_test.cc @@ -43,6 +43,7 @@ #include "chrome/test/automation/automation_proxy.h" #include "chrome/test/automation/browser_proxy.h" #include "chrome/test/automation/javascript_execution_controller.h" +#include "chrome/test/automation/proxy_launcher.h" #include "chrome/test/automation/tab_proxy.h" #include "chrome/test/automation/window_proxy.h" #include "chrome/test/chrome_process_util.h" @@ -140,7 +141,9 @@ void UITestBase::SetUp() { JavaScriptExecutionController::set_timeout( TestTimeouts::action_max_timeout_ms()); test_start_time_ = Time::NowFromSystemTime(); - LaunchBrowserAndServer(); + + launcher_.reset(CreateProxyLauncher()); + launcher_->InitializeConnection(this); } void UITestBase::TearDown() { @@ -175,24 +178,39 @@ void UITestBase::TearDown() { // TODO(phajdan.jr): get rid of set_command_execution_timeout_ms. void UITestBase::set_command_execution_timeout_ms(int timeout) { - server_->set_command_execution_timeout_ms(timeout); + automation_proxy_->set_command_execution_timeout_ms(timeout); VLOG(1) << "Automation command execution timeout set to " << timeout << " ms"; } -AutomationProxy* UITestBase::CreateAutomationProxy(int execution_timeout) { - return new AutomationProxy(execution_timeout, false); +ProxyLauncher* UITestBase::CreateProxyLauncher() { + return new AnonymousProxyLauncher(false); +} + +void UITestBase::LaunchBrowser() { + LaunchBrowser(launch_arguments_, clear_profile_); } void UITestBase::LaunchBrowserAndServer() { - // Set up IPC testing interface server. - server_.reset(CreateAutomationProxy( - TestTimeouts::command_execution_timeout_ms())); + // Set up IPC testing interface as a server. + automation_proxy_.reset(launcher_->CreateAutomationProxy( + TestTimeouts::command_execution_timeout_ms())); LaunchBrowser(launch_arguments_, clear_profile_); - ASSERT_EQ(AUTOMATION_SUCCESS, server_->WaitForAppLaunch()) + WaitForBrowserLaunch(); +} + +void UITestBase::ConnectToRunningBrowser() { + // Set up IPC testing interface as a client. + automation_proxy_.reset(launcher_->CreateAutomationProxy( + TestTimeouts::command_execution_timeout_ms())); + WaitForBrowserLaunch(); +} + +void UITestBase::WaitForBrowserLaunch() { + ASSERT_EQ(AUTOMATION_SUCCESS, automation_proxy_->WaitForAppLaunch()) << "Error while awaiting automation ping from browser process"; if (wait_for_initial_loads_) - ASSERT_TRUE(server_->WaitForInitialLoads()); + ASSERT_TRUE(automation_proxy_->WaitForInitialLoads()); else PlatformThread::Sleep(sleep_timeout_ms()); @@ -210,7 +228,7 @@ void UITestBase::CloseBrowserAndServer() { AssertAppNotRunning(StringPrintf( L"Unable to quit all browser processes. Original PID %d", process_id_)); - server_.reset(); // Shut down IPC testing interface. + automation_proxy_.reset(); // Shut down IPC testing interface. } void UITestBase::LaunchBrowser(const CommandLine& arguments, @@ -567,7 +585,7 @@ FilePath UITestBase::GetDownloadDirectory() { } void UITestBase::CloseBrowserAsync(BrowserProxy* browser) const { - ASSERT_TRUE(server_->Send( + ASSERT_TRUE(automation_proxy_->Send( new AutomationMsg_CloseBrowserRequestAsync(0, browser->handle()))); } @@ -579,7 +597,7 @@ bool UITestBase::CloseBrowser(BrowserProxy* browser, bool result = true; - bool succeeded = server_->Send(new AutomationMsg_CloseBrowser( + bool succeeded = automation_proxy_->Send(new AutomationMsg_CloseBrowser( 0, browser->handle(), &result, application_closed)); if (!succeeded) @@ -694,10 +712,9 @@ void UITestBase::PrepareTestCommandline(CommandLine* command_line) { if (dom_automation_enabled_) command_line->AppendSwitch(switches::kDomAutomationController); - if (include_testing_id_) { + if (include_testing_id_) command_line->AppendSwitchASCII(switches::kTestingChannelID, - server_->channel_id()); - } + launcher_->PrefixedChannelID()); if (!show_error_dialogs_ && !CommandLine::ForCurrentProcess()->HasSwitch( @@ -786,10 +803,11 @@ bool UITestBase::LaunchBrowserHelper(const CommandLine& arguments, << browser_wrapper; } - bool started = base::LaunchApp(command_line.argv(), - server_->fds_to_map(), - wait, - process); + base::file_handle_mapping_vector fds; + if (automation_proxy_.get()) + fds = automation_proxy_->fds_to_map(); + + bool started = base::LaunchApp(command_line.argv(), fds, wait, process); #endif return started; @@ -867,11 +885,11 @@ void UITest::TearDown() { PlatformTest::TearDown(); } -AutomationProxy* UITest::CreateAutomationProxy(int execution_timeout) { +ProxyLauncher* UITest::CreateProxyLauncher() { // Make the AutomationProxy disconnect the channel on the first error, // so that we avoid spending a lot of time in timeouts. The browser is likely // hosed if we hit those errors. - return new AutomationProxy(execution_timeout, true); + return new AnonymousProxyLauncher(true); } static CommandLine* CreatePythonCommandLine() { diff --git a/chrome/test/ui/ui_test.h b/chrome/test/ui/ui_test.h index ced9f34..3305cc5 100644 --- a/chrome/test/ui/ui_test.h +++ b/chrome/test/ui/ui_test.h @@ -38,6 +38,7 @@ class BrowserProxy; class DictionaryValue; class FilePath; class GURL; +class ProxyLauncher; class ScopedTempDir; class TabProxy; @@ -68,14 +69,21 @@ class UITestBase { public: // ********* Utility functions ********* - // Launches the browser and IPC testing server. + // Launches the browser only. + void LaunchBrowser(); + + // Launches the browser and IPC testing connection in server mode. void LaunchBrowserAndServer(); + // Launches the IPC testing connection in client mode, + // which then attempts to connect to a browser. + void ConnectToRunningBrowser(); + // Only for pyauto. void set_command_execution_timeout_ms(int timeout); - // Overridable so that derived classes can provide their own AutomationProxy. - virtual AutomationProxy* CreateAutomationProxy(int execution_timeout); + // Overridable so that derived classes can provide their own ProxyLauncher. + virtual ProxyLauncher* CreateProxyLauncher(); // Closes the browser and IPC testing server. void CloseBrowserAndServer(); @@ -102,7 +110,7 @@ class UITestBase { // Terminates the browser, simulates end of session. void TerminateBrowser(); - // Tells the browser to navigato to the givne URL in the active tab + // Tells the browser to navigate to the given URL in the active tab // of the first app window. // Does not wait for the navigation to complete to return. void NavigateToURLAsync(const GURL& url); @@ -361,8 +369,8 @@ class UITestBase { protected: AutomationProxy* automation() { - EXPECT_TRUE(server_.get()); - return server_.get(); + EXPECT_TRUE(automation_proxy_.get()); + return automation_proxy_.get(); } virtual bool ShouldFilterInet() { @@ -412,6 +420,7 @@ class UITestBase { // id on the command line? Default is // true. bool enable_file_cookies_; // Enable file cookies, default is true. + scoped_ptr<ProxyLauncher> launcher_; // Launches browser and AutomationProxy. ProfileType profile_type_; // Are we using a profile with a // complex theme? FilePath websocket_pid_file_; // PID file for websocket server. @@ -419,6 +428,8 @@ class UITestBase { // the browser. Used in ShutdownTest. private: + void WaitForBrowserLaunch(); + bool LaunchBrowserHelper(const CommandLine& arguments, bool wait, base::ProcessHandle* process); @@ -450,7 +461,7 @@ class UITestBase { static std::string js_flags_; // Flags passed to the JS engine. static std::string log_level_; // Logging level. - scoped_ptr<AutomationProxy> server_; + scoped_ptr<AutomationProxy> automation_proxy_; std::string ui_test_name_; @@ -468,7 +479,7 @@ class UITest : public UITestBase, public PlatformTest { virtual void SetUp(); virtual void TearDown(); - virtual AutomationProxy* CreateAutomationProxy(int execution_timeout); + virtual ProxyLauncher* CreateProxyLauncher(); // Synchronously launches local http server normally used to run LayoutTests. void StartHttpServer(const FilePath& root_directory); diff --git a/chrome_frame/chrome_frame.gyp b/chrome_frame/chrome_frame.gyp index 39d5063..66d1dad 100644 --- a/chrome_frame/chrome_frame.gyp +++ b/chrome_frame/chrome_frame.gyp @@ -550,7 +550,10 @@ 'test/win_event_receiver.h', 'chrome_tab.h', '../base/test/test_file_util_win.cc', + '../chrome/test/automation/proxy_launcher.cc', + '../chrome/test/automation/proxy_launcher.h', '../chrome/test/ui/ui_test.cc', + '../chrome/test/ui/ui_test.h', '../chrome/test/ui/ui_test_suite.cc', '../chrome/test/ui/ui_test_suite.h', '../chrome/test/chrome_process_util.cc', @@ -809,7 +812,8 @@ ], 'process_outputs_as_sources': 0, 'message': - 'Assembling <(RULE_INPUT_PATH) to <(INTERMEDIATE_DIR)\<(RULE_INPUT_ROOT).obj.', + 'Assembling <(RULE_INPUT_PATH) to ' \ + '<(INTERMEDIATE_DIR)\<(RULE_INPUT_ROOT).obj.', }, ], 'msvs_settings': { diff --git a/chrome_frame/chrome_frame_automation.cc b/chrome_frame/chrome_frame_automation.cc index 7a49f18..94d30b8 100644 --- a/chrome_frame/chrome_frame_automation.cc +++ b/chrome_frame/chrome_frame_automation.cc @@ -144,10 +144,13 @@ class ChromeFrameAutomationProxyImpl::CFMsgDispatcher }; ChromeFrameAutomationProxyImpl::ChromeFrameAutomationProxyImpl( - AutomationProxyCacheEntry* entry, int launch_timeout) + AutomationProxyCacheEntry* entry, + std::string channel_id, int launch_timeout) : AutomationProxy(launch_timeout, false), proxy_entry_(entry) { TRACE_EVENT_BEGIN("chromeframe.automationproxy", this, ""); + InitializeChannel(channel_id, false); + sync_ = new CFMsgDispatcher(); message_filter_ = new TabProxyNotificationMessageFilter(tracker_.get()); @@ -271,8 +274,10 @@ void AutomationProxyCacheEntry::CreateProxy(ChromeFrameLaunchParams* params, // destruction notification. // At same time we must destroy/stop the thread from another thread. + std::string channel_id = AutomationProxy::GenerateChannelID(); ChromeFrameAutomationProxyImpl* proxy = - new ChromeFrameAutomationProxyImpl(this, params->launch_timeout()); + new ChromeFrameAutomationProxyImpl(this, channel_id, + params->launch_timeout()); // Ensure that the automation proxy actually respects our choice on whether // or not to check the version. @@ -282,7 +287,7 @@ void AutomationProxyCacheEntry::CreateProxy(ChromeFrameLaunchParams* params, scoped_ptr<CommandLine> command_line( chrome_launcher::CreateLaunchCommandLine()); command_line->AppendSwitchASCII(switches::kAutomationClientChannelID, - proxy->channel_id()); + channel_id); // Run Chrome in Chrome Frame mode. In practice, this modifies the paths // and registry keys that Chrome looks in via the BrowserDistribution diff --git a/chrome_frame/chrome_frame_automation.h b/chrome_frame/chrome_frame_automation.h index cb1b282..5cf1304 100644 --- a/chrome_frame/chrome_frame_automation.h +++ b/chrome_frame/chrome_frame_automation.h @@ -92,6 +92,7 @@ class ChromeFrameAutomationProxyImpl protected: friend class AutomationProxyCacheEntry; ChromeFrameAutomationProxyImpl(AutomationProxyCacheEntry* entry, + std::string channel_id, int launch_timeout); class CFMsgDispatcher; diff --git a/chrome_frame/test/automation_client_mock.cc b/chrome_frame/test/automation_client_mock.cc index 7e8f317..2cf981b 100644 --- a/chrome_frame/test/automation_client_mock.cc +++ b/chrome_frame/test/automation_client_mock.cc @@ -315,7 +315,8 @@ class TestChromeFrameAutomationProxyImpl public: TestChromeFrameAutomationProxyImpl() // 1 is an unneeded timeout. - : ChromeFrameAutomationProxyImpl(NULL, 1) { + : ChromeFrameAutomationProxyImpl( + NULL, AutomationProxy::GenerateChannelID(), 1) { } MOCK_METHOD3( SendAsAsync, @@ -469,4 +470,3 @@ TEST_F(CFACMockTest, NavigateTwiceAfterInitToSameUrl) { EXPECT_CALL(mock_proxy_, ReleaseTabProxy(testing::Eq(tab_handle_))).Times(1); client_->Uninitialize(); } - diff --git a/chrome_frame/test/net/test_automation_provider.cc b/chrome_frame/test/net/test_automation_provider.cc index 5b8a9a6..d6a0676 100644 --- a/chrome_frame/test/net/test_automation_provider.cc +++ b/chrome_frame/test/net/test_automation_provider.cc @@ -119,7 +119,7 @@ TestAutomationProvider* TestAutomationProvider::NewAutomationProvider( Profile* p, const std::string& channel, TestAutomationProviderDelegate* delegate) { TestAutomationProvider* automation = new TestAutomationProvider(p, delegate); - automation->ConnectToChannel(channel); + automation->InitializeChannel(channel); automation->SetExpectedTabCount(1); return automation; } diff --git a/ipc/ipc_channel.h b/ipc/ipc_channel.h index 2445e51..27b055c1 100644 --- a/ipc/ipc_channel.h +++ b/ipc/ipc_channel.h @@ -38,7 +38,9 @@ class Channel : public Message::Sender { enum Mode { MODE_NONE, MODE_SERVER, - MODE_CLIENT + MODE_CLIENT, + MODE_NAMED_SERVER, + MODE_NAMED_CLIENT }; enum { diff --git a/ipc/ipc_channel_posix.cc b/ipc/ipc_channel_posix.cc index 86d1673..65a04e1 100644 --- a/ipc/ipc_channel_posix.cc +++ b/ipc/ipc_channel_posix.cc @@ -273,8 +273,9 @@ Channel::ChannelImpl::ChannelImpl(const std::string& channel_id, Mode mode, : mode_(mode), is_blocked_on_write_(false), message_send_bytes_written_(0), - uses_fifo_(CommandLine::ForCurrentProcess()->HasSwitch( - switches::kIPCUseFIFO)), + uses_fifo_( + CommandLine::ForCurrentProcess()->HasSwitch(switches::kIPCUseFIFO) || + mode == MODE_NAMED_SERVER || mode == MODE_NAMED_CLIENT), server_listen_pipe_(-1), pipe_(-1), client_pipe_(-1), @@ -285,10 +286,15 @@ Channel::ChannelImpl::ChannelImpl(const std::string& channel_id, Mode mode, listener_(listener), waiting_connect_(true), factory_(this) { - if (!CreatePipe(channel_id, mode)) { + if (mode_ == MODE_NAMED_SERVER) + mode_ = MODE_SERVER; + if (mode_ == MODE_NAMED_CLIENT) + mode_ = MODE_CLIENT; + + if (!CreatePipe(channel_id, mode_)) { // The pipe may have been closed already. PLOG(WARNING) << "Unable to create pipe named \"" << channel_id - << "\" in " << (mode == MODE_SERVER ? "server" : "client") + << "\" in " << (mode_ == MODE_SERVER ? "server" : "client") << " mode"; } } @@ -346,7 +352,7 @@ bool Channel::ChannelImpl::CreatePipe(const std::string& channel_id, // TODO(playmobil): We shouldn't need to create fifos on disk. // TODO(playmobil): If we do, they should be in the user data directory. // TODO(playmobil): Cleanup any stale fifos. - pipe_name_ = "/var/tmp/chrome_" + channel_id; + pipe_name_ = channel_id; if (mode == MODE_SERVER) { if (!CreateServerFifo(pipe_name_, &server_listen_pipe_)) { return false; |