1 // Copyright 2013 The Chromium Authors
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/notreached.h"
11 #include "base/path_service.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/threading/thread_restrictions.h"
14 #include "base/values.h"
15 #include "net/base/host_port_pair.h"
16 #include "net/base/net_errors.h"
17 #include "net/test/python_utils.h"
18 #include "url/gurl.h"
19
20 namespace net {
21
22 namespace {
23
AppendArgumentFromJSONValue(const std::string & key,const base::Value & value_node,base::CommandLine * command_line)24 bool AppendArgumentFromJSONValue(const std::string& key,
25 const base::Value& value_node,
26 base::CommandLine* command_line) {
27 std::string argument_name = "--" + key;
28 switch (value_node.type()) {
29 case base::Value::Type::NONE:
30 command_line->AppendArg(argument_name);
31 break;
32 case base::Value::Type::INTEGER: {
33 command_line->AppendArg(argument_name + "=" +
34 base::NumberToString(value_node.GetInt()));
35 break;
36 }
37 case base::Value::Type::STRING: {
38 if (!value_node.is_string())
39 return false;
40 const std::string value = value_node.GetString();
41 if (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::DICT:
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
LocalTestServer(Type type,const base::FilePath & document_root)60 LocalTestServer::LocalTestServer(Type type, const base::FilePath& document_root)
61 : BaseTestServer(type) {
62 if (!Init(document_root))
63 NOTREACHED();
64 }
65
LocalTestServer(Type type,const SSLOptions & ssl_options,const base::FilePath & document_root)66 LocalTestServer::LocalTestServer(Type type,
67 const SSLOptions& ssl_options,
68 const base::FilePath& document_root)
69 : BaseTestServer(type, ssl_options) {
70 if (!Init(document_root))
71 NOTREACHED();
72 }
73
~LocalTestServer()74 LocalTestServer::~LocalTestServer() {
75 Stop();
76 }
77
GetTestServerPath(base::FilePath * testserver_path) const78 bool LocalTestServer::GetTestServerPath(base::FilePath* testserver_path) const {
79 base::FilePath testserver_dir;
80 if (!base::PathService::Get(base::DIR_SOURCE_ROOT, &testserver_dir)) {
81 LOG(ERROR) << "Failed to get DIR_SOURCE_ROOT";
82 return false;
83 }
84 testserver_dir = testserver_dir.Append(FILE_PATH_LITERAL("net"))
85 .Append(FILE_PATH_LITERAL("tools"))
86 .Append(FILE_PATH_LITERAL("testserver"));
87 *testserver_path = testserver_dir.Append(FILE_PATH_LITERAL("testserver.py"));
88 return true;
89 }
90
StartInBackground()91 bool LocalTestServer::StartInBackground() {
92 DCHECK(!started());
93
94 base::ScopedAllowBlockingForTesting allow_blocking;
95
96 // Get path to Python server script.
97 base::FilePath testserver_path;
98 if (!GetTestServerPath(&testserver_path)) {
99 LOG(ERROR) << "Could not get test server path.";
100 return false;
101 }
102
103 absl::optional<std::vector<base::FilePath>> python_path = GetPythonPath();
104 if (!python_path) {
105 LOG(ERROR) << "Could not get Python path.";
106 return false;
107 }
108
109 if (!LaunchPython(testserver_path, *python_path)) {
110 LOG(ERROR) << "Could not launch Python with path " << testserver_path;
111 return false;
112 }
113
114 return true;
115 }
116
BlockUntilStarted()117 bool LocalTestServer::BlockUntilStarted() {
118 if (!WaitToStart()) {
119 Stop();
120 return false;
121 }
122
123 return SetupWhenServerStarted();
124 }
125
Stop()126 bool LocalTestServer::Stop() {
127 CleanUpWhenStoppingServer();
128
129 if (!process_.IsValid())
130 return true;
131
132 // First check if the process has already terminated.
133 bool ret = process_.WaitForExitWithTimeout(base::TimeDelta(), nullptr);
134 if (!ret) {
135 base::ScopedAllowBaseSyncPrimitivesForTesting allow_wait_process;
136 ret = process_.Terminate(1, true);
137 }
138
139 if (ret)
140 process_.Close();
141 else
142 VLOG(1) << "Kill failed?";
143
144 return ret;
145 }
146
Init(const base::FilePath & document_root)147 bool LocalTestServer::Init(const base::FilePath& document_root) {
148 if (document_root.IsAbsolute())
149 return false;
150
151 // At this point, the port that the test server will listen on is unknown.
152 // The test server will listen on an ephemeral port, and write the port
153 // number out over a pipe that this TestServer object will read from. Once
154 // that is complete, the host port pair will contain the actual port.
155 DCHECK(!GetPort());
156
157 base::FilePath src_dir;
158 if (!base::PathService::Get(base::DIR_SOURCE_ROOT, &src_dir))
159 return false;
160 SetResourcePath(src_dir.Append(document_root),
161 src_dir.AppendASCII("net")
162 .AppendASCII("data")
163 .AppendASCII("ssl")
164 .AppendASCII("certificates"));
165 return true;
166 }
167
GetPythonPath() const168 absl::optional<std::vector<base::FilePath>> LocalTestServer::GetPythonPath()
169 const {
170 base::FilePath third_party_dir;
171 if (!base::PathService::Get(base::DIR_SOURCE_ROOT, &third_party_dir)) {
172 LOG(ERROR) << "Failed to get DIR_SOURCE_ROOT";
173 return absl::nullopt;
174 }
175 third_party_dir = third_party_dir.AppendASCII("third_party");
176
177 std::vector<base::FilePath> ret = {
178 third_party_dir.AppendASCII("pywebsocket3").AppendASCII("src"),
179 };
180
181 return ret;
182 }
183
AddCommandLineArguments(base::CommandLine * command_line) const184 bool LocalTestServer::AddCommandLineArguments(
185 base::CommandLine* command_line) const {
186 absl::optional<base::Value::Dict> arguments_dict = GenerateArguments();
187 if (!arguments_dict)
188 return false;
189
190 // Serialize the argument dictionary into CommandLine.
191 for (auto it = arguments_dict->begin(); it != arguments_dict->end(); ++it) {
192 const base::Value& value = it->second;
193 const std::string& key = it->first;
194
195 // Add arguments from a list.
196 if (value.is_list()) {
197 if (value.GetList().empty())
198 return false;
199 for (const auto& entry : value.GetList()) {
200 if (!AppendArgumentFromJSONValue(key, entry, command_line))
201 return false;
202 }
203 } else if (!AppendArgumentFromJSONValue(key, value, command_line)) {
204 return false;
205 }
206 }
207
208 // Append the appropriate server type argument.
209 switch (type()) {
210 case TYPE_WS:
211 case TYPE_WSS:
212 command_line->AppendArg("--websocket");
213 break;
214 case TYPE_BASIC_AUTH_PROXY:
215 command_line->AppendArg("--basic-auth-proxy");
216 break;
217 case TYPE_PROXY:
218 command_line->AppendArg("--proxy");
219 break;
220 default:
221 NOTREACHED();
222 return false;
223 }
224
225 return true;
226 }
227
228 } // namespace net
229