| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/test/local_test_server.h" |
| |
| #include <poll.h> |
| |
| #include <vector> |
| |
| #include "base/command_line.h" |
| #include "base/file_util.h" |
| #include "base/logging.h" |
| #include "base/process_util.h" |
| #include "base/string_number_conversions.h" |
| #include "base/string_util.h" |
| #include "base/test/test_timeouts.h" |
| #include "net/test/python_utils.h" |
| |
| namespace { |
| |
| // Helper class used to detect and kill orphaned python test server processes. |
| // Checks if the command line of a process contains |path_string| (the path |
| // from which the test server was launched) and |port_string| (the port used by |
| // the test server), and if the parent pid of the process is 1 (indicating that |
| // it is an orphaned process). |
| class OrphanedTestServerFilter : public base::ProcessFilter { |
| public: |
| OrphanedTestServerFilter( |
| const std::string& path_string, const std::string& port_string) |
| : path_string_(path_string), |
| port_string_(port_string) {} |
| |
| virtual bool Includes(const base::ProcessEntry& entry) const OVERRIDE { |
| if (entry.parent_pid() != 1) |
| return false; |
| bool found_path_string = false; |
| bool found_port_string = false; |
| for (std::vector<std::string>::const_iterator it = |
| entry.cmd_line_args().begin(); |
| it != entry.cmd_line_args().end(); |
| ++it) { |
| if (it->find(path_string_) != std::string::npos) |
| found_path_string = true; |
| if (it->find(port_string_) != std::string::npos) |
| found_port_string = true; |
| } |
| return found_path_string && found_port_string; |
| } |
| |
| private: |
| std::string path_string_; |
| std::string port_string_; |
| DISALLOW_COPY_AND_ASSIGN(OrphanedTestServerFilter); |
| }; |
| |
| // Given a file descriptor, reads into |buffer| until |bytes_max| |
| // bytes has been read or an error has been encountered. Returns true |
| // if the read was successful. |remaining_time| is used as a timeout. |
| bool ReadData(int fd, ssize_t bytes_max, uint8* buffer, |
| base::TimeDelta* remaining_time) { |
| ssize_t bytes_read = 0; |
| base::Time previous_time = base::Time::Now(); |
| while (bytes_read < bytes_max) { |
| struct pollfd poll_fds[1]; |
| |
| poll_fds[0].fd = fd; |
| poll_fds[0].events = POLLIN | POLLPRI; |
| poll_fds[0].revents = 0; |
| |
| int rv = HANDLE_EINTR(poll(poll_fds, 1, |
| remaining_time->InMilliseconds())); |
| if (rv != 1) { |
| PLOG(ERROR) << "poll() failed for child file descriptor"; |
| return false; |
| } |
| |
| base::Time current_time = base::Time::Now(); |
| base::TimeDelta elapsed_time_cycle = current_time - previous_time; |
| DCHECK_GE(elapsed_time_cycle.InMilliseconds(), 0); |
| *remaining_time -= elapsed_time_cycle; |
| previous_time = current_time; |
| |
| ssize_t num_bytes = HANDLE_EINTR(read(fd, buffer + bytes_read, |
| bytes_max - bytes_read)); |
| if (num_bytes <= 0) |
| return false; |
| bytes_read += num_bytes; |
| } |
| return true; |
| } |
| |
| } // namespace |
| |
| namespace net { |
| |
| bool LocalTestServer::LaunchPython(const FilePath& testserver_path) { |
| // Log is useful in the event you want to run a nearby script (e.g. a test) in |
| // the same environment as the TestServer. |
| VLOG(1) << "LaunchPython called with PYTHONPATH = " << getenv(kPythonPathEnv); |
| |
| CommandLine python_command(CommandLine::NO_PROGRAM); |
| if (!GetPythonCommand(&python_command)) |
| return false; |
| |
| python_command.AppendArgPath(testserver_path); |
| if (!AddCommandLineArguments(&python_command)) |
| return false; |
| |
| int pipefd[2]; |
| if (pipe(pipefd) != 0) { |
| PLOG(ERROR) << "Could not create pipe."; |
| return false; |
| } |
| |
| // Save the read half. The write half is sent to the child. |
| child_fd_ = pipefd[0]; |
| child_fd_closer_.reset(&child_fd_); |
| file_util::ScopedFD write_closer(&pipefd[1]); |
| base::FileHandleMappingVector map_write_fd; |
| map_write_fd.push_back(std::make_pair(pipefd[1], pipefd[1])); |
| |
| python_command.AppendArg("--startup-pipe=" + base::IntToString(pipefd[1])); |
| |
| // Try to kill any orphaned testserver processes that may be running. |
| OrphanedTestServerFilter filter(testserver_path.value(), |
| base::IntToString(GetPort())); |
| if (!base::KillProcesses("python", -1, &filter)) { |
| LOG(WARNING) << "Failed to clean up older orphaned testserver instances."; |
| } |
| |
| // Launch a new testserver process. |
| base::LaunchOptions options; |
| options.fds_to_remap = &map_write_fd; |
| if (!base::LaunchProcess(python_command, options, &process_handle_)) { |
| LOG(ERROR) << "Failed to launch " << python_command.GetCommandLineString(); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool LocalTestServer::WaitToStart() { |
| file_util::ScopedFD child_fd_closer(child_fd_closer_.release()); |
| |
| base::TimeDelta remaining_time = TestTimeouts::action_timeout(); |
| |
| uint32 server_data_len = 0; |
| if (!ReadData(child_fd_, sizeof(server_data_len), |
| reinterpret_cast<uint8*>(&server_data_len), |
| &remaining_time)) { |
| LOG(ERROR) << "Could not read server_data_len"; |
| return false; |
| } |
| std::string server_data(server_data_len, '\0'); |
| if (!ReadData(child_fd_, server_data_len, |
| reinterpret_cast<uint8*>(&server_data[0]), |
| &remaining_time)) { |
| LOG(ERROR) << "Could not read server_data (" << server_data_len |
| << " bytes)"; |
| return false; |
| } |
| |
| if (!ParseServerData(server_data)) { |
| LOG(ERROR) << "Could not parse server_data: " << server_data; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| } // namespace net |