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