Added exit code checking to process.py (twisted process utils). A bit of class refactoring to make it work & cleaner.
Also added some more instructive messages to install_venv.py, because otherwise people that don't know what they're doing will install the wrong pip... i.e. I did :-)
This commit is contained in:
		@@ -54,19 +54,20 @@ class UnexpectedErrorOutput(IOError):
 | 
				
			|||||||
        IOError.__init__(self, "got stdout: %r\nstderr: %r" % (stdout, stderr))
 | 
					        IOError.__init__(self, "got stdout: %r\nstderr: %r" % (stdout, stderr))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# NOTE(termie): this too
 | 
					# This is based on _BackRelay from twister.internal.utils, but modified to capture 
 | 
				
			||||||
class _BackRelay(protocol.ProcessProtocol):
 | 
					#  both stdout and stderr without odd stderr handling,  and also to handle stdin
 | 
				
			||||||
 | 
					class BackRelayWithInput(protocol.ProcessProtocol):
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    Trivial protocol for communicating with a process and turning its output
 | 
					    Trivial protocol for communicating with a process and turning its output
 | 
				
			||||||
    into the result of a L{Deferred}.
 | 
					    into the result of a L{Deferred}.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @ivar deferred: A L{Deferred} which will be called back with all of stdout
 | 
					    @ivar deferred: A L{Deferred} which will be called back with all of stdout
 | 
				
			||||||
        and, if C{errortoo} is true, all of stderr as well (mixed together in
 | 
					        and all of stderr as well (as a tuple).  C{terminate_on_stderr} is true
 | 
				
			||||||
        one string).  If C{errortoo} is false and any bytes are received over
 | 
					        and any bytes are received over stderr, this will fire with an
 | 
				
			||||||
        stderr, this will fire with an L{_UnexpectedErrorOutput} instance and
 | 
					        L{_UnexpectedErrorOutput} instance and the attribute will be set to 
 | 
				
			||||||
        the attribute will be set to C{None}.
 | 
					        C{None}.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @ivar onProcessEnded: If C{errortoo} is false and bytes are received over
 | 
					    @ivar onProcessEnded: If C{terminate_on_stderr} is false and bytes are received over
 | 
				
			||||||
        stderr, this attribute will refer to a L{Deferred} which will be called
 | 
					        stderr, this attribute will refer to a L{Deferred} which will be called
 | 
				
			||||||
        back when the process ends.  This C{Deferred} is also associated with
 | 
					        back when the process ends.  This C{Deferred} is also associated with
 | 
				
			||||||
        the L{_UnexpectedErrorOutput} which C{deferred} fires with earlier in
 | 
					        the L{_UnexpectedErrorOutput} which C{deferred} fires with earlier in
 | 
				
			||||||
@@ -74,52 +75,43 @@ class _BackRelay(protocol.ProcessProtocol):
 | 
				
			|||||||
        ended, in addition to knowing when bytes have been received via stderr.
 | 
					        ended, in addition to knowing when bytes have been received via stderr.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def __init__(self, deferred, errortoo=0):
 | 
					    def __init__(self, deferred, startedDeferred=None, terminate_on_stderr=False,
 | 
				
			||||||
 | 
					                    check_exit_code=True, input=None):
 | 
				
			||||||
        self.deferred = deferred
 | 
					        self.deferred = deferred
 | 
				
			||||||
        self.s = StringIO.StringIO()
 | 
					        self.stdout = StringIO.StringIO()
 | 
				
			||||||
        if errortoo:
 | 
					        self.stderr = StringIO.StringIO()
 | 
				
			||||||
            self.errReceived = self.errReceivedIsGood
 | 
					        self.startedDeferred = startedDeferred
 | 
				
			||||||
        else:
 | 
					        self.terminate_on_stderr = terminate_on_stderr
 | 
				
			||||||
            self.errReceived = self.errReceivedIsBad
 | 
					        self.check_exit_code = check_exit_code
 | 
				
			||||||
 | 
					        self.input = input
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    def errReceivedIsBad(self, text):
 | 
					    def errReceived(self, text):
 | 
				
			||||||
        if self.deferred is not None:
 | 
					        self.sterr.write(text)
 | 
				
			||||||
 | 
					        if self.terminate_on_stderr and (self.deferred is not None):
 | 
				
			||||||
            self.onProcessEnded = defer.Deferred()
 | 
					            self.onProcessEnded = defer.Deferred()
 | 
				
			||||||
            err = UnexpectedErrorOutput(text, self.onProcessEnded)
 | 
					            self.deferred.errback(UnexpectedErrorOutput(stdout=self.stdout.getvalue(), stderr=self.stderr.getvalue()))
 | 
				
			||||||
            self.deferred.errback(failure.Failure(err))
 | 
					 | 
				
			||||||
            self.deferred = None
 | 
					            self.deferred = None
 | 
				
			||||||
            self.transport.loseConnection()
 | 
					            self.transport.loseConnection()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def errReceivedIsGood(self, text):
 | 
					    def errReceived(self, text):
 | 
				
			||||||
        self.s.write(text)
 | 
					        self.stderr.write(text)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def outReceived(self, text):
 | 
					    def outReceived(self, text):
 | 
				
			||||||
        self.s.write(text)
 | 
					        self.stdout.write(text)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def processEnded(self, reason):
 | 
					    def processEnded(self, reason):
 | 
				
			||||||
        if self.deferred is not None:
 | 
					        if self.deferred is not None:
 | 
				
			||||||
            self.deferred.callback(self.s.getvalue())
 | 
					            stdout, stderr = self.stdout.getvalue(), self.stderr.getvalue()
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                if self.check_exit_code:
 | 
				
			||||||
 | 
					                    reason.trap(error.ProcessDone)
 | 
				
			||||||
 | 
					                self.deferred.callback((stdout, stderr))
 | 
				
			||||||
 | 
					            except:
 | 
				
			||||||
 | 
					                self.deferred.errback(UnexpectedErrorOutput(stdout, stderr))
 | 
				
			||||||
        elif self.onProcessEnded is not None:
 | 
					        elif self.onProcessEnded is not None:
 | 
				
			||||||
            self.onProcessEnded.errback(reason)
 | 
					            self.onProcessEnded.errback(reason)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class BackRelayWithInput(_BackRelay):
 | 
					 | 
				
			||||||
    def __init__(self, deferred, startedDeferred=None, error_ok=0,
 | 
					 | 
				
			||||||
                 input=None):
 | 
					 | 
				
			||||||
        # Twisted doesn't use new-style classes in most places :(
 | 
					 | 
				
			||||||
        _BackRelay.__init__(self, deferred, errortoo=error_ok)
 | 
					 | 
				
			||||||
        self.error_ok = error_ok
 | 
					 | 
				
			||||||
        self.input = input
 | 
					 | 
				
			||||||
        self.stderr = StringIO.StringIO()
 | 
					 | 
				
			||||||
        self.startedDeferred = startedDeferred
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def errReceivedIsBad(self, text):
 | 
					 | 
				
			||||||
        self.stderr.write(text)
 | 
					 | 
				
			||||||
        self.transport.loseConnection()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def errReceivedIsGood(self, text):
 | 
					 | 
				
			||||||
        self.stderr.write(text)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def connectionMade(self):
 | 
					    def connectionMade(self):
 | 
				
			||||||
        if self.startedDeferred:
 | 
					        if self.startedDeferred:
 | 
				
			||||||
            self.startedDeferred.callback(self)
 | 
					            self.startedDeferred.callback(self)
 | 
				
			||||||
@@ -127,31 +119,15 @@ class BackRelayWithInput(_BackRelay):
 | 
				
			|||||||
            self.transport.write(self.input)
 | 
					            self.transport.write(self.input)
 | 
				
			||||||
        self.transport.closeStdin()
 | 
					        self.transport.closeStdin()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def processEnded(self, reason):
 | 
					 | 
				
			||||||
        if self.deferred is not None:
 | 
					 | 
				
			||||||
            stdout, stderr = self.s.getvalue(), self.stderr.getvalue()
 | 
					 | 
				
			||||||
            try:
 | 
					 | 
				
			||||||
                # NOTE(termie): current behavior means if error_ok is True
 | 
					 | 
				
			||||||
                #               we won't throw an error even if the process
 | 
					 | 
				
			||||||
                #               exited with a non-0 status, so you can't be
 | 
					 | 
				
			||||||
                #               okay with stderr output and not with bad exit
 | 
					 | 
				
			||||||
                #               codes.
 | 
					 | 
				
			||||||
                if not self.error_ok:
 | 
					 | 
				
			||||||
                    reason.trap(error.ProcessDone)
 | 
					 | 
				
			||||||
                self.deferred.callback((stdout, stderr))
 | 
					 | 
				
			||||||
            except:
 | 
					 | 
				
			||||||
                self.deferred.errback(UnexpectedErrorOutput(stdout, stderr))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def getProcessOutput(executable, args=None, env=None, path=None, reactor=None,
 | 
					def getProcessOutput(executable, args=None, env=None, path=None, reactor=None,
 | 
				
			||||||
                     error_ok=0, input=None, startedDeferred=None):
 | 
					                     check_exit_code=True, input=None, startedDeferred=None):
 | 
				
			||||||
    if reactor is None:
 | 
					    if reactor is None:
 | 
				
			||||||
        from twisted.internet import reactor
 | 
					        from twisted.internet import reactor
 | 
				
			||||||
    args = args and args or ()
 | 
					    args = args and args or ()
 | 
				
			||||||
    env = env and env and {}
 | 
					    env = env and env and {}
 | 
				
			||||||
    d = defer.Deferred()
 | 
					    d = defer.Deferred()
 | 
				
			||||||
    p = BackRelayWithInput(
 | 
					    p = BackRelayWithInput(
 | 
				
			||||||
            d, startedDeferred=startedDeferred, error_ok=error_ok, input=input)
 | 
					            d, startedDeferred=startedDeferred, check_exit_code=check_exit_code, input=input)
 | 
				
			||||||
    # NOTE(vish): commands come in as unicode, but self.executes needs
 | 
					    # NOTE(vish): commands come in as unicode, but self.executes needs
 | 
				
			||||||
    #             strings or process.spawn raises a deprecation warning
 | 
					    #             strings or process.spawn raises a deprecation warning
 | 
				
			||||||
    executable = str(executable)
 | 
					    executable = str(executable)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -48,7 +48,7 @@ class ProcessTestCase(test.TrialTestCase):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def test_execute_stderr(self):
 | 
					    def test_execute_stderr(self):
 | 
				
			||||||
        pool = process.ProcessPool(2)
 | 
					        pool = process.ProcessPool(2)
 | 
				
			||||||
        d = pool.simple_execute('cat BAD_FILE', error_ok=1)
 | 
					        d = pool.simple_execute('cat BAD_FILE', check_exit_code=False)
 | 
				
			||||||
        def _check(rv):
 | 
					        def _check(rv):
 | 
				
			||||||
            self.assertEqual(rv[0], '')
 | 
					            self.assertEqual(rv[0], '')
 | 
				
			||||||
            self.assert_('No such file' in rv[1])
 | 
					            self.assert_('No such file' in rv[1])
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user