[CalendarServer-changes] [5055] CalendarServer/branches/users/sagen/locations-resources-2/ calendarserver/tools/test/test_gateway.py

source_changes at macosforge.org source_changes at macosforge.org
Mon Feb 8 12:05:57 PST 2010


Revision: 5055
          http://trac.macosforge.org/projects/calendarserver/changeset/5055
Author:   glyph at apple.com
Date:     2010-02-08 12:05:57 -0800 (Mon, 08 Feb 2010)
Log Message:
-----------
twisted-friendly, timeout-friendly subprocess calls

Modified Paths:
--------------
    CalendarServer/branches/users/sagen/locations-resources-2/calendarserver/tools/test/test_gateway.py

Modified: CalendarServer/branches/users/sagen/locations-resources-2/calendarserver/tools/test/test_gateway.py
===================================================================
--- CalendarServer/branches/users/sagen/locations-resources-2/calendarserver/tools/test/test_gateway.py	2010-02-08 20:05:10 UTC (rev 5054)
+++ CalendarServer/branches/users/sagen/locations-resources-2/calendarserver/tools/test/test_gateway.py	2010-02-08 20:05:57 UTC (rev 5055)
@@ -17,15 +17,87 @@
 import os
 import plistlib
 import xml
+
+from twisted.internet import reactor
+from twisted.internet.defer import inlineCallbacks, Deferred, returnValue
+from twisted.internet.error import ProcessDone
+from twisted.internet.protocol import ProcessProtocol
+
 from twistedcaldav.config import config
 from twistedcaldav.test.util import TestCase
 from calendarserver.tools.util import getDirectory
 from twisted.python.filepath import FilePath
 from twistedcaldav.directory.directory import DirectoryError
-from subprocess import Popen, PIPE, STDOUT
 
+class ErrorOutput(Exception):
+    """
+    The process produced some error output and exited with a non-zero exit
+    code.
+    """
 
 
+class CapturingProcessProtocol(ProcessProtocol):
+    """
+    A L{ProcessProtocol} that captures its output and error.
+
+    @ivar output: a C{list} of all C{str}s received to stderr.
+
+    @ivar error: a C{list} of all C{str}s received to stderr.
+    """
+
+    def __init__(self, deferred, inputData):
+        """
+        Initialize a L{CapturingProcessProtocol}.
+
+        @param deferred: the L{Deferred} to fire when the process is complete.
+
+        @param inputData: a C{str} to feed to the subprocess's stdin.
+        """
+        self.deferred = deferred
+        self.input = inputData
+        self.output = []
+        self.error = []
+
+
+    def connectionMade(self):
+        """
+        The process started; feed its input on stdin.
+        """
+        self.transport.write(self.input)
+        self.transport.closeStdin()
+
+
+    def outReceived(self, data):
+        """
+        Some output was received on stdout.
+        """
+        self.output.append(data)
+
+
+    def errReceived(self, data):
+        """
+        Some output was received on stderr.
+        """
+        self.error.append(data)
+        # Attempt to exit promptly if a traceback is displayed, so we don't
+        # deal with timeouts.
+        lines = ''.join(self.error).split("\n")
+        if len(lines) > 1:
+            errorReportLine = lines[-2].split(": ", 1)
+            if len(errorReportLine) == 2 and ' ' not in errorReportLine[0] and '\t' not in errorReportLine[0]:
+                self.transport.signalProcess("TERM")
+
+
+    def processEnded(self, why):
+        """
+        The process is over, fire the Deferred with the output.
+        """
+        if why.check(ProcessDone) and not self.error:
+            self.deferred.callback(''.join(self.output))
+        else:
+            self.deferred.errback(ErrorOutput(''.join(self.error)))
+
+
 class GatewayTestCase(TestCase):
 
     def setUp(self):
@@ -74,67 +146,85 @@
 
         super(GatewayTestCase, self).setUp()
 
+        # Make sure trial puts the reactor in the right state, by letting it
+        # run one reactor iteration.  (Ignore me, please.)
+        d = Deferred()
+        reactor.callLater(0, d.callback, True)
+        return d
+
+    @inlineCallbacks
     def runCommand(self, command):
+        """
+        Run the given command by feeding it as standard input to
+        calendarserver_command_gateway in a subprocess.
+        """
         sourceRoot = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
         python = os.path.join(sourceRoot, "python")
         gateway = os.path.join(sourceRoot, "bin", "calendarserver_command_gateway")
-        child = Popen(
-            args=[python, gateway, "-f", self.configFileName],
-            cwd=sourceRoot,
-            stdin=PIPE, stdout=PIPE, stderr=STDOUT,
-        )
-        output, error = child.communicate(input=command)
+
+        args = [python, gateway, "-f", self.configFileName]
+        cwd = sourceRoot
+
+        deferred = Deferred()
+        reactor.spawnProcess(CapturingProcessProtocol(deferred, command), python, args, env=os.environ, path=cwd)
+        output = yield deferred
         try:
             plist = plistlib.readPlistFromString(output)
         except xml.parsers.expat.ExpatError, e:
             print "Error (%s) parsing (%s)" % (e, output)
             raise
 
-        return plist
+        returnValue(plist)
 
+    @inlineCallbacks
     def test_getLocationList(self):
-        results = self.runCommand(command_getLocationList)
+        results = yield self.runCommand(command_getLocationList)
         self.assertEquals(len(results['result']), 10)
 
+    @inlineCallbacks
     def test_getResourceList(self):
-        results = self.runCommand(command_getResourceList)
+        results = yield self.runCommand(command_getResourceList)
         self.assertEquals(len(results['result']), 10)
 
+    @inlineCallbacks
     def test_createLocation(self):
         directory = getDirectory()
 
         record = directory.recordWithUID("createdlocation01")
         self.assertEquals(record, None)
 
-        results = self.runCommand(command_createLocation)
+        yield self.runCommand(command_createLocation)
 
         directory.flushCaches()
         record = directory.recordWithUID("createdlocation01")
         self.assertNotEquals(record, None)
 
+    @inlineCallbacks
     def test_destroyRecord(self):
         directory = getDirectory()
 
         record = directory.recordWithUID("location01")
         self.assertNotEquals(record, None)
 
-        results = self.runCommand(command_deleteLocation)
+        yield self.runCommand(command_deleteLocation)
 
         directory.flushCaches()
         record = directory.recordWithUID("location01")
         self.assertEquals(record, None)
 
+    @inlineCallbacks
     def test_addWriteProxy(self):
         directory = getDirectory()
 
-        results = self.runCommand(command_addWriteProxy)
+        results = yield self.runCommand(command_addWriteProxy)
         self.assertEquals(len(results['result']['Proxies']), 1)
 
+    @inlineCallbacks
     def test_removeWriteProxy(self):
         directory = getDirectory()
 
-        results = self.runCommand(command_addWriteProxy)
-        results = self.runCommand(command_removeWriteProxy)
+        results = yield self.runCommand(command_addWriteProxy)
+        results = yield self.runCommand(command_removeWriteProxy)
         self.assertEquals(len(results['result']['Proxies']), 0)
 
 
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100208/da8a4644/attachment-0001.html>


More information about the calendarserver-changes mailing list