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