Package flumotion :: Package common :: Module testsuite
[hide private]

Source Code for Module flumotion.common.testsuite

  1  # -*- Mode: Python -*- 
  2  # vi:si:et:sw=4:sts=4:ts=4 
  3   
  4  # Flumotion - a streaming media server 
  5  # Copyright (C) 2004,2005,2006,2007,2008,2009 Fluendo, S.L. 
  6  # Copyright (C) 2010,2011 Flumotion Services, S.A. 
  7  # All rights reserved. 
  8  # 
  9  # This file may be distributed and/or modified under the terms of 
 10  # the GNU Lesser General Public License version 2.1 as published by 
 11  # the Free Software Foundation. 
 12  # This file is distributed without any warranty; without even the implied 
 13  # warranty of merchantability or fitness for a particular purpose. 
 14  # See "LICENSE.LGPL" in the source distribution for more information. 
 15  # 
 16  # Headers in this file shall remain intact. 
 17   
 18  """testsuite base classes and helpers for diffing strings 
 19  """ 
 20   
 21  from twisted.spread import pb 
 22  from twisted.internet import reactor, defer, selectreactor, pollreactor 
 23  from twisted.scripts import trial 
 24  from twisted.trial import unittest, util 
 25   
 26  from flumotion.common import log 
 27  from flumotion.configure import configure 
 28   
 29  __version__ = "$Rev$" 
 30   
 31   
 32  try: 
 33      _getConfig = trial.getConfig 
 34  except AttributeError: 
 35      # trial.getConfig() is only available when using flumotion-trial 
 36      _getConfig = dict 
 37   
 38   
39 -def attr(*args, **kwargs):
40 """Decorator that adds attributes to objects. 41 42 It can be used to set the 'slow', 'skip', or 'todo' flags in test cases. 43 """ 44 45 def wrap(func): 46 for name in args: 47 # these are just True flags: 48 setattr(func, name, True) 49 for name, value in kwargs.items(): 50 setattr(func, name, value) 51 return func
52 return wrap 53 54
55 -class TestCase(unittest.TestCase, log.Loggable):
56 57 # A sequence of reactors classes that this test supports, can be 58 # overridden in subclasses. You can also set this to an empty 59 # sequence, which means "any reactor" 60 supportedReactors = [selectreactor.SelectReactor, pollreactor.PollReactor] 61 62 # TestCase in Twisted 2.0 doesn't define failUnlessFailure method. 63 if not hasattr(unittest.TestCase, 'failUnlessFailure'): 64
65 - def failUnlessFailure(self, deferred, *expectedFailures):
66 67 def _cb(result): 68 self.fail("did not catch an error, instead got %r" % 69 (result, ))
70 71 def _eb(failure): 72 failure.trap(*expectedFailures) 73 return failure.value
74 return deferred.addCallbacks(_cb, _eb) 75 assertFailure = failUnlessFailure 76 77 78 # notice the two spaces and read the following comment 79
80 - def __init__(self, methodName=' impossible-name '):
81 # Skip the test if the class specifies supportedReactors and 82 # the current reactor is not among them. Use 83 # reactor.__class__ rather than type(reactor), because in old 84 # Twisted the reactor was not a new-style class and 85 # type(reactor) returns 'instance' 86 reactor.killed = False 87 if (self.supportedReactors and 88 reactor.__class__ not in self.supportedReactors): 89 # Set the 'skip' attribute on the class rather than on the 90 # instance, because otherwise Twisted 2.0.1 refuses to 91 # ignore the testcase 92 self.__class__.skip = "this test case does not support " \ 93 "running with %s as the reactor" % reactor 94 95 # Twisted changed the TestCase.__init__ signature several 96 # times. 97 # 98 # In versions older than 2.1.0 there was no __init__ method. 99 # 100 # In versions 2.1.0 up to 2.4.0 there is a __init__ method 101 # with a methodName kwarg that has a default value of None. 102 # 103 # In version 2.5.0 the default value of the kwarg was changed 104 # to "runTest". 105 # 106 # In versions above 2.5.0 God only knows what's the default 107 # value, as we do not currently support them. 108 import inspect 109 if not inspect.ismethod(unittest.TestCase.__init__): 110 # it's Twisted < 2.1.0 111 unittest.TestCase.__init__(self) 112 else: 113 # it's Twisted >= 2.1.0 114 if methodName == ' impossible-name ': 115 # we've been called with no parameters, use the 116 # default parameter value from the superclass 117 defaults = inspect.getargspec(unittest.TestCase.__init__)[3] 118 methodName = defaults[0] 119 unittest.TestCase.__init__(self, methodName=methodName) 120 121 # Skip slow tests if '--skip-slow' option is enabled 122 if _getConfig().get('skip-slow'): 123 if self.getSlow() and not self.getSkip(): 124 self.skip = 'slow test'
125
126 - def getSlow(self):
127 """ 128 Return whether this test has been marked as slow. Checks on the 129 instance first, then the class, then the module, then packages. As 130 soon as it finds something with a C{slow} attribute, returns that. 131 Returns C{False} if it cannot find anything. 132 """ 133 return util.acquireAttribute(self._parents, 'slow', False)
134 135 # Loggable and TestCase both have a debug method; prefer ours 136
137 - def debug(self, *args, **kwargs):
138 log.Loggable.debug(self, *args, **kwargs)
139 140 141 # test objects to be used in unittests to simulate the processes 142 # subclass them to add your own methods 143 144
145 -class TestClient(pb.Referenceable):
146 147 type = "client" # override in subclass 148 remoteRoot = None # RemoteReference to the server-side root 149
150 - def run(self, port):
151 """ 152 Start the client by connecting to the server on the given port. 153 154 @type port: int 155 156 @rtype: L{twisted.internet.defer.Deferred} 157 """ 158 self._f = pb.PBClientFactory() 159 self._p = reactor.connectTCP("127.0.0.1", port, self._f) 160 d = self._f.getRootObject() 161 d.addCallback(self._gotRootObject) 162 return d
163
164 - def stop(self):
165 """ 166 Stop the client. 167 168 @rtype: L{twisted.internet.defer.Deferred} 169 """ 170 self._p.disconnect() 171 return self._dDisconnect
172
173 - def _gotRootObject(self, remoteReference):
174 self.remoteRoot = remoteReference 175 176 # make sure we will get a deferred fired on disconnect 177 # so that the broker gets cleaned up from the reactor as well 178 self._dDisconnect = defer.Deferred() 179 self.remoteRoot.notifyOnDisconnect( 180 lambda r: self._dDisconnect.callback(None)) 181 return self.remoteRoot.callRemote('identify', self.type, self)
182
183 - def remote_receive(self, object):
184 # called by the server to send us an object 185 self.object = object
186 187
188 -class TestAdmin(TestClient):
189 type = 'admin'
190 191
192 -class TestWorker(TestClient):
193 type = 'worker'
194 195
196 -class TestManagerRoot(pb.Root, log.Loggable):
197 logCategory = "testmanagerroot" 198
199 - def remote_identify(self, who, reference):
200 """ 201 Called by a TestClient to announce the type of client, and give 202 a reference. 203 """ 204 self.debug('remote_identify: who %r, ref %r' % (who, reference)) 205 key = who + 'Reference' 206 setattr(self, key, reference)
207
208 - def remote_receive(self, object):
209 # called by the client to send us an object 210 self.object = object
211 212
213 -class TestManager:
214
215 - def run(self, rootClass):
216 """ 217 Run the test manager. Return port it is listening on. 218 219 @type rootClass: subclass of L{TestManagerRoot} 220 221 @rtype: int 222 """ 223 self.root = rootClass() 224 factory = pb.PBServerFactory(self.root) 225 factory.unsafeTracebacks = 1 226 self._p = reactor.listenTCP(0, factory, interface="127.0.0.1") 227 port = self._p.getHost().port 228 return port
229
230 - def stop(self):
231 """ 232 Stop the server. 233 """ 234 return self._p.stopListening()
235 236
237 -class TestPB(log.Loggable):
238 """ 239 I combine a manager and a client to test passing back and forth objects. 240 """ 241 logCategory = "testpb" 242
243 - def __init__(self):
244 self.manager = TestManager() 245 self.client = TestClient()
246
247 - def start(self):
248 port = self.manager.run(TestManagerRoot) 249 return self.client.run(port)
250
251 - def stop(self):
252 d = self.manager.stop() 253 d.addCallback(lambda r: self.client.stop()) 254 return d
255
256 - def send(self, object):
257 """ 258 Send the object from client to server. 259 Return the server's idea of the object. 260 """ 261 self.debug('sending object %r from broker %r' % ( 262 object, self.client.remoteRoot.broker)) 263 d = self.client.remoteRoot.callRemote('receive', object) 264 d.addCallback(lambda r: self.manager.root.object) 265 return d
266
267 - def receive(self, object):
268 """ 269 Receive the object from server to client. 270 Return the client's idea of the object. 271 """ 272 self.debug('receiving object %r' % object) 273 d = self.manager.root.clientReference.callRemote('receive', object) 274 d.addCallback(lambda r: self.client.object) 275 return d
276 277
278 -class TestCaseWithManager(TestCase):
279
280 - def setUp(self):
281 from flumotion.twisted import pb 282 from flumotion.common import server, connection 283 from flumotion.manager import manager, config 284 from StringIO import StringIO 285 286 managerConf = """ 287 <planet> 288 <manager name="planet"> 289 <host>localhost</host> 290 <port>0</port> 291 <transport>tcp</transport> 292 <component name="manager-bouncer" type="htpasswdcrypt-bouncer"> 293 <property name="data"><![CDATA[ 294 user:PSfNpHTkpTx1M 295 ]]></property> 296 </component> 297 </manager> 298 </planet> 299 """ 300 301 conf = config.ManagerConfigParser(StringIO(managerConf)).manager 302 self.vishnu = manager.Vishnu(conf.name, 303 unsafeTracebacks=True) 304 self.vishnu.loadManagerConfigurationXML(StringIO(managerConf)) 305 s = server.Server(self.vishnu) 306 if conf.transport == "ssl": 307 p = s.startSSL(conf.host, conf.port, conf.certificate, 308 configure.configdir) 309 elif conf.transport == "tcp": 310 p = s.startTCP(conf.host, conf.port) 311 self.tport = p 312 self.port = p.getHost().port 313 i = connection.PBConnectionInfo('localhost', self.port, 314 conf.transport == 'ssl', 315 pb.Authenticator(username='user', 316 password='test')) 317 self.connectionInfo = i
318
319 - def _flushErrors(self, *types):
320 # This bit about log flushing seems to be necessary with twisted < 2.5. 321 try: 322 self.flushLoggedErrors(*types) 323 except AttributeError: 324 from twisted.python import log as tlog 325 tlog.flushErrors(*types)
326
327 - def tearDown(self):
328 from flumotion.common import errors 329 self._flushErrors(errors.NotAuthenticatedError) 330 331 d = self.vishnu.shutdown() 332 d.addCallback(lambda _: self.tport.stopListening()) 333 return d
334 335
336 -def _diff(old, new, desc):
337 import difflib 338 lines = difflib.unified_diff(old, new) 339 lines = list(lines) 340 if not lines: 341 return 342 output = '' 343 for line in lines: 344 output += '%s: %s\n' % (desc, line[:-1]) 345 346 raise AssertionError( 347 ("\nError while comparing strings:\n" 348 "%s") % (output, ))
349 350
351 -def diffStrings(orig, new, desc='input'):
352 353 def _tolines(s): 354 return [line + '\n' for line in s.split('\n')]
355 356 return _diff(_tolines(orig), 357 _tolines(new), 358 desc=desc) 359