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 23 namespace { 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). 30 class 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. 63 bool 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 102 namespace net { 103 104 bool 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 150 bool 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