1// Copyright (c) 2012 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 <poll.h>
8
9#include <vector>
10
11#include "base/command_line.h"
12#include "base/files/file_util.h"
13#include "base/files/scoped_file.h"
14#include "base/logging.h"
15#include "base/process/kill.h"
16#include "base/process/launch.h"
17#include "base/process/process_iterator.h"
18#include "base/strings/string_number_conversions.h"
19#include "base/strings/string_util.h"
20#include "base/test/test_timeouts.h"
21#include "net/test/python_utils.h"
22
23namespace {
24
25// Helper class used to detect and kill orphaned python test server processes.
26// Checks if the command line of a process contains |path_string| (the path
27// from which the test server was launched) and |port_string| (the port used by
28// the test server), and if the parent pid of the process is 1 (indicating that
29// it is an orphaned process).
30class OrphanedTestServerFilter : public base::ProcessFilter {
31 public:
32  OrphanedTestServerFilter(
33      const std::string& path_string, const std::string& port_string)
34      : path_string_(path_string),
35        port_string_(port_string) {}
36
37  virtual bool Includes(const base::ProcessEntry& entry) const OVERRIDE {
38    if (entry.parent_pid() != 1)
39      return false;
40    bool found_path_string = false;
41    bool found_port_string = false;
42    for (std::vector<std::string>::const_iterator it =
43         entry.cmd_line_args().begin();
44         it != entry.cmd_line_args().end();
45         ++it) {
46      if (it->find(path_string_) != std::string::npos)
47        found_path_string = true;
48      if (it->find(port_string_) != std::string::npos)
49        found_port_string = true;
50    }
51    return found_path_string && found_port_string;
52  }
53
54 private:
55  std::string path_string_;
56  std::string port_string_;
57  DISALLOW_COPY_AND_ASSIGN(OrphanedTestServerFilter);
58};
59
60// Given a file descriptor, reads into |buffer| until |bytes_max|
61// bytes has been read or an error has been encountered.  Returns true
62// if the read was successful.  |remaining_time| is used as a timeout.
63bool ReadData(int fd, ssize_t bytes_max, uint8* buffer,
64              base::TimeDelta* remaining_time) {
65  ssize_t bytes_read = 0;
66  base::TimeTicks previous_time = base::TimeTicks::Now();
67  while (bytes_read < bytes_max) {
68    struct pollfd poll_fds[1];
69
70    poll_fds[0].fd = fd;
71    poll_fds[0].events = POLLIN | POLLPRI;
72    poll_fds[0].revents = 0;
73
74    int rv = HANDLE_EINTR(poll(poll_fds, 1,
75                               remaining_time->InMilliseconds()));
76    if (rv == 0) {
77      LOG(ERROR) << "poll() timed out; bytes_read=" << bytes_read;
78      return false;
79    } else if (rv < 0) {
80      PLOG(ERROR) << "poll() failed for child file descriptor; bytes_read="
81                  << bytes_read;
82      return false;
83    }
84
85    base::TimeTicks current_time = base::TimeTicks::Now();
86    base::TimeDelta elapsed_time_cycle = current_time - previous_time;
87    DCHECK_GE(elapsed_time_cycle.InMilliseconds(), 0);
88    *remaining_time -= elapsed_time_cycle;
89    previous_time = current_time;
90
91    ssize_t num_bytes = HANDLE_EINTR(read(fd, buffer + bytes_read,
92                                          bytes_max - bytes_read));
93    if (num_bytes <= 0)
94      return false;
95    bytes_read += num_bytes;
96  }
97  return true;
98}
99
100}  // namespace
101
102namespace net {
103
104bool LocalTestServer::LaunchPython(const base::FilePath& testserver_path) {
105  // Log is useful in the event you want to run a nearby script (e.g. a test) in
106  // the same environment as the TestServer.
107  VLOG(1) << "LaunchPython called with PYTHONPATH = " << getenv(kPythonPathEnv);
108
109  base::CommandLine python_command(base::CommandLine::NO_PROGRAM);
110  if (!GetPythonCommand(&python_command))
111    return false;
112
113  python_command.AppendArgPath(testserver_path);
114  if (!AddCommandLineArguments(&python_command))
115    return false;
116
117  int pipefd[2];
118  if (pipe(pipefd) != 0) {
119    PLOG(ERROR) << "Could not create pipe.";
120    return false;
121  }
122
123  // Save the read half. The write half is sent to the child.
124  child_fd_.reset(pipefd[0]);
125  base::ScopedFD write_closer(pipefd[1]);
126  base::FileHandleMappingVector map_write_fd;
127  map_write_fd.push_back(std::make_pair(pipefd[1], pipefd[1]));
128
129  python_command.AppendArg("--startup-pipe=" + base::IntToString(pipefd[1]));
130
131  // Try to kill any orphaned testserver processes that may be running.
132  OrphanedTestServerFilter filter(testserver_path.value(),
133                                  base::IntToString(GetPort()));
134  if (!base::KillProcesses("python", -1, &filter)) {
135    LOG(WARNING) << "Failed to clean up older orphaned testserver instances.";
136  }
137
138  // Launch a new testserver process.
139  base::LaunchOptions options;
140
141  options.fds_to_remap = &map_write_fd;
142  if (!base::LaunchProcess(python_command, options, &process_handle_)) {
143    LOG(ERROR) << "Failed to launch " << python_command.GetCommandLineString();
144    return false;
145  }
146
147  return true;
148}
149
150bool LocalTestServer::WaitToStart() {
151  base::ScopedFD our_fd(child_fd_.release());
152
153  base::TimeDelta remaining_time = TestTimeouts::action_timeout();
154
155  uint32 server_data_len = 0;
156  if (!ReadData(our_fd.get(), sizeof(server_data_len),
157                reinterpret_cast<uint8*>(&server_data_len),
158                &remaining_time)) {
159    LOG(ERROR) << "Could not read server_data_len";
160    return false;
161  }
162  std::string server_data(server_data_len, '\0');
163  if (!ReadData(our_fd.get(), server_data_len,
164                reinterpret_cast<uint8*>(&server_data[0]),
165                &remaining_time)) {
166    LOG(ERROR) << "Could not read server_data (" << server_data_len
167               << " bytes)";
168    return false;
169  }
170
171  if (!ParseServerData(server_data)) {
172    LOG(ERROR) << "Could not parse server_data: " << server_data;
173    return false;
174  }
175
176  return true;
177}
178
179}  // namespace net
180