Phillip sounds good. Please see comments inline.
Phillip J. Eby wrote:
At 04:23 PM 6/6/2005 -0700, Brian Kirsch wrote:
Ok sounds good. Especially if you wanna do the work of implementing
the mock reactor :)
Well, I took a look this morning, and implementing simulated time may
actually be easier than I thought, because ReactorBase now has a
'timeout()' method that does the hard part - computing how much time
there is until the next delayed call. In earlier versions of Twisted,
that logic was buried inside something else. There are also nice new
functions like 'installReactor' to play with.
So, I'm thinking I'll create a 'testreactor' module (maybe living in
osaf.framework.twisted?) with 'install()' and 'uninstall()'
functions. When installed, it'll mixin test methods into the standard
reactor, and the reactor will run on simulated time. I'm thinking
that these will probably be very simple, like a 'reset()' to
completely clear the reactor's state between tests.
I've also investigated the run()/stop() issue, and as far as I can
tell this should be a non-issue for tests that run using the Twisted
default reactor (which is what Chandler uses). The reason that you
aren't supposed to use run()/stop() or certain other functions
multiple times (AFAICT), is because certain GUI-platform-specific
reactor implementations won't tolerate it. However, tests that don't
run with a GUI don't need to use a GUI-specific reactor, and we're not
using one in Chandler currently anyway.
Assuming that StringTransport is sufficient for all our
simulated-socket needs, I should only need to add a few methods. Here
are the ones I have in mind:
StringTransport is one way of doing it. You can also use the loopback
mechanism in Twisted to hook up real Twisted client to Twisted server
communication. The test_pop3client.py unittest I modified for Twisted
core does this to test timeouts. I Wrote a testPop3Server that runs
under Twisted to test against the pop3client.py code and it worked like
a charm:
class POP3HelperMixin:
serverCTX = None
clientCTX = None
def setUp(self):
d = defer.Deferred()
self.server =
pop3TestServer.POP3TestServer(contextFactory=self.serverCTX)
self.client = SimpleClient(d, contextFactory=self.clientCTX)
self.client.timeout = 30
self.connected = d
def tearDown(self):
del self.server
del self.client
del self.connected
def _cbStopClient(self, ignore):
self.client.transport.loseConnection()
def _ebGeneral(self, failure):
self.client.transport.loseConnection()
self.server.transport.loseConnection()
failure.printTraceback(open('failure.log', 'w'))
failure.printTraceback()
raise failure.value
def loopback(self):
loopback.loopbackTCP(self.server, self.client, noisy=False)
class POP3TimeoutTestCase(POP3HelperMixin, unittest.TestCase):
def testTimeout(self):
def login():
#this will startTLS automatically
d = self.client.login('test', 'twisted')
d.addErrback(timedOut)
return d
def timedOut(failure):
self._cbStopClient(None)
failure.trap(error.TimeoutError)
def quit():
return self.client.quit()
self.client.timeout = 3
#No need to leverage SSL for timeout test
pop3TestServer.SSL_SUPPORT = False
#Tell the server to not return a response to client.
#This will trigger a timeout.
pop3TestServer.TIMEOUT_RESPONSE = True
methods = [login, quit]
map(self.connected.addCallback, map(strip, methods))
self.connected.addCallback(login)
self.connected.addCallback(quit)
self.connected.addCallback(self._cbStopClient)
self.connected.addErrback(self._ebGeneral)
self.loopback()
reactor.start() -- completely reset the reactor's state and
reinitialize it so that the current test runs with a clean slate.
Also sets the reactor's state to "running", so that the test will
appear to be called from within the reactor run loop, but in fact will
have control over the reactor.
reactor.getTime() -- return the current simulated "now" moment
(initially set to the time when the test mixin is installed)
reactor.setTime(seconds) -- change the simulated "now" moment
reactor.waitFor(deferred, timeout) -- iterate until the deferred
succeeds or fails, or until the timeout occurs. Raise a TimeoutError
if the timeout happens first, or raises the deferred's exception if
the deferred fails. Otherwise, returns the deferred's value. (Note:
the timeout is in *simulated* time, not real time, so you can safely
set large values if you have a test with complex timing.)
So, I'm thinking there would also be a 'testreactor.ReactorTestCase'
whose setUp() would testreactor.install(), and reactor.start(), and
whose tearDown() would reactor.stop() (if it's still running), and
perhaps testreactor.uninstall() as well.
Then, test methods would just set up whatever conditions they want,
and then reactor.waitFor() results. Tests could safely call waitFor()
multiple times, even within the same test.
Heikki, does that sound about like what you need for your tests?
--
Brian Kirsch - Email Framework Engineer
Open Source Applications Foundation
543 Howard St. 5th Floor
San Francisco, CA 94105
(415) 946-3056
http://www.osafoundation.org
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
Open Source Applications Foundation "Dev" mailing list
http://lists.osafoundation.org/mailman/listinfo/dev