[Dev] Mock Reactor for tests?
Brian Kirsch
bkirsch at osafoundation.org
Tue Jun 7 12:05:53 PDT 2005
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
More information about the Dev
mailing list