| Index: testing/legion/process.py
 | 
| diff --git a/testing/legion/rpc_methods.py b/testing/legion/process.py
 | 
| similarity index 65%
 | 
| copy from testing/legion/rpc_methods.py
 | 
| copy to testing/legion/process.py
 | 
| index 1a59f288afde5fb03c1f8fb3d0ebf4fa6de5bae4..356db6131bfa123c0a97f2bc1b7f0d908544ab22 100644
 | 
| --- a/testing/legion/rpc_methods.py
 | 
| +++ b/testing/legion/process.py
 | 
| @@ -2,11 +2,15 @@
 | 
|  # Use of this source code is governed by a BSD-style license that can be
 | 
|  # found in the LICENSE file.
 | 
|  
 | 
| -"""Defines the task RPC methods."""
 | 
| +"""RPC compatible subprocess-type module.
 | 
| +
 | 
| +This module defined both a task-side process class as well as a controller-side
 | 
| +process wrapper for easier access and usage of the task-side process.
 | 
| +"""
 | 
|  
 | 
| -import os
 | 
| -import sys
 | 
|  import logging
 | 
| +import subprocess
 | 
| +import sys
 | 
|  import threading
 | 
|  
 | 
|  #pylint: disable=relative-import
 | 
| @@ -18,46 +22,75 @@ sys.path.append(common_lib.SWARMING_DIR)
 | 
|  from utils import subprocess42
 | 
|  
 | 
|  
 | 
| -class RPCMethods(object):
 | 
| -  """Class exposing RPC methods."""
 | 
| +class ControllerProcessWrapper(object):
 | 
| +  """Controller-side process wrapper class.
 | 
| +
 | 
| +  This class provides a more intuitive interface to task-side processes
 | 
| +  than calling the methods directly using the RPC object.
 | 
| +  """
 | 
| +
 | 
| +  def __init__(self, rpc, cmd, verbose=False, detached=False, cwd=None):
 | 
| +    self._rpc = rpc
 | 
| +    self._id = rpc.subprocess.Process(cmd)
 | 
| +    if verbose:
 | 
| +      self._rpc.subprocess.SetVerbose(self._id)
 | 
| +    if detached:
 | 
| +      self._rpc.subprocess.SetDetached(self._id)
 | 
| +    if cwd:
 | 
| +      self._rpc.subprocess.SetCwd(self._rpc, cwd)
 | 
| +    self._rpc.subprocess.Start(self._id)
 | 
| +
 | 
| +  def Terminate(self):
 | 
| +    logging.debug('Terminating process %s', self._id)
 | 
| +    return self._rpc.subprocess.Terminate(self._id)
 | 
| +
 | 
| +  def Kill(self):
 | 
| +    logging.debug('Killing process %s', self._id)
 | 
| +    self._rpc.subprocess.Kill(self._id)
 | 
| +
 | 
| +  def Delete(self):
 | 
| +    return self._rpc.subprocess.Delete(self._id)
 | 
|  
 | 
| -  _dotted_whitelist = ['subprocess']
 | 
| +  def GetReturncode(self):
 | 
| +    return self._rpc.subprocess.GetReturncode(self._id)
 | 
|  
 | 
| -  def __init__(self, server):
 | 
| -    self._server = server
 | 
| -    self.subprocess = Subprocess
 | 
| +  def ReadStdout(self):
 | 
| +    """Returns all stdout since the last call to ReadStdout.
 | 
|  
 | 
| -  def _dispatch(self, method, params):
 | 
| -    obj = self
 | 
| -    if '.' in method:
 | 
| -      # Allow only white listed dotted names
 | 
| -      name, method = method.split('.')
 | 
| -      assert name in self._dotted_whitelist
 | 
| -      obj = getattr(self, name)
 | 
| -    return getattr(obj, method)(*params)
 | 
| +    This call allows the user to read stdout while the process is running.
 | 
| +    However each call will flush the local stdout buffer. In order to make
 | 
| +    multiple calls to ReadStdout and to retain the entire output the results
 | 
| +    of this call will need to be buffered in the calling code.
 | 
| +    """
 | 
| +    return self._rpc.subprocess.ReadStdout(self._id)
 | 
|  
 | 
| -  def Echo(self, message):
 | 
| -    """Simple RPC method to print and return a message."""
 | 
| -    logging.info('Echoing %s', message)
 | 
| -    return 'echo %s' % str(message)
 | 
| +  def ReadStderr(self):
 | 
| +    """Returns all stderr read since the last call to ReadStderr.
 | 
|  
 | 
| -  def AbsPath(self, path):
 | 
| -    """Returns the absolute path."""
 | 
| -    return os.path.abspath(path)
 | 
| +    See ReadStdout for additional details.
 | 
| +    """
 | 
| +    return self._rpc.subprocess.ReadStderr(self._id)
 | 
|  
 | 
| -  def Quit(self):
 | 
| -    """Call _server.shutdown in another thread.
 | 
| +  def ReadOutput(self):
 | 
| +    """Returns the (stdout, stderr) since the last Read* call.
 | 
|  
 | 
| -    This is needed because server.shutdown waits for the server to actually
 | 
| -    quit. However the server cannot shutdown until it completes handling this
 | 
| -    call. Calling this in the same thread results in a deadlock.
 | 
| +    See ReadStdout for additional details.
 | 
|      """
 | 
| -    t = threading.Thread(target=self._server.shutdown)
 | 
| -    t.start()
 | 
| +    return self._rpc.subprocess.ReadOutput(self._id)
 | 
| +
 | 
| +  def Wait(self):
 | 
| +    return self._rpc.subprocess.Wait(self._id)
 | 
| +
 | 
| +  def Poll(self):
 | 
| +    return self._rpc.subprocess.Poll(self._id)
 | 
| +
 | 
| +  def GetPid(self):
 | 
| +    return self._rpc.subprocess.GetPid(self._id)
 | 
| +
 | 
|  
 | 
|  
 | 
| -class Subprocess(object):
 | 
| -  """Implements a server-based non-blocking subprocess.
 | 
| +class Process(object):
 | 
| +  """Implements a task-side non-blocking subprocess.
 | 
|  
 | 
|    This non-blocking subprocess allows the caller to continue operating while
 | 
|    also able to interact with this subprocess based on a key returned to
 | 
| @@ -108,7 +141,7 @@ class Subprocess(object):
 | 
|      with cls._creation_lock:
 | 
|        key = 'Process%d' % cls._process_next_id
 | 
|        cls._process_next_id += 1
 | 
| -    logging.debug('Creating process %s', key)
 | 
| +    logging.debug('Creating process %s with cmd %r', key, cmd)
 | 
|      process = cls(cmd)
 | 
|      cls._processes[key] = process
 | 
|      return key
 | 
| @@ -133,13 +166,13 @@ class Subprocess(object):
 | 
|    @classmethod
 | 
|    def SetDetached(cls, key):
 | 
|      """Creates a detached process."""
 | 
| -    logging.debug('Setting %s to run detached', key)
 | 
| +    logging.debug('Setting %s.detached = True', key)
 | 
|      cls._processes[key].detached = True
 | 
|  
 | 
|    @classmethod
 | 
|    def SetVerbose(cls, key):
 | 
|      """Sets the stdout and stderr to be emitted locally."""
 | 
| -    logging.debug('Setting %s to be verbose', key)
 | 
| +    logging.debug('Setting %s.verbose = True', key)
 | 
|      cls._processes[key].verbose = True
 | 
|  
 | 
|    @classmethod
 | 
| 
 |