[Twisted-Python] Global reactor unit tests in the Twisted test suite
glyph at twistedmatrix.com
Tue Nov 1 14:48:02 EDT 2011
On Nov 1, 2011, at 12:14 PM, Phil Mayers wrote:
> On 01/11/11 15:28, Christopher Armstrong wrote:
>> I whole-heartedly agree with the sentiment, though. We need to get rid
>> of the global reactor.
Sometimes you want a different reactor. The most common reason is unit testing, although if we could successfully eliminate the global reactor everywhere, there are other things that we might be able to do: slowing down or speeding up time from a protocol's perspective (by replacing IReactorTime), grouping related objects together in reactors that can be shut down together (so that reactor.stop() doesn't actually end the process); or, similarly, suspending a group of related objects (removeReader()/removeWriter() on everything) so that they can be inspected for debugging purposes, without suspending the thing doing the inspecting (a manhole python prompt).
> I find the "pass reactor as 1st argument to everything" API pattern
> messy. I'm sure there's a good reason. What is it?
This pattern is a solution to the problem, but I agree that it is possibly not the optimal solution. It sort of points in a direction where every possible module that might be imported becomes an argument to your function. After all, there are plenty of other modules which have to be mocked for testing, why not just make everyone's __init__ method take sys.modules as an argument too, and never import anything? In more complex systems this can definitely turn into a bit of a mess.
Nevertheless, "real reactor as default argument" is not a huge improvement either, because it typically breaks one level out. If you have 'a(reactor=defaultReactor)' and then 'b()' needs to call 'a', half the time 'b' will forget to supply a reactor argument and call 'a()' passing nothing, because that appears to be the suggested API. Any code that calls 'b()' then needs to deal with the fact that the global reactor is going to get used, even if it has a cleanly encapsulated parameter of its own. With sufficient discipline, of course, this approach is equivalent.
So despite its imperfections, I don't have a solution better than "pass the reactor to everything" yet. It seems to be better than the alternatives in almost every case. The one place it's not better is when writing brief example scripts, but I suspect this is only the case because examples have to expose the semantic conflict between the way you actually get the reactor (import the singleton) and the way that you want to deal with the reactor (as a parameter). If we had a sensible top-level way of getting the reactor I think examples which didn't use the global would read much more cleanly.
If you can think of a better solution that addresses all of these concerns simultaneously somehow, please share, I'd love to hear it :-).
More information about the Twisted-Python