// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef CHROME_FRAME_TEST_CHROME_FRAME_TEST_UTILS_H_ #define CHROME_FRAME_TEST_CHROME_FRAME_TEST_UTILS_H_ #include #include #include #include #include "base/basictypes.h" #include "base/cancelable_callback.h" #include "base/compiler_specific.h" #include "base/file_path.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop.h" #include "base/process_util.h" #include "base/time.h" #include "base/test/test_reg_util_win.h" #include "base/time.h" #include "base/win/registry.h" #include "base/win/scoped_comptr.h" #include "chrome/test/base/ui_test_utils.h" #include "chrome_frame/chrome_tab.h" #include "chrome_frame/test/simulate_input.h" #include "chrome_frame/test_utils.h" #include "chrome_frame/utils.h" #include "gtest/gtest.h" // Needed for CreateFunctor. #define GMOCK_MUTANT_INCLUDE_LATE_OBJECT_BINDING #include "testing/gmock_mutant.h" class FilePath; interface IWebBrowser2; namespace chrome_frame_test { int CloseVisibleWindowsOnAllThreads(HANDLE process); base::ProcessHandle LaunchIE(const std::wstring& url); base::ProcessHandle LaunchChrome(const std::wstring& url, const FilePath& user_data_dir); // Attempts to close all open IE windows. // The return value is the number of windows closed. // @note: this function requires COM to be initialized on the calling thread. // Since the caller might be running in either MTA or STA, the function does // not perform this initialization itself. int CloseAllIEWindows(); extern const wchar_t kIEImageName[]; extern const wchar_t kIEBrokerImageName[]; extern const char kChromeImageName[]; extern const wchar_t kChromeLauncher[]; extern const base::TimeDelta kChromeFrameLongNavigationTimeout; extern const base::TimeDelta kChromeFrameVeryLongNavigationTimeout; // Temporarily impersonate the current thread to low integrity for the lifetime // of the object. Destructor will automatically revert integrity level. class LowIntegrityToken { public: LowIntegrityToken(); ~LowIntegrityToken(); BOOL Impersonate(); BOOL RevertToSelf(); protected: static bool IsImpersonated(); bool impersonated_; }; // This class implements the COM IMessageFilter interface and is used to detect // hung outgoing COM calls which are typically to IE. If IE is hung for any // reason the chrome frame tests should not hang indefinitely. This class // basically cancels the outgoing call if the WM_TIMER which is set by the // TimedMsgLoop object is posted to the message queue. class HungCOMCallDetector : public CComObjectRootEx, public IMessageFilter, public CWindowImpl { public: HungCOMCallDetector() : is_hung_(false) { } ~HungCOMCallDetector() { TearDown(); } BEGIN_MSG_MAP(HungCOMCallDetector) MESSAGE_HANDLER(WM_TIMER, OnTimer) END_MSG_MAP() BEGIN_COM_MAP(HungCOMCallDetector) COM_INTERFACE_ENTRY(IMessageFilter) END_COM_MAP() static CComObject* Setup(int timeout_seconds) { CComObject* this_instance = NULL; CComObject::CreateInstance(&this_instance); EXPECT_TRUE(this_instance != NULL); scoped_refptr ref_instance = this_instance; HRESULT hr = ref_instance->Initialize(timeout_seconds); if (FAILED(hr)) { LOG(ERROR) << "Failed to Initialize Hung COM call detector. Error:" << hr; return NULL; } return this_instance; } void TearDown() { if (prev_filter_) { base::win::ScopedComPtr prev_filter; CoRegisterMessageFilter(prev_filter_.get(), prev_filter.Receive()); DCHECK(prev_filter.IsSameObject(this)); prev_filter_.Release(); } if (IsWindow()) { DestroyWindow(); m_hWnd = NULL; } } STDMETHOD_(DWORD, HandleInComingCall)(DWORD call_type, HTASK caller, DWORD tick_count, LPINTERFACEINFO interface_info) { return SERVERCALL_ISHANDLED; } STDMETHOD_(DWORD, RetryRejectedCall)(HTASK callee, DWORD tick_count, DWORD reject_type) { return -1; } STDMETHOD_(DWORD, MessagePending)(HTASK callee, DWORD tick_count, DWORD pending_type) { MSG msg = {0}; if (is_hung_) { return PENDINGMSG_CANCELCALL; } return PENDINGMSG_WAITDEFPROCESS; } bool is_hung() const { return is_hung_; } LRESULT OnTimer(UINT msg, WPARAM wp, LPARAM lp, BOOL& handled) { // NOLINT is_hung_ = true; return 1; } private: HRESULT Initialize(int timeout_seconds) { // Create a window for the purpose of setting a windows timer for // detecting whether any outgoing COM call issued in this context // hangs. // We then register a COM message filter which basically looks for the // timer posted to this window and cancels the outgoing call. Create(HWND_MESSAGE); if (!IsWindow()) { LOG(ERROR) << "Failed to create window. Error:" << GetLastError(); return E_FAIL; } HRESULT hr = CoRegisterMessageFilter(this, prev_filter_.Receive()); if (FAILED(hr)) { LOG(ERROR) << "Failed to register message filter. Error:" << hr; return hr; } static const int kHungDetectTimerId = 0x0000baba; SetTimer(kHungDetectTimerId, 1000 * (timeout_seconds + 40), NULL); return S_OK; } // used to detect if outgoing COM calls hung. bool is_hung_; base::win::ScopedComPtr prev_filter_; }; // MessageLoopForUI wrapper that runs only for a limited time. // We need a UI message loop in the main thread. class TimedMsgLoop { public: TimedMsgLoop() : snapshot_on_timeout_(false), quit_loop_invoked_(false) { } void set_snapshot_on_timeout(bool value) { snapshot_on_timeout_ = value; } void RunFor(base::TimeDelta duration) { quit_loop_invoked_ = false; if (snapshot_on_timeout_) timeout_closure_.Reset(base::Bind(&TimedMsgLoop::SnapshotAndQuit)); else timeout_closure_.Reset(MessageLoop::QuitClosure()); loop_.PostDelayedTask(FROM_HERE, timeout_closure_.callback(), duration); loop_.MessageLoop::Run(); timeout_closure_.Cancel(); } void PostTask(const tracked_objects::Location& from_here, const base::Closure& task) { loop_.PostTask(from_here, task); } void PostDelayedTask(const tracked_objects::Location& from_here, const base::Closure& task, base::TimeDelta delay) { loop_.PostDelayedTask(from_here, task, delay); } void Quit() { // Quit after no delay. QuitAfter(base::TimeDelta()); } void QuitAfter(base::TimeDelta delay) { timeout_closure_.Cancel(); quit_loop_invoked_ = true; loop_.PostDelayedTask(FROM_HERE, MessageLoop::QuitClosure(), delay); } bool WasTimedOut() const { return !quit_loop_invoked_; } void RunAllPending() { loop_.RunAllPending(); } private: static void SnapshotAndQuit() { FilePath snapshot; if (ui_test_utils::SaveScreenSnapshotToDesktop(&snapshot)) { testing::UnitTest* unit_test = testing::UnitTest::GetInstance(); const testing::TestInfo* test_info = unit_test->current_test_info(); std::string name; if (test_info != NULL) { name.append(test_info->test_case_name()) .append(1, '.') .append(test_info->name()); } else { name = "unknown test"; } LOG(ERROR) << name << " timed out. Screen snapshot saved to " << snapshot.value(); } MessageLoop::current()->Quit(); } MessageLoopForUI loop_; base::CancelableClosure timeout_closure_; bool snapshot_on_timeout_; bool quit_loop_invoked_; }; // Saves typing. It's somewhat hard to create a wrapper around // testing::InvokeWithoutArgs since it returns a // non-public (testing::internal) type. #define QUIT_LOOP(loop) testing::InvokeWithoutArgs(\ testing::CreateFunctor(&loop, &chrome_frame_test::TimedMsgLoop::Quit)) #define QUIT_LOOP_SOON(loop, delay) testing::InvokeWithoutArgs(\ testing::CreateFunctor(&loop, &chrome_frame_test::TimedMsgLoop::QuitAfter, \ delay)) // Launches IE as a COM server and returns the corresponding IWebBrowser2 // interface pointer. // Returns S_OK on success. HRESULT LaunchIEAsComServer(IWebBrowser2** web_browser); // Returns the path of the exe passed in. std::wstring GetExecutableAppPath(const std::wstring& file); // Returns the profile path to be used for IE. This varies as per version. FilePath GetProfilePathForIE(); // Returns the version of the exe passed in. std::wstring GetExeVersion(const std::wstring& exe_path); // Returns the version of Internet Explorer on the machine. IEVersion GetInstalledIEVersion(); // Returns the folder for CF test data. FilePath GetTestDataFolder(); // Returns the folder for Selenium core. FilePath GetSeleniumTestFolder(); // Returns the path portion of the url. std::wstring GetPathFromUrl(const std::wstring& url); // Returns the path and query portion of the url. std::wstring GetPathAndQueryFromUrl(const std::wstring& url); // Adds the CF meta tag to the html page. Returns true if successful. bool AddCFMetaTag(std::string* html_data); // Get text data from the clipboard. std::wstring GetClipboardText(); // Puts the given text data on the clipboard. All previous items on the // clipboard are removed. void SetClipboardText(const std::wstring& text); // A convenience class to close all open IE windows at the end // of a scope. It's more convenient to do it this way than to // explicitly call chrome_frame_test::CloseAllIEWindows at the // end of a test since part of the test's cleanup code may be // in object destructors that would run after CloseAllIEWindows // would get called. // Ideally all IE windows should be closed when this happens so // if the test ran normally, we should not have any windows to // close at this point. class CloseIeAtEndOfScope { public: CloseIeAtEndOfScope() {} ~CloseIeAtEndOfScope(); }; // Starts the Chrome crash service which enables us to gather crash dumps // during test runs. base::ProcessHandle StartCrashService(); // Used in tests where we reference the registry and don't want to run into // problems where existing registry settings might conflict with the // expectations of the test. class ScopedVirtualizeHklmAndHkcu { public: ScopedVirtualizeHklmAndHkcu(); ~ScopedVirtualizeHklmAndHkcu(); // Removes all overrides and deletes all temporary test keys used by the // overrides. void RemoveAllOverrides(); protected: registry_util::RegistryOverrideManager override_manager_; }; // Attempts to kill all the processes on the current machine that were launched // from the given executable name, ending them with the given exit code. If // filter is non-null, then only processes selected by the filter are killed. // Returns true if all processes were able to be killed off, false if at least // one couldn't be killed. // NOTE: This function is based on the base\process_util.h helper function // KillProcesses. Takes in the wait flag as a parameter. bool KillProcesses(const std::wstring& executable_name, int exit_code, bool wait); // Returns the type of test bed, PER_USER or SYSTEM_LEVEL. ScopedChromeFrameRegistrar::RegistrationType GetTestBedType(); // Clears IE8 session restore history. void ClearIESessionHistory(); // Returns a local IPv4 address for the current machine. The address // corresponding to a NIC is preferred over the loopback address. std::string GetLocalIPv4Address(); } // namespace chrome_frame_test // TODO(tommi): This is a temporary workaround while we're getting our // Singleton story straight. Ideally each test should clear up any singletons // it might have created, but test cases do not implicitly have their own // AtExitManager, so we have this workaround method for tests that depend on // "fresh" singletons. The implementation is in chrome_frame_unittest_main.cc. void DeleteAllSingletons(); #endif // CHROME_FRAME_TEST_CHROME_FRAME_TEST_UTILS_H_