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 }
54 return true;
55 }
56
57 } // namespace
58
LocalTestServer(Type type,const base::FilePath & document_root)59 LocalTestServer::LocalTestServer(Type type, const base::FilePath& document_root)
60 : BaseTestServer(type) {
61 if (!Init(document_root))
62 NOTREACHED();
63 }
64
LocalTestServer(Type type,const SSLOptions & ssl_options,const base::FilePath & document_root)65 LocalTestServer::LocalTestServer(Type type,
66 const SSLOptions& ssl_options,
67 const base::FilePath& document_root)
68 : BaseTestServer(type, ssl_options) {
69 if (!Init(document_root))
70 NOTREACHED();
71 }
72
~LocalTestServer()73 LocalTestServer::~LocalTestServer() {
74 Stop();
75 }
76
GetTestServerPath(base::FilePath * testserver_path) const77 bool LocalTestServer::GetTestServerPath(base::FilePath* testserver_path) const {
78 base::FilePath testserver_dir;
79 if (!base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &testserver_dir)) {
80 LOG(ERROR) << "Failed to get DIR_SRC_TEST_DATA_ROOT";
81 return false;
82 }
83 testserver_dir = testserver_dir.Append(FILE_PATH_LITERAL("net"))
84 .Append(FILE_PATH_LITERAL("tools"))
85 .Append(FILE_PATH_LITERAL("testserver"));
86 *testserver_path = testserver_dir.Append(FILE_PATH_LITERAL("testserver.py"));
87 return true;
88 }
89
StartInBackground()90 bool LocalTestServer::StartInBackground() {
91 DCHECK(!started());
92
93 base::ScopedAllowBlockingForTesting allow_blocking;
94
95 // Get path to Python server script.
96 base::FilePath testserver_path;
97 if (!GetTestServerPath(&testserver_path)) {
98 LOG(ERROR) << "Could not get test server path.";
99 return false;
100 }
101
102 std::optional<std::vector<base::FilePath>> python_path = GetPythonPath();
103 if (!python_path) {
104 LOG(ERROR) << "Could not get Python path.";
105 return false;
106 }
107
108 if (!LaunchPython(testserver_path, *python_path)) {
109 LOG(ERROR) << "Could not launch Python with path " << testserver_path;
110 return false;
111 }
112
113 return true;
114 }
115
BlockUntilStarted()116 bool LocalTestServer::BlockUntilStarted() {
117 if (!WaitToStart()) {
118 Stop();
119 return false;
120 }
121
122 return SetupWhenServerStarted();
123 }
124
Stop()125 bool LocalTestServer::Stop() {
126 CleanUpWhenStoppingServer();
127
128 if (!process_.IsValid())
129 return true;
130
131 // First check if the process has already terminated.
132 bool ret = process_.WaitForExitWithTimeout(base::TimeDelta(), nullptr);
133 if (!ret) {
134 base::ScopedAllowBaseSyncPrimitivesForTesting allow_wait_process;
135 ret = process_.Terminate(1, true);
136 }
137
138 if (ret)
139 process_.Close();
140 else
141 VLOG(1) << "Kill failed?";
142
143 return ret;
144 }
145
Init(const base::FilePath & document_root)146 bool LocalTestServer::Init(const base::FilePath& document_root) {
147 if (document_root.IsAbsolute())
148 return false;
149
150 // At this point, the port that the test server will listen on is unknown.
151 // The test server will listen on an ephemeral port, and write the port
152 // number out over a pipe that this TestServer object will read from. Once
153 // that is complete, the host port pair will contain the actual port.
154 DCHECK(!GetPort());
155
156 base::FilePath src_dir;
157 if (!base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &src_dir)) {
158 return false;
159 }
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 std::optional<std::vector<base::FilePath>> LocalTestServer::GetPythonPath()
169 const {
170 base::FilePath third_party_dir;
171 if (!base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &third_party_dir)) {
172 LOG(ERROR) << "Failed to get DIR_SRC_TEST_DATA_ROOT";
173 return std::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 std::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 }
223
224 return true;
225 }
226
227 } // namespace net
228