xref: /aosp_15_r20/external/cronet/net/test/spawned_test_server/local_test_server.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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