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 #ifndef CHROME_FRAME_TEST_TEST_WITH_WEB_SERVER_H_ 6 #define CHROME_FRAME_TEST_TEST_WITH_WEB_SERVER_H_ 7 8 #include <windows.h> 9 #include <string> 10 11 #include "base/files/scoped_temp_dir.h" 12 #include "base/strings/string_util.h" 13 #include "base/strings/stringprintf.h" 14 #include "base/strings/utf_string_conversions.h" 15 #include "base/win/scoped_handle.h" 16 #include "chrome_frame/chrome_tab.h" 17 #include "chrome_frame/test/chrome_frame_test_utils.h" 18 #include "chrome_frame/test/test_server.h" 19 #include "testing/gmock/include/gmock/gmock.h" 20 #include "testing/gtest/include/gtest/gtest.h" 21 22 // Specifies the invocation method for CF. 23 class CFInvocation { 24 public: 25 enum Type { 26 NONE = 0, 27 META_TAG, 28 HTTP_HEADER 29 }; 30 31 CFInvocation(): method_(NONE) {} 32 explicit CFInvocation(Type method): method_(method) {} 33 34 // Convience methods for creating this class. 35 static CFInvocation None() { return CFInvocation(NONE); } 36 static CFInvocation MetaTag() { return CFInvocation(META_TAG); } 37 static CFInvocation HttpHeader() { return CFInvocation(HTTP_HEADER); } 38 39 // Returns whether this page does invoke CF. 40 bool invokes_cf() const { 41 return method_ != NONE; 42 } 43 44 Type type() const { return method_; } 45 46 private: 47 Type method_; 48 }; 49 50 // An interface for listeners of interesting events on a MockWebServer. 51 class WebServerListener { 52 public: 53 virtual ~WebServerListener() {} 54 55 // Invoked when a MockWebServer receives an expected response; see 56 // MockWebServer::ExpectAndHandlePostedResult. 57 virtual void OnExpectedResponse() = 0; 58 }; 59 60 // Simple Gmock friendly web server. Sample usage: 61 // MockWebServer mock(9999, "0.0.0.0"); 62 // EXPECT_CALL(mock, Get(_, StrEq("/favicon.ico"), _)).WillRepeatedly(SendFast( 63 // "HTTP/1.1 404 Not Found" 64 // "text/html; charset=UTF-8", std::string())); 65 // 66 // EXPECT_CALL(mock, Get(_, StrEq("/book"), _)).WillRepeatedly(Send( 67 // "HTTP/1.1 302 Found\r\n" 68 // "Connection: close\r\n" 69 // "Content-Type: text/html\r\n" 70 // "Location: library\r\n", 71 // "<html>Lalalala</html>", 3, 1000)); 72 // 73 // EXPECT_CALL(mock, Get(_, StrEq("/library"), _)).WillRepeatedly(Send( 74 // "HTTP/1.1 200 OK\r\n" 75 // "Connection: close\r\n" 76 // "Content-Type: text/html\r\n", 77 // "<html><meta http-equiv=\"X-UA-Compatible\" content=\"chrome=1\" />" 78 // "<body>Rendered in CF.</body></html>", 4, 1000)); 79 class MockWebServer : public test_server::HTTPTestServer { 80 public: 81 MockWebServer(int port, const std::wstring& address, base::FilePath root_dir) 82 : test_server::HTTPTestServer(port, address, root_dir), listener_(NULL) {} 83 84 // Overriden from test_server::HTTPTestServer. 85 MOCK_METHOD3(Get, void(test_server::ConfigurableConnection* connection, 86 const std::wstring& path, 87 const test_server::Request& r)); 88 MOCK_METHOD3(Post, void(test_server::ConfigurableConnection* connection, 89 const std::wstring& path, 90 const test_server::Request& r)); 91 92 // Expect a GET request for |url|. Respond with the file appropriate for 93 // the given |url|. Modify the file to follow the given CFInvocation method. 94 // The response includes a no-cache header. |allow_meta_tag_double_req| 95 // specifies whether to allow the request to happen twice if the invocation 96 // is using the CF meta tag. 97 void ExpectAndServeRequest(CFInvocation invocation, const std::wstring& url); 98 99 // Expect a number of GET requests for |url|. Rest is similar to the function 100 // ExpectAndServeRequest. 101 void ExpectAndServeRequestWithCardinality(CFInvocation invocation, 102 const std::wstring& url, 103 testing::Cardinality cardinality); 104 105 // Same as above except do not include the no-cache header. 106 void ExpectAndServeRequestAllowCache(CFInvocation invocation, 107 const std::wstring& url); 108 109 // Expect any number of GETs for the given resource path (e.g, /favicon.ico) 110 // and respond with the file, if it exists, or a 404 if it does not. 111 void ExpectAndServeRequestAnyNumberTimes(CFInvocation invocation, 112 const std::wstring& path_prefix); 113 114 void set_listener(WebServerListener* listener) { listener_ = listener; } 115 116 // Expect a POST to an URL containing |post_suffix|, saving the response 117 // contents for retrieval by posted_result(). Invokes the listener's 118 // OnExpectedResponse method if the posted response matches the expected 119 // result. 120 void ExpectAndHandlePostedResult(CFInvocation invocation, 121 const std::wstring& post_suffix); 122 123 // Expect and serve all incoming GET requests. 124 void ExpectAndServeAnyRequests(CFInvocation invocation) { 125 ExpectAndServeRequestAnyNumberTimes(invocation, L""); 126 } 127 128 129 // Send a response on the given connection appropriate for |resource_uri|. 130 // If the file referred to by |path| exists, send the file data, otherwise 131 // send 404. Modify the file data according to the given invocation method. 132 void SendResponseHelper(test_server::ConfigurableConnection* connection, 133 const std::wstring& resource_uri, 134 const test_server::Request& request, 135 CFInvocation invocation, 136 bool add_no_cache_header); 137 // Handles the posted /writefile response 138 void HandlePostedResponse(test_server::ConfigurableConnection* connection, 139 const test_server::Request& request); 140 141 void ClearResults() { 142 posted_result_.clear(); 143 expected_result_.clear(); 144 } 145 146 void set_expected_result(const std::string& expected_result) { 147 expected_result_ = expected_result; 148 } 149 150 const std::string& posted_result() const { 151 return posted_result_; 152 } 153 154 private: 155 WebServerListener* listener_; 156 // Holds the results of tests which post success/failure. 157 std::string posted_result_; 158 std::string expected_result_; 159 }; 160 161 class MockWebServerListener : public WebServerListener { 162 public: 163 MOCK_METHOD0(OnExpectedResponse, void()); 164 }; 165 166 // Class that: 167 // 1) Starts the local webserver, 168 // 2) Supports launching browsers - Internet Explorer with local url 169 // 3) Wait the webserver to finish. It is supposed the test webpage to shutdown 170 // the server by navigating to "kill" page 171 // 4) Supports read the posted results from the test webpage to the "dump" 172 // webserver directory 173 class ChromeFrameTestWithWebServer : public testing::Test { 174 public: 175 ChromeFrameTestWithWebServer(); 176 177 protected: 178 enum BrowserKind { INVALID, IE, CHROME }; 179 180 bool LaunchBrowser(BrowserKind browser, const wchar_t* url); 181 182 // Returns true if the test completed in time, or false if it timed out. 183 bool WaitForTestToComplete(base::TimeDelta duration); 184 185 // Waits for the page to notify us of the window.onload event firing. 186 // Note that the milliseconds value is only approximate. 187 bool WaitForOnLoad(int milliseconds); 188 189 // Launches the specified browser and waits for the test to complete (see 190 // WaitForTestToComplete). Then checks that the outcome is equal to the 191 // expected result. The test is repeated once if it fails due to a timeout. 192 // This function uses EXPECT_TRUE and ASSERT_TRUE for all steps performed 193 // hence no return value. 194 void SimpleBrowserTestExpectedResult(BrowserKind browser, 195 const wchar_t* page, const char* result); 196 void SimpleBrowserTest(BrowserKind browser, const wchar_t* page); 197 198 // Sets up expectations for a page to post back a result. 199 void ExpectAndHandlePostedResult(); 200 201 // Test if chrome frame correctly reports its version. 202 void VersionTest(BrowserKind browser, const wchar_t* page); 203 204 void CloseBrowser(); 205 206 // Ensures (well, at least tries to ensure) that the browser window has focus. 207 bool BringBrowserToTop(); 208 209 const base::FilePath& GetCFTestFilePath() { 210 return test_file_path_; 211 } 212 213 static chrome_frame_test::TimedMsgLoop& loop() { 214 return *loop_; 215 } 216 217 static testing::StrictMock<MockWebServerListener>& listener_mock() { 218 return *listener_mock_; 219 } 220 221 static testing::StrictMock<MockWebServer>& server_mock() { 222 return *server_mock_; 223 } 224 225 static void SetUpTestCase(); 226 static void TearDownTestCase(); 227 228 static const base::FilePath& GetChromeUserDataDirectory(); 229 230 virtual void SetUp() OVERRIDE; 231 virtual void TearDown() OVERRIDE; 232 233 // The on-disk path to our html test files. 234 static base::FilePath test_file_path_; 235 static base::FilePath results_dir_; 236 static base::FilePath CFInstall_path_; 237 static base::FilePath CFInstance_path_; 238 static base::FilePath chrome_user_data_dir_; 239 240 // The user data directory used for Chrome instances. 241 static base::ScopedTempDir temp_dir_; 242 243 // The web server from which we serve the web! 244 static chrome_frame_test::TimedMsgLoop* loop_; 245 static std::string local_address_; 246 static testing::StrictMock<MockWebServerListener>* listener_mock_; 247 static testing::StrictMock<MockWebServer>* server_mock_; 248 249 BrowserKind browser_; 250 base::win::ScopedHandle browser_handle_; 251 }; 252 253 // A helper class for doing some bookkeeping when using the 254 // SimpleWebServer class. 255 class SimpleWebServerTest { 256 public: 257 SimpleWebServerTest(const std::string& address, int port) 258 : server_(address, port), port_(port) { 259 } 260 261 ~SimpleWebServerTest() { 262 server_.DeleteAllResponses(); 263 } 264 265 template <class ResponseClass> 266 void PopulateStaticFileListT(const wchar_t* pages[], int count, 267 const base::FilePath& directory) { 268 for (int i = 0; i < count; ++i) { 269 server_.AddResponse(new ResponseClass( 270 base::StringPrintf("/%ls", pages[i]).c_str(), 271 directory.Append(pages[i]))); 272 } 273 } 274 275 std::wstring FormatHttpPath(const wchar_t* document_path) { 276 return base::StringPrintf(L"http://%ls:%i/%ls", 277 ASCIIToWide(server_.host()).c_str(), port_, 278 document_path); 279 } 280 281 // Returns the last client request object. 282 // Under normal circumstances this will be the request for /quit. 283 const test_server::Request& last_request() const { 284 const test_server::ConnectionList& connections = server_.connections(); 285 DCHECK(connections.size()); 286 const test_server::Connection* c = connections.back(); 287 return c->request(); 288 } 289 290 bool FindRequest(const std::string& path, 291 const test_server::Request** request) { 292 test_server::ConnectionList::const_iterator index; 293 for (index = server_.connections().begin(); 294 index != server_.connections().end(); index++) { 295 const test_server::Connection* connection = *index; 296 if (!lstrcmpiA(connection->request().path().c_str(), path.c_str())) { 297 if (request) 298 *request = &connection->request(); 299 return true; 300 } 301 } 302 return false; 303 } 304 305 // Counts the number of times a page was requested. 306 // Optionally checks if the request method for each is equal to 307 // |expected_method|. If expected_method is NULL no such check is made. 308 int GetRequestCountForPage(const wchar_t* page, const char* expected_method) { 309 // Check how many requests we got for the cf page. 310 test_server::ConnectionList::const_iterator it; 311 int requests = 0; 312 const test_server::ConnectionList& connections = server_.connections(); 313 for (it = connections.begin(); it != connections.end(); ++it) { 314 const test_server::Connection* c = (*it); 315 const test_server::Request& r = c->request(); 316 if (!r.path().empty() && 317 ASCIIToWide(r.path().substr(1)).compare(page) == 0) { 318 if (expected_method) { 319 EXPECT_EQ(expected_method, r.method()); 320 } 321 requests++; 322 } 323 } 324 return requests; 325 } 326 327 test_server::SimpleWebServer* web_server() { 328 return &server_; 329 } 330 331 protected: 332 test_server::SimpleWebServer server_; 333 int port_; 334 }; 335 336 ACTION_P2(SendFast, headers, content) { 337 arg0->Send(headers, content); 338 } 339 340 ACTION_P4(Send, headers, content, chunk, timeout) { 341 test_server::ConfigurableConnection::SendOptions options( 342 test_server::ConfigurableConnection::SendOptions:: 343 IMMEDIATE_HEADERS_DELAYED_CONTENT, chunk, timeout); 344 arg0->SendWithOptions(std::string(headers), 345 std::string(content), 346 options); 347 } 348 349 ACTION_P4(SendSlow, headers, content, chunk, timeout) { 350 test_server::ConfigurableConnection::SendOptions options( 351 test_server::ConfigurableConnection::SendOptions::DELAYED, chunk, timeout); 352 arg0->SendWithOptions(std::string(headers), 353 std::string(content), 354 options); 355 } 356 357 // Sends a response with the file data for the given path, if the file exists, 358 // or a 404 if the file does not. This response includes a no-cache header. 359 ACTION_P2(SendResponse, server, invocation) { 360 server->SendResponseHelper(arg0, arg1, arg2, invocation, true); 361 } 362 363 // Same as above except that the response does not include the no-cache header. 364 ACTION_P2(SendAllowCacheResponse, server, invocation) { 365 server->SendResponseHelper(arg0, arg1, invocation, false); 366 } 367 368 ACTION_P2(HandlePostedResponseHelper, server, invocation) { 369 server->HandlePostedResponse(arg0, arg2); 370 } 371 372 #endif // CHROME_FRAME_TEST_TEST_WITH_WEB_SERVER_H_ 373