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 
 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] 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 # notice the two spaces and read the following comment 78
79 - def __init__(self, methodName=' impossible-name '):
80 # Skip the test if the class specifies supportedReactors and 81 # the current reactor is not among them. Use 82 # reactor.__class__ rather than type(reactor), because in old 83 # Twisted the reactor was not a new-style class and 84 # type(reactor) returns 'instance' 85 if (self.supportedReactors and 86 reactor.__class__ not in self.supportedReactors): 87 # Set the 'skip' attribute on the class rather than on the 88 # instance, because otherwise Twisted 2.0.1 refuses to 89 # ignore the testcase 90 self.__class__.skip = "this test case does not support " \ 91 "running with %s as the reactor" % reactor 92 93 # Twisted changed the TestCase.__init__ signature several 94 # times. 95 # 96 # In versions older than 2.1.0 there was no __init__ method. 97 # 98 # In versions 2.1.0 up to 2.4.0 there is a __init__ method 99 # with a methodName kwarg that has a default value of None. 100 # 101 # In version 2.5.0 the default value of the kwarg was changed 102 # to "runTest". 103 # 104 # In versions above 2.5.0 God only knows what's the default 105 # value, as we do not currently support them. 106 import inspect 107 if not inspect.ismethod(unittest.TestCase.__init__): 108 # it's Twisted < 2.1.0 109 unittest.TestCase.__init__(self) 110 else: 111 # it's Twisted >= 2.1.0 112 if methodName == ' impossible-name ': 113 # we've been called with no parameters, use the 114 # default parameter value from the superclass 115 defaults = inspect.getargspec(unittest.TestCase.__init__)[3] 116 methodName = defaults[0] 117 unittest.TestCase.__init__(self, methodName=methodName) 118 119 # Skip slow tests if '--skip-slow' option is enabled 120 if _getConfig().get('skip-slow'): 121 if self.getSlow() and not self.getSkip(): 122 self.skip = 'slow test'
123
124 - def getSlow(self):
125 """ 126 Return whether this test has been marked as slow. Checks on the 127 instance first, then the class, then the module, then packages. As 128 soon as it finds something with a C{slow} attribute, returns that. 129 Returns C{False} if it cannot find anything. 130 """ 131 return util.acquireAttribute(self._parents, 'slow', False)
132 133 # Loggable and TestCase both have a debug method; prefer ours 134
135 - def debug(self, *args, **kwargs):
136 log.Loggable.debug(self, *args, **kwargs)
137 138 139 # test objects to be used in unittests to simulate the processes 140 # subclass them to add your own methods 141 142
143 -class TestClient(pb.Referenceable):
144 145 type = "client" # override in subclass 146 remoteRoot = None # RemoteReference to the server-side root 147
148 - def run(self, port):
149 """ 150 Start the client by connecting to the server on the given port. 151 152 @type port: int 153 154 @rtype: L{twisted.internet.defer.Deferred} 155 """ 156 self._f = pb.PBClientFactory() 157 self._p = reactor.connectTCP("127.0.0.1", port, self._f) 158 d = self._f.getRootObject() 159 d.addCallback(self._gotRootObject) 160 return d
161
162 - def stop(self):
163 """ 164 Stop the client. 165 166 @rtype: L{twisted.internet.defer.Deferred} 167 """ 168 self._p.disconnect() 169 return self._dDisconnect
170
171 - def _gotRootObject(self, remoteReference):
172 self.remoteRoot = remoteReference 173 174 # make sure we will get a deferred fired on disconnect 175 # so that the broker gets cleaned up from the reactor as well 176 self._dDisconnect = defer.Deferred() 177 self.remoteRoot.notifyOnDisconnect( 178 lambda r: self._dDisconnect.callback(None)) 179 return self.remoteRoot.callRemote('identify', self.type, self)
180
181 - def remote_receive(self, object):
182 # called by the server to send us an object 183 self.object = object
184 185
186 -class TestAdmin(TestClient):
187 type = 'admin'
188 189
190 -class TestWorker(TestClient):
191 type = 'worker'
192 193
194 -class TestManagerRoot(pb.Root, log.Loggable):
195 logCategory = "testmanagerroot" 196
197 - def remote_identify(self, who, reference):
198 """ 199 Called by a TestClient to announce the type of client, and give 200 a reference. 201 """ 202 self.debug('remote_identify: who %r, ref %r' % (who, reference)) 203 key = who + 'Reference' 204 setattr(self, key, reference)
205
206 - def remote_receive(self, object):
207 # called by the client to send us an object 208 self.object = object
209 210
211 -class TestManager:
212
213 - def run(self, rootClass):
214 """ 215 Run the test manager. Return port it is listening on. 216 217 @type rootClass: subclass of L{TestManagerRoot} 218 219 @rtype: int 220 """ 221 self.root = rootClass() 222 factory = pb.PBServerFactory(self.root) 223 factory.unsafeTracebacks = 1 224 self._p = reactor.listenTCP(0, factory, interface="127.0.0.1") 225 port = self._p.getHost().port 226 return port
227
228 - def stop(self):
229 """ 230 Stop the server. 231 """ 232 return self._p.stopListening()
233 234
235 -class TestPB(log.Loggable):
236 """ 237 I combine a manager and a client to test passing back and forth objects. 238 """ 239 logCategory = "testpb" 240
241 - def __init__(self):
242 self.manager = TestManager() 243 self.client = TestClient()
244
245 - def start(self):
246 port = self.manager.run(TestManagerRoot) 247 return self.client.run(port)
248
249 - def stop(self):
250 d = self.manager.stop() 251 d.addCallback(lambda r: self.client.stop()) 252 return d
253
254 - def send(self, object):
255 """ 256 Send the object from client to server. 257 Return the server's idea of the object. 258 """ 259 self.debug('sending object %r from broker %r' % ( 260 object, self.client.remoteRoot.broker)) 261 d = self.client.remoteRoot.callRemote('receive', object) 262 d.addCallback(lambda r: self.manager.root.object) 263 return d
264
265 - def receive(self, object):
266 """ 267 Receive the object from server to client. 268 Return the client's idea of the object. 269 """ 270 self.debug('receiving object %r' % object) 271 d = self.manager.root.clientReference.callRemote('receive', object) 272 d.addCallback(lambda r: self.client.object) 273 return d
274 275
276 -class TestCaseWithManager(TestCase):
277
278 - def setUp(self):
279 from flumotion.twisted import pb 280 from flumotion.common import server, connection 281 from flumotion.manager import manager, config 282 from StringIO import StringIO 283 284 managerConf = """ 285 <planet> 286 <manager name="planet"> 287 <host>localhost</host> 288 <port>0</port> 289 <transport>tcp</transport> 290 <component name="manager-bouncer" type="htpasswdcrypt-bouncer"> 291 <property name="data"><![CDATA[ 292 user:PSfNpHTkpTx1M 293 ]]></property> 294 </component> 295 </manager> 296 </planet> 297 """ 298 299 conf = config.ManagerConfigParser(StringIO(managerConf)).manager 300 self.vishnu = manager.Vishnu(conf.name, 301 unsafeTracebacks=True) 302 self.vishnu.loadManagerConfigurationXML(StringIO(managerConf)) 303 s = server.Server(self.vishnu) 304 if conf.transport == "ssl": 305 p = s.startSSL(conf.host, conf.port, conf.certificate, 306 configure.configdir) 307 elif conf.transport == "tcp": 308 p = s.startTCP(conf.host, conf.port) 309 self.tport = p 310 self.port = p.getHost().port 311 i = connection.PBConnectionInfo('localhost', self.port, 312 conf.transport == 'ssl', 313 pb.Authenticator(username='user', 314 password='test')) 315 self.connectionInfo = i
316
317 - def _flushErrors(self, *types):
318 # This bit about log flushing seems to be necessary with twisted < 2.5. 319 try: 320 self.flushLoggedErrors(*types) 321 except AttributeError: 322 from twisted.python import log as tlog 323 tlog.flushErrors(*types)
324
325 - def tearDown(self):
326 from flumotion.common import errors 327 self._flushErrors(errors.NotAuthenticatedError) 328 329 d = self.vishnu.shutdown() 330 d.addCallback(lambda _: self.tport.stopListening()) 331 return d
332 333
334 -def _diff(old, new, desc):
335 import difflib 336 lines = difflib.unified_diff(old, new) 337 lines = list(lines) 338 if not lines: 339 return 340 output = '' 341 for line in lines: 342 output += '%s: %s\n' % (desc, line[:-1]) 343 344 raise AssertionError( 345 ("\nError while comparing strings:\n" 346 "%s") % (output, ))
347 348
349 -def diffStrings(orig, new, desc='input'):
350 351 def _tolines(s): 352 return [line + '\n' for line in s.split('\n')]
353 354 return _diff(_tolines(orig), 355 _tolines(new), 356 desc=desc) 357