Home | History | Annotate | Download | only in spawned_test_server
      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