diff options
author | Ben Murdoch <benm@google.com> | 2010-07-29 17:14:53 +0100 |
---|---|---|
committer | Ben Murdoch <benm@google.com> | 2010-08-04 14:29:45 +0100 |
commit | c407dc5cd9bdc5668497f21b26b09d988ab439de (patch) | |
tree | 7eaf8707c0309516bdb042ad976feedaf72b0bb1 /net/test | |
parent | 0998b1cdac5733f299c12d88bc31ef9c8035b8fa (diff) | |
download | external_chromium-c407dc5cd9bdc5668497f21b26b09d988ab439de.zip external_chromium-c407dc5cd9bdc5668497f21b26b09d988ab439de.tar.gz external_chromium-c407dc5cd9bdc5668497f21b26b09d988ab439de.tar.bz2 |
Merge Chromium src@r53293
Change-Id: Ia79acf8670f385cee48c45b0a75371d8e950af34
Diffstat (limited to 'net/test')
-rw-r--r-- | net/test/test_server.cc | 414 | ||||
-rw-r--r-- | net/test/test_server.h | 149 |
2 files changed, 563 insertions, 0 deletions
diff --git a/net/test/test_server.cc b/net/test/test_server.cc new file mode 100644 index 0000000..4abed54 --- /dev/null +++ b/net/test/test_server.cc @@ -0,0 +1,414 @@ +// 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 "net/test/test_server.h" + +#include <algorithm> +#include <string> +#include <vector> + +#include "build/build_config.h" + +#if defined(OS_WIN) +#include <windows.h> +#include <wincrypt.h> +#elif defined(OS_MACOSX) +#include "net/base/x509_certificate.h" +#endif + +#include "base/file_util.h" +#include "base/leak_annotations.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "base/utf_string_conversions.h" +#include "net/base/cert_test_util.h" +#include "net/base/host_resolver.h" +#include "net/base/net_test_constants.h" +#include "net/base/test_completion_callback.h" +#include "net/socket/tcp_client_socket.h" +#include "net/socket/tcp_pinger.h" +#include "testing/platform_test.h" + +#if defined(OS_WIN) +#pragma comment(lib, "crypt32.lib") +#endif + +namespace net { + +#if defined(OS_MACOSX) +void SetMacTestCertificate(X509Certificate* cert); +#endif + +// static +const char TestServerLauncher::kHostName[] = "127.0.0.1"; +const char TestServerLauncher::kMismatchedHostName[] = "localhost"; +const int TestServerLauncher::kOKHTTPSPort = 9443; +const int TestServerLauncher::kBadHTTPSPort = 9666; + +// The issuer name of the cert that should be trusted for the test to work. +const wchar_t TestServerLauncher::kCertIssuerName[] = L"Test CA"; + +TestServerLauncher::TestServerLauncher() : process_handle_( + base::kNullProcessHandle), + forking_(false), + connection_attempts_(kDefaultTestConnectionAttempts), + connection_timeout_(kDefaultTestConnectionTimeout) +{ + InitCertPath(); +} + +TestServerLauncher::TestServerLauncher(int connection_attempts, + int connection_timeout) + : process_handle_(base::kNullProcessHandle), + forking_(false), + connection_attempts_(connection_attempts), + connection_timeout_(connection_timeout) +{ + InitCertPath(); +} + +void TestServerLauncher::InitCertPath() { + PathService::Get(base::DIR_SOURCE_ROOT, &cert_dir_); + cert_dir_ = cert_dir_.Append(FILE_PATH_LITERAL("net")) + .Append(FILE_PATH_LITERAL("data")) + .Append(FILE_PATH_LITERAL("ssl")) + .Append(FILE_PATH_LITERAL("certificates")); +} + +namespace { + +void AppendToPythonPath(const FilePath& dir) { + // Do nothing if dir already on path. + +#if defined(OS_WIN) + const wchar_t kPythonPath[] = L"PYTHONPATH"; + // TODO(dkegel): handle longer PYTHONPATH variables + wchar_t oldpath[4096]; + if (GetEnvironmentVariable(kPythonPath, oldpath, arraysize(oldpath)) == 0) { + SetEnvironmentVariableW(kPythonPath, dir.value().c_str()); + } else if (!wcsstr(oldpath, dir.value().c_str())) { + std::wstring newpath(oldpath); + newpath.append(L";"); + newpath.append(dir.value()); + SetEnvironmentVariableW(kPythonPath, newpath.c_str()); + } +#elif defined(OS_POSIX) + const char kPythonPath[] = "PYTHONPATH"; + const char* oldpath = getenv(kPythonPath); + // setenv() leaks memory intentionally on Mac + if (!oldpath) { + setenv(kPythonPath, dir.value().c_str(), 1); + } else if (!strstr(oldpath, dir.value().c_str())) { + std::string newpath(oldpath); + newpath.append(":"); + newpath.append(dir.value()); + setenv(kPythonPath, newpath.c_str(), 1); + } +#endif +} + +} // end namespace + +void TestServerLauncher::SetPythonPath() { + FilePath third_party_dir; + CHECK(PathService::Get(base::DIR_SOURCE_ROOT, &third_party_dir)); + third_party_dir = third_party_dir.Append(FILE_PATH_LITERAL("third_party")); + + AppendToPythonPath(third_party_dir.Append(FILE_PATH_LITERAL("tlslite"))); + AppendToPythonPath(third_party_dir.Append(FILE_PATH_LITERAL("pyftpdlib"))); + + // Locate the Python code generated by the protocol buffers compiler. + FilePath generated_code_dir; + CHECK(PathService::Get(base::DIR_EXE, &generated_code_dir)); + generated_code_dir = generated_code_dir.Append(FILE_PATH_LITERAL("pyproto")); + AppendToPythonPath(generated_code_dir); + AppendToPythonPath(generated_code_dir.Append(FILE_PATH_LITERAL("sync_pb"))); +} + +bool TestServerLauncher::Start(Protocol protocol, + const std::string& host_name, int port, + const FilePath& document_root, + const FilePath& cert_path, + const std::wstring& file_root_url) { + if (!cert_path.value().empty()) { + if (!LoadTestRootCert()) + return false; + if (!CheckCATrusted()) + return false; + } + + std::string port_str = IntToString(port); + + // Get path to python server script + FilePath testserver_path; + if (!PathService::Get(base::DIR_SOURCE_ROOT, &testserver_path)) + return false; + testserver_path = testserver_path + .Append(FILE_PATH_LITERAL("net")) + .Append(FILE_PATH_LITERAL("tools")) + .Append(FILE_PATH_LITERAL("testserver")) + .Append(FILE_PATH_LITERAL("testserver.py")); + + PathService::Get(base::DIR_SOURCE_ROOT, &document_root_dir_); + document_root_dir_ = document_root_dir_.Append(document_root); + + SetPythonPath(); + +#if defined(OS_WIN) + // Get path to python interpreter + if (!PathService::Get(base::DIR_SOURCE_ROOT, &python_runtime_)) + return false; + python_runtime_ = python_runtime_ + .Append(FILE_PATH_LITERAL("third_party")) + .Append(FILE_PATH_LITERAL("python_24")) + .Append(FILE_PATH_LITERAL("python.exe")); + + std::wstring command_line = + L"\"" + python_runtime_.ToWStringHack() + L"\" " + + L"\"" + testserver_path.ToWStringHack() + + L"\" --port=" + UTF8ToWide(port_str) + + L" --data-dir=\"" + document_root_dir_.ToWStringHack() + L"\""; + if (protocol == ProtoFTP) + command_line.append(L" -f"); + if (!cert_path.value().empty()) { + command_line.append(L" --https=\""); + command_line.append(cert_path.ToWStringHack()); + command_line.append(L"\""); + } + if (!file_root_url.empty()) { + command_line.append(L" --file-root-url=\""); + command_line.append(file_root_url); + command_line.append(L"\""); + } + // Deliberately do not pass the --forking flag. It breaks the tests + // on Windows. + + if (!LaunchTestServerAsJob(command_line, + true, + &process_handle_, + &job_handle_)) { + LOG(ERROR) << "Failed to launch " << command_line; + return false; + } +#elif defined(OS_POSIX) + std::vector<std::string> command_line; + command_line.push_back("python"); + command_line.push_back(testserver_path.value()); + command_line.push_back("--port=" + port_str); + command_line.push_back("--data-dir=" + document_root_dir_.value()); + if (protocol == ProtoFTP) + command_line.push_back("-f"); + if (!cert_path.value().empty()) + command_line.push_back("--https=" + cert_path.value()); + if (forking_) + command_line.push_back("--forking"); + + base::file_handle_mapping_vector no_mappings; + LOG(INFO) << "Trying to launch " << command_line[0] << " ..."; + if (!base::LaunchApp(command_line, no_mappings, false, &process_handle_)) { + LOG(ERROR) << "Failed to launch " << command_line[0] << " ..."; + return false; + } +#endif + + // Let the server start, then verify that it's up. + // Our server is Python, and takes about 500ms to start + // up the first time, and about 200ms after that. + if (!WaitToStart(host_name, port)) { + LOG(ERROR) << "Failed to connect to server"; + Stop(); + return false; + } + + LOG(INFO) << "Started on port " << port_str; + return true; +} + +bool TestServerLauncher::WaitToStart(const std::string& host_name, int port) { + // Verify that the webserver is actually started. + // Otherwise tests can fail if they run faster than Python can start. + net::AddressList addr; + scoped_refptr<net::HostResolver> resolver( + net::CreateSystemHostResolver(net::HostResolver::kDefaultParallelism)); + net::HostResolver::RequestInfo info(host_name, port); + int rv = resolver->Resolve(info, &addr, NULL, NULL, BoundNetLog()); + if (rv != net::OK) + return false; + + net::TCPPinger pinger(addr); + rv = pinger.Ping(base::TimeDelta::FromMilliseconds(connection_timeout_), + connection_attempts_); + return rv == net::OK; +} + +bool TestServerLauncher::WaitToFinish(int timeout_ms) { + if (!process_handle_) + return true; + + bool ret = base::WaitForSingleProcess(process_handle_, timeout_ms); + if (ret) { + base::CloseProcessHandle(process_handle_); + process_handle_ = base::kNullProcessHandle; + LOG(INFO) << "Finished."; + } else { + LOG(INFO) << "Timed out."; + } + return ret; +} + +bool TestServerLauncher::Stop() { + if (!process_handle_) + return true; + + // First check if the process has already terminated. + bool ret = base::WaitForSingleProcess(process_handle_, 0); + if (!ret) + ret = base::KillProcess(process_handle_, 1, true); + + if (ret) { + base::CloseProcessHandle(process_handle_); + process_handle_ = base::kNullProcessHandle; + LOG(INFO) << "Stopped."; + } else { + LOG(INFO) << "Kill failed?"; + } + + return ret; +} + +TestServerLauncher::~TestServerLauncher() { +#if defined(OS_MACOSX) + SetMacTestCertificate(NULL); +#endif + Stop(); +} + +FilePath TestServerLauncher::GetRootCertPath() { + FilePath path(cert_dir_); + path = path.AppendASCII("root_ca_cert.crt"); + return path; +} + +FilePath TestServerLauncher::GetOKCertPath() { + FilePath path(cert_dir_); + path = path.AppendASCII("ok_cert.pem"); + return path; +} + +FilePath TestServerLauncher::GetExpiredCertPath() { + FilePath path(cert_dir_); + path = path.AppendASCII("expired_cert.pem"); + return path; +} + +bool TestServerLauncher::LoadTestRootCert() { +#if defined(USE_NSS) + if (cert_) + return true; + + // TODO(dkegel): figure out how to get this to only happen once? + + // This currently leaks a little memory. + // TODO(dkegel): fix the leak and remove the entry in + // tools/valgrind/memcheck/suppressions.txt + ANNOTATE_SCOPED_MEMORY_LEAK; // Tell heap checker about the leak. + cert_ = LoadTemporaryRootCert(GetRootCertPath()); + DCHECK(cert_); + return (cert_ != NULL); +#elif defined(OS_MACOSX) + X509Certificate* cert = LoadTemporaryRootCert(GetRootCertPath()); + if (!cert) + return false; + SetMacTestCertificate(cert); + return true; +#else + return true; +#endif +} + +bool TestServerLauncher::CheckCATrusted() { +#if defined(OS_WIN) + HCERTSTORE cert_store = CertOpenSystemStore(NULL, L"ROOT"); + if (!cert_store) { + LOG(ERROR) << " could not open trusted root CA store"; + return false; + } + PCCERT_CONTEXT cert = + CertFindCertificateInStore(cert_store, + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + 0, + CERT_FIND_ISSUER_STR, + kCertIssuerName, + NULL); + if (cert) + CertFreeCertificateContext(cert); + CertCloseStore(cert_store, 0); + + if (!cert) { + LOG(ERROR) << " TEST CONFIGURATION ERROR: you need to import the test ca " + "certificate to your trusted roots for this test to work. " + "For more info visit:\n" + "http://dev.chromium.org/developers/testing\n"; + return false; + } +#endif + return true; +} + +#if defined(OS_WIN) +bool LaunchTestServerAsJob(const std::wstring& cmdline, + bool start_hidden, + base::ProcessHandle* process_handle, + ScopedHandle* job_handle) { + // Launch test server process. + STARTUPINFO startup_info = {0}; + startup_info.cb = sizeof(startup_info); + startup_info.dwFlags = STARTF_USESHOWWINDOW; + startup_info.wShowWindow = start_hidden ? SW_HIDE : SW_SHOW; + PROCESS_INFORMATION process_info; + + // If this code is run under a debugger, the test server process is + // automatically associated with a job object created by the debugger. + // The CREATE_BREAKAWAY_FROM_JOB flag is used to prevent this. + if (!CreateProcess(NULL, + const_cast<wchar_t*>(cmdline.c_str()), NULL, NULL, + FALSE, CREATE_BREAKAWAY_FROM_JOB, NULL, NULL, + &startup_info, &process_info)) { + LOG(ERROR) << "Could not create process."; + return false; + } + CloseHandle(process_info.hThread); + + // If the caller wants the process handle, we won't close it. + if (process_handle) { + *process_handle = process_info.hProcess; + } else { + CloseHandle(process_info.hProcess); + } + + // Create a JobObject and associate the test server process with it. + job_handle->Set(CreateJobObject(NULL, NULL)); + if (!job_handle->IsValid()) { + LOG(ERROR) << "Could not create JobObject."; + return false; + } else { + JOBOBJECT_EXTENDED_LIMIT_INFORMATION limit_info = {0}; + limit_info.BasicLimitInformation.LimitFlags = + JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + if (0 == SetInformationJobObject(job_handle->Get(), + JobObjectExtendedLimitInformation, &limit_info, sizeof(limit_info))) { + LOG(ERROR) << "Could not SetInformationJobObject."; + return false; + } + if (0 == AssignProcessToJobObject(job_handle->Get(), + process_info.hProcess)) { + LOG(ERROR) << "Could not AssignProcessToObject."; + return false; + } + } + return true; +} +#endif + +} // namespace net diff --git a/net/test/test_server.h b/net/test/test_server.h new file mode 100644 index 0000000..25f2c8b --- /dev/null +++ b/net/test/test_server.h @@ -0,0 +1,149 @@ +// 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 NET_TEST_TEST_SERVER_H_ +#define NET_TEST_TEST_SERVER_H_ + +#include "build/build_config.h" + +#include <string> + +#include "base/file_path.h" +#include "base/process_util.h" + +#if defined(OS_WIN) +#include "base/scoped_handle_win.h" +#endif + +#if defined(USE_NSS) +#include "base/ref_counted.h" +#include "net/base/x509_certificate.h" +#endif + +namespace net { + +// This object bounds the lifetime of an external python-based HTTP/HTTPS/FTP +// server that can provide various responses useful for testing. +// A few basic convenience methods are provided, but no +// URL handling methods (those belong at a higher layer, e.g. in +// url_request_unittest.h). + +class TestServerLauncher { + public: + TestServerLauncher(); + TestServerLauncher(int connection_attempts, int connection_timeout); + + virtual ~TestServerLauncher(); + + enum Protocol { + ProtoHTTP, ProtoFTP + }; + + // Load the test root cert, if it hasn't been loaded yet. + bool LoadTestRootCert(); + + // Tells the server to enable/disable servicing each request + // in a separate process. Takes effect only if called before Start. + void set_forking(bool forking) { forking_ = forking; } + + // Start src/net/tools/testserver/testserver.py and + // ask it to serve the given protocol. + // If protocol is HTTP, and cert_path is not empty, serves HTTPS. + // file_root_url specifies the root url on the server that documents will be + // served out of. This is /files/ by default. + // Returns true on success, false if files not found or root cert + // not trusted. + bool Start(net::TestServerLauncher::Protocol protocol, + const std::string& host_name, int port, + const FilePath& document_root, + const FilePath& cert_path, + const std::wstring& file_root_url); + + // Stop the server started by Start(). + bool Stop(); + + // If you access the server's Kill url, it will exit by itself + // without a call to Stop(). + // WaitToFinish is handy in that case. + // It returns true if the server exited cleanly. + bool WaitToFinish(int milliseconds); + + // Paths to a good, an expired, and an invalid server certificate + // (use as arguments to Start()). + FilePath GetOKCertPath(); + FilePath GetExpiredCertPath(); + + FilePath GetDocumentRootPath() { return document_root_dir_; } + + // Issuer name of the root cert that should be trusted for the test to work. + static const wchar_t kCertIssuerName[]; + + // Hostname to use for test server + static const char kHostName[]; + + // Different hostname to use for test server (that still resolves to same IP) + static const char kMismatchedHostName[]; + + // Port to use for test server + static const int kOKHTTPSPort; + + // Port to use for bad test server + static const int kBadHTTPSPort; + + private: + // Wait a while for the server to start, return whether + // we were able to make a connection to it. + bool WaitToStart(const std::string& host_name, int port); + + // Append to PYTHONPATH so Python can find pyftpdlib and tlslite. + void SetPythonPath(); + + // Path to our test root certificate. + FilePath GetRootCertPath(); + + // Returns false if our test root certificate is not trusted. + bool CheckCATrusted(); + + // Initilize the certificate path. + void InitCertPath(); + + FilePath document_root_dir_; + + FilePath cert_dir_; + + FilePath python_runtime_; + + base::ProcessHandle process_handle_; + +#if defined(OS_WIN) + // JobObject used to clean up orphaned child processes. + ScopedHandle job_handle_; +#endif + + // True if the server should handle each request in a separate process. + bool forking_; + + // Number of tries and timeout for each try used for WaitToStart. + int connection_attempts_; + int connection_timeout_; + +#if defined(USE_NSS) + scoped_refptr<X509Certificate> cert_; +#endif + + DISALLOW_COPY_AND_ASSIGN(TestServerLauncher); +}; + +#if defined(OS_WIN) +// Launch test server as a job so that it is not orphaned if the test case is +// abnormally terminated. +bool LaunchTestServerAsJob(const std::wstring& cmdline, + bool start_hidden, + base::ProcessHandle* process_handle, + ScopedHandle* job_handle); +#endif + +} // namespace net + +#endif // NET_TEST_TEST_SERVER_H_ |