diff options
Diffstat (limited to 'net/url_request/url_request_unittest.h')
-rw-r--r-- | net/url_request/url_request_unittest.h | 380 |
1 files changed, 380 insertions, 0 deletions
diff --git a/net/url_request/url_request_unittest.h b/net/url_request/url_request_unittest.h new file mode 100644 index 0000000..7e40710 --- /dev/null +++ b/net/url_request/url_request_unittest.h @@ -0,0 +1,380 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef BASE_URL_REQUEST_URL_REQUEST_UNITTEST_H_ +#define BASE_URL_REQUEST_URL_REQUEST_UNITTEST_H_ + +#include <sstream> +#include <string> + +#include "base/file_util.h" +#include "base/message_loop.h" +#include "base/path_service.h" +#include "base/process_util.h" +#include "base/string_util.h" +#include "net/base/net_errors.h" +#include "net/http/http_network_layer.h" +#include "net/url_request/url_request.h" +#include "testing/gtest/include/gtest/gtest.h" + +const int kDefaultPort = 1337; +const std::string kDefaultHostName("localhost"); + +// This URLRequestContext does not use a local cache. +class TestURLRequestContext : public URLRequestContext { + public: + TestURLRequestContext() { + http_transaction_factory_ = net::HttpNetworkLayer::CreateFactory(NULL); + } + + virtual ~TestURLRequestContext() { + delete http_transaction_factory_; + } +}; + +class TestDelegate : public URLRequest::Delegate { + public: + TestDelegate() + : cancel_in_rr_(false), + cancel_in_rs_(false), + cancel_in_rd_(false), + cancel_in_rd_pending_(false), + quit_on_complete_(true), + response_started_count_(0), + received_bytes_count_(0), + received_redirect_count_(0), + received_data_before_response_(false), + request_failed_(false) { + } + + virtual void OnReceivedRedirect(URLRequest* request, const GURL& new_url) { + received_redirect_count_++; + if (cancel_in_rr_) + request->Cancel(); + } + + virtual void OnResponseStarted(URLRequest* request) { + // It doesn't make sense for the request to have IO pending at this point. + DCHECK(!request->status().is_io_pending()); + + response_started_count_++; + if (cancel_in_rs_) { + request->Cancel(); + OnResponseCompleted(request); + } else if (!request->status().is_success()) { + DCHECK(request->status().status() == URLRequestStatus::FAILED || + request->status().status() == URLRequestStatus::CANCELED); + request_failed_ = true; + OnResponseCompleted(request); + } else { + // Initiate the first read. + int bytes_read = 0; + if (request->Read(buf_, sizeof(buf_), &bytes_read)) + OnReadCompleted(request, bytes_read); + else if (!request->status().is_io_pending()) + OnResponseCompleted(request); + } + } + + virtual void OnReadCompleted(URLRequest* request, int bytes_read) { + // It doesn't make sense for the request to have IO pending at this point. + DCHECK(!request->status().is_io_pending()); + + if (response_started_count_ == 0) + received_data_before_response_ = true; + + if (cancel_in_rd_) + request->Cancel(); + + if (bytes_read >= 0) { + // There is data to read. + received_bytes_count_ += bytes_read; + + // consume the data + data_received_.append(buf_, bytes_read); + } + + // If it was not end of stream, request to read more. + if (request->status().is_success() && bytes_read > 0) { + bytes_read = 0; + while (request->Read(buf_, sizeof(buf_), &bytes_read)) { + if (bytes_read > 0) { + data_received_.append(buf_, bytes_read); + received_bytes_count_ += bytes_read; + } else { + break; + } + } + } + if (!request->status().is_io_pending()) + OnResponseCompleted(request); + else if (cancel_in_rd_pending_) + request->Cancel(); + } + + void OnResponseCompleted(URLRequest* request) { + if (quit_on_complete_) + MessageLoop::current()->Quit(); + } + + void OnAuthRequired(URLRequest* request, AuthChallengeInfo* auth_info) { + if (!username_.empty() || !password_.empty()) { + request->SetAuth(username_, password_); + } else { + request->CancelAuth(); + } + } + + virtual void OnSSLCertificateError(URLRequest* request, + int cert_error, + X509Certificate* cert) { + // Ignore SSL errors, we test the server is started and shut it down by + // performing GETs, no security restrictions should apply as we always want + // these GETs to go through. + request->ContinueDespiteLastError(); + } + + void set_cancel_in_received_redirect(bool val) { cancel_in_rr_ = val; } + void set_cancel_in_response_started(bool val) { cancel_in_rs_ = val; } + void set_cancel_in_received_data(bool val) { cancel_in_rd_ = val; } + void set_cancel_in_received_data_pending(bool val) { + cancel_in_rd_pending_ = val; + } + void set_quit_on_complete(bool val) { quit_on_complete_ = val; } + void set_username(const std::wstring& u) { username_ = u; } + void set_password(const std::wstring& p) { password_ = p; } + + // query state + const std::string& data_received() const { return data_received_; } + int bytes_received() const { return static_cast<int>(data_received_.size()); } + int response_started_count() const { return response_started_count_; } + int received_redirect_count() const { return received_redirect_count_; } + bool received_data_before_response() const { + return received_data_before_response_; + } + bool request_failed() const { return request_failed_; } + + private: + // options for controlling behavior + bool cancel_in_rr_; + bool cancel_in_rs_; + bool cancel_in_rd_; + bool cancel_in_rd_pending_; + bool quit_on_complete_; + + std::wstring username_; + std::wstring password_; + + // tracks status of callbacks + int response_started_count_; + int received_bytes_count_; + int received_redirect_count_; + bool received_data_before_response_; + bool request_failed_; + std::string data_received_; + + // our read buffer + char buf_[4096]; +}; + +// This object bounds the lifetime of an external python-based HTTP server +// that can provide various responses useful for testing. +class TestServer : public process_util::ProcessFilter { + public: + TestServer(const std::wstring& document_root) + : context_(new TestURLRequestContext), + process_handle_(NULL), + is_shutdown_(true) { + Init(kDefaultHostName, kDefaultPort, document_root, std::wstring()); + } + + virtual ~TestServer() { + Shutdown(); + } + + // Implementation of ProcessFilter + virtual bool Includes(uint32 pid, uint32 parent_pid) const { + // This function may be called after Shutdown(), in which process_handle_ is + // set to NULL. Since no process handle is set, it can't be included in the + // filter. + if (!process_handle_) + return false; + return pid == process_util::GetProcId(process_handle_); + } + + GURL TestServerPage(const std::string& path) { + return GURL(base_address_ + path); + } + + GURL TestServerPageW(const std::wstring& path) { + return GURL(UTF8ToWide(base_address_) + path); + } + + // A subclass may wish to send the request in a different manner + virtual bool MakeGETRequest(const std::string& page_name) { + TestDelegate d; + URLRequest r(TestServerPage(page_name), &d); + r.set_context(context_); + r.set_method("GET"); + r.Start(); + EXPECT_TRUE(r.is_pending()); + + MessageLoop::current()->Run(); + + return r.status().is_success(); + } + + protected: + struct ManualInit {}; + + // Used by subclasses that need to defer initialization until they are fully + // constructed. The subclass should call Init once it is ready (usually in + // its constructor). + TestServer(ManualInit) + : context_(new TestURLRequestContext), + process_handle_(NULL), + is_shutdown_(true) { + } + + virtual std::string scheme() { return std::string("http"); } + + // This is in a separate function so that we can have assertions and so that + // subclasses can call this later. + void Init(const std::string& host_name, int port, + const std::wstring& document_root, + const std::wstring& cert_path) { + std::stringstream ss; + std::string port_str; + ss << port ? port : kDefaultPort; + ss >> port_str; + base_address_ = scheme() + "://" + host_name + ":" + port_str + "/"; + + std::wstring testserver_path; + ASSERT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &testserver_path)); + file_util::AppendToPath(&testserver_path, L"net"); + file_util::AppendToPath(&testserver_path, L"tools"); + file_util::AppendToPath(&testserver_path, L"testserver"); + file_util::AppendToPath(&testserver_path, L"testserver.py"); + + ASSERT_TRUE(PathService::Get(base::DIR_SOURCE_ROOT, &python_runtime_)); + file_util::AppendToPath(&python_runtime_, L"third_party"); + file_util::AppendToPath(&python_runtime_, L"python_24"); + file_util::AppendToPath(&python_runtime_, L"python.exe"); + + std::wstring test_data_directory; + PathService::Get(base::DIR_SOURCE_ROOT, &test_data_directory); + std::wstring normalized_document_root = document_root; + std::replace(normalized_document_root.begin(), + normalized_document_root.end(), + L'/', file_util::kPathSeparator); + file_util::AppendToPath(&test_data_directory, normalized_document_root); + + std::wstring command_line = + L"\"" + python_runtime_ + L"\" " + L"\"" + testserver_path + + L"\" --port=" + UTF8ToWide(port_str) + L" --data-dir=\"" + + test_data_directory + L"\""; + if (!cert_path.empty()) { + command_line.append(L" --https=\""); + command_line.append(cert_path); + command_line.append(L"\""); + } + + ASSERT_TRUE( + process_util::LaunchApp(command_line, false, true, &process_handle_)) << + "Failed to launch " << command_line; + + // Verify that the webserver is actually started. + // Otherwise tests can fail if they run faster than Python can start. + int retries = 10; + bool success; + while ((success = MakeGETRequest("hello.html")) == false && retries > 0) { + retries--; + ::Sleep(500); + } + ASSERT_TRUE(success) << "Webserver not starting properly."; + + is_shutdown_ = false; + } + + void Shutdown() { + if (is_shutdown_) + return; + + // here we append the time to avoid problems where the kill page + // is being cached rather than being executed on the server + std::ostringstream page_name; + page_name << "kill?" << GetTickCount(); + int retry_count = 5; + while (retry_count > 0) { + bool r = MakeGETRequest(page_name.str()); + // BUG #1048625 causes the kill GET to fail. For now we just retry. + // Once the bug is fixed, we should remove the while loop and put back + // the following DCHECK. + // DCHECK(r); + if (r) + break; + retry_count--; + } + // Make sure we were successfull in stopping the testserver. + DCHECK(retry_count > 0); + + if (process_handle_) { + CloseHandle(process_handle_); + process_handle_ = NULL; + } + + // Make sure we don't leave any stray testserver processes laying around. + std::wstring testserver_name = + file_util::GetFilenameFromPath(python_runtime_); + process_util::CleanupProcesses(testserver_name, 10000, 1, this); + EXPECT_EQ(0, process_util::GetProcessCount(testserver_name, this)); + + is_shutdown_ = true; + } + + private: + scoped_refptr<TestURLRequestContext> context_; + std::string base_address_; + std::wstring python_runtime_; + HANDLE process_handle_; + bool is_shutdown_; +}; + +class HTTPSTestServer : public TestServer { + public: + HTTPSTestServer(const std::string& host_name, int port, + const std::wstring& document_root, + const std::wstring& cert_path) : TestServer(ManualInit()) { + Init(host_name, port, document_root, cert_path); + } + + virtual std::string scheme() { return std::string("https"); } +}; + +#endif // BASE_URL_REQUEST_URL_REQUEST_UNITTEST_H_ |