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_SRC_TEST_DATA_ROOT, &testserver_dir)) {
81 LOG(ERROR) << "Failed to get DIR_SRC_TEST_DATA_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 std::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_SRC_TEST_DATA_ROOT, &src_dir)) {
159 return false;
160 }
161 SetResourcePath(src_dir.Append(document_root),
162 src_dir.AppendASCII("net")
163 .AppendASCII("data")
164 .AppendASCII("ssl")
165 .AppendASCII("certificates"));
166 return true;
167 }
168
GetPythonPath() const169 std::optional<std::vector<base::FilePath>> LocalTestServer::GetPythonPath()
170 const {
171 base::FilePath third_party_dir;
172 if (!base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &third_party_dir)) {
173 LOG(ERROR) << "Failed to get DIR_SRC_TEST_DATA_ROOT";
174 return std::nullopt;
175 }
176 third_party_dir = third_party_dir.AppendASCII("third_party");
177
178 std::vector<base::FilePath> ret = {
179 third_party_dir.AppendASCII("pywebsocket3").AppendASCII("src"),
180 };
181
182 return ret;
183 }
184
AddCommandLineArguments(base::CommandLine * command_line) const185 bool LocalTestServer::AddCommandLineArguments(
186 base::CommandLine* command_line) const {
187 std::optional<base::Value::Dict> arguments_dict = GenerateArguments();
188 if (!arguments_dict)
189 return false;
190
191 // Serialize the argument dictionary into CommandLine.
192 for (auto it = arguments_dict->begin(); it != arguments_dict->end(); ++it) {
193 const base::Value& value = it->second;
194 const std::string& key = it->first;
195
196 // Add arguments from a list.
197 if (value.is_list()) {
198 if (value.GetList().empty())
199 return false;
200 for (const auto& entry : value.GetList()) {
201 if (!AppendArgumentFromJSONValue(key, entry, command_line))
202 return false;
203 }
204 } else if (!AppendArgumentFromJSONValue(key, value, command_line)) {
205 return false;
206 }
207 }
208
209 // Append the appropriate server type argument.
210 switch (type()) {
211 case TYPE_WS:
212 case TYPE_WSS:
213 command_line->AppendArg("--websocket");
214 break;
215 case TYPE_BASIC_AUTH_PROXY:
216 command_line->AppendArg("--basic-auth-proxy");
217 break;
218 case TYPE_PROXY:
219 command_line->AppendArg("--proxy");
220 break;
221 default:
222 NOTREACHED();
223 return false;
224 }
225
226 return true;
227 }
228
229 } // namespace net
230