local_test_server.cc revision a1401311d1ab56c4ed0a474bd38c108f75cb0cd9
1// Copyright 2013 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "net/test/spawned_test_server/local_test_server.h" 6 7#include "base/command_line.h" 8#include "base/json/json_reader.h" 9#include "base/logging.h" 10#include "base/path_service.h" 11#include "base/process/kill.h" 12#include "base/strings/string_number_conversions.h" 13#include "base/values.h" 14#include "net/base/host_port_pair.h" 15#include "net/base/net_errors.h" 16#include "net/test/python_utils.h" 17#include "url/gurl.h" 18 19namespace net { 20 21namespace { 22 23bool AppendArgumentFromJSONValue(const std::string& key, 24 const base::Value& value_node, 25 base::CommandLine* command_line) { 26 std::string argument_name = "--" + key; 27 switch (value_node.GetType()) { 28 case base::Value::TYPE_NULL: 29 command_line->AppendArg(argument_name); 30 break; 31 case base::Value::TYPE_INTEGER: { 32 int value; 33 bool result = value_node.GetAsInteger(&value); 34 DCHECK(result); 35 command_line->AppendArg(argument_name + "=" + base::IntToString(value)); 36 break; 37 } 38 case base::Value::TYPE_STRING: { 39 std::string value; 40 bool result = value_node.GetAsString(&value); 41 if (!result || value.empty()) 42 return false; 43 command_line->AppendArg(argument_name + "=" + value); 44 break; 45 } 46 case base::Value::TYPE_BOOLEAN: 47 case base::Value::TYPE_DOUBLE: 48 case base::Value::TYPE_LIST: 49 case base::Value::TYPE_DICTIONARY: 50 case base::Value::TYPE_BINARY: 51 default: 52 NOTREACHED() << "improper json type"; 53 return false; 54 } 55 return true; 56} 57 58} // namespace 59 60LocalTestServer::LocalTestServer(Type type, 61 const std::string& host, 62 const base::FilePath& document_root) 63 : BaseTestServer(type, host) { 64 if (!Init(document_root)) 65 NOTREACHED(); 66} 67 68LocalTestServer::LocalTestServer(Type type, 69 const SSLOptions& ssl_options, 70 const base::FilePath& document_root) 71 : BaseTestServer(type, ssl_options) { 72 if (!Init(document_root)) 73 NOTREACHED(); 74} 75 76LocalTestServer::~LocalTestServer() { 77 Stop(); 78} 79 80bool LocalTestServer::GetTestServerPath(base::FilePath* testserver_path) const { 81 base::FilePath testserver_dir; 82 if (!PathService::Get(base::DIR_SOURCE_ROOT, &testserver_dir)) { 83 LOG(ERROR) << "Failed to get DIR_SOURCE_ROOT"; 84 return false; 85 } 86 testserver_dir = testserver_dir.Append(FILE_PATH_LITERAL("net")) 87 .Append(FILE_PATH_LITERAL("tools")) 88 .Append(FILE_PATH_LITERAL("testserver")); 89 *testserver_path = testserver_dir.Append(FILE_PATH_LITERAL("testserver.py")); 90 return true; 91} 92 93bool LocalTestServer::Start() { 94 return StartInBackground() && BlockUntilStarted(); 95} 96 97bool LocalTestServer::StartInBackground() { 98 // Get path to Python server script. 99 base::FilePath testserver_path; 100 if (!GetTestServerPath(&testserver_path)) 101 return false; 102 103 if (!SetPythonPath()) 104 return false; 105 106 if (!LaunchPython(testserver_path)) 107 return false; 108 109 return true; 110} 111 112bool LocalTestServer::BlockUntilStarted() { 113 if (!WaitToStart()) { 114 Stop(); 115 return false; 116 } 117 118 return SetupWhenServerStarted(); 119} 120 121bool LocalTestServer::Stop() { 122 CleanUpWhenStoppingServer(); 123 124 if (!process_handle_) 125 return true; 126 127#if defined(OS_WIN) 128 // This kills all the processes in the job object. 129 job_handle_.Close(); 130#endif 131 132 // First check if the process has already terminated. 133 bool ret = base::WaitForSingleProcess(process_handle_, base::TimeDelta()); 134 if (!ret) { 135 ret = base::KillProcess(process_handle_, 1, true); 136 } 137 138 if (ret) { 139 base::CloseProcessHandle(process_handle_); 140 process_handle_ = base::kNullProcessHandle; 141 } else { 142 VLOG(1) << "Kill failed?"; 143 } 144 145 return ret; 146} 147 148bool LocalTestServer::Init(const base::FilePath& document_root) { 149 if (document_root.IsAbsolute()) 150 return false; 151 152 // At this point, the port that the test server will listen on is unknown. 153 // The test server will listen on an ephemeral port, and write the port 154 // number out over a pipe that this TestServer object will read from. Once 155 // that is complete, the host port pair will contain the actual port. 156 DCHECK(!GetPort()); 157 process_handle_ = base::kNullProcessHandle; 158 159 base::FilePath src_dir; 160 if (!PathService::Get(base::DIR_SOURCE_ROOT, &src_dir)) 161 return false; 162 SetResourcePath(src_dir.Append(document_root), 163 src_dir.AppendASCII("net") 164 .AppendASCII("data") 165 .AppendASCII("ssl") 166 .AppendASCII("certificates")); 167 return true; 168} 169 170bool LocalTestServer::SetPythonPath() const { 171 base::FilePath third_party_dir; 172 if (!PathService::Get(base::DIR_SOURCE_ROOT, &third_party_dir)) { 173 LOG(ERROR) << "Failed to get DIR_SOURCE_ROOT"; 174 return false; 175 } 176 third_party_dir = third_party_dir.AppendASCII("third_party"); 177 178 // For simplejson. (simplejson, unlike all the other Python modules 179 // we include, doesn't have an extra 'simplejson' directory, so we 180 // need to include its parent directory, i.e. third_party_dir). 181 AppendToPythonPath(third_party_dir); 182 183 AppendToPythonPath(third_party_dir.AppendASCII("tlslite")); 184 AppendToPythonPath( 185 third_party_dir.AppendASCII("pyftpdlib").AppendASCII("src")); 186 AppendToPythonPath( 187 third_party_dir.AppendASCII("pywebsocket").AppendASCII("src")); 188 189 // Locate the Python code generated by the protocol buffers compiler. 190 base::FilePath pyproto_dir; 191 if (!GetPyProtoPath(&pyproto_dir)) { 192 LOG(WARNING) << "Cannot find pyproto dir for generated code. " 193 << "Testserver features that rely on it will not work"; 194 return true; 195 } 196 AppendToPythonPath(pyproto_dir); 197 198 return true; 199} 200 201bool LocalTestServer::AddCommandLineArguments( 202 base::CommandLine* command_line) const { 203 base::DictionaryValue arguments_dict; 204 if (!GenerateArguments(&arguments_dict)) 205 return false; 206 207 // Serialize the argument dictionary into CommandLine. 208 for (base::DictionaryValue::Iterator it(arguments_dict); !it.IsAtEnd(); 209 it.Advance()) { 210 const base::Value& value = it.value(); 211 const std::string& key = it.key(); 212 213 // Add arguments from a list. 214 if (value.IsType(base::Value::TYPE_LIST)) { 215 const base::ListValue* list = NULL; 216 if (!value.GetAsList(&list) || !list || list->empty()) 217 return false; 218 for (base::ListValue::const_iterator list_it = list->begin(); 219 list_it != list->end(); ++list_it) { 220 if (!AppendArgumentFromJSONValue(key, *(*list_it), command_line)) 221 return false; 222 } 223 } else if (!AppendArgumentFromJSONValue(key, value, command_line)) { 224 return false; 225 } 226 } 227 228 // Append the appropriate server type argument. 229 switch (type()) { 230 case TYPE_HTTP: // The default type is HTTP, no argument required. 231 break; 232 case TYPE_HTTPS: 233 command_line->AppendArg("--https"); 234 break; 235 case TYPE_WS: 236 case TYPE_WSS: 237 command_line->AppendArg("--websocket"); 238 break; 239 case TYPE_FTP: 240 command_line->AppendArg("--ftp"); 241 break; 242 case TYPE_TCP_ECHO: 243 command_line->AppendArg("--tcp-echo"); 244 break; 245 case TYPE_UDP_ECHO: 246 command_line->AppendArg("--udp-echo"); 247 break; 248 case TYPE_BASIC_AUTH_PROXY: 249 command_line->AppendArg("--basic-auth-proxy"); 250 break; 251 default: 252 NOTREACHED(); 253 return false; 254 } 255 256 return true; 257} 258 259} // namespace net 260