diff options
author | sky@google.com <sky@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-01-30 00:40:43 +0000 |
---|---|---|
committer | sky@google.com <sky@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-01-30 00:40:43 +0000 |
commit | d4515eb94ad33ac2ab9bfd1094c8e1edd1eefa1b (patch) | |
tree | add4a98df5bcdbb3ca0e1f4471c77f453d52a1f4 /chrome/test | |
parent | 0815731ac884b41853725e51e26b0697449747b4 (diff) | |
download | chromium_src-d4515eb94ad33ac2ab9bfd1094c8e1edd1eefa1b.zip chromium_src-d4515eb94ad33ac2ab9bfd1094c8e1edd1eefa1b.tar.gz chromium_src-d4515eb94ad33ac2ab9bfd1094c8e1edd1eefa1b.tar.bz2 |
Provides the ability to write a unit test that brings up a browser. As
a proof of concept I converted
FindInPageControllerTest.FindInPageFrames over to this.
See the description in InProcessBrowserTest for how it all works.
BUG=none
TEST=none
Review URL: http://codereview.chromium.org/19644
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@8934 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/test')
-rw-r--r-- | chrome/test/in_process_browser_test.cc | 179 | ||||
-rw-r--r-- | chrome/test/in_process_browser_test.h | 125 | ||||
-rw-r--r-- | chrome/test/ui_test_utils.cc | 77 | ||||
-rw-r--r-- | chrome/test/ui_test_utils.h | 30 | ||||
-rw-r--r-- | chrome/test/unit/unittests.vcproj | 20 |
5 files changed, 431 insertions, 0 deletions
diff --git a/chrome/test/in_process_browser_test.cc b/chrome/test/in_process_browser_test.cc new file mode 100644 index 0000000..581ddab --- /dev/null +++ b/chrome/test/in_process_browser_test.cc @@ -0,0 +1,179 @@ +// Copyright (c) 2006-2008 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/in_process_browser_test.h" + +#include "base/command_line.h" +#include "base/file_util.h" +#include "base/path_service.h" +#include "chrome/browser/browser.h" +#include "chrome/browser/browser_process.h" +#include "chrome/browser/browser_shutdown.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/profile_manager.h" +#include "chrome/browser/views/frame/browser_view.h" +#include "chrome/common/chrome_constants.h" +#include "chrome/common/chrome_paths.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/main_function_params.h" +#include "chrome/test/testing_browser_process.h" +#include "chrome/test/ui_test_utils.h" +#include "sandbox/src/sandbox_factory.h" +#include "sandbox/src/dep.h" + +extern int BrowserMain(const MainFunctionParams&); + +const wchar_t kUnitTestShowWindows[] = L"show-windows"; + +namespace { + +bool DieFileDie(const std::wstring& file, bool recurse) { + if (!file_util::PathExists(file)) + return true; + + // Sometimes Delete fails, so try a few more times. + for (int i = 0; i < 10; ++i) { + if (file_util::Delete(file, recurse)) + return true; + PlatformThread::Sleep(100); + } + return false; +} + +} // namespace + +InProcessBrowserTest::InProcessBrowserTest() : browser_(NULL) { +} + +void InProcessBrowserTest::SetUp() { + // Cleanup the user data dir. + std::wstring user_data_dir; + PathService::Get(chrome::DIR_USER_DATA, &user_data_dir); + ASSERT_LT(10, static_cast<int>(user_data_dir.size())) << + "The user data directory name passed into this test was too " + "short to delete safely. Please check the user-data-dir " + "argument and try again."; + ASSERT_TRUE(DieFileDie(user_data_dir, true)); + + // The unit test suite creates a testingbrowser, but we want the real thing. + // Delete the current one. We'll install the testing one in TearDown. + delete g_browser_process; + + // Don't delete the resources when BrowserMain returns. Many ui classes + // cache SkBitmaps in a static field so that if we delete the resource + // bundle we'll crash. + browser_shutdown::delete_resources_on_shutdown = false; + + CommandLine* command_line = CommandLine::ForCurrentProcessMutable(); + + // Hide windows on show. + if (!command_line->HasSwitch(kUnitTestShowWindows)) + BrowserView::SetShowState(SW_HIDE); + + command_line->AppendSwitchWithValue(switches::kUserDataDir, user_data_dir); + + // For some reason the sandbox wasn't happy running in test mode. These + // tests aren't intended to test the sandbox, so we turn it off. + command_line->AppendSwitch(switches::kNoSandbox); + + // Explicitly set the path of the exe used for the renderer, otherwise it'll + // try to use unit_test.exe. + std::wstring renderer_path; + PathService::Get(base::FILE_EXE, &renderer_path); + file_util::TrimFilename(&renderer_path); + file_util::AppendToPath(&renderer_path, + chrome::kBrowserProcessExecutableName); + command_line->AppendSwitchWithValue(switches::kRendererPath, renderer_path); + + sandbox::SandboxInterfaceInfo sandbox_info = {0}; + SandboxInitWrapper sandbox_wrapper; + MainFunctionParams params(*command_line, sandbox_wrapper); + params.ui_task = + NewRunnableMethod(this, &InProcessBrowserTest::RunTestOnMainThreadLoop); + BrowserMain(params); +} + +void InProcessBrowserTest::TearDown() { + // Reinstall testing browser process. + delete g_browser_process; + g_browser_process = new TestingBrowserProcess(); + + browser_shutdown::delete_resources_on_shutdown = true; + + BrowserView::SetShowState(-1); +} + +void InProcessBrowserTest::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + if (type == NOTIFY_BROWSER_CLOSED) { + DCHECK(Source<Browser>(source).ptr() == browser_); + browser_ = NULL; + } else { + NOTREACHED(); + } +} + +HTTPTestServer* InProcessBrowserTest::StartHTTPServer() { + // The HTTPServer must run on the IO thread. + DCHECK(!http_server_.get()); + http_server_ = HTTPTestServer::CreateServer( + L"chrome/test/data", + g_browser_process->io_thread()->message_loop()); + return http_server_.get(); +} + +// Creates a browser with a single tab (about:blank), waits for the tab to +// finish loading and shows the browser. +Browser* InProcessBrowserTest::CreateBrowser(Profile* profile) { + Browser* browser = Browser::Create(profile); + + browser->AddTabWithURL( + GURL("about:blank"), GURL(), PageTransition::START_PAGE, true, NULL); + + // Wait for the page to finish loading. + ui_test_utils::WaitForNavigation( + browser->GetSelectedTabContents()->controller()); + + browser->window()->Show(); + + return browser; +} + +void InProcessBrowserTest::RunTestOnMainThreadLoop() { + // In the long term it would be great if we could use a TestingProfile + // here and only enable services you want tested, but that requires all + // consumers of Profile to handle NULL services. + FilePath user_data_dir; + PathService::Get(chrome::DIR_USER_DATA, &user_data_dir); + ProfileManager* profile_manager = g_browser_process->profile_manager(); + Profile* profile = profile_manager->GetDefaultProfile(user_data_dir); + if (!profile) { + // We should only be able to get here if the profile already exists and + // has been created. + NOTREACHED(); + MessageLoopForUI::current()->Quit(); + return; + } + + profile->InitExtensions(); + + browser_ = CreateBrowser(profile); + + registrar_.Add(this, NOTIFY_BROWSER_CLOSED, Source<Browser>(browser_)); + + RunTestOnMainThread(); + + if (browser_) + browser_->CloseAllTabs(); + + // Remove all registered notifications, otherwise by the time the + // destructor is run the NotificationService is dead. + registrar_.RemoveAll(); + + // Stop the HTTP server. + http_server_ = NULL; + + MessageLoopForUI::current()->Quit(); +} diff --git a/chrome/test/in_process_browser_test.h b/chrome/test/in_process_browser_test.h new file mode 100644 index 0000000..fb8bd9d --- /dev/null +++ b/chrome/test/in_process_browser_test.h @@ -0,0 +1,125 @@ +// Copyright (c) 2006-2008 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_IN_PROCESS_BROWSER_TEST_H_ +#define CHROME_TEST_IN_PROCESS_BROWSER_TEST_H_ + +#include "chrome/app/scoped_ole_initializer.h" +#include "chrome/common/notification_registrar.h" +#include "chrome/common/notification_service.h" +#include "net/url_request/url_request_unittest.h" +#include "testing/gtest/include/gtest/gtest.h" + +class Browser; +class Profile; + +// Base class for tests wanting to bring up a browser in the unit test process. +// Writing tests with InProcessBrowserTest is slightly different than that of +// other tests. This is necessitated by InProcessBrowserTest running a message +// loop. To use InProcessBrowserTest do the following: +// . Use the macro IN_PROC_BROWSER_TEST_F to define your test. +// . Your test method is invoked on the ui thread. If you need to block until +// state changes you'll need to run the message loop from your test method. +// For example, if you need to wait till a find bar has completely been shown +// you'll need to invoke ui_test_utils::RunMessageLoop. When the message bar +// is shown, invoke MessageLoop::current()->Quit() to return control back to +// your test method. +// . If you subclass and override SetUp, be sure and invoke +// InProcessBrowserTest::SetUp. +// +// By default InProcessBrowserTest creates a single Browser (as returned from +// the CreateBrowser method). You can obviously create more as needed. + +// Browsers created while InProcessBrowserTest is running are shown hidden. Use +// the command line switch --show-windows to make them visible when debugging. +// +// InProcessBrowserTest disables the sandbox when running. +// +// See ui_test_utils for a handful of methods designed for use with this class. +class InProcessBrowserTest : public testing::Test, public NotificationObserver { + public: + InProcessBrowserTest(); + + // We do this so we can be used in a Task. + void AddRef() {} + void Release() {} + + // Configures everything for an in process browser test, then invokes + // BrowserMain. BrowserMain ends up invoking RunTestOnMainThreadLoop. + virtual void SetUp(); + + // Restores state configured in SetUp. + virtual void TearDown(); + + // Used to track when the browser_ is destroyed. Resets the |browser_| field + // to NULL. + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + protected: + // Returns the browser created by CreateBrowser. + Browser* browser() const { return browser_; } + + // Override this rather than TestBody. + virtual void RunTestOnMainThread() = 0; + + // Starts an HTTP server. + HTTPTestServer* StartHTTPServer(); + + // Creates a browser with a single tab (about:blank), waits for the tab to + // finish loading and shows the browser. + // + // This is invoked from Setup. + virtual Browser* CreateBrowser(Profile* profile); + + private: + // Invokes CreateBrowser to create a browser, then RunTestOnMainThread, and + // destroys the browser. + void RunTestOnMainThreadLoop(); + + // Browser created from CreateBrowser. + Browser* browser_; + + // Used to track when the browser is deleted. + NotificationRegistrar registrar_; + + // HTTPServer, created when StartHTTPServer is invoked. + scoped_refptr<HTTPTestServer> http_server_; + + ScopedOleInitializer ole_initializer_; + + DISALLOW_COPY_AND_ASSIGN(InProcessBrowserTest); +}; + +#define IN_PROC_BROWSER_TEST_(test_case_name, test_name, parent_class,\ + parent_id)\ +class GTEST_TEST_CLASS_NAME_(test_case_name, test_name) : public parent_class {\ + public:\ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)() {}\ + protected:\ + virtual void RunTestOnMainThread();\ + private:\ + virtual void TestBody() {}\ + static ::testing::TestInfo* const test_info_;\ + GTEST_DISALLOW_COPY_AND_ASSIGN_(\ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name));\ +};\ +\ +::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(test_case_name, test_name)\ + ::test_info_ =\ + ::testing::internal::MakeAndRegisterTestInfo(\ + #test_case_name, #test_name, "", "", \ + (parent_id), \ + parent_class::SetUpTestCase, \ + parent_class::TearDownTestCase, \ + new ::testing::internal::TestFactoryImpl<\ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)>);\ +void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::RunTestOnMainThread() + +#define IN_PROC_BROWSER_TEST_F(test_fixture, test_name)\ + IN_PROC_BROWSER_TEST_(test_fixture, test_name, test_fixture,\ + ::testing::internal::GetTypeId<test_fixture>()) + +#endif // CHROME_TEST_IN_PROCESS_BROWSER_TEST_H_ diff --git a/chrome/test/ui_test_utils.cc b/chrome/test/ui_test_utils.cc new file mode 100644 index 0000000..07016f7 --- /dev/null +++ b/chrome/test/ui_test_utils.cc @@ -0,0 +1,77 @@ +// Copyright (c) 2006-2008 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_test_utils.h" + +#include "base/message_loop.h" +#include "chrome/browser/browser.h" +#include "chrome/browser/tab_contents/navigation_controller.h" +#include "chrome/browser/tab_contents/web_contents.h" +#include "chrome/common/notification_registrar.h" +#include "chrome/common/notification_service.h" +#include "googleurl/src/gurl.h" + +namespace ui_test_utils { + +namespace { + +// Used to block until a navigation completes. +class NavigationNotificationObserver : public NotificationObserver { + public: + explicit NavigationNotificationObserver(NavigationController* controller) + : navigation_started_(false) { + registrar_.Add(this, NOTIFY_NAV_ENTRY_COMMITTED, + Source<NavigationController>(controller)); + registrar_.Add(this, NOTIFY_LOAD_START, + Source<NavigationController>(controller)); + registrar_.Add(this, NOTIFY_LOAD_STOP, + Source<NavigationController>(controller)); + RunMessageLoop(); + } + + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + if (type == NOTIFY_NAV_ENTRY_COMMITTED || type == NOTIFY_LOAD_START) { + navigation_started_ = true; + } else if (type == NOTIFY_LOAD_STOP) { + if (navigation_started_) { + navigation_started_ = false; + MessageLoopForUI::current()->Quit(); + } + } + } + + private: + NotificationRegistrar registrar_; + + // If true the navigation has started. + bool navigation_started_; + + DISALLOW_COPY_AND_ASSIGN(NavigationNotificationObserver); +}; + +} // namespace + +void RunMessageLoop() { + MessageLoopForUI* loop = MessageLoopForUI::current(); + bool did_allow_task_nesting = loop->NestableTasksAllowed(); + loop->SetNestableTasksAllowed(true); + loop->Run(NULL); + loop->SetNestableTasksAllowed(did_allow_task_nesting); +} + +void WaitForNavigation(NavigationController* controller) { + NavigationNotificationObserver observer(controller); +} + +void NavigateToURL(Browser* browser, const GURL& url) { + NavigationController* controller = + browser->GetSelectedTabContents()->controller(); + browser->OpenURLFromTab(browser->GetSelectedTabContents(), url, GURL(), + CURRENT_TAB, PageTransition::TYPED); + WaitForNavigation(controller); +} + +} // namespace ui_test_utils diff --git a/chrome/test/ui_test_utils.h b/chrome/test/ui_test_utils.h new file mode 100644 index 0000000..f0433b0 --- /dev/null +++ b/chrome/test/ui_test_utils.h @@ -0,0 +1,30 @@ +// Copyright (c) 2006-2008 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_UI_TEST_UTILS_H_ +#define CHROME_TEST_UI_TEST_UTILS_H_ + +class Browser; +class GURL; +class NavigationController; + +// A collections of functions designed for use with InProcessBrowserTest. +namespace ui_test_utils { + +// Turns on nestable tasks, runs the message loop, then resets nestable tasks +// to what they were originally. Prefer this over MessageLoop::Run for in +// process browser tests that need to block until a condition is met. +void RunMessageLoop(); + +// Waits for |controller| to complete a navigation. This blocks until +// the navigation finishes. +void WaitForNavigation(NavigationController* controller); + +// Navigates the selected tab of |browser| to |url|, blocking until the +// navigation finishes. +void NavigateToURL(Browser* browser, const GURL& url); + +} + +#endif // CHROME_TEST_UI_TEST_UTILS_H_ diff --git a/chrome/test/unit/unittests.vcproj b/chrome/test/unit/unittests.vcproj index d5d5e95..eafd8c4 100644 --- a/chrome/test/unit/unittests.vcproj +++ b/chrome/test/unit/unittests.vcproj @@ -159,6 +159,14 @@ > </File> <File + RelativePath="..\in_process_browser_test.cc" + > + </File> + <File + RelativePath="..\in_process_browser_test.h" + > + </File> + <File RelativePath="..\..\common\ipc_test_sink.cc" > </File> @@ -207,6 +215,14 @@ > </File> <File + RelativePath="..\ui_test_utils.cc" + > + </File> + <File + RelativePath="..\ui_test_utils.h" + > + </File> + <File RelativePath="..\..\..\net\url_request\url_request_test_job.cc" > </File> @@ -367,6 +383,10 @@ Name="browser" > <File + RelativePath="..\..\browser\views\find_bar_win_unittest.cc" + > + </File> + <File RelativePath="..\..\browser\autocomplete\autocomplete_unittest.cc" > </File> |