diff options
Diffstat (limited to 'net/test')
-rw-r--r-- | net/test/base_test_server.cc | 358 | ||||
-rw-r--r-- | net/test/base_test_server.h | 213 | ||||
-rw-r--r-- | net/test/local_test_server.cc | 237 | ||||
-rw-r--r-- | net/test/local_test_server.h | 87 | ||||
-rw-r--r-- | net/test/local_test_server_posix.cc (renamed from net/test/test_server_posix.cc) | 10 | ||||
-rw-r--r-- | net/test/local_test_server_win.cc (renamed from net/test/test_server_win.cc) | 13 | ||||
-rw-r--r-- | net/test/remote_test_server.cc | 168 | ||||
-rw-r--r-- | net/test/remote_test_server.h | 53 | ||||
-rw-r--r-- | net/test/spawner_communicator.cc | 367 | ||||
-rw-r--r-- | net/test/spawner_communicator.h | 150 | ||||
-rw-r--r-- | net/test/test_server.cc | 427 | ||||
-rw-r--r-- | net/test/test_server.h | 225 |
12 files changed, 1654 insertions, 654 deletions
diff --git a/net/test/base_test_server.cc b/net/test/base_test_server.cc new file mode 100644 index 0000000..ce28980 --- /dev/null +++ b/net/test/base_test_server.cc @@ -0,0 +1,358 @@ +// 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. + +#include "net/test/base_test_server.h" + +#include <string> +#include <vector> + +#include "base/base64.h" +#include "base/file_util.h" +#include "base/json/json_reader.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "base/values.h" +#include "googleurl/src/gurl.h" +#include "net/base/address_list.h" +#include "net/base/host_port_pair.h" +#include "net/base/host_resolver.h" +#include "net/base/net_errors.h" +#include "net/base/net_log.h" +#include "net/base/net_util.h" +#include "net/base/test_completion_callback.h" +#include "net/base/test_root_certs.h" + +namespace net { + +namespace { + +std::string GetHostname(BaseTestServer::Type type, + const BaseTestServer::HTTPSOptions& options) { + if (type == BaseTestServer::TYPE_HTTPS && + options.server_certificate == + BaseTestServer::HTTPSOptions::CERT_MISMATCHED_NAME) { + // Return a different hostname string that resolves to the same hostname. + return "localhost"; + } + + // Use the 127.0.0.1 as default. + return BaseTestServer::kLocalhost; +} + +void GetCiphersList(int cipher, base::ListValue* values) { + if (cipher & BaseTestServer::HTTPSOptions::BULK_CIPHER_RC4) + values->Append(base::Value::CreateStringValue("rc4")); + if (cipher & BaseTestServer::HTTPSOptions::BULK_CIPHER_AES128) + values->Append(base::Value::CreateStringValue("aes128")); + if (cipher & BaseTestServer::HTTPSOptions::BULK_CIPHER_AES256) + values->Append(base::Value::CreateStringValue("aes256")); + if (cipher & BaseTestServer::HTTPSOptions::BULK_CIPHER_3DES) + values->Append(base::Value::CreateStringValue("3des")); +} + +} // namespace + +BaseTestServer::HTTPSOptions::HTTPSOptions() + : server_certificate(CERT_OK), + request_client_certificate(false), + bulk_ciphers(HTTPSOptions::BULK_CIPHER_ANY), + record_resume(false) {} + +BaseTestServer::HTTPSOptions::HTTPSOptions( + BaseTestServer::HTTPSOptions::ServerCertificate cert) + : server_certificate(cert), + request_client_certificate(false), + bulk_ciphers(HTTPSOptions::BULK_CIPHER_ANY), + record_resume(false) {} + +BaseTestServer::HTTPSOptions::~HTTPSOptions() {} + +FilePath BaseTestServer::HTTPSOptions::GetCertificateFile() const { + switch (server_certificate) { + case CERT_OK: + case CERT_MISMATCHED_NAME: + return FilePath(FILE_PATH_LITERAL("ok_cert.pem")); + case CERT_EXPIRED: + return FilePath(FILE_PATH_LITERAL("expired_cert.pem")); + case CERT_CHAIN_WRONG_ROOT: + // This chain uses its own dedicated test root certificate to avoid + // side-effects that may affect testing. + return FilePath(FILE_PATH_LITERAL("redundant-server-chain.pem")); + default: + NOTREACHED(); + } + return FilePath(); +} + +const char BaseTestServer::kLocalhost[] = "127.0.0.1"; +const char BaseTestServer::kGDataAuthToken[] = "testtoken"; + +BaseTestServer::BaseTestServer(Type type, const std::string& host) + : type_(type), + started_(false), + log_to_console_(false) { + Init(host); +} + +BaseTestServer::BaseTestServer(const HTTPSOptions& https_options) + : https_options_(https_options), + type_(TYPE_HTTPS), + started_(false), + log_to_console_(false) { + Init(GetHostname(TYPE_HTTPS, https_options)); +} + +BaseTestServer::~BaseTestServer() {} + +const HostPortPair& BaseTestServer::host_port_pair() const { + DCHECK(started_); + return host_port_pair_; +} + +const DictionaryValue& BaseTestServer::server_data() const { + DCHECK(started_); + DCHECK(server_data_.get()); + return *server_data_; +} + +std::string BaseTestServer::GetScheme() const { + switch (type_) { + case TYPE_FTP: + return "ftp"; + case TYPE_GDATA: + case TYPE_HTTP: + case TYPE_SYNC: + return "http"; + case TYPE_HTTPS: + return "https"; + case TYPE_TCP_ECHO: + case TYPE_UDP_ECHO: + default: + NOTREACHED(); + } + return std::string(); +} + +bool BaseTestServer::GetAddressList(AddressList* address_list) const { + DCHECK(address_list); + + scoped_ptr<HostResolver> resolver( + CreateSystemHostResolver(HostResolver::kDefaultParallelism, + HostResolver::kDefaultRetryAttempts, + NULL)); + HostResolver::RequestInfo info(host_port_pair_); + TestCompletionCallback callback; + int rv = resolver->Resolve(info, address_list, callback.callback(), NULL, + BoundNetLog()); + if (rv == ERR_IO_PENDING) + rv = callback.WaitForResult(); + if (rv != net::OK) { + LOG(ERROR) << "Failed to resolve hostname: " << host_port_pair_.host(); + return false; + } + return true; +} + +uint16 BaseTestServer::GetPort() { + return host_port_pair_.port(); +} + +void BaseTestServer::SetPort(uint16 port) { + host_port_pair_.set_port(port); +} + +GURL BaseTestServer::GetURL(const std::string& path) const { + return GURL(GetScheme() + "://" + host_port_pair_.ToString() + "/" + path); +} + +GURL BaseTestServer::GetURLWithUser(const std::string& path, + const std::string& user) const { + return GURL(GetScheme() + "://" + user + "@" + host_port_pair_.ToString() + + "/" + path); +} + +GURL BaseTestServer::GetURLWithUserAndPassword(const std::string& path, + const std::string& user, + const std::string& password) const { + return GURL(GetScheme() + "://" + user + ":" + password + "@" + + host_port_pair_.ToString() + "/" + path); +} + +// static +bool BaseTestServer::GetFilePathWithReplacements( + const std::string& original_file_path, + const std::vector<StringPair>& text_to_replace, + std::string* replacement_path) { + std::string new_file_path = original_file_path; + bool first_query_parameter = true; + const std::vector<StringPair>::const_iterator end = text_to_replace.end(); + for (std::vector<StringPair>::const_iterator it = text_to_replace.begin(); + it != end; + ++it) { + const std::string& old_text = it->first; + const std::string& new_text = it->second; + std::string base64_old; + std::string base64_new; + if (!base::Base64Encode(old_text, &base64_old)) + return false; + if (!base::Base64Encode(new_text, &base64_new)) + return false; + if (first_query_parameter) { + new_file_path += "?"; + first_query_parameter = false; + } else { + new_file_path += "&"; + } + new_file_path += "replace_text="; + new_file_path += base64_old; + new_file_path += ":"; + new_file_path += base64_new; + } + + *replacement_path = new_file_path; + return true; +} + +void BaseTestServer::Init(const std::string& host) { + host_port_pair_ = HostPortPair(host, 0); + + // TODO(battre) Remove this after figuring out why the TestServer is flaky. + // http://crbug.com/96594 + log_to_console_ = true; +} + +void BaseTestServer::SetResourcePath(const FilePath& document_root, + const FilePath& certificates_dir) { + // This method shouldn't get called twice. + DCHECK(certificates_dir_.empty()); + document_root_ = document_root; + certificates_dir_ = certificates_dir; + DCHECK(!certificates_dir_.empty()); +} + +bool BaseTestServer::ParseServerData(const std::string& server_data) { + VLOG(1) << "Server data: " << server_data; + base::JSONReader json_reader; + scoped_ptr<Value> value(json_reader.JsonToValue(server_data, true, false)); + if (!value.get() || !value->IsType(Value::TYPE_DICTIONARY)) { + LOG(ERROR) << "Could not parse server data: " + << json_reader.GetErrorMessage(); + return false; + } + + server_data_.reset(static_cast<DictionaryValue*>(value.release())); + int port = 0; + if (!server_data_->GetInteger("port", &port)) { + LOG(ERROR) << "Could not find port value"; + return false; + } + if ((port <= 0) || (port > kuint16max)) { + LOG(ERROR) << "Invalid port value: " << port; + return false; + } + host_port_pair_.set_port(port); + + return true; +} + +bool BaseTestServer::LoadTestRootCert() const { + TestRootCerts* root_certs = TestRootCerts::GetInstance(); + if (!root_certs) + return false; + + // Should always use absolute path to load the root certificate. + FilePath root_certificate_path = certificates_dir_; + if (!certificates_dir_.IsAbsolute()) { + FilePath src_dir; + if (!PathService::Get(base::DIR_SOURCE_ROOT, &src_dir)) + return false; + root_certificate_path = src_dir.Append(certificates_dir_); + } + + return root_certs->AddFromFile( + root_certificate_path.AppendASCII("root_ca_cert.crt")); +} + +bool BaseTestServer::SetupWhenServerStarted() { + DCHECK(host_port_pair_.port()); + + if (type_ == TYPE_HTTPS && !LoadTestRootCert()) + return false; + + started_ = true; + allowed_port_.reset(new ScopedPortException(host_port_pair_.port())); + return true; +} + +void BaseTestServer::CleanUpWhenStoppingServer() { + TestRootCerts* root_certs = TestRootCerts::GetInstance(); + root_certs->Clear(); + + host_port_pair_.set_port(0); + allowed_port_.reset(); + started_ = false; +} + +// Generates a dictionary of arguments to pass to the Python test server via +// the test server spawner, in the form of +// { argument-name: argument-value, ... } +// Returns false if an invalid configuration is specified. +bool BaseTestServer::GenerateArguments(base::DictionaryValue* arguments) const { + DCHECK(arguments); + + arguments->SetString("host", host_port_pair_.host()); + arguments->SetInteger("port", host_port_pair_.port()); + arguments->SetString("data-dir", document_root_.value()); + + if (VLOG_IS_ON(1) || log_to_console_) + arguments->Set("log-to-console", base::Value::CreateNullValue()); + + if (type_ == TYPE_GDATA) { + // --auth-token will be used in tests for chrome/browser/chromeos/gdata. + arguments->SetString("auth-token", kGDataAuthToken); + } else if (type_ == TYPE_HTTPS) { + // Check the certificate arguments of the HTTPS server. + FilePath certificate_path(certificates_dir_); + certificate_path = certificate_path.Append( + https_options_.GetCertificateFile()); + if (certificate_path.IsAbsolute() && + !file_util::PathExists(certificate_path)) { + LOG(ERROR) << "Certificate path " << certificate_path.value() + << " doesn't exist. Can't launch https server."; + return false; + } + arguments->SetString("https", certificate_path.value()); + + // Check the client certificate related arguments. + if (https_options_.request_client_certificate) + arguments->Set("ssl-client-auth", base::Value::CreateNullValue()); + scoped_ptr<base::ListValue> ssl_client_certs(new base::ListValue()); + + std::vector<FilePath>::const_iterator it; + for (it = https_options_.client_authorities.begin(); + it != https_options_.client_authorities.end(); ++it) { + if (it->IsAbsolute() && !file_util::PathExists(*it)) { + LOG(ERROR) << "Client authority path " << it->value() + << " doesn't exist. Can't launch https server."; + return false; + } + ssl_client_certs->Append(base::Value::CreateStringValue(it->value())); + } + + if (ssl_client_certs->GetSize()) + arguments->Set("ssl-client-ca", ssl_client_certs.release()); + + // Check bulk cipher argument. + scoped_ptr<base::ListValue> bulk_cipher_values(new base::ListValue()); + GetCiphersList(https_options_.bulk_ciphers, bulk_cipher_values.get()); + if (bulk_cipher_values->GetSize()) + arguments->Set("ssl-bulk-cipher", bulk_cipher_values.release()); + if (https_options_.record_resume) + arguments->Set("https-record-resume", base::Value::CreateNullValue()); + } + return true; +} + +} // namespace net + diff --git a/net/test/base_test_server.h b/net/test/base_test_server.h new file mode 100644 index 0000000..38f755d --- /dev/null +++ b/net/test/base_test_server.h @@ -0,0 +1,213 @@ +// 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 NET_TEST_BASE_TEST_SERVER_H_ +#define NET_TEST_BASE_TEST_SERVER_H_ +#pragma once + +#include <string> +#include <utility> +#include <vector> + +#include "base/compiler_specific.h" +#include "base/file_path.h" +#include "base/memory/scoped_ptr.h" +#include "net/base/host_port_pair.h" + +class GURL; + +namespace base { +class DictionaryValue; +} + +namespace net { + +class AddressList; +class ScopedPortException; + +// The base class of Test server implementation. +class BaseTestServer { + public: + typedef std::pair<std::string, std::string> StringPair; + + enum Type { + TYPE_FTP, + TYPE_GDATA, + TYPE_HTTP, + TYPE_HTTPS, + TYPE_SYNC, + TYPE_TCP_ECHO, + TYPE_UDP_ECHO, + }; + + // Container for various options to control how the HTTPS server is + // initialized. + struct HTTPSOptions { + enum ServerCertificate { + CERT_OK, + CERT_MISMATCHED_NAME, + CERT_EXPIRED, + // Cross-signed certificate to test PKIX path building. Contains an + // intermediate cross-signed by an unknown root, while the client (via + // TestRootStore) is expected to have a self-signed version of the + // intermediate. + CERT_CHAIN_WRONG_ROOT, + }; + + // Bitmask of bulk encryption algorithms that the test server supports + // and that can be selectively enabled or disabled. + enum BulkCipher { + // Special value used to indicate that any algorithm the server supports + // is acceptable. Preferred over explicitly OR-ing all ciphers. + BULK_CIPHER_ANY = 0, + + BULK_CIPHER_RC4 = (1 << 0), + BULK_CIPHER_AES128 = (1 << 1), + BULK_CIPHER_AES256 = (1 << 2), + + // NOTE: 3DES support in the Python test server has external + // dependencies and not be available on all machines. Clients may not + // be able to connect if only 3DES is specified. + BULK_CIPHER_3DES = (1 << 3), + }; + + // Initialize a new HTTPSOptions using CERT_OK as the certificate. + HTTPSOptions(); + + // Initialize a new HTTPSOptions that will use the specified certificate. + explicit HTTPSOptions(ServerCertificate cert); + ~HTTPSOptions(); + + // Returns the relative filename of the file that contains the + // |server_certificate|. + FilePath GetCertificateFile() const; + + // The certificate to use when serving requests. + ServerCertificate server_certificate; + + // True if a CertificateRequest should be sent to the client during + // handshaking. + bool request_client_certificate; + + // If |request_client_certificate| is true, an optional list of files, + // each containing a single, PEM-encoded X.509 certificates. The subject + // from each certificate will be added to the certificate_authorities + // field of the CertificateRequest. + std::vector<FilePath> client_authorities; + + // A bitwise-OR of BulkCipher that should be used by the + // HTTPS server, or BULK_CIPHER_ANY to indicate that all implemented + // ciphers are acceptable. + int bulk_ciphers; + + // If true, pass the --https-record-resume argument to testserver.py which + // causes it to log session cache actions and echo the log on + // /ssl-session-cache. + bool record_resume; + }; + + // Pass as the 'host' parameter during construction to server on 127.0.0.1 + static const char kLocalhost[]; + + // The auth token to be used for TYPE_GDATA server. + static const char kGDataAuthToken[]; + + // Initialize a TestServer listening on a specific host (IP or hostname). + BaseTestServer(Type type, const std::string& host); + + // Initialize a HTTPS TestServer with a specific set of HTTPSOptions. + explicit BaseTestServer(const HTTPSOptions& https_options); + + ~BaseTestServer(); + + // Returns the host port pair used by current Python based test server only + // if the server is started. + const HostPortPair& host_port_pair() const; + + const FilePath& document_root() const { return document_root_; } + const base::DictionaryValue& server_data() const; + std::string GetScheme() const; + bool GetAddressList(AddressList* address_list) const WARN_UNUSED_RESULT; + + GURL GetURL(const std::string& path) const; + + GURL GetURLWithUser(const std::string& path, + const std::string& user) const; + + GURL GetURLWithUserAndPassword(const std::string& path, + const std::string& user, + const std::string& password) const; + + static bool GetFilePathWithReplacements( + const std::string& original_path, + const std::vector<StringPair>& text_to_replace, + std::string* replacement_path); + + protected: + Type type() const { return type_; } + + // Gets port currently assigned to host_port_pair_ without checking + // whether it's available (server started) or not. + uint16 GetPort(); + + // Sets |port| as the actual port used by Python based test server. + void SetPort(uint16 port); + + // Set up internal status when the server is started. + bool SetupWhenServerStarted() WARN_UNUSED_RESULT; + + // Clean up internal status when starting to stop server. + void CleanUpWhenStoppingServer(); + + // Set path of test resources. + void SetResourcePath(const FilePath& document_root, + const FilePath& certificates_dir); + + // Parses the server data read from the test server. Returns true + // on success. + bool ParseServerData(const std::string& server_data) WARN_UNUSED_RESULT; + + // Generates a DictionaryValue with the arguments for launching the external + // Python test server. + bool GenerateArguments(base::DictionaryValue* arguments) const; + + private: + void Init(const std::string& host); + + // Marks the root certificate of an HTTPS test server as trusted for + // the duration of tests. + bool LoadTestRootCert() const WARN_UNUSED_RESULT; + + // Document root of the test server. + FilePath document_root_; + + // Directory that contains the SSL certificates. + FilePath certificates_dir_; + + // Address the test server listens on. + HostPortPair host_port_pair_; + + // Holds the data sent from the server (e.g., port number). + scoped_ptr<base::DictionaryValue> server_data_; + + // If |type_| is TYPE_HTTPS, the TLS settings to use for the test server. + HTTPSOptions https_options_; + + Type type_; + + // Has the server been started? + bool started_; + + // Enables logging of the server to the console. + bool log_to_console_; + + scoped_ptr<ScopedPortException> allowed_port_; + + DISALLOW_COPY_AND_ASSIGN(BaseTestServer); +}; + +} // namespace net + +#endif // NET_TEST_BASE_TEST_SERVER_H_ + diff --git a/net/test/local_test_server.cc b/net/test/local_test_server.cc new file mode 100644 index 0000000..c36d2ff --- /dev/null +++ b/net/test/local_test_server.cc @@ -0,0 +1,237 @@ +// 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. + +#include "net/test/local_test_server.h" + +#include "base/command_line.h" +#include "base/json/json_reader.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "base/process_util.h" +#include "base/string_number_conversions.h" +#include "base/values.h" +#include "googleurl/src/gurl.h" +#include "net/base/host_port_pair.h" +#include "net/base/net_errors.h" +#include "net/test/python_utils.h" + +namespace net { + +namespace { + +bool AppendArgumentFromJSONValue(const std::string& key, + const base::Value& value_node, + CommandLine* command_line) { + std::string argument_name = "--" + key; + switch (value_node.GetType()) { + case base::Value::TYPE_NULL: + command_line->AppendArg(argument_name); + break; + case base::Value::TYPE_INTEGER: { + int value; + bool result = value_node.GetAsInteger(&value); + DCHECK(result); + command_line->AppendArg(argument_name + "=" + base::IntToString(value)); + break; + } + case Value::TYPE_STRING: { + std::string value; + bool result = value_node.GetAsString(&value); + if (!result || value.empty()) + return false; + command_line->AppendArg(argument_name + "=" + value); + break; + } + case base::Value::TYPE_BOOLEAN: + case base::Value::TYPE_DOUBLE: + case base::Value::TYPE_LIST: + case base::Value::TYPE_DICTIONARY: + case base::Value::TYPE_BINARY: + default: + NOTREACHED() << "improper json type"; + return false; + } + return true; +} + +} // namespace + +LocalTestServer::LocalTestServer(Type type, + const std::string& host, + const FilePath& document_root) + : BaseTestServer(type, host) { + if (!Init(document_root)) + NOTREACHED(); +} + +LocalTestServer::LocalTestServer(const HTTPSOptions& https_options, + const FilePath& document_root) + : BaseTestServer(https_options) { + if (!Init(document_root)) + NOTREACHED(); +} + +LocalTestServer::~LocalTestServer() { + Stop(); +} + +bool LocalTestServer::Start() { + // Get path to Python server script. + FilePath testserver_path; + if (!PathService::Get(base::DIR_SOURCE_ROOT, &testserver_path)) { + LOG(ERROR) << "Failed to get DIR_SOURCE_ROOT"; + 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")); + + if (!SetPythonPath()) + return false; + + if (!LaunchPython(testserver_path)) + return false; + + if (!WaitToStart()) { + Stop(); + return false; + } + + return SetupWhenServerStarted(); +} + +bool LocalTestServer::Stop() { + CleanUpWhenStoppingServer(); + + 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; + } else { + VLOG(1) << "Kill failed?"; + } + + return ret; +} + +bool LocalTestServer::Init(const FilePath& document_root) { + if (document_root.IsAbsolute()) + return false; + + // At this point, the port that the test server will listen on is unknown. + // The test server will listen on an ephemeral port, and write the port + // number out over a pipe that this TestServer object will read from. Once + // that is complete, the host port pair will contain the actual port. + DCHECK(!GetPort()); + process_handle_ = base::kNullProcessHandle; + + FilePath src_dir; + if (!PathService::Get(base::DIR_SOURCE_ROOT, &src_dir)) + return false; + SetResourcePath(src_dir.Append(document_root), + src_dir.AppendASCII("net") + .AppendASCII("data") + .AppendASCII("ssl") + .AppendASCII("certificates")); + return true; +} + +bool LocalTestServer::SetPythonPath() const { + FilePath third_party_dir; + if (!PathService::Get(base::DIR_SOURCE_ROOT, &third_party_dir)) { + LOG(ERROR) << "Failed to get DIR_SOURCE_ROOT"; + return false; + } + third_party_dir = third_party_dir.AppendASCII("third_party"); + + // For simplejson. (simplejson, unlike all the other Python modules + // we include, doesn't have an extra 'simplejson' directory, so we + // need to include its parent directory, i.e. third_party_dir). + AppendToPythonPath(third_party_dir); + + AppendToPythonPath(third_party_dir.AppendASCII("tlslite")); + AppendToPythonPath( + third_party_dir.AppendASCII("pyftpdlib").AppendASCII("src")); + + // Locate the Python code generated by the protocol buffers compiler. + FilePath pyproto_dir; + if (!GetPyProtoPath(&pyproto_dir)) { + LOG(WARNING) << "Cannot find pyproto dir for generated code. " + << "Testserver features that rely on it will not work"; + return true; + } + + AppendToPythonPath(pyproto_dir); + AppendToPythonPath(pyproto_dir.AppendASCII("sync").AppendASCII("protocol")); + AppendToPythonPath(pyproto_dir.AppendASCII("chrome") + .AppendASCII("browser") + .AppendASCII("policy") + .AppendASCII("proto")); + + return true; +} + +bool LocalTestServer::AddCommandLineArguments(CommandLine* command_line) const { + base::DictionaryValue arguments_dict; + if (!GenerateArguments(&arguments_dict)) + return false; + + // Serialize the argument dictionary into CommandLine. + for (DictionaryValue::Iterator it(arguments_dict); it.HasNext(); + it.Advance()) { + const base::Value& value = it.value(); + const std::string& key = it.key(); + + // Add arguments from a list. + if (value.IsType(Value::TYPE_LIST)) { + const base::ListValue* list = NULL; + if (!value.GetAsList(&list) || !list || list->empty()) + return false; + for (base::ListValue::const_iterator list_it = list->begin(); + list_it != list->end(); ++list_it) { + if (!AppendArgumentFromJSONValue(key, *(*list_it), command_line)) + return false; + } + } else if (!AppendArgumentFromJSONValue(key, value, command_line)) { + return false; + } + } + + // Append the appropriate server type argument. + switch (type()) { + case TYPE_HTTP: + case TYPE_HTTPS: + // The default type is HTTP, no argument required. + break; + case TYPE_FTP: + command_line->AppendArg("-f"); + break; + case TYPE_SYNC: + command_line->AppendArg("--sync"); + break; + case TYPE_TCP_ECHO: + command_line->AppendArg("--tcp-echo"); + break; + case TYPE_UDP_ECHO: + command_line->AppendArg("--udp-echo"); + break; + default: + NOTREACHED(); + return false; + } + + return true; +} + +} // namespace net + diff --git a/net/test/local_test_server.h b/net/test/local_test_server.h new file mode 100644 index 0000000..48c2186 --- /dev/null +++ b/net/test/local_test_server.h @@ -0,0 +1,87 @@ +// 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 NET_TEST_LOCAL_TEST_SERVER_H_ +#define NET_TEST_LOCAL_TEST_SERVER_H_ +#pragma once + +#include <string> + +#include "base/file_util.h" +#include "base/process.h" +#include "net/test/base_test_server.h" + +#if defined(OS_WIN) +#include "base/win/scoped_handle.h" +#endif + +class CommandLine; + +namespace net { + +// The LocalTestServer runs an external Python-based test server in the +// same machine in which the LocalTestServer runs. +class LocalTestServer : public BaseTestServer { + public: + // Initialize a TestServer listening on a specific host (IP or hostname). + // |document_root| must be a relative path under the root tree. + LocalTestServer(Type type, + const std::string& host, + const FilePath& document_root); + + // Initialize a HTTPS TestServer with a specific set of HTTPSOptions. + // |document_root| must be a relative path under the root tree. + LocalTestServer(const HTTPSOptions& https_options, + const FilePath& document_root); + + ~LocalTestServer(); + + bool Start() WARN_UNUSED_RESULT; + + // Stop the server started by Start(). + bool Stop(); + + private: + bool Init(const FilePath& document_root); + + // Modify PYTHONPATH to contain libraries we need. + bool SetPythonPath() const WARN_UNUSED_RESULT; + + // Launches the Python test server. Returns true on success. + bool LaunchPython(const FilePath& testserver_path) WARN_UNUSED_RESULT; + + // Waits for the server to start. Returns true on success. + bool WaitToStart() WARN_UNUSED_RESULT; + + // Add the command line arguments for the Python test server to + // |command_line|. Return true on success. + bool AddCommandLineArguments(CommandLine* command_line) const; + + // Handle of the Python process running the test server. + base::ProcessHandle process_handle_; + +#if defined(OS_WIN) + // JobObject used to clean up orphaned child processes. + base::win::ScopedHandle job_handle_; + + // The pipe file handle we read from. + base::win::ScopedHandle child_read_fd_; + + // The pipe file handle the child and we write to. + base::win::ScopedHandle child_write_fd_; +#endif + +#if defined(OS_POSIX) + // The file descriptor the child writes to when it starts. + int child_fd_; + file_util::ScopedFD child_fd_closer_; +#endif + + DISALLOW_COPY_AND_ASSIGN(LocalTestServer); +}; + +} // namespace net + +#endif // NET_TEST_LOCAL_TEST_SERVER_H_ + diff --git a/net/test/test_server_posix.cc b/net/test/local_test_server_posix.cc index f15acc3..0cb4857 100644 --- a/net/test/test_server_posix.cc +++ b/net/test/local_test_server_posix.cc @@ -2,7 +2,7 @@ // 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 "net/test/local_test_server.h" #include <poll.h> @@ -76,7 +76,7 @@ bool ReadData(int fd, ssize_t bytes_max, uint8* buffer, base::Time current_time = base::Time::Now(); base::TimeDelta elapsed_time_cycle = current_time - previous_time; - DCHECK(elapsed_time_cycle.InMilliseconds() >= 0); + DCHECK_GE(elapsed_time_cycle.InMilliseconds(), 0); *remaining_time -= elapsed_time_cycle; previous_time = current_time; @@ -93,7 +93,7 @@ bool ReadData(int fd, ssize_t bytes_max, uint8* buffer, namespace net { -bool TestServer::LaunchPython(const FilePath& testserver_path) { +bool LocalTestServer::LaunchPython(const FilePath& testserver_path) { CommandLine python_command(FilePath(FILE_PATH_LITERAL("python"))); python_command.AppendArgPath(testserver_path); if (!AddCommandLineArguments(&python_command)) @@ -116,7 +116,7 @@ bool TestServer::LaunchPython(const FilePath& testserver_path) { // Try to kill any orphaned testserver processes that may be running. OrphanedTestServerFilter filter(testserver_path.value(), - base::IntToString(host_port_pair_.port())); + base::IntToString(GetPort())); if (!base::KillProcesses("python", -1, &filter)) { LOG(WARNING) << "Failed to clean up older orphaned testserver instances."; } @@ -132,7 +132,7 @@ bool TestServer::LaunchPython(const FilePath& testserver_path) { return true; } -bool TestServer::WaitToStart() { +bool LocalTestServer::WaitToStart() { file_util::ScopedFD child_fd_closer(child_fd_closer_.release()); base::TimeDelta remaining_time = base::TimeDelta::FromMilliseconds( diff --git a/net/test/test_server_win.cc b/net/test/local_test_server_win.cc index 07bcee5..b56e98a 100644 --- a/net/test/test_server_win.cc +++ b/net/test/local_test_server_win.cc @@ -1,8 +1,8 @@ -// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// 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. -#include "net/test/test_server.h" +#include "net/test/local_test_server.h" #include <windows.h> #include <wincrypt.h> @@ -29,8 +29,8 @@ namespace { // Used as a crude timeout mechanism by ReadData(). void UnblockPipe(HANDLE handle, DWORD size, bool* unblocked) { std::string unblock_data(size, '\0'); - // Unblock the ReadFile in TestServer::WaitToStart by writing to the pipe. - // Make sure the call succeeded, otherwise we are very likely to hang. + // Unblock the ReadFile in LocalTestServer::WaitToStart by writing to the + // pipe. Make sure the call succeeded, otherwise we are very likely to hang. DWORD bytes_written = 0; LOG(WARNING) << "Timeout reached; unblocking pipe by writing " << size << " bytes"; @@ -84,7 +84,7 @@ bool ReadData(HANDLE read_fd, HANDLE write_fd, namespace net { -bool TestServer::LaunchPython(const FilePath& testserver_path) { +bool LocalTestServer::LaunchPython(const FilePath& testserver_path) { FilePath python_exe; if (!PathService::Get(base::DIR_SOURCE_ROOT, &python_exe)) return false; @@ -149,7 +149,7 @@ bool TestServer::LaunchPython(const FilePath& testserver_path) { return true; } -bool TestServer::WaitToStart() { +bool LocalTestServer::WaitToStart() { base::win::ScopedHandle read_fd(child_read_fd_.Take()); base::win::ScopedHandle write_fd(child_write_fd_.Take()); @@ -176,3 +176,4 @@ bool TestServer::WaitToStart() { } } // namespace net + diff --git a/net/test/remote_test_server.cc b/net/test/remote_test_server.cc new file mode 100644 index 0000000..fc144f2 --- /dev/null +++ b/net/test/remote_test_server.cc @@ -0,0 +1,168 @@ +// 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. + +#include "net/test/remote_test_server.h" + +#include <vector> + +#include "base/file_util.h" +#include "base/json/json_writer.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "base/string_number_conversions.h" +#include "base/string_split.h" +#include "base/values.h" +#include "googleurl/src/gurl.h" +#include "net/base/host_port_pair.h" +#include "net/base/net_errors.h" +#include "net/test/spawner_communicator.h" + +namespace net { + +namespace { + +// To reduce the running time of tests, tests may be sharded across several +// devices. This means that it may be necessary to support multiple instances +// of the test server spawner and the Python test server simultaneously on the +// same host. Each pair of (test server spawner, Python test server) correspond +// to a single testing device. +// The mapping between the test server spawner and the individual Python test +// servers is written to a file on the device prior to executing any tests. +const char kTestServerPortInfoFile[] = "/data/local/tmp/net-test-server-ports"; + +// Please keep it sync with dictionary SERVER_TYPES in testserver.py +std::string GetServerTypeString(BaseTestServer::Type type) { + switch (type) { + case BaseTestServer::TYPE_FTP: + return "ftp"; + case BaseTestServer::TYPE_GDATA: + case BaseTestServer::TYPE_HTTP: + case BaseTestServer::TYPE_HTTPS: + return "http"; + case BaseTestServer::TYPE_SYNC: + return "sync"; + case BaseTestServer::TYPE_TCP_ECHO: + return "tcpecho"; + case BaseTestServer::TYPE_UDP_ECHO: + return "udpecho"; + default: + NOTREACHED(); + } + return std::string(); +} + +} // namespace + +RemoteTestServer::RemoteTestServer(Type type, + const std::string& host, + const FilePath& document_root) + : BaseTestServer(type, host) { + if (!Init(document_root)) + NOTREACHED(); +} + +RemoteTestServer::RemoteTestServer( + const HTTPSOptions& https_options, + const FilePath& document_root) + : BaseTestServer(https_options) { + if (!Init(document_root)) + NOTREACHED(); +} + +RemoteTestServer::~RemoteTestServer() { + Stop(); +} + +bool RemoteTestServer::Start() { + if (!spawner_communicator_.get()) + return false; + + base::DictionaryValue arguments_dict; + if (!GenerateArguments(&arguments_dict)) + return false; + + // Append the 'server-type' argument which is used by spawner server to + // pass right server type to Python test server. + arguments_dict.SetString("server-type", GetServerTypeString(type())); + + // Generate JSON-formatted argument string. + std::string arguments_string; + base::JSONWriter::Write(&arguments_dict, false, &arguments_string); + if (arguments_string.empty()) + return false; + + // Start the Python test server on the remote machine. + uint16 test_server_port; + if (!spawner_communicator_->StartServer(arguments_string, + &test_server_port)) { + return false; + } + if (0 == test_server_port) + return false; + + // Construct server data to initialize BaseTestServer::server_data_. + base::DictionaryValue server_data_dict; + // At this point, the test server should be spawned on the host. Update the + // local port to real port of Python test server, which will be forwarded to + // the remote server. + server_data_dict.SetInteger("port", test_server_port); + std::string server_data; + base::JSONWriter::Write(&server_data_dict, false, &server_data); + if (server_data.empty() || !ParseServerData(server_data)) { + LOG(ERROR) << "Could not parse server_data: " << server_data; + return false; + } + + return SetupWhenServerStarted(); +} + +bool RemoteTestServer::Stop() { + CleanUpWhenStoppingServer(); + return spawner_communicator_->StopServer(); +} + +bool RemoteTestServer::Init(const FilePath& document_root) { + if (document_root.IsAbsolute()) + return false; + + // Gets ports information used by test server spawner and Python test server. + int test_server_port = 0; + int spawner_server_port = 0; + + // Parse file to extract the ports information. + std::string port_info; + if (!file_util::ReadFileToString(FilePath(kTestServerPortInfoFile), + &port_info) || + port_info.empty()) { + return false; + } + + std::vector<std::string> ports; + base::SplitString(port_info, ':', &ports); + if (ports.size() != 2u) + return false; + + // Verify the ports information. + base::StringToInt(ports[0], &spawner_server_port); + if (!spawner_server_port || + static_cast<uint32>(spawner_server_port) >= kuint16max) + return false; + spawner_communicator_.reset(new SpawnerCommunicator(spawner_server_port)); + + // Allow the test_server_port to be 0, which means the test server spawner + // will pick up a random port to run the test server. + base::StringToInt(ports[1], &test_server_port); + if (static_cast<uint32>(test_server_port) >= kuint16max) + return false; + SetPort(test_server_port); + + SetResourcePath(document_root, FilePath().AppendASCII("net") + .AppendASCII("data") + .AppendASCII("ssl") + .AppendASCII("certificates")); + return true; +} + +} // namespace net + diff --git a/net/test/remote_test_server.h b/net/test/remote_test_server.h new file mode 100644 index 0000000..1f375cb --- /dev/null +++ b/net/test/remote_test_server.h @@ -0,0 +1,53 @@ +// 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 NET_TEST_REMOTE_TEST_SERVER_H_ +#define NET_TEST_REMOTE_TEST_SERVER_H_ +#pragma once + +#include <string> + +#include "net/test/base_test_server.h" + +namespace net { + +class SpawnerCommunicator; + +// The RemoteTestServer runs an external Python-based test server in another +// machine that is different from the machine in which RemoteTestServer runs. +class RemoteTestServer : public BaseTestServer { + public: + // Initialize a TestServer listening on a specific host (IP or hostname). + // |document_root| must be a relative path under the root tree. + RemoteTestServer(Type type, + const std::string& host, + const FilePath& document_root); + + // Initialize a HTTPS TestServer with a specific set of HTTPSOptions. + // |document_root| must be a relative path under the root tree. + RemoteTestServer(const HTTPSOptions& https_options, + const FilePath& document_root); + + ~RemoteTestServer(); + + // Starts the Python test server on the host, instead of on the device. + bool Start() WARN_UNUSED_RESULT; + + // Stops the Python test server that is running on the host machine. + bool Stop(); + + private: + bool Init(const FilePath& document_root); + + // Helper to start and stop instances of the Python test server that runs on + // the host machine. + scoped_ptr<SpawnerCommunicator> spawner_communicator_; + + DISALLOW_COPY_AND_ASSIGN(RemoteTestServer); +}; + +} // namespace net + +#endif // NET_TEST_REMOTE_TEST_SERVER_H_ + diff --git a/net/test/spawner_communicator.cc b/net/test/spawner_communicator.cc new file mode 100644 index 0000000..8b454f0 --- /dev/null +++ b/net/test/spawner_communicator.cc @@ -0,0 +1,367 @@ +// 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. + +#include "net/test/spawner_communicator.h" + +#include "base/json/json_reader.h" +#include "base/logging.h" +#include "base/stringprintf.h" +#include "base/supports_user_data.h" +#include "base/test/test_timeouts.h" +#include "base/time.h" +#include "base/values.h" +#include "build/build_config.h" +#include "googleurl/src/gurl.h" +#include "net/base/net_util.h" +#include "net/http/http_response_headers.h" +#include "net/url_request/url_request_test_util.h" + +namespace net { + +namespace { + +GURL GenerateSpawnerCommandURL(const std::string& command, uint16 port) { + // Always performs HTTP request for sending command to the spawner server. + return GURL(base::StringPrintf("%s:%u/%s", "http://127.0.0.1", port, + command.c_str())); +} + +int kBufferSize = 2048; + +// A class to hold all data needed to send a command to spawner server. +class SpawnerRequestData : public base::SupportsUserData::Data { + public: + SpawnerRequestData(int id, int* result_code, std::string* data_received) + : request_id_(id), + buf_(new IOBuffer(kBufferSize)), + result_code_(result_code), + data_received_(data_received), + response_started_count_(0) { + DCHECK(result_code); + *result_code_ = OK; + DCHECK(data_received); + data_received_->clear(); + } + + virtual ~SpawnerRequestData() {} + + bool DoesRequestIdMatch(int request_id) const { + return request_id_ == request_id; + } + + IOBuffer* buf() const { return buf_.get(); } + + bool IsResultOK() const { return *result_code_ == OK; } + + void ClearReceivedData() { data_received_->clear(); } + + void SetResultCode(int result_code) { *result_code_ = result_code; } + + void IncreaseResponseStartedCount() { response_started_count_++; } + + int response_started_count() const { return response_started_count_; } + + // Write data read from URLRequest::Read() to |data_received_|. Returns true + // if |num_bytes| is great than 0. |num_bytes| is 0 for EOF, < 0 on errors. + bool ConsumeBytesRead(int num_bytes) { + // Error while reading, or EOF. + if (num_bytes <= 0) + return false; + + data_received_->append(buf_->data(), num_bytes); + return true; + } + + private: + // Unique ID for the current request. + int request_id_; + + // Buffer that URLRequest writes into. + scoped_refptr<IOBuffer> buf_; + + // Holds the error condition that was hit on the current request, or OK. + int* result_code_; + + // Data received from server; + std::string* data_received_; + + // Used to track how many times the OnResponseStarted get called after + // sending a command to spawner server. + int response_started_count_; + + DISALLOW_COPY_AND_ASSIGN(SpawnerRequestData); +}; + +} // namespace + +SpawnerCommunicator::SpawnerCommunicator(uint16 port) + : io_thread_("spawner_communicator"), + event_(false, false), + port_(port), + next_id_(0), + ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)), + is_running_(false) {} + +SpawnerCommunicator::~SpawnerCommunicator() {} + +void SpawnerCommunicator::WaitForResponse() { + DCHECK_NE(MessageLoop::current(), io_thread_.message_loop()); + event_.Wait(); + event_.Reset(); +} + +void SpawnerCommunicator::StartIOThread() { + DCHECK_NE(MessageLoop::current(), io_thread_.message_loop()); + if (is_running_) + return; + + allowed_port_.reset(new ScopedPortException(port_)); + base::Thread::Options options; + options.message_loop_type = MessageLoop::TYPE_IO; + is_running_ = io_thread_.StartWithOptions(options); + DCHECK(is_running_); +} + +void SpawnerCommunicator::Shutdown() { + DCHECK_NE(MessageLoop::current(), io_thread_.message_loop()); + DCHECK(is_running_); + DCHECK(!cur_request_.get()); + io_thread_.Stop(); + allowed_port_.reset(); +} + +void SpawnerCommunicator::SendCommandAndWaitForResult( + const std::string& command, + const std::string& post_data, + int* result_code, + std::string* data_received) { + if (!result_code || !data_received) + return; + // Start the communicator thread to talk to test server spawner. + StartIOThread(); + DCHECK(io_thread_.message_loop()); + + // Since the method will be blocked until SpawnerCommunicator gets result + // from the spawner server or timed-out. It's safe to use base::Unretained + // when using base::Bind. + io_thread_.message_loop()->PostTask(FROM_HERE, base::Bind( + &SpawnerCommunicator::SendCommandAndWaitForResultOnIOThread, + base::Unretained(this), command, post_data, result_code, data_received)); + WaitForResponse(); +} + +void SpawnerCommunicator::SendCommandAndWaitForResultOnIOThread( + const std::string& command, + const std::string& post_data, + int* result_code, + std::string* data_received) { + MessageLoop* loop = io_thread_.message_loop(); + DCHECK(loop); + DCHECK_EQ(MessageLoop::current(), loop); + + // Prepare the URLRequest for sending the command. + DCHECK(!cur_request_.get()); + cur_request_.reset(new URLRequest(GenerateSpawnerCommandURL(command, port_), + this)); + DCHECK(cur_request_.get()); + int current_request_id = ++next_id_; + SpawnerRequestData* data = new SpawnerRequestData(current_request_id, + result_code, + data_received); + DCHECK(data); + cur_request_->SetUserData(this, data); + + // Build the URLRequest. + scoped_refptr<TestURLRequestContext> context(new TestURLRequestContext()); + cur_request_->set_context(context); + if (post_data.empty()) { + cur_request_->set_method("GET"); + } else { + cur_request_->set_method("POST"); + cur_request_->AppendBytesToUpload(post_data.c_str(), post_data.size()); + net::HttpRequestHeaders headers; + headers.SetHeader(net::HttpRequestHeaders::kContentType, + "application/json"); + cur_request_->SetExtraRequestHeaders(headers); + } + + // Post a task to timeout this request if it takes too long. + MessageLoop::current()->PostDelayedTask( + FROM_HERE, + base::Bind(&SpawnerCommunicator::OnTimeout, weak_factory_.GetWeakPtr(), + current_request_id), + TestTimeouts::action_max_timeout()); + + // Start the request. + cur_request_->Start(); +} + +void SpawnerCommunicator::OnTimeout(int id) { + // Timeout tasks may outlive the URLRequest they reference. Make sure it + // is still applicable. + if (!cur_request_.get()) + return; + SpawnerRequestData* data = + static_cast<SpawnerRequestData*>(cur_request_->GetUserData(this)); + DCHECK(data); + + if (!data->DoesRequestIdMatch(id)) + return; + // Set the result code and cancel the timed-out task. + data->SetResultCode(ERR_TIMED_OUT); + cur_request_->Cancel(); + OnSpawnerCommandCompleted(cur_request_.get()); +} + +void SpawnerCommunicator::OnSpawnerCommandCompleted(URLRequest* request) { + if (!cur_request_.get()) + return; + DCHECK_EQ(request, cur_request_.get()); + SpawnerRequestData* data = + static_cast<SpawnerRequestData*>(cur_request_->GetUserData(this)); + DCHECK(data); + + // If request is faild,return the error code. + if (!cur_request_->status().is_success()) + data->SetResultCode(cur_request_->status().error()); + + if (!data->IsResultOK()) { + LOG(ERROR) << "request failed, status: " + << static_cast<int>(request->status().status()) + << ", error: " << request->status().error(); + // Clear the buffer of received data if any net error happened. + data->ClearReceivedData(); + } else { + DCHECK_EQ(1, data->response_started_count()); + } + + // Clear current request to indicate the completion of sending a command + // to spawner server and getting the result. + cur_request_.reset(); + // Wakeup the caller in user thread. + event_.Signal(); +} + +void SpawnerCommunicator::ReadResult(URLRequest* request) { + DCHECK_EQ(request, cur_request_.get()); + SpawnerRequestData* data = + static_cast<SpawnerRequestData*>(cur_request_->GetUserData(this)); + DCHECK(data); + + IOBuffer* buf = data->buf(); + // Read as many bytes as are available synchronously. + while (true) { + int num_bytes; + if (!request->Read(buf, kBufferSize, &num_bytes)) { + // Check whether the read failed synchronously. + if (!request->status().is_io_pending()) + OnSpawnerCommandCompleted(request); + return; + } + if (!data->ConsumeBytesRead(num_bytes)) { + OnSpawnerCommandCompleted(request); + return; + } + } +} + +void SpawnerCommunicator::OnResponseStarted(URLRequest* request) { + DCHECK_EQ(request, cur_request_.get()); + SpawnerRequestData* data = + static_cast<SpawnerRequestData*>(cur_request_->GetUserData(this)); + DCHECK(data); + + data->IncreaseResponseStartedCount(); + + if (!request->status().is_success()) { + OnSpawnerCommandCompleted(request); + return; + } + + // Require HTTP responses to have a success status code. + if (request->GetResponseCode() != 200) { + LOG(ERROR) << "Spawner server returned bad status: " + << request->response_headers()->GetStatusLine(); + data->SetResultCode(ERR_FAILED); + request->Cancel(); + OnSpawnerCommandCompleted(request); + return; + } + + ReadResult(request); +} + +void SpawnerCommunicator::OnReadCompleted(URLRequest* request, int num_bytes) { + if (!cur_request_.get()) + return; + DCHECK_EQ(request, cur_request_.get()); + SpawnerRequestData* data = + static_cast<SpawnerRequestData*>(cur_request_->GetUserData(this)); + DCHECK(data); + + if (data->ConsumeBytesRead(num_bytes)) { + // Keep reading. + ReadResult(request); + } else { + OnSpawnerCommandCompleted(request); + } +} + +bool SpawnerCommunicator::StartServer(const std::string& arguments, + uint16* port) { + *port = 0; + // Send the start command to spawner server to start the Python test server + // on remote machine. + std::string server_return_data; + int result_code; + SendCommandAndWaitForResult("start", arguments, &result_code, + &server_return_data); + if (OK != result_code || server_return_data.empty()) + return false; + + // Check whether the data returned from spawner server is JSON-formatted. + base::JSONReader json_reader; + scoped_ptr<base::Value> value(json_reader.JsonToValue(server_return_data, + true, false)); + if (!value.get() || !value->IsType(base::Value::TYPE_DICTIONARY)) { + LOG(ERROR) << "Invalid server data: " << server_return_data.c_str(); + return false; + } + + // Check whether spawner server returns valid data. + DictionaryValue* server_data = static_cast<DictionaryValue*>(value.get()); + std::string message; + if (!server_data->GetString("message", &message) || message != "started") { + LOG(ERROR) << "Invalid message in server data: "; + return false; + } + int int_port; + if (!server_data->GetInteger("port", &int_port) || int_port <= 0 || + int_port > kuint16max) { + LOG(ERROR) << "Invalid port value: " << int_port; + return false; + } + *port = static_cast<uint16>(int_port); + return true; +} + +bool SpawnerCommunicator::StopServer() { + // It's OK to stop the SpawnerCommunicator without starting it. Some tests + // have test server on their test fixture but do not actually use it. + if (!is_running_) + return true; + + // When the test is done, ask the test server spawner to kill the test server + // on the remote machine. + std::string server_return_data; + int result_code; + SendCommandAndWaitForResult("kill", "", &result_code, &server_return_data); + if (OK != result_code || server_return_data != "killed") + return false; + Shutdown(); + return true; +} + +} // namespace net + diff --git a/net/test/spawner_communicator.h b/net/test/spawner_communicator.h new file mode 100644 index 0000000..43068f5 --- /dev/null +++ b/net/test/spawner_communicator.h @@ -0,0 +1,150 @@ +// 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 NET_TEST_SPAWNER_COMMUNICATOR_H_ +#define NET_TEST_SPAWNER_COMMUNICATOR_H_ +#pragma once + +#include <string> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread.h" +#include "net/url_request/url_request.h" + +namespace net { + +class ScopedPortException; + +// SpawnerCommunicator communicates with a spawner server that runs on a +// remote system. +// +// The test server used by unit tests is written in Python. However, Android +// does not support running Python code, so the test server cannot run on the +// same device running unit tests. +// +// The actual test server is executed on the host machine, while the unit tests +// themselves continue running on the device. To control the test server on the +// host machine, a second HTTP server is started, the spawner server, which +// controls the life cycle of remote test servers. Calls to start/kill the +// net::TestServer are then redirected to the spawner server via this spawner +// communicator. +// +// Currently only three commands are supported by spawner. +// +// (1) Start Python test server, format is: +// Path: "/start". +// Method: "POST". +// Data to server: all arguments needed to launch the Python test server, in +// JSON format. +// Data from server: a JSON dict includes the following two field if success, +// "port": the port the Python test server actually listen on that. +// "message": must be "started". +// +// (2) Kill Python test server, format is: +// Path: "/kill". +// Method: "GET". +// Data to server: None. +// Data from server: String "killed" returned if success. +// +// (3) Ping Python test server to see whether it is alive, format is: +// Path: "/ping". +// Method: "GET". +// Data to server: None. +// Data from server: String "ready" returned if success. +// +// The internal I/O thread is required by net stack to perform net I/O. +// The Start/StopServer methods block the caller thread until result is +// fetched from spawner server or timed-out. +class SpawnerCommunicator : public net::URLRequest::Delegate { + public: + explicit SpawnerCommunicator(uint16 port); + ~SpawnerCommunicator(); + + // Starts an instance of the Python test server on the host/ machine. + // If successfully started, returns true, setting |*port| to the port + // on the local machine that can be used to communicate with the remote + // test server. + bool StartServer(const std::string& arguments, + uint16* port) WARN_UNUSED_RESULT; + + bool StopServer() WARN_UNUSED_RESULT; + + private: + // Starts the IO thread. Called on the user thread. + void StartIOThread(); + + // Shuts down the remote test server spawner. Called on the user thread. + void Shutdown(); + + // Waits for the server response on IO thread. Called on the user thread. + void WaitForResponse(); + + // Sends a command to the test server over HTTP, returning the result code + // |*result_code| and response data in |*data_received|, those two arguments + // must be not NULL, otherwise the method returns immediately without sending + // the |command|. If |post_data| is empty, HTTP GET will be used to send + // |command|. If |post_data| is non-empty, performs an HTTP POST. + // This method is called on the user thread. + void SendCommandAndWaitForResult(const std::string& command, + const std::string& post_data, + int* result_code, + std::string* data_received); + + // Performs the command sending on the IO thread. Called on the IO thread. + void SendCommandAndWaitForResultOnIOThread(const std::string& command, + const std::string& post_data, + int* result_code, + std::string* data_received); + + // URLRequest::Delegate methods. Called on the IO thread. + virtual void OnResponseStarted(URLRequest* request) OVERRIDE; + virtual void OnReadCompleted(URLRequest* request, int num_bytes) OVERRIDE; + + // Reads Result from the response. Called on the IO thread. + void ReadResult(URLRequest* request); + + // Called on the IO thread upon completion of the spawner command. + void OnSpawnerCommandCompleted(URLRequest* request); + + // Callback on the IO thread for time-out task of request with id |id|. + void OnTimeout(int id); + + // A thread to communicate with test_spawner server. + base::Thread io_thread_; + + // WaitableEvent to notify whether the communication is done. + base::WaitableEvent event_; + + // The local port used to communicate with the TestServer spawner. This is + // used to control the startup and shutdown of the Python TestServer running + // on the remote machine. On Android, this port will be redirected to the + // same port on the host machine. + uint16 port_; + + // Helper to add |port_| to the list of the globally explicitly allowed ports. + scoped_ptr<ScopedPortException> allowed_port_; + + // The next ID to use for |cur_request_| (monotonically increasing). + int next_id_; + + // Factory for creating the time-out task. This takes care of revoking + // outstanding tasks when |this| is deleted. + base::WeakPtrFactory<SpawnerCommunicator> weak_factory_; + + // The current (in progress) request, or NULL. + scoped_ptr<URLRequest> cur_request_; + + // Only gets/sets |is_running_| on user's thread to avoid race-condition. + bool is_running_; + + DISALLOW_COPY_AND_ASSIGN(SpawnerCommunicator); +}; + +} // namespace net + +#endif // NET_TEST_SPAWNER_COMMUNICATOR_H_ + diff --git a/net/test/test_server.cc b/net/test/test_server.cc deleted file mode 100644 index 4aee7d6..0000000 --- a/net/test/test_server.cc +++ /dev/null @@ -1,427 +0,0 @@ -// 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. - -#include "net/test/test_server.h" - -#include <algorithm> -#include <string> -#include <vector> - -#include "build/build_config.h" - -#if defined(OS_MACOSX) -#include "net/base/x509_certificate.h" -#endif - -#include "base/base64.h" -#include "base/command_line.h" -#include "base/debug/leak_annotations.h" -#include "base/file_util.h" -#include "base/json/json_reader.h" -#include "base/logging.h" -#include "base/memory/scoped_ptr.h" -#include "base/path_service.h" -#include "base/string_number_conversions.h" -#include "base/utf_string_conversions.h" -#include "base/values.h" -#include "googleurl/src/gurl.h" -#include "net/base/host_port_pair.h" -#include "net/base/host_resolver.h" -#include "net/base/net_errors.h" -#include "net/base/test_completion_callback.h" -#include "net/base/test_root_certs.h" -#include "net/socket/tcp_client_socket.h" -#include "net/test/python_utils.h" -#include "testing/platform_test.h" - -namespace net { - -TestServer::HTTPSOptions::HTTPSOptions() - : server_certificate(CERT_OK), - request_client_certificate(false), - bulk_ciphers(HTTPSOptions::BULK_CIPHER_ANY), - record_resume(false) {} - -TestServer::HTTPSOptions::HTTPSOptions( - TestServer::HTTPSOptions::ServerCertificate cert) - : server_certificate(cert), - request_client_certificate(false), - bulk_ciphers(HTTPSOptions::BULK_CIPHER_ANY), - record_resume(false) {} - -TestServer::HTTPSOptions::~HTTPSOptions() {} - -FilePath TestServer::HTTPSOptions::GetCertificateFile() const { - switch (server_certificate) { - case CERT_OK: - case CERT_MISMATCHED_NAME: - return FilePath(FILE_PATH_LITERAL("ok_cert.pem")); - case CERT_EXPIRED: - return FilePath(FILE_PATH_LITERAL("expired_cert.pem")); - case CERT_CHAIN_WRONG_ROOT: - // This chain uses its own dedicated test root certificate to avoid - // side-effects that may affect testing. - return FilePath(FILE_PATH_LITERAL("redundant-server-chain.pem")); - default: - NOTREACHED(); - } - return FilePath(); -} - -const char TestServer::kLocalhost[] = "127.0.0.1"; -const char TestServer::kGDataAuthToken[] = "testtoken"; - -TestServer::TestServer(Type type, - const std::string& host, - const FilePath& document_root) - : type_(type), - started_(false), - log_to_console_(false) { - Init(host, document_root); -} - -TestServer::TestServer(const HTTPSOptions& https_options, - const FilePath& document_root) - : https_options_(https_options), - type_(TYPE_HTTPS), - started_(false), - log_to_console_(false) { - const char* host = "127.0.0.1"; - if (https_options_.server_certificate == - TestServer::HTTPSOptions::CERT_MISMATCHED_NAME) { - // Use a different hostname string that resolves to the same address. - host = "localhost"; - } - - Init(host, document_root); -} - -TestServer::~TestServer() { - TestRootCerts* root_certs = TestRootCerts::GetInstance(); - root_certs->Clear(); - Stop(); -} - -bool TestServer::Start() { - if (type_ == TYPE_HTTPS) { - if (!LoadTestRootCert()) - return false; - } - - // Get path to python server script - FilePath testserver_path; - if (!PathService::Get(base::DIR_SOURCE_ROOT, &testserver_path)) { - LOG(ERROR) << "Failed to get DIR_SOURCE_ROOT"; - 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")); - - if (!SetPythonPath()) - return false; - - if (!LaunchPython(testserver_path)) - return false; - - if (!WaitToStart()) { - Stop(); - return false; - } - - allowed_port_.reset(new ScopedPortException(host_port_pair_.port())); - - started_ = true; - return true; -} - -bool TestServer::Stop() { - if (!process_handle_) - return true; - - started_ = false; - - // 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; - } else { - VLOG(1) << "Kill failed?"; - } - - allowed_port_.reset(); - - return ret; -} - -const HostPortPair& TestServer::host_port_pair() const { - DCHECK(started_); - return host_port_pair_; -} - -const DictionaryValue& TestServer::server_data() const { - DCHECK(started_); - return *server_data_; -} - -std::string TestServer::GetScheme() const { - switch (type_) { - case TYPE_FTP: - return "ftp"; - case TYPE_GDATA: - case TYPE_HTTP: - case TYPE_SYNC: - return "http"; - case TYPE_HTTPS: - return "https"; - case TYPE_TCP_ECHO: - NOTREACHED(); - case TYPE_UDP_ECHO: - NOTREACHED(); - default: - NOTREACHED(); - } - return std::string(); -} - -bool TestServer::GetAddressList(AddressList* address_list) const { - DCHECK(address_list); - - scoped_ptr<HostResolver> resolver( - CreateSystemHostResolver(HostResolver::kDefaultParallelism, - HostResolver::kDefaultRetryAttempts, - NULL)); - HostResolver::RequestInfo info(host_port_pair_); - TestCompletionCallback callback; - int rv = resolver->Resolve(info, address_list, callback.callback(), NULL, - BoundNetLog()); - if (rv == ERR_IO_PENDING) - rv = callback.WaitForResult(); - if (rv != net::OK) { - LOG(ERROR) << "Failed to resolve hostname: " << host_port_pair_.host(); - return false; - } - return true; -} - -GURL TestServer::GetURL(const std::string& path) const { - return GURL(GetScheme() + "://" + host_port_pair_.ToString() + - "/" + path); -} - -GURL TestServer::GetURLWithUser(const std::string& path, - const std::string& user) const { - return GURL(GetScheme() + "://" + user + "@" + - host_port_pair_.ToString() + - "/" + path); -} - -GURL TestServer::GetURLWithUserAndPassword(const std::string& path, - const std::string& user, - const std::string& password) const { - return GURL(GetScheme() + "://" + user + ":" + password + - "@" + host_port_pair_.ToString() + - "/" + path); -} - -// static -bool TestServer::GetFilePathWithReplacements( - const std::string& original_file_path, - const std::vector<StringPair>& text_to_replace, - std::string* replacement_path) { - std::string new_file_path = original_file_path; - bool first_query_parameter = true; - const std::vector<StringPair>::const_iterator end = text_to_replace.end(); - for (std::vector<StringPair>::const_iterator it = text_to_replace.begin(); - it != end; - ++it) { - const std::string& old_text = it->first; - const std::string& new_text = it->second; - std::string base64_old; - std::string base64_new; - if (!base::Base64Encode(old_text, &base64_old)) - return false; - if (!base::Base64Encode(new_text, &base64_new)) - return false; - if (first_query_parameter) { - new_file_path += "?"; - first_query_parameter = false; - } else { - new_file_path += "&"; - } - new_file_path += "replace_text="; - new_file_path += base64_old; - new_file_path += ":"; - new_file_path += base64_new; - } - - *replacement_path = new_file_path; - return true; -} - -void TestServer::Init(const std::string& host, const FilePath& document_root) { - // At this point, the port that the testserver will listen on is unknown. - // The testserver will listen on an ephemeral port, and write the port - // number out over a pipe that this TestServer object will read from. Once - // that is complete, the host_port_pair_ will contain the actual port. - host_port_pair_ = HostPortPair(host, 0); - process_handle_ = base::kNullProcessHandle; - - FilePath src_dir; - PathService::Get(base::DIR_SOURCE_ROOT, &src_dir); - - document_root_ = src_dir.Append(document_root); - - certificates_dir_ = src_dir.Append(FILE_PATH_LITERAL("net")) - .Append(FILE_PATH_LITERAL("data")) - .Append(FILE_PATH_LITERAL("ssl")) - .Append(FILE_PATH_LITERAL("certificates")); - - // TODO(battre) Remove this after figuring out why the TestServer is flaky. - // http://crbug.com/96594 - log_to_console_ = true; -} - -bool TestServer::SetPythonPath() { - FilePath third_party_dir; - if (!PathService::Get(base::DIR_SOURCE_ROOT, &third_party_dir)) { - LOG(ERROR) << "Failed to get DIR_SOURCE_ROOT"; - return false; - } - third_party_dir = third_party_dir.AppendASCII("third_party"); - - // For simplejson. (simplejson, unlike all the other python modules - // we include, doesn't have an extra 'simplejson' directory, so we - // need to include its parent directory, i.e. third_party_dir). - AppendToPythonPath(third_party_dir); - - AppendToPythonPath(third_party_dir.AppendASCII("tlslite")); - AppendToPythonPath( - third_party_dir.AppendASCII("pyftpdlib").AppendASCII("src")); - - // Locate the Python code generated by the protocol buffers compiler. - FilePath pyproto_dir; - if (!GetPyProtoPath(&pyproto_dir)) { - LOG(WARNING) << "Cannot find pyproto dir for generated code. " - << "Testserver features that rely on it will not work"; - return true; - } - - AppendToPythonPath(pyproto_dir); - AppendToPythonPath(pyproto_dir.AppendASCII("sync").AppendASCII("protocol")); - AppendToPythonPath(pyproto_dir.AppendASCII("chrome") - .AppendASCII("browser") - .AppendASCII("policy") - .AppendASCII("proto")); - - return true; -} - -bool TestServer::ParseServerData(const std::string& server_data) { - VLOG(1) << "Server data: " << server_data; - base::JSONReader json_reader; - scoped_ptr<Value> value(json_reader.JsonToValue(server_data, true, false)); - if (!value.get() || - !value->IsType(Value::TYPE_DICTIONARY)) { - LOG(ERROR) << "Could not parse server data: " - << json_reader.GetErrorMessage(); - return false; - } - server_data_.reset(static_cast<DictionaryValue*>(value.release())); - int port = 0; - if (!server_data_->GetInteger("port", &port)) { - LOG(ERROR) << "Could not find port value"; - return false; - } - if ((port <= 0) || (port > kuint16max)) { - LOG(ERROR) << "Invalid port value: " << port; - return false; - } - host_port_pair_.set_port(port); - return true; -} - -FilePath TestServer::GetRootCertificatePath() const { - return certificates_dir_.AppendASCII("root_ca_cert.crt"); -} - -bool TestServer::LoadTestRootCert() { - TestRootCerts* root_certs = TestRootCerts::GetInstance(); - return root_certs->AddFromFile(GetRootCertificatePath()); -} - -bool TestServer::AddCommandLineArguments(CommandLine* command_line) const { - command_line->AppendArg("--host=" + host_port_pair_.host()); - command_line->AppendArg("--port=" + - base::IntToString(host_port_pair_.port())); - command_line->AppendArgNative(FILE_PATH_LITERAL("--data-dir=") + - document_root_.value()); - - if (VLOG_IS_ON(1) || log_to_console_) { - command_line->AppendArg("--log-to-console"); - } - - if (type_ == TYPE_FTP) { - command_line->AppendArg("-f"); - } else if (type_ == TYPE_SYNC) { - command_line->AppendArg("--sync"); - } else if (type_ == TYPE_TCP_ECHO) { - command_line->AppendArg("--tcp-echo"); - } else if (type_ == TYPE_UDP_ECHO) { - command_line->AppendArg("--udp-echo"); - } else if (type_ == TYPE_GDATA) { - // --auth-token will be used in tests for chrome/browser/chromeos/gdata. - command_line->AppendArg(std::string("--auth-token=") + kGDataAuthToken); - } else if (type_ == TYPE_HTTPS) { - FilePath certificate_path(certificates_dir_); - certificate_path = certificate_path.Append( - https_options_.GetCertificateFile()); - if (!file_util::PathExists(certificate_path)) { - LOG(ERROR) << "Certificate path " << certificate_path.value() - << " doesn't exist. Can't launch https server."; - return false; - } - command_line->AppendArgNative(FILE_PATH_LITERAL("--https=") + - certificate_path.value()); - - if (https_options_.request_client_certificate) - command_line->AppendArg("--ssl-client-auth"); - - for (std::vector<FilePath>::const_iterator it = - https_options_.client_authorities.begin(); - it != https_options_.client_authorities.end(); ++it) { - if (!file_util::PathExists(*it)) { - LOG(ERROR) << "Client authority path " << it->value() - << " doesn't exist. Can't launch https server."; - return false; - } - - command_line->AppendArgNative(FILE_PATH_LITERAL("--ssl-client-ca=") + - it->value()); - } - - const std::string kBulkCipherSwitch("--ssl-bulk-cipher"); - if (https_options_.bulk_ciphers & HTTPSOptions::BULK_CIPHER_RC4) - command_line->AppendArg(kBulkCipherSwitch + "=rc4"); - if (https_options_.bulk_ciphers & HTTPSOptions::BULK_CIPHER_AES128) - command_line->AppendArg(kBulkCipherSwitch + "=aes128"); - if (https_options_.bulk_ciphers & HTTPSOptions::BULK_CIPHER_AES256) - command_line->AppendArg(kBulkCipherSwitch + "=aes256"); - if (https_options_.bulk_ciphers & HTTPSOptions::BULK_CIPHER_3DES) - command_line->AppendArg(kBulkCipherSwitch + "=3des"); - - if (https_options_.record_resume) - command_line->AppendArg("--https-record-resume"); - } - - return true; -} - -} // namespace net diff --git a/net/test/test_server.h b/net/test/test_server.h index 5c04a40..a3e0da0 100644 --- a/net/test/test_server.h +++ b/net/test/test_server.h @@ -6,230 +6,23 @@ #define NET_TEST_TEST_SERVER_H_ #pragma once -#include <string> -#include <utility> -#include <vector> - #include "build/build_config.h" -#include "base/compiler_specific.h" -#include "base/file_path.h" -#include "base/file_util.h" -#include "base/process_util.h" -#include "net/base/host_port_pair.h" -#include "net/base/net_util.h" - -#if defined(OS_WIN) -#include "base/win/scoped_handle.h" +#if defined(OS_ANDROID) +#include "net/test/remote_test_server.h" +#else +#include "net/test/local_test_server.h" #endif -class CommandLine; -class GURL; - -namespace base { -class DictionaryValue; -} - namespace net { -class AddressList; - -// This object bounds the lifetime of an external python-based HTTP/FTP server -// that can provide various responses useful for testing. -class TestServer { - public: - typedef std::pair<std::string, std::string> StringPair; - - enum Type { - TYPE_FTP, - TYPE_GDATA, - TYPE_HTTP, - TYPE_HTTPS, - TYPE_SYNC, - TYPE_TCP_ECHO, - TYPE_UDP_ECHO, - }; - - // Container for various options to control how the HTTPS server is - // initialized. - struct HTTPSOptions { - enum ServerCertificate { - CERT_OK, - CERT_MISMATCHED_NAME, - CERT_EXPIRED, - // Cross-signed certificate to test PKIX path building. Contains an - // intermediate cross-signed by an unknown root, while the client (via - // TestRootStore) is expected to have a self-signed version of the - // intermediate. - CERT_CHAIN_WRONG_ROOT, - }; - - // Bitmask of bulk encryption algorithms that the test server supports - // and that can be selectively enabled or disabled. - enum BulkCipher { - // Special value used to indicate that any algorithm the server supports - // is acceptable. Preferred over explicitly OR-ing all ciphers. - BULK_CIPHER_ANY = 0, - - BULK_CIPHER_RC4 = (1 << 0), - BULK_CIPHER_AES128 = (1 << 1), - BULK_CIPHER_AES256 = (1 << 2), - - // NOTE: 3DES support in the Python test server has external - // dependencies and not be available on all machines. Clients may not - // be able to connect if only 3DES is specified. - BULK_CIPHER_3DES = (1 << 3), - }; - - // Initialize a new HTTPSOptions using CERT_OK as the certificate. - HTTPSOptions(); - - // Initialize a new HTTPSOptions that will use the specified certificate. - explicit HTTPSOptions(ServerCertificate cert); - ~HTTPSOptions(); - - // Returns the relative filename of the file that contains the - // |server_certificate|. - FilePath GetCertificateFile() const; - - // The certificate to use when serving requests. - ServerCertificate server_certificate; - - // True if a CertificateRequest should be sent to the client during - // handshaking. - bool request_client_certificate; - - // If |request_client_certificate| is true, an optional list of files, - // each containing a single, PEM-encoded X.509 certificates. The subject - // from each certificate will be added to the certificate_authorities - // field of the CertificateRequest. - std::vector<FilePath> client_authorities; - - // A bitwise-OR of BulkCipher that should be used by the - // HTTPS server, or BULK_CIPHER_ANY to indicate that all implemented - // ciphers are acceptable. - int bulk_ciphers; - - // If true, pass the --https-record-resume argument to testserver.py which - // causes it to log session cache actions and echo the log on - // /ssl-session-cache. - bool record_resume; - }; - - // Pass as the 'host' parameter during construction to server on 127.0.0.1 - static const char kLocalhost[]; - - // The auth token to be used for TYPE_GDATA server. - static const char kGDataAuthToken[]; - - // Initialize a TestServer listening on the specified host (IP or hostname). - TestServer(Type type, const std::string& host, const FilePath& document_root); - - // Initialize a HTTPS TestServer with a specific set of HTTPSOptions. - TestServer(const HTTPSOptions& https_options, - const FilePath& document_root); - - ~TestServer(); - - bool Start() WARN_UNUSED_RESULT; - - // Stop the server started by Start(). - bool Stop(); - - const FilePath& document_root() const { return document_root_; } - const HostPortPair& host_port_pair() const; - const base::DictionaryValue& server_data() const; - std::string GetScheme() const; - bool GetAddressList(AddressList* address_list) const WARN_UNUSED_RESULT; - - GURL GetURL(const std::string& path) const; - - GURL GetURLWithUser(const std::string& path, - const std::string& user) const; - - GURL GetURLWithUserAndPassword(const std::string& path, - const std::string& user, - const std::string& password) const; - - static bool GetFilePathWithReplacements( - const std::string& original_path, - const std::vector<StringPair>& text_to_replace, - std::string* replacement_path); - - private: - void Init(const std::string& host, const FilePath& document_root); - - // Modify PYTHONPATH to contain libraries we need. - bool SetPythonPath() WARN_UNUSED_RESULT; - - // Launches the Python test server. Returns true on success. - bool LaunchPython(const FilePath& testserver_path) WARN_UNUSED_RESULT; - - // Waits for the server to start. Returns true on success. - bool WaitToStart() WARN_UNUSED_RESULT; - - // Parses the server data read from the test server. Returns true - // on success. - bool ParseServerData(const std::string& server_data) WARN_UNUSED_RESULT; - - // Returns path to the root certificate. - FilePath GetRootCertificatePath() const; - - // Load the test root cert, if it hasn't been loaded yet. - bool LoadTestRootCert() WARN_UNUSED_RESULT; - - // Add the command line arguments for the Python test server to - // |command_line|. Return true on success. - bool AddCommandLineArguments(CommandLine* command_line) const; - - // Document root of the test server. - FilePath document_root_; - - // Directory that contains the SSL certificates. - FilePath certificates_dir_; - - // Address the test server listens on. - HostPortPair host_port_pair_; - - // Holds the data sent from the server (e.g., port number). - scoped_ptr<base::DictionaryValue> server_data_; - - // Handle of the Python process running the test server. - base::ProcessHandle process_handle_; - - scoped_ptr<net::ScopedPortException> allowed_port_; - -#if defined(OS_WIN) - // JobObject used to clean up orphaned child processes. - base::win::ScopedHandle job_handle_; - - // The pipe file handle we read from. - base::win::ScopedHandle child_read_fd_; - - // The pipe file handle the child and we write to. - base::win::ScopedHandle child_write_fd_; -#endif - -#if defined(OS_POSIX) - // The file descriptor the child writes to when it starts. - int child_fd_; - file_util::ScopedFD child_fd_closer_; +#if defined(OS_ANDROID) +typedef RemoteTestServer TestServer; +#else +typedef LocalTestServer TestServer; #endif - // If |type_| is TYPE_HTTPS, the TLS settings to use for the test server. - HTTPSOptions https_options_; - - Type type_; - - // Has the server been started? - bool started_; - - // Enables logging of the server to the console. - bool log_to_console_; - - DISALLOW_COPY_AND_ASSIGN(TestServer); -}; - } // namespace net #endif // NET_TEST_TEST_SERVER_H_ + |