| OLD | NEW |
| (Empty) |
| 1 # Copyright (c) 2001-2008 Twisted Matrix Laboratories. | |
| 2 # See LICENSE for details. | |
| 3 | |
| 4 """ | |
| 5 Tests for Perspective Broker module. | |
| 6 | |
| 7 TODO: update protocol level tests to use new connection API, leaving | |
| 8 only specific tests for old API. | |
| 9 """ | |
| 10 | |
| 11 # issue1195 TODOs: replace pump.pump() with something involving Deferreds. | |
| 12 # Clean up warning suppression. Find a better replacement for the handful of | |
| 13 # reactor.callLater(0.1, ..) calls. | |
| 14 | |
| 15 import sys, os, time, gc | |
| 16 | |
| 17 from cStringIO import StringIO | |
| 18 from zope.interface import implements, Interface | |
| 19 | |
| 20 from twisted.trial import unittest | |
| 21 | |
| 22 from twisted.spread import pb, util, publish, jelly | |
| 23 from twisted.internet import protocol, main, reactor, defer | |
| 24 from twisted.internet.error import ConnectionRefusedError | |
| 25 from twisted.internet.defer import Deferred, gatherResults | |
| 26 from twisted.python import failure, log | |
| 27 from twisted.cred.error import UnauthorizedLogin, UnhandledCredentials | |
| 28 from twisted.cred import portal, checkers, credentials | |
| 29 | |
| 30 | |
| 31 class Dummy(pb.Viewable): | |
| 32 def view_doNothing(self, user): | |
| 33 if isinstance(user, DummyPerspective): | |
| 34 return 'hello world!' | |
| 35 else: | |
| 36 return 'goodbye, cruel world!' | |
| 37 | |
| 38 | |
| 39 class DummyPerspective(pb.Avatar): | |
| 40 def perspective_getDummyViewPoint(self): | |
| 41 return Dummy() | |
| 42 | |
| 43 | |
| 44 class DummyRealm(object): | |
| 45 implements(portal.IRealm) | |
| 46 | |
| 47 def requestAvatar(self, avatarId, mind, *interfaces): | |
| 48 for iface in interfaces: | |
| 49 if iface is pb.IPerspective: | |
| 50 return iface, DummyPerspective(), lambda: None | |
| 51 | |
| 52 | |
| 53 class IOPump: | |
| 54 """ | |
| 55 Utility to pump data between clients and servers for protocol testing. | |
| 56 | |
| 57 Perhaps this is a utility worthy of being in protocol.py? | |
| 58 """ | |
| 59 def __init__(self, client, server, clientIO, serverIO): | |
| 60 self.client = client | |
| 61 self.server = server | |
| 62 self.clientIO = clientIO | |
| 63 self.serverIO = serverIO | |
| 64 | |
| 65 def flush(self): | |
| 66 """ | |
| 67 Pump until there is no more input or output. This does not run any | |
| 68 timers, so don't use it with any code that calls reactor.callLater. | |
| 69 """ | |
| 70 # failsafe timeout | |
| 71 timeout = time.time() + 5 | |
| 72 while self.pump(): | |
| 73 if time.time() > timeout: | |
| 74 return | |
| 75 | |
| 76 def pump(self): | |
| 77 """ | |
| 78 Move data back and forth. | |
| 79 | |
| 80 Returns whether any data was moved. | |
| 81 """ | |
| 82 self.clientIO.seek(0) | |
| 83 self.serverIO.seek(0) | |
| 84 cData = self.clientIO.read() | |
| 85 sData = self.serverIO.read() | |
| 86 self.clientIO.seek(0) | |
| 87 self.serverIO.seek(0) | |
| 88 self.clientIO.truncate() | |
| 89 self.serverIO.truncate() | |
| 90 self.client.transport._checkProducer() | |
| 91 self.server.transport._checkProducer() | |
| 92 for byte in cData: | |
| 93 self.server.dataReceived(byte) | |
| 94 for byte in sData: | |
| 95 self.client.dataReceived(byte) | |
| 96 if cData or sData: | |
| 97 return 1 | |
| 98 else: | |
| 99 return 0 | |
| 100 | |
| 101 | |
| 102 def connectedServerAndClient(): | |
| 103 """ | |
| 104 Returns a 3-tuple: (client, server, pump). | |
| 105 """ | |
| 106 clientBroker = pb.Broker() | |
| 107 checker = checkers.InMemoryUsernamePasswordDatabaseDontUse(guest='guest') | |
| 108 factory = pb.PBServerFactory(portal.Portal(DummyRealm(), [checker])) | |
| 109 serverBroker = factory.buildProtocol(('127.0.0.1',)) | |
| 110 | |
| 111 clientTransport = StringIO() | |
| 112 serverTransport = StringIO() | |
| 113 clientBroker.makeConnection(protocol.FileWrapper(clientTransport)) | |
| 114 serverBroker.makeConnection(protocol.FileWrapper(serverTransport)) | |
| 115 pump = IOPump(clientBroker, serverBroker, clientTransport, serverTransport) | |
| 116 # Challenge-response authentication: | |
| 117 pump.flush() | |
| 118 return clientBroker, serverBroker, pump | |
| 119 | |
| 120 | |
| 121 class SimpleRemote(pb.Referenceable): | |
| 122 def remote_thunk(self, arg): | |
| 123 self.arg = arg | |
| 124 return arg + 1 | |
| 125 | |
| 126 def remote_knuth(self, arg): | |
| 127 raise Exception() | |
| 128 | |
| 129 | |
| 130 class NestedRemote(pb.Referenceable): | |
| 131 def remote_getSimple(self): | |
| 132 return SimpleRemote() | |
| 133 | |
| 134 | |
| 135 class SimpleCopy(pb.Copyable): | |
| 136 def __init__(self): | |
| 137 self.x = 1 | |
| 138 self.y = {"Hello":"World"} | |
| 139 self.z = ['test'] | |
| 140 | |
| 141 | |
| 142 class SimpleLocalCopy(pb.RemoteCopy): | |
| 143 pass | |
| 144 | |
| 145 pb.setCopierForClass(SimpleCopy, SimpleLocalCopy) | |
| 146 | |
| 147 | |
| 148 class SimpleFactoryCopy(pb.Copyable): | |
| 149 """ | |
| 150 @cvar allIDs: hold every created instances of this class. | |
| 151 @type allIDs: C{dict} | |
| 152 """ | |
| 153 allIDs = {} | |
| 154 def __init__(self, id): | |
| 155 self.id = id | |
| 156 SimpleFactoryCopy.allIDs[id] = self | |
| 157 | |
| 158 | |
| 159 def createFactoryCopy(state): | |
| 160 """ | |
| 161 Factory of L{SimpleFactoryCopy}, getting a created instance given the | |
| 162 C{id} found in C{state}. | |
| 163 """ | |
| 164 stateId = state.get("id", None) | |
| 165 if stateId is None: | |
| 166 raise RuntimeError("factory copy state has no 'id' member %s" % | |
| 167 (repr(state),)) | |
| 168 if not stateId in SimpleFactoryCopy.allIDs: | |
| 169 raise RuntimeError("factory class has no ID: %s" % | |
| 170 (SimpleFactoryCopy.allIDs,)) | |
| 171 inst = SimpleFactoryCopy.allIDs[stateId] | |
| 172 if not inst: | |
| 173 raise RuntimeError("factory method found no object with id") | |
| 174 return inst | |
| 175 | |
| 176 pb.setFactoryForClass(SimpleFactoryCopy, createFactoryCopy) | |
| 177 | |
| 178 | |
| 179 class NestedCopy(pb.Referenceable): | |
| 180 def remote_getCopy(self): | |
| 181 return SimpleCopy() | |
| 182 | |
| 183 def remote_getFactory(self, value): | |
| 184 return SimpleFactoryCopy(value) | |
| 185 | |
| 186 | |
| 187 | |
| 188 class SimpleCache(pb.Cacheable): | |
| 189 def __init___(self): | |
| 190 self.x = 1 | |
| 191 self.y = {"Hello":"World"} | |
| 192 self.z = ['test'] | |
| 193 | |
| 194 | |
| 195 class NestedComplicatedCache(pb.Referenceable): | |
| 196 def __init__(self): | |
| 197 self.c = VeryVeryComplicatedCacheable() | |
| 198 | |
| 199 def remote_getCache(self): | |
| 200 return self.c | |
| 201 | |
| 202 | |
| 203 class VeryVeryComplicatedCacheable(pb.Cacheable): | |
| 204 def __init__(self): | |
| 205 self.x = 1 | |
| 206 self.y = 2 | |
| 207 self.foo = 3 | |
| 208 | |
| 209 def setFoo4(self): | |
| 210 self.foo = 4 | |
| 211 self.observer.callRemote('foo',4) | |
| 212 | |
| 213 def getStateToCacheAndObserveFor(self, perspective, observer): | |
| 214 self.observer = observer | |
| 215 return {"x": self.x, | |
| 216 "y": self.y, | |
| 217 "foo": self.foo} | |
| 218 | |
| 219 def stoppedObserving(self, perspective, observer): | |
| 220 log.msg("stopped observing") | |
| 221 observer.callRemote("end") | |
| 222 if observer == self.observer: | |
| 223 self.observer = None | |
| 224 | |
| 225 | |
| 226 class RatherBaroqueCache(pb.RemoteCache): | |
| 227 def observe_foo(self, newFoo): | |
| 228 self.foo = newFoo | |
| 229 | |
| 230 def observe_end(self): | |
| 231 log.msg("the end of things") | |
| 232 | |
| 233 pb.setCopierForClass(VeryVeryComplicatedCacheable, RatherBaroqueCache) | |
| 234 | |
| 235 | |
| 236 class SimpleLocalCache(pb.RemoteCache): | |
| 237 def setCopyableState(self, state): | |
| 238 self.__dict__.update(state) | |
| 239 | |
| 240 def checkMethod(self): | |
| 241 return self.check | |
| 242 | |
| 243 def checkSelf(self): | |
| 244 return self | |
| 245 | |
| 246 def check(self): | |
| 247 return 1 | |
| 248 | |
| 249 pb.setCopierForClass(SimpleCache, SimpleLocalCache) | |
| 250 | |
| 251 | |
| 252 class NestedCache(pb.Referenceable): | |
| 253 def __init__(self): | |
| 254 self.x = SimpleCache() | |
| 255 | |
| 256 def remote_getCache(self): | |
| 257 return [self.x,self.x] | |
| 258 | |
| 259 def remote_putCache(self, cache): | |
| 260 return (self.x is cache) | |
| 261 | |
| 262 | |
| 263 class Observable(pb.Referenceable): | |
| 264 def __init__(self): | |
| 265 self.observers = [] | |
| 266 | |
| 267 def remote_observe(self, obs): | |
| 268 self.observers.append(obs) | |
| 269 | |
| 270 def remote_unobserve(self, obs): | |
| 271 self.observers.remove(obs) | |
| 272 | |
| 273 def notify(self, obj): | |
| 274 for observer in self.observers: | |
| 275 observer.callRemote('notify', self, obj) | |
| 276 | |
| 277 | |
| 278 class DeferredRemote(pb.Referenceable): | |
| 279 def __init__(self): | |
| 280 self.run = 0 | |
| 281 | |
| 282 def runMe(self, arg): | |
| 283 self.run = arg | |
| 284 return arg + 1 | |
| 285 | |
| 286 def dontRunMe(self, arg): | |
| 287 assert 0, "shouldn't have been run!" | |
| 288 | |
| 289 def remote_doItLater(self): | |
| 290 d = defer.Deferred() | |
| 291 d.addCallbacks(self.runMe, self.dontRunMe) | |
| 292 self.d = d | |
| 293 return d | |
| 294 | |
| 295 | |
| 296 class Observer(pb.Referenceable): | |
| 297 notified = 0 | |
| 298 obj = None | |
| 299 def remote_notify(self, other, obj): | |
| 300 self.obj = obj | |
| 301 self.notified = self.notified + 1 | |
| 302 other.callRemote('unobserve',self) | |
| 303 | |
| 304 | |
| 305 class NewStyleCopy(pb.Copyable, pb.RemoteCopy, object): | |
| 306 def __init__(self, s): | |
| 307 self.s = s | |
| 308 pb.setUnjellyableForClass(NewStyleCopy, NewStyleCopy) | |
| 309 | |
| 310 | |
| 311 class NewStyleCopy2(pb.Copyable, pb.RemoteCopy, object): | |
| 312 allocated = 0 | |
| 313 initialized = 0 | |
| 314 value = 1 | |
| 315 | |
| 316 def __new__(self): | |
| 317 NewStyleCopy2.allocated += 1 | |
| 318 inst = object.__new__(self) | |
| 319 inst.value = 2 | |
| 320 return inst | |
| 321 | |
| 322 def __init__(self): | |
| 323 NewStyleCopy2.initialized += 1 | |
| 324 | |
| 325 pb.setUnjellyableForClass(NewStyleCopy2, NewStyleCopy2) | |
| 326 | |
| 327 | |
| 328 class NewStyleCacheCopy(pb.Cacheable, pb.RemoteCache, object): | |
| 329 def getStateToCacheAndObserveFor(self, perspective, observer): | |
| 330 return self.__dict__ | |
| 331 | |
| 332 pb.setCopierForClass(NewStyleCacheCopy, NewStyleCacheCopy) | |
| 333 | |
| 334 | |
| 335 class Echoer(pb.Root): | |
| 336 def remote_echo(self, st): | |
| 337 return st | |
| 338 | |
| 339 | |
| 340 class CachedReturner(pb.Root): | |
| 341 def __init__(self, cache): | |
| 342 self.cache = cache | |
| 343 def remote_giveMeCache(self, st): | |
| 344 return self.cache | |
| 345 | |
| 346 | |
| 347 class NewStyleTestCase(unittest.TestCase): | |
| 348 def setUp(self): | |
| 349 """ | |
| 350 Create a pb server using L{Echoer} protocol and connect a client to it. | |
| 351 """ | |
| 352 self.server = reactor.listenTCP(0, pb.PBServerFactory(Echoer())) | |
| 353 clientFactory = pb.PBClientFactory() | |
| 354 reactor.connectTCP("localhost", self.server.getHost().port, | |
| 355 clientFactory) | |
| 356 def gotRoot(ref): | |
| 357 self.ref = ref | |
| 358 return clientFactory.getRootObject().addCallback(gotRoot) | |
| 359 | |
| 360 | |
| 361 def tearDown(self): | |
| 362 """ | |
| 363 Close client and server connections, reset values of L{NewStyleCopy2} | |
| 364 class variables. | |
| 365 """ | |
| 366 NewStyleCopy2.allocated = 0 | |
| 367 NewStyleCopy2.initialized = 0 | |
| 368 NewStyleCopy2.value = 1 | |
| 369 self.ref.broker.transport.loseConnection() | |
| 370 return self.server.stopListening() | |
| 371 | |
| 372 def test_newStyle(self): | |
| 373 """ | |
| 374 Create a new style object, send it over the wire, and check the result. | |
| 375 """ | |
| 376 orig = NewStyleCopy("value") | |
| 377 d = self.ref.callRemote("echo", orig) | |
| 378 def cb(res): | |
| 379 self.failUnless(isinstance(res, NewStyleCopy)) | |
| 380 self.failUnlessEqual(res.s, "value") | |
| 381 self.failIf(res is orig) # no cheating :) | |
| 382 d.addCallback(cb) | |
| 383 return d | |
| 384 | |
| 385 def test_alloc(self): | |
| 386 """ | |
| 387 Send a new style object and check the number of allocations. | |
| 388 """ | |
| 389 orig = NewStyleCopy2() | |
| 390 self.failUnlessEqual(NewStyleCopy2.allocated, 1) | |
| 391 self.failUnlessEqual(NewStyleCopy2.initialized, 1) | |
| 392 d = self.ref.callRemote("echo", orig) | |
| 393 def cb(res): | |
| 394 # receiving the response creates a third one on the way back | |
| 395 self.failUnless(isinstance(res, NewStyleCopy2)) | |
| 396 self.failUnlessEqual(res.value, 2) | |
| 397 self.failUnlessEqual(NewStyleCopy2.allocated, 3) | |
| 398 self.failUnlessEqual(NewStyleCopy2.initialized, 1) | |
| 399 self.failIf(res is orig) # no cheating :) | |
| 400 # sending the object creates a second one on the far side | |
| 401 d.addCallback(cb) | |
| 402 return d | |
| 403 | |
| 404 | |
| 405 | |
| 406 class ConnectionNotifyServerFactory(pb.PBServerFactory): | |
| 407 """ | |
| 408 A server factory which stores the last connection and fires a | |
| 409 L{defer.Deferred} on connection made. This factory can handle only one | |
| 410 client connection. | |
| 411 | |
| 412 @ivar protocolInstance: the last protocol instance. | |
| 413 @type protocolInstance: C{pb.Broker} | |
| 414 | |
| 415 @ivar connectionMade: the deferred fired upon connection. | |
| 416 @type connectionMade: C{defer.Deferred} | |
| 417 """ | |
| 418 protocolInstance = None | |
| 419 | |
| 420 def __init__(self, root): | |
| 421 """ | |
| 422 Initialize the factory. | |
| 423 """ | |
| 424 pb.PBServerFactory.__init__(self, root) | |
| 425 self.connectionMade = defer.Deferred() | |
| 426 | |
| 427 | |
| 428 def clientConnectionMade(self, protocol): | |
| 429 """ | |
| 430 Store the protocol and fire the connection deferred. | |
| 431 """ | |
| 432 self.protocolInstance = protocol | |
| 433 self.connectionMade.callback(None) | |
| 434 | |
| 435 | |
| 436 | |
| 437 class NewStyleCachedTestCase(unittest.TestCase): | |
| 438 def setUp(self): | |
| 439 """ | |
| 440 Create a pb server using L{CachedReturner} protocol and connect a | |
| 441 client to it. | |
| 442 """ | |
| 443 self.orig = NewStyleCacheCopy() | |
| 444 self.orig.s = "value" | |
| 445 self.server = reactor.listenTCP(0, | |
| 446 ConnectionNotifyServerFactory(CachedReturner(self.orig))) | |
| 447 clientFactory = pb.PBClientFactory() | |
| 448 reactor.connectTCP("localhost", self.server.getHost().port, | |
| 449 clientFactory) | |
| 450 def gotRoot(ref): | |
| 451 self.ref = ref | |
| 452 d1 = clientFactory.getRootObject().addCallback(gotRoot) | |
| 453 d2 = self.server.factory.connectionMade | |
| 454 return defer.gatherResults([d1, d2]) | |
| 455 | |
| 456 | |
| 457 def tearDown(self): | |
| 458 """ | |
| 459 Close client and server connections. | |
| 460 """ | |
| 461 self.server.factory.protocolInstance.transport.loseConnection() | |
| 462 self.ref.broker.transport.loseConnection() | |
| 463 return self.server.stopListening() | |
| 464 | |
| 465 | |
| 466 def test_newStyleCache(self): | |
| 467 """ | |
| 468 Get the object from the cache, and checks its properties. | |
| 469 """ | |
| 470 d = self.ref.callRemote("giveMeCache", self.orig) | |
| 471 def cb(res): | |
| 472 self.failUnless(isinstance(res, NewStyleCacheCopy)) | |
| 473 self.failUnlessEqual(res.s, "value") | |
| 474 self.failIf(res is self.orig) # no cheating :) | |
| 475 d.addCallback(cb) | |
| 476 return d | |
| 477 | |
| 478 | |
| 479 | |
| 480 class BrokerTestCase(unittest.TestCase): | |
| 481 thunkResult = None | |
| 482 | |
| 483 def tearDown(self): | |
| 484 try: | |
| 485 # from RemotePublished.getFileName | |
| 486 os.unlink('None-None-TESTING.pub') | |
| 487 except OSError: | |
| 488 pass | |
| 489 | |
| 490 def thunkErrorBad(self, error): | |
| 491 self.fail("This should cause a return value, not %s" % (error,)) | |
| 492 | |
| 493 def thunkResultGood(self, result): | |
| 494 self.thunkResult = result | |
| 495 | |
| 496 def thunkErrorGood(self, tb): | |
| 497 pass | |
| 498 | |
| 499 def thunkResultBad(self, result): | |
| 500 self.fail("This should cause an error, not %s" % (result,)) | |
| 501 | |
| 502 def test_reference(self): | |
| 503 c, s, pump = connectedServerAndClient() | |
| 504 | |
| 505 class X(pb.Referenceable): | |
| 506 def remote_catch(self,arg): | |
| 507 self.caught = arg | |
| 508 | |
| 509 class Y(pb.Referenceable): | |
| 510 def remote_throw(self, a, b): | |
| 511 a.callRemote('catch', b) | |
| 512 | |
| 513 s.setNameForLocal("y", Y()) | |
| 514 y = c.remoteForName("y") | |
| 515 x = X() | |
| 516 z = X() | |
| 517 y.callRemote('throw', x, z) | |
| 518 pump.pump() | |
| 519 pump.pump() | |
| 520 pump.pump() | |
| 521 self.assertIdentical(x.caught, z, "X should have caught Z") | |
| 522 | |
| 523 # make sure references to remote methods are equals | |
| 524 self.assertEquals(y.remoteMethod('throw'), y.remoteMethod('throw')) | |
| 525 | |
| 526 def test_result(self): | |
| 527 c, s, pump = connectedServerAndClient() | |
| 528 for x, y in (c, s), (s, c): | |
| 529 # test reflexivity | |
| 530 foo = SimpleRemote() | |
| 531 x.setNameForLocal("foo", foo) | |
| 532 bar = y.remoteForName("foo") | |
| 533 self.expectedThunkResult = 8 | |
| 534 bar.callRemote('thunk',self.expectedThunkResult - 1 | |
| 535 ).addCallbacks(self.thunkResultGood, self.thunkErrorBad) | |
| 536 # Send question. | |
| 537 pump.pump() | |
| 538 # Send response. | |
| 539 pump.pump() | |
| 540 # Shouldn't require any more pumping than that... | |
| 541 self.assertEquals(self.thunkResult, self.expectedThunkResult, | |
| 542 "result wasn't received.") | |
| 543 | |
| 544 def refcountResult(self, result): | |
| 545 self.nestedRemote = result | |
| 546 | |
| 547 def test_tooManyRefs(self): | |
| 548 l = [] | |
| 549 e = [] | |
| 550 c, s, pump = connectedServerAndClient() | |
| 551 foo = NestedRemote() | |
| 552 s.setNameForLocal("foo", foo) | |
| 553 x = c.remoteForName("foo") | |
| 554 for igno in xrange(pb.MAX_BROKER_REFS + 10): | |
| 555 if s.transport.closed or c.transport.closed: | |
| 556 break | |
| 557 x.callRemote("getSimple").addCallbacks(l.append, e.append) | |
| 558 pump.pump() | |
| 559 expected = (pb.MAX_BROKER_REFS - 1) | |
| 560 self.assertTrue(s.transport.closed, "transport was not closed") | |
| 561 self.assertEquals(len(l), expected, | |
| 562 "expected %s got %s" % (expected, len(l))) | |
| 563 | |
| 564 def test_copy(self): | |
| 565 c, s, pump = connectedServerAndClient() | |
| 566 foo = NestedCopy() | |
| 567 s.setNameForLocal("foo", foo) | |
| 568 x = c.remoteForName("foo") | |
| 569 x.callRemote('getCopy' | |
| 570 ).addCallbacks(self.thunkResultGood, self.thunkErrorBad) | |
| 571 pump.pump() | |
| 572 pump.pump() | |
| 573 self.assertEquals(self.thunkResult.x, 1) | |
| 574 self.assertEquals(self.thunkResult.y['Hello'], 'World') | |
| 575 self.assertEquals(self.thunkResult.z[0], 'test') | |
| 576 | |
| 577 def test_observe(self): | |
| 578 c, s, pump = connectedServerAndClient() | |
| 579 | |
| 580 # this is really testing the comparison between remote objects, to make | |
| 581 # sure that you can *UN*observe when you have an observer architecture. | |
| 582 a = Observable() | |
| 583 b = Observer() | |
| 584 s.setNameForLocal("a", a) | |
| 585 ra = c.remoteForName("a") | |
| 586 ra.callRemote('observe',b) | |
| 587 pump.pump() | |
| 588 a.notify(1) | |
| 589 pump.pump() | |
| 590 pump.pump() | |
| 591 a.notify(10) | |
| 592 pump.pump() | |
| 593 pump.pump() | |
| 594 self.assertNotIdentical(b.obj, None, "didn't notify") | |
| 595 self.assertEquals(b.obj, 1, 'notified too much') | |
| 596 | |
| 597 def test_defer(self): | |
| 598 c, s, pump = connectedServerAndClient() | |
| 599 d = DeferredRemote() | |
| 600 s.setNameForLocal("d", d) | |
| 601 e = c.remoteForName("d") | |
| 602 pump.pump(); pump.pump() | |
| 603 results = [] | |
| 604 e.callRemote('doItLater').addCallback(results.append) | |
| 605 pump.pump(); pump.pump() | |
| 606 self.assertFalse(d.run, "Deferred method run too early.") | |
| 607 d.d.callback(5) | |
| 608 self.assertEquals(d.run, 5, "Deferred method run too late.") | |
| 609 pump.pump(); pump.pump() | |
| 610 self.assertEquals(results[0], 6, "Incorrect result.") | |
| 611 | |
| 612 | |
| 613 def test_refcount(self): | |
| 614 c, s, pump = connectedServerAndClient() | |
| 615 foo = NestedRemote() | |
| 616 s.setNameForLocal("foo", foo) | |
| 617 bar = c.remoteForName("foo") | |
| 618 bar.callRemote('getSimple' | |
| 619 ).addCallbacks(self.refcountResult, self.thunkErrorBad) | |
| 620 | |
| 621 # send question | |
| 622 pump.pump() | |
| 623 # send response | |
| 624 pump.pump() | |
| 625 | |
| 626 # delving into internal structures here, because GC is sort of | |
| 627 # inherently internal. | |
| 628 rluid = self.nestedRemote.luid | |
| 629 self.assertIn(rluid, s.localObjects) | |
| 630 del self.nestedRemote | |
| 631 # nudge the gc | |
| 632 if sys.hexversion >= 0x2000000 and os.name != "java": | |
| 633 gc.collect() | |
| 634 # try to nudge the GC even if we can't really | |
| 635 pump.pump() | |
| 636 pump.pump() | |
| 637 pump.pump() | |
| 638 self.assertNotIn(rluid, s.localObjects) | |
| 639 | |
| 640 def test_cache(self): | |
| 641 c, s, pump = connectedServerAndClient() | |
| 642 obj = NestedCache() | |
| 643 obj2 = NestedComplicatedCache() | |
| 644 vcc = obj2.c | |
| 645 s.setNameForLocal("obj", obj) | |
| 646 s.setNameForLocal("xxx", obj2) | |
| 647 o2 = c.remoteForName("obj") | |
| 648 o3 = c.remoteForName("xxx") | |
| 649 coll = [] | |
| 650 o2.callRemote("getCache" | |
| 651 ).addCallback(coll.append).addErrback(coll.append) | |
| 652 o2.callRemote("getCache" | |
| 653 ).addCallback(coll.append).addErrback(coll.append) | |
| 654 complex = [] | |
| 655 o3.callRemote("getCache").addCallback(complex.append) | |
| 656 o3.callRemote("getCache").addCallback(complex.append) | |
| 657 pump.flush() | |
| 658 # `worst things first' | |
| 659 self.assertEquals(complex[0].x, 1) | |
| 660 self.assertEquals(complex[0].y, 2) | |
| 661 self.assertEquals(complex[0].foo, 3) | |
| 662 | |
| 663 vcc.setFoo4() | |
| 664 pump.flush() | |
| 665 self.assertEquals(complex[0].foo, 4) | |
| 666 self.assertEquals(len(coll), 2) | |
| 667 cp = coll[0][0] | |
| 668 self.assertIdentical(cp.checkMethod().im_self, cp, | |
| 669 "potential refcounting issue") | |
| 670 self.assertIdentical(cp.checkSelf(), cp, | |
| 671 "other potential refcounting issue") | |
| 672 col2 = [] | |
| 673 o2.callRemote('putCache',cp).addCallback(col2.append) | |
| 674 pump.flush() | |
| 675 # The objects were the same (testing lcache identity) | |
| 676 self.assertTrue(col2[0]) | |
| 677 # test equality of references to methods | |
| 678 self.assertEquals(o2.remoteMethod("getCache"), | |
| 679 o2.remoteMethod("getCache")) | |
| 680 | |
| 681 # now, refcounting (similiar to testRefCount) | |
| 682 luid = cp.luid | |
| 683 baroqueLuid = complex[0].luid | |
| 684 self.assertIn(luid, s.remotelyCachedObjects, | |
| 685 "remote cache doesn't have it") | |
| 686 del coll | |
| 687 del cp | |
| 688 pump.flush() | |
| 689 del complex | |
| 690 del col2 | |
| 691 # extra nudge... | |
| 692 pump.flush() | |
| 693 # del vcc.observer | |
| 694 # nudge the gc | |
| 695 if sys.hexversion >= 0x2000000 and os.name != "java": | |
| 696 gc.collect() | |
| 697 # try to nudge the GC even if we can't really | |
| 698 pump.flush() | |
| 699 # The GC is done with it. | |
| 700 self.assertNotIn(luid, s.remotelyCachedObjects, | |
| 701 "Server still had it after GC") | |
| 702 self.assertNotIn(luid, c.locallyCachedObjects, | |
| 703 "Client still had it after GC") | |
| 704 self.assertNotIn(baroqueLuid, s.remotelyCachedObjects, | |
| 705 "Server still had complex after GC") | |
| 706 self.assertNotIn(baroqueLuid, c.locallyCachedObjects, | |
| 707 "Client still had complex after GC") | |
| 708 self.assertIdentical(vcc.observer, None, "observer was not removed") | |
| 709 | |
| 710 def test_publishable(self): | |
| 711 try: | |
| 712 os.unlink('None-None-TESTING.pub') # from RemotePublished.getFileNam
e | |
| 713 except OSError: | |
| 714 pass # Sometimes it's not there. | |
| 715 c, s, pump = connectedServerAndClient() | |
| 716 foo = GetPublisher() | |
| 717 # foo.pub.timestamp = 1.0 | |
| 718 s.setNameForLocal("foo", foo) | |
| 719 bar = c.remoteForName("foo") | |
| 720 accum = [] | |
| 721 bar.callRemote('getPub').addCallbacks(accum.append, self.thunkErrorBad) | |
| 722 pump.flush() | |
| 723 obj = accum.pop() | |
| 724 self.assertEquals(obj.activateCalled, 1) | |
| 725 self.assertEquals(obj.isActivated, 1) | |
| 726 self.assertEquals(obj.yayIGotPublished, 1) | |
| 727 # timestamp's dirty, we don't have a cache file | |
| 728 self.assertEquals(obj._wasCleanWhenLoaded, 0) | |
| 729 c, s, pump = connectedServerAndClient() | |
| 730 s.setNameForLocal("foo", foo) | |
| 731 bar = c.remoteForName("foo") | |
| 732 bar.callRemote('getPub').addCallbacks(accum.append, self.thunkErrorBad) | |
| 733 pump.flush() | |
| 734 obj = accum.pop() | |
| 735 # timestamp's clean, our cache file is up-to-date | |
| 736 self.assertEquals(obj._wasCleanWhenLoaded, 1) | |
| 737 | |
| 738 def gotCopy(self, val): | |
| 739 self.thunkResult = val.id | |
| 740 | |
| 741 | |
| 742 def test_factoryCopy(self): | |
| 743 c, s, pump = connectedServerAndClient() | |
| 744 ID = 99 | |
| 745 obj = NestedCopy() | |
| 746 s.setNameForLocal("foo", obj) | |
| 747 x = c.remoteForName("foo") | |
| 748 x.callRemote('getFactory', ID | |
| 749 ).addCallbacks(self.gotCopy, self.thunkResultBad) | |
| 750 pump.pump() | |
| 751 pump.pump() | |
| 752 pump.pump() | |
| 753 self.assertEquals(self.thunkResult, ID, | |
| 754 "ID not correct on factory object %s" % (self.thunkResult,)) | |
| 755 | |
| 756 | |
| 757 bigString = "helloworld" * 50 | |
| 758 | |
| 759 callbackArgs = None | |
| 760 callbackKeyword = None | |
| 761 | |
| 762 def finishedCallback(*args, **kw): | |
| 763 global callbackArgs, callbackKeyword | |
| 764 callbackArgs = args | |
| 765 callbackKeyword = kw | |
| 766 | |
| 767 | |
| 768 class Pagerizer(pb.Referenceable): | |
| 769 def __init__(self, callback, *args, **kw): | |
| 770 self.callback, self.args, self.kw = callback, args, kw | |
| 771 | |
| 772 def remote_getPages(self, collector): | |
| 773 util.StringPager(collector, bigString, 100, | |
| 774 self.callback, *self.args, **self.kw) | |
| 775 self.args = self.kw = None | |
| 776 | |
| 777 | |
| 778 class FilePagerizer(pb.Referenceable): | |
| 779 pager = None | |
| 780 | |
| 781 def __init__(self, filename, callback, *args, **kw): | |
| 782 self.filename = filename | |
| 783 self.callback, self.args, self.kw = callback, args, kw | |
| 784 | |
| 785 def remote_getPages(self, collector): | |
| 786 self.pager = util.FilePager(collector, file(self.filename), | |
| 787 self.callback, *self.args, **self.kw) | |
| 788 self.args = self.kw = None | |
| 789 | |
| 790 | |
| 791 | |
| 792 class PagingTestCase(unittest.TestCase): | |
| 793 """ | |
| 794 Test pb objects sending data by pages. | |
| 795 """ | |
| 796 | |
| 797 def setUp(self): | |
| 798 """ | |
| 799 Create a file used to test L{util.FilePager}. | |
| 800 """ | |
| 801 self.filename = self.mktemp() | |
| 802 fd = file(self.filename, 'w') | |
| 803 fd.write(bigString) | |
| 804 fd.close() | |
| 805 | |
| 806 | |
| 807 def test_pagingWithCallback(self): | |
| 808 """ | |
| 809 Test L{util.StringPager}, passing a callback to fire when all pages | |
| 810 are sent. | |
| 811 """ | |
| 812 c, s, pump = connectedServerAndClient() | |
| 813 s.setNameForLocal("foo", Pagerizer(finishedCallback, 'hello', value=10)) | |
| 814 x = c.remoteForName("foo") | |
| 815 l = [] | |
| 816 util.getAllPages(x, "getPages").addCallback(l.append) | |
| 817 while not l: | |
| 818 pump.pump() | |
| 819 self.assertEquals(''.join(l[0]), bigString, | |
| 820 "Pages received not equal to pages sent!") | |
| 821 self.assertEquals(callbackArgs, ('hello',), | |
| 822 "Completed callback not invoked") | |
| 823 self.assertEquals(callbackKeyword, {'value': 10}, | |
| 824 "Completed callback not invoked") | |
| 825 | |
| 826 | |
| 827 def test_pagingWithoutCallback(self): | |
| 828 """ | |
| 829 Test L{util.StringPager} without a callback. | |
| 830 """ | |
| 831 c, s, pump = connectedServerAndClient() | |
| 832 s.setNameForLocal("foo", Pagerizer(None)) | |
| 833 x = c.remoteForName("foo") | |
| 834 l = [] | |
| 835 util.getAllPages(x, "getPages").addCallback(l.append) | |
| 836 while not l: | |
| 837 pump.pump() | |
| 838 self.assertEquals(''.join(l[0]), bigString, | |
| 839 "Pages received not equal to pages sent!") | |
| 840 | |
| 841 | |
| 842 def test_emptyFilePaging(self): | |
| 843 """ | |
| 844 Test L{util.FilePager}, sending an empty file. | |
| 845 """ | |
| 846 filenameEmpty = self.mktemp() | |
| 847 fd = file(filenameEmpty, 'w') | |
| 848 fd.close() | |
| 849 c, s, pump = connectedServerAndClient() | |
| 850 pagerizer = FilePagerizer(filenameEmpty, None) | |
| 851 s.setNameForLocal("bar", pagerizer) | |
| 852 x = c.remoteForName("bar") | |
| 853 l = [] | |
| 854 util.getAllPages(x, "getPages").addCallback(l.append) | |
| 855 ttl = 10 | |
| 856 while not l and ttl > 0: | |
| 857 pump.pump() | |
| 858 ttl -= 1 | |
| 859 if not ttl: | |
| 860 self.fail('getAllPages timed out') | |
| 861 self.assertEquals(''.join(l[0]), '', | |
| 862 "Pages received not equal to pages sent!") | |
| 863 | |
| 864 | |
| 865 def test_filePagingWithCallback(self): | |
| 866 """ | |
| 867 Test L{util.FilePager}, passing a callback to fire when all pages | |
| 868 are sent, and verify that the pager doesn't keep chunks in memory. | |
| 869 """ | |
| 870 c, s, pump = connectedServerAndClient() | |
| 871 pagerizer = FilePagerizer(self.filename, finishedCallback, | |
| 872 'frodo', value = 9) | |
| 873 s.setNameForLocal("bar", pagerizer) | |
| 874 x = c.remoteForName("bar") | |
| 875 l = [] | |
| 876 util.getAllPages(x, "getPages").addCallback(l.append) | |
| 877 while not l: | |
| 878 pump.pump() | |
| 879 self.assertEquals(''.join(l[0]), bigString, | |
| 880 "Pages received not equal to pages sent!") | |
| 881 self.assertEquals(callbackArgs, ('frodo',), | |
| 882 "Completed callback not invoked") | |
| 883 self.assertEquals(callbackKeyword, {'value': 9}, | |
| 884 "Completed callback not invoked") | |
| 885 self.assertEquals(pagerizer.pager.chunks, []) | |
| 886 | |
| 887 | |
| 888 def test_filePagingWithoutCallback(self): | |
| 889 """ | |
| 890 Test L{util.FilePager} without a callback. | |
| 891 """ | |
| 892 c, s, pump = connectedServerAndClient() | |
| 893 pagerizer = FilePagerizer(self.filename, None) | |
| 894 s.setNameForLocal("bar", pagerizer) | |
| 895 x = c.remoteForName("bar") | |
| 896 l = [] | |
| 897 util.getAllPages(x, "getPages").addCallback(l.append) | |
| 898 while not l: | |
| 899 pump.pump() | |
| 900 self.assertEquals(''.join(l[0]), bigString, | |
| 901 "Pages received not equal to pages sent!") | |
| 902 self.assertEquals(pagerizer.pager.chunks, []) | |
| 903 | |
| 904 | |
| 905 | |
| 906 class DumbPublishable(publish.Publishable): | |
| 907 def getStateToPublish(self): | |
| 908 return {"yayIGotPublished": 1} | |
| 909 | |
| 910 | |
| 911 class DumbPub(publish.RemotePublished): | |
| 912 def activated(self): | |
| 913 self.activateCalled = 1 | |
| 914 | |
| 915 | |
| 916 class GetPublisher(pb.Referenceable): | |
| 917 def __init__(self): | |
| 918 self.pub = DumbPublishable("TESTING") | |
| 919 | |
| 920 def remote_getPub(self): | |
| 921 return self.pub | |
| 922 | |
| 923 | |
| 924 pb.setCopierForClass(DumbPublishable, DumbPub) | |
| 925 | |
| 926 class DisconnectionTestCase(unittest.TestCase): | |
| 927 """ | |
| 928 Test disconnection callbacks. | |
| 929 """ | |
| 930 | |
| 931 def error(self, *args): | |
| 932 raise RuntimeError("I shouldn't have been called: %s" % (args,)) | |
| 933 | |
| 934 | |
| 935 def gotDisconnected(self): | |
| 936 """ | |
| 937 Called on broker disconnect. | |
| 938 """ | |
| 939 self.gotCallback = 1 | |
| 940 | |
| 941 def objectDisconnected(self, o): | |
| 942 """ | |
| 943 Called on RemoteReference disconnect. | |
| 944 """ | |
| 945 self.assertEquals(o, self.remoteObject) | |
| 946 self.objectCallback = 1 | |
| 947 | |
| 948 def test_badSerialization(self): | |
| 949 c, s, pump = connectedServerAndClient() | |
| 950 pump.pump() | |
| 951 s.setNameForLocal("o", BadCopySet()) | |
| 952 g = c.remoteForName("o") | |
| 953 l = [] | |
| 954 g.callRemote("setBadCopy", BadCopyable()).addErrback(l.append) | |
| 955 pump.flush() | |
| 956 self.assertEquals(len(l), 1) | |
| 957 | |
| 958 def test_disconnection(self): | |
| 959 c, s, pump = connectedServerAndClient() | |
| 960 pump.pump() | |
| 961 s.setNameForLocal("o", SimpleRemote()) | |
| 962 | |
| 963 # get a client reference to server object | |
| 964 r = c.remoteForName("o") | |
| 965 pump.pump() | |
| 966 pump.pump() | |
| 967 pump.pump() | |
| 968 | |
| 969 # register and then unregister disconnect callbacks | |
| 970 # making sure they get unregistered | |
| 971 c.notifyOnDisconnect(self.error) | |
| 972 self.assertIn(self.error, c.disconnects) | |
| 973 c.dontNotifyOnDisconnect(self.error) | |
| 974 self.assertNotIn(self.error, c.disconnects) | |
| 975 | |
| 976 r.notifyOnDisconnect(self.error) | |
| 977 self.assertIn(r._disconnected, c.disconnects) | |
| 978 self.assertIn(self.error, r.disconnectCallbacks) | |
| 979 r.dontNotifyOnDisconnect(self.error) | |
| 980 self.assertNotIn(r._disconnected, c.disconnects) | |
| 981 self.assertNotIn(self.error, r.disconnectCallbacks) | |
| 982 | |
| 983 # register disconnect callbacks | |
| 984 c.notifyOnDisconnect(self.gotDisconnected) | |
| 985 r.notifyOnDisconnect(self.objectDisconnected) | |
| 986 self.remoteObject = r | |
| 987 | |
| 988 # disconnect | |
| 989 c.connectionLost(failure.Failure(main.CONNECTION_DONE)) | |
| 990 self.assertTrue(self.gotCallback) | |
| 991 self.assertTrue(self.objectCallback) | |
| 992 | |
| 993 | |
| 994 class FreakOut(Exception): | |
| 995 pass | |
| 996 | |
| 997 | |
| 998 class BadCopyable(pb.Copyable): | |
| 999 def getStateToCopyFor(self, p): | |
| 1000 raise FreakOut() | |
| 1001 | |
| 1002 | |
| 1003 class BadCopySet(pb.Referenceable): | |
| 1004 def remote_setBadCopy(self, bc): | |
| 1005 return None | |
| 1006 | |
| 1007 | |
| 1008 class LocalRemoteTest(util.LocalAsRemote): | |
| 1009 reportAllTracebacks = 0 | |
| 1010 | |
| 1011 def sync_add1(self, x): | |
| 1012 return x + 1 | |
| 1013 | |
| 1014 def async_add(self, x=0, y=1): | |
| 1015 return x + y | |
| 1016 | |
| 1017 def async_fail(self): | |
| 1018 raise RuntimeError() | |
| 1019 | |
| 1020 | |
| 1021 | |
| 1022 class TestRealm(object): | |
| 1023 """ | |
| 1024 A realm which repeatedly gives out a single instance of L{MyPerspective} | |
| 1025 for non-anonymous logins and which gives out a new instance of L{Echoer} | |
| 1026 for each anonymous login. | |
| 1027 """ | |
| 1028 | |
| 1029 def __init__(self): | |
| 1030 self.p = MyPerspective() | |
| 1031 | |
| 1032 def requestAvatar(self, avatarId, mind, interface): | |
| 1033 """ | |
| 1034 Verify that the mind and interface supplied have the expected values | |
| 1035 (this should really be done somewhere else, like inside a test method) | |
| 1036 and return an avatar appropriate for the given identifier. | |
| 1037 """ | |
| 1038 assert interface == pb.IPerspective | |
| 1039 assert mind == "BRAINS!" | |
| 1040 if avatarId is checkers.ANONYMOUS: | |
| 1041 return pb.IPerspective, Echoer(), lambda: None | |
| 1042 else: | |
| 1043 self.p.loggedIn = 1 | |
| 1044 return pb.IPerspective, self.p, self.p.logout | |
| 1045 | |
| 1046 | |
| 1047 | |
| 1048 class MyPerspective(pb.Avatar): | |
| 1049 | |
| 1050 implements(pb.IPerspective) | |
| 1051 | |
| 1052 loggedIn = loggedOut = False | |
| 1053 | |
| 1054 def __init__(self): | |
| 1055 pass | |
| 1056 | |
| 1057 | |
| 1058 def perspective_getViewPoint(self): | |
| 1059 return MyView() | |
| 1060 | |
| 1061 | |
| 1062 def perspective_add(self, a, b): | |
| 1063 """ | |
| 1064 Add the given objects and return the result. This is a method | |
| 1065 unavailable on L{Echoer}, so it can only be invoked by authenticated | |
| 1066 users who received their avatar from L{TestRealm}. | |
| 1067 """ | |
| 1068 return a + b | |
| 1069 | |
| 1070 | |
| 1071 def logout(self): | |
| 1072 self.loggedOut = True | |
| 1073 | |
| 1074 | |
| 1075 | |
| 1076 class MyView(pb.Viewable): | |
| 1077 | |
| 1078 def view_check(self, user): | |
| 1079 return isinstance(user, MyPerspective) | |
| 1080 | |
| 1081 | |
| 1082 | |
| 1083 class NewCredTestCase(unittest.TestCase): | |
| 1084 """ | |
| 1085 Tests related to the L{twisted.cred} support in PB. | |
| 1086 """ | |
| 1087 def setUp(self): | |
| 1088 """ | |
| 1089 Create a portal with no checkers and wrap it around a simple test | |
| 1090 realm. Set up a PB server on a TCP port which serves perspectives | |
| 1091 using that portal. | |
| 1092 """ | |
| 1093 self.realm = TestRealm() | |
| 1094 self.portal = portal.Portal(self.realm) | |
| 1095 self.factory = pb.PBServerFactory(self.portal) | |
| 1096 self.port = reactor.listenTCP(0, self.factory, interface="127.0.0.1") | |
| 1097 self.portno = self.port.getHost().port | |
| 1098 | |
| 1099 | |
| 1100 def tearDown(self): | |
| 1101 """ | |
| 1102 Shut down the TCP port created by L{setUp}. | |
| 1103 """ | |
| 1104 return self.port.stopListening() | |
| 1105 | |
| 1106 def getFactoryAndRootObject(self, clientFactory=pb.PBClientFactory): | |
| 1107 factory = clientFactory() | |
| 1108 rootObjDeferred = factory.getRootObject() | |
| 1109 reactor.connectTCP('127.0.0.1', self.portno, factory) | |
| 1110 return factory, rootObjDeferred | |
| 1111 | |
| 1112 | |
| 1113 def test_getRootObject(self): | |
| 1114 """ | |
| 1115 Assert only that L{PBClientFactory.getRootObject}'s Deferred fires with | |
| 1116 a L{RemoteReference}. | |
| 1117 """ | |
| 1118 factory, rootObjDeferred = self.getFactoryAndRootObject() | |
| 1119 | |
| 1120 def gotRootObject(rootObj): | |
| 1121 self.failUnless(isinstance(rootObj, pb.RemoteReference)) | |
| 1122 disconnectedDeferred = defer.Deferred() | |
| 1123 rootObj.notifyOnDisconnect(disconnectedDeferred.callback) | |
| 1124 factory.disconnect() | |
| 1125 return disconnectedDeferred | |
| 1126 | |
| 1127 return rootObjDeferred.addCallback(gotRootObject) | |
| 1128 | |
| 1129 | |
| 1130 def test_deadReferenceError(self): | |
| 1131 """ | |
| 1132 Test that when a connection is lost, calling a method on a | |
| 1133 RemoteReference obtained from it raises DeadReferenceError. | |
| 1134 """ | |
| 1135 factory, rootObjDeferred = self.getFactoryAndRootObject() | |
| 1136 | |
| 1137 def gotRootObject(rootObj): | |
| 1138 disconnectedDeferred = defer.Deferred() | |
| 1139 rootObj.notifyOnDisconnect(disconnectedDeferred.callback) | |
| 1140 | |
| 1141 def lostConnection(ign): | |
| 1142 self.assertRaises( | |
| 1143 pb.DeadReferenceError, | |
| 1144 rootObj.callRemote, 'method') | |
| 1145 | |
| 1146 disconnectedDeferred.addCallback(lostConnection) | |
| 1147 factory.disconnect() | |
| 1148 return disconnectedDeferred | |
| 1149 | |
| 1150 return rootObjDeferred.addCallback(gotRootObject) | |
| 1151 | |
| 1152 | |
| 1153 def test_clientConnectionLost(self): | |
| 1154 """ | |
| 1155 Test that if the L{reconnecting} flag is passed with a True value then | |
| 1156 a remote call made from a disconnection notification callback gets a | |
| 1157 result successfully. | |
| 1158 """ | |
| 1159 class ReconnectOnce(pb.PBClientFactory): | |
| 1160 reconnectedAlready = False | |
| 1161 def clientConnectionLost(self, connector, reason): | |
| 1162 reconnecting = not self.reconnectedAlready | |
| 1163 self.reconnectedAlready = True | |
| 1164 if reconnecting: | |
| 1165 connector.connect() | |
| 1166 return pb.PBClientFactory.clientConnectionLost( | |
| 1167 self, connector, reason, reconnecting) | |
| 1168 | |
| 1169 factory, rootObjDeferred = self.getFactoryAndRootObject(ReconnectOnce) | |
| 1170 | |
| 1171 def gotRootObject(rootObj): | |
| 1172 self.assertIsInstance(rootObj, pb.RemoteReference) | |
| 1173 | |
| 1174 d = defer.Deferred() | |
| 1175 rootObj.notifyOnDisconnect(d.callback) | |
| 1176 factory.disconnect() | |
| 1177 | |
| 1178 def disconnected(ign): | |
| 1179 d = factory.getRootObject() | |
| 1180 | |
| 1181 def gotAnotherRootObject(anotherRootObj): | |
| 1182 self.assertIsInstance(anotherRootObj, pb.RemoteReference) | |
| 1183 | |
| 1184 d = defer.Deferred() | |
| 1185 anotherRootObj.notifyOnDisconnect(d.callback) | |
| 1186 factory.disconnect() | |
| 1187 return d | |
| 1188 return d.addCallback(gotAnotherRootObject) | |
| 1189 return d.addCallback(disconnected) | |
| 1190 return rootObjDeferred.addCallback(gotRootObject) | |
| 1191 | |
| 1192 | |
| 1193 def test_immediateClose(self): | |
| 1194 """ | |
| 1195 Test that if a Broker loses its connection without receiving any bytes, | |
| 1196 it doesn't raise any exceptions or log any errors. | |
| 1197 """ | |
| 1198 serverProto = self.factory.buildProtocol(('127.0.0.1', 12345)) | |
| 1199 serverProto.makeConnection(protocol.FileWrapper(StringIO())) | |
| 1200 serverProto.connectionLost(failure.Failure(main.CONNECTION_DONE)) | |
| 1201 | |
| 1202 | |
| 1203 def test_loginConnectionRefused(self): | |
| 1204 """ | |
| 1205 L{PBClientFactory.login} returns a L{Deferred} which is errbacked | |
| 1206 with the L{ConnectionRefusedError} if the underlying connection is | |
| 1207 refused. | |
| 1208 """ | |
| 1209 clientFactory = pb.PBClientFactory() | |
| 1210 loginDeferred = clientFactory.login( | |
| 1211 credentials.UsernamePassword("foo", "bar")) | |
| 1212 clientFactory.clientConnectionFailed( | |
| 1213 None, | |
| 1214 failure.Failure( | |
| 1215 ConnectionRefusedError("Test simulated refused connection"))) | |
| 1216 return self.assertFailure(loginDeferred, ConnectionRefusedError) | |
| 1217 | |
| 1218 | |
| 1219 def test_loginLogout(self): | |
| 1220 """ | |
| 1221 Test that login can be performed with IUsernamePassword credentials and | |
| 1222 that when the connection is dropped the avatar is logged out. | |
| 1223 """ | |
| 1224 self.portal.registerChecker( | |
| 1225 checkers.InMemoryUsernamePasswordDatabaseDontUse(user='pass')) | |
| 1226 factory = pb.PBClientFactory() | |
| 1227 creds = credentials.UsernamePassword("user", "pass") | |
| 1228 | |
| 1229 # NOTE: real code probably won't need anything where we have the | |
| 1230 # "BRAINS!" argument, passing None is fine. We just do it here to | |
| 1231 # test that it is being passed. It is used to give additional info to | |
| 1232 # the realm to aid perspective creation, if you don't need that, | |
| 1233 # ignore it. | |
| 1234 mind = "BRAINS!" | |
| 1235 | |
| 1236 d = factory.login(creds, mind) | |
| 1237 def cbLogin(perspective): | |
| 1238 self.assertEquals(self.realm.p.loggedIn, 1) | |
| 1239 self.assert_(isinstance(perspective, pb.RemoteReference)) | |
| 1240 | |
| 1241 factory.disconnect() | |
| 1242 d = Deferred() | |
| 1243 reactor.callLater(1.0, d.callback, None) | |
| 1244 return d | |
| 1245 d.addCallback(cbLogin) | |
| 1246 | |
| 1247 def cbLogout(ignored): | |
| 1248 self.assertEquals(self.realm.p.loggedOut, 1) | |
| 1249 d.addCallback(cbLogout) | |
| 1250 | |
| 1251 reactor.connectTCP("127.0.0.1", self.portno, factory) | |
| 1252 return d | |
| 1253 | |
| 1254 | |
| 1255 def test_badUsernamePasswordLogin(self): | |
| 1256 """ | |
| 1257 Test that a login attempt with an invalid user or invalid password | |
| 1258 fails in the appropriate way. | |
| 1259 """ | |
| 1260 self.portal.registerChecker( | |
| 1261 checkers.InMemoryUsernamePasswordDatabaseDontUse(user='pass')) | |
| 1262 factory = pb.PBClientFactory() | |
| 1263 | |
| 1264 firstLogin = factory.login( | |
| 1265 credentials.UsernamePassword('nosuchuser', 'pass')) | |
| 1266 secondLogin = factory.login( | |
| 1267 credentials.UsernamePassword('user', 'wrongpass')) | |
| 1268 | |
| 1269 self.assertFailure(firstLogin, UnauthorizedLogin) | |
| 1270 self.assertFailure(secondLogin, UnauthorizedLogin) | |
| 1271 d = gatherResults([firstLogin, secondLogin]) | |
| 1272 | |
| 1273 def cleanup(passthrough): | |
| 1274 self.flushLoggedErrors(UnauthorizedLogin) | |
| 1275 factory.disconnect() | |
| 1276 return passthrough | |
| 1277 d.addBoth(cleanup) | |
| 1278 | |
| 1279 reactor.connectTCP("127.0.0.1", self.portno, factory) | |
| 1280 return d | |
| 1281 | |
| 1282 | |
| 1283 def test_anonymousLogin(self): | |
| 1284 """ | |
| 1285 Verify that a PB server using a portal configured with an checker which | |
| 1286 allows IAnonymous credentials can be logged into using IAnonymous | |
| 1287 credentials. | |
| 1288 """ | |
| 1289 self.portal.registerChecker(checkers.AllowAnonymousAccess()) | |
| 1290 factory = pb.PBClientFactory() | |
| 1291 d = factory.login(credentials.Anonymous(), "BRAINS!") | |
| 1292 | |
| 1293 def cbLoggedIn(perspective): | |
| 1294 return perspective.callRemote('echo', 123) | |
| 1295 d.addCallback(cbLoggedIn) | |
| 1296 | |
| 1297 d.addCallback(self.assertEqual, 123) | |
| 1298 | |
| 1299 def cleanup(passthrough): | |
| 1300 factory.disconnect() | |
| 1301 d = Deferred() | |
| 1302 reactor.callLater(1.0, d.callback, None) | |
| 1303 return d | |
| 1304 d.addBoth(cleanup) | |
| 1305 | |
| 1306 reactor.connectTCP("127.0.0.1", self.portno, factory) | |
| 1307 return d | |
| 1308 | |
| 1309 | |
| 1310 def test_anonymousLoginNotPermitted(self): | |
| 1311 """ | |
| 1312 Verify that without an anonymous checker set up, anonymous login is | |
| 1313 rejected. | |
| 1314 """ | |
| 1315 self.portal.registerChecker( | |
| 1316 checkers.InMemoryUsernamePasswordDatabaseDontUse(user='pass')) | |
| 1317 factory = pb.PBClientFactory() | |
| 1318 d = factory.login(credentials.Anonymous(),"BRAINS!") | |
| 1319 self.assertFailure(d, UnhandledCredentials) | |
| 1320 | |
| 1321 def cleanup(passthrough): | |
| 1322 self.flushLoggedErrors(UnhandledCredentials) | |
| 1323 factory.disconnect() | |
| 1324 return passthrough | |
| 1325 d.addBoth(cleanup) | |
| 1326 | |
| 1327 reactor.connectTCP('127.0.0.1', self.portno, factory) | |
| 1328 return d | |
| 1329 | |
| 1330 | |
| 1331 def test_anonymousLoginWithMultipleCheckers(self): | |
| 1332 """ | |
| 1333 Like L{test_anonymousLogin} but against a portal with a checker for | |
| 1334 both IAnonymous and IUsernamePassword. | |
| 1335 """ | |
| 1336 self.portal.registerChecker(checkers.AllowAnonymousAccess()) | |
| 1337 self.portal.registerChecker( | |
| 1338 checkers.InMemoryUsernamePasswordDatabaseDontUse(user='pass')) | |
| 1339 factory = pb.PBClientFactory() | |
| 1340 d = factory.login(credentials.Anonymous(), "BRAINS!") | |
| 1341 | |
| 1342 def cbLogin(perspective): | |
| 1343 return perspective.callRemote('echo', 123) | |
| 1344 d.addCallback(cbLogin) | |
| 1345 | |
| 1346 d.addCallback(self.assertEqual, 123) | |
| 1347 | |
| 1348 def cleanup(passthrough): | |
| 1349 factory.disconnect() | |
| 1350 return passthrough | |
| 1351 d.addBoth(cleanup) | |
| 1352 | |
| 1353 reactor.connectTCP('127.0.0.1', self.portno, factory) | |
| 1354 return d | |
| 1355 | |
| 1356 | |
| 1357 def test_authenticatedLoginWithMultipleCheckers(self): | |
| 1358 """ | |
| 1359 Like L{test_anonymousLoginWithMultipleCheckers} but check that | |
| 1360 username/password authentication works. | |
| 1361 """ | |
| 1362 self.portal.registerChecker(checkers.AllowAnonymousAccess()) | |
| 1363 self.portal.registerChecker( | |
| 1364 checkers.InMemoryUsernamePasswordDatabaseDontUse(user='pass')) | |
| 1365 factory = pb.PBClientFactory() | |
| 1366 d = factory.login( | |
| 1367 credentials.UsernamePassword('user', 'pass'), "BRAINS!") | |
| 1368 | |
| 1369 def cbLogin(perspective): | |
| 1370 return perspective.callRemote('add', 100, 23) | |
| 1371 d.addCallback(cbLogin) | |
| 1372 | |
| 1373 d.addCallback(self.assertEqual, 123) | |
| 1374 | |
| 1375 def cleanup(passthrough): | |
| 1376 factory.disconnect() | |
| 1377 return passthrough | |
| 1378 d.addBoth(cleanup) | |
| 1379 | |
| 1380 reactor.connectTCP('127.0.0.1', self.portno, factory) | |
| 1381 return d | |
| 1382 | |
| 1383 | |
| 1384 def test_view(self): | |
| 1385 """ | |
| 1386 Verify that a viewpoint can be retrieved after authenticating with | |
| 1387 cred. | |
| 1388 """ | |
| 1389 self.portal.registerChecker( | |
| 1390 checkers.InMemoryUsernamePasswordDatabaseDontUse(user='pass')) | |
| 1391 factory = pb.PBClientFactory() | |
| 1392 d = factory.login( | |
| 1393 credentials.UsernamePassword("user", "pass"), "BRAINS!") | |
| 1394 | |
| 1395 def cbLogin(perspective): | |
| 1396 return perspective.callRemote("getViewPoint") | |
| 1397 d.addCallback(cbLogin) | |
| 1398 | |
| 1399 def cbView(viewpoint): | |
| 1400 return viewpoint.callRemote("check") | |
| 1401 d.addCallback(cbView) | |
| 1402 | |
| 1403 d.addCallback(self.failUnless) | |
| 1404 | |
| 1405 def cleanup(passthrough): | |
| 1406 factory.disconnect() | |
| 1407 d = Deferred() | |
| 1408 reactor.callLater(1.0, d.callback, None) | |
| 1409 return d | |
| 1410 d.addBoth(cleanup) | |
| 1411 | |
| 1412 reactor.connectTCP("127.0.0.1", self.portno, factory) | |
| 1413 return d | |
| 1414 | |
| 1415 | |
| 1416 | |
| 1417 class NonSubclassingPerspective: | |
| 1418 implements(pb.IPerspective) | |
| 1419 | |
| 1420 # IPerspective implementation | |
| 1421 def perspectiveMessageReceived(self, broker, message, args, kwargs): | |
| 1422 args = broker.unserialize(args, self) | |
| 1423 kwargs = broker.unserialize(kwargs, self) | |
| 1424 return broker.serialize((message, args, kwargs)) | |
| 1425 | |
| 1426 # Methods required by TestRealm | |
| 1427 def logout(self): | |
| 1428 self.loggedOut = True | |
| 1429 | |
| 1430 | |
| 1431 | |
| 1432 class NSPTestCase(unittest.TestCase): | |
| 1433 def setUp(self): | |
| 1434 self.realm = TestRealm() | |
| 1435 self.realm.p = NonSubclassingPerspective() | |
| 1436 self.portal = portal.Portal(self.realm) | |
| 1437 self.checker = checkers.InMemoryUsernamePasswordDatabaseDontUse() | |
| 1438 self.checker.addUser("user", "pass") | |
| 1439 self.portal.registerChecker(self.checker) | |
| 1440 self.factory = pb.PBServerFactory(self.portal) | |
| 1441 self.port = reactor.listenTCP(0, self.factory, interface="127.0.0.1") | |
| 1442 self.portno = self.port.getHost().port | |
| 1443 | |
| 1444 def tearDown(self): | |
| 1445 return self.port.stopListening() | |
| 1446 | |
| 1447 def test_NSP(self): | |
| 1448 factory = pb.PBClientFactory() | |
| 1449 d = factory.login(credentials.UsernamePassword('user', 'pass'), | |
| 1450 "BRAINS!") | |
| 1451 reactor.connectTCP('127.0.0.1', self.portno, factory) | |
| 1452 d.addCallback(lambda p: p.callRemote('ANYTHING', 'here', bar='baz')) | |
| 1453 d.addCallback(self.assertEquals, | |
| 1454 ('ANYTHING', ('here',), {'bar': 'baz'})) | |
| 1455 d.addCallback(lambda res: factory.disconnect()) | |
| 1456 return d | |
| 1457 | |
| 1458 | |
| 1459 | |
| 1460 class IForwarded(Interface): | |
| 1461 """ | |
| 1462 Interface used for testing L{util.LocalAsyncForwarder}. | |
| 1463 """ | |
| 1464 | |
| 1465 def forwardMe(): | |
| 1466 """ | |
| 1467 Simple synchronous method. | |
| 1468 """ | |
| 1469 | |
| 1470 def forwardDeferred(): | |
| 1471 """ | |
| 1472 Simple asynchronous method. | |
| 1473 """ | |
| 1474 | |
| 1475 | |
| 1476 class Forwarded: | |
| 1477 """ | |
| 1478 Test implementation of L{IForwarded}. | |
| 1479 | |
| 1480 @ivar forwarded: set if C{forwardMe} is called. | |
| 1481 @type forwarded: C{bool} | |
| 1482 @ivar unforwarded: set if C{dontForwardMe} is called. | |
| 1483 @type unforwarded: C{bool} | |
| 1484 """ | |
| 1485 implements(IForwarded) | |
| 1486 forwarded = False | |
| 1487 unforwarded = False | |
| 1488 | |
| 1489 def forwardMe(self): | |
| 1490 """ | |
| 1491 Set a local flag to test afterwards. | |
| 1492 """ | |
| 1493 self.forwarded = True | |
| 1494 | |
| 1495 def dontForwardMe(self): | |
| 1496 """ | |
| 1497 Set a local flag to test afterwards. This should not be called as it's | |
| 1498 not in the interface. | |
| 1499 """ | |
| 1500 self.unforwarded = True | |
| 1501 | |
| 1502 def forwardDeferred(self): | |
| 1503 """ | |
| 1504 Asynchronously return C{True}. | |
| 1505 """ | |
| 1506 return defer.succeed(True) | |
| 1507 | |
| 1508 | |
| 1509 class SpreadUtilTestCase(unittest.TestCase): | |
| 1510 """ | |
| 1511 Tests for L{twisted.spread.util}. | |
| 1512 """ | |
| 1513 | |
| 1514 def test_sync(self): | |
| 1515 """ | |
| 1516 Call a synchronous method of a L{util.LocalAsRemote} object and check | |
| 1517 the result. | |
| 1518 """ | |
| 1519 o = LocalRemoteTest() | |
| 1520 self.assertEquals(o.callRemote("add1", 2), 3) | |
| 1521 | |
| 1522 def test_async(self): | |
| 1523 """ | |
| 1524 Call an asynchronous method of a L{util.LocalAsRemote} object and check | |
| 1525 the result. | |
| 1526 """ | |
| 1527 o = LocalRemoteTest() | |
| 1528 o = LocalRemoteTest() | |
| 1529 d = o.callRemote("add", 2, y=4) | |
| 1530 self.assertTrue(isinstance(d, defer.Deferred)) | |
| 1531 d.addCallback(self.assertEquals, 6) | |
| 1532 return d | |
| 1533 | |
| 1534 def test_asyncFail(self): | |
| 1535 """ | |
| 1536 Test a asynchronous failure on a remote method call. | |
| 1537 """ | |
| 1538 l = [] | |
| 1539 o = LocalRemoteTest() | |
| 1540 d = o.callRemote("fail") | |
| 1541 def eb(f): | |
| 1542 self.assertTrue(isinstance(f, failure.Failure)) | |
| 1543 f.trap(RuntimeError) | |
| 1544 d.addCallbacks(lambda res: self.fail("supposed to fail"), eb) | |
| 1545 return d | |
| 1546 | |
| 1547 def test_remoteMethod(self): | |
| 1548 """ | |
| 1549 Test the C{remoteMethod} facility of L{util.LocalAsRemote}. | |
| 1550 """ | |
| 1551 o = LocalRemoteTest() | |
| 1552 m = o.remoteMethod("add1") | |
| 1553 self.assertEquals(m(3), 4) | |
| 1554 | |
| 1555 def test_localAsyncForwarder(self): | |
| 1556 """ | |
| 1557 Test a call to L{util.LocalAsyncForwarder} using L{Forwarded} local | |
| 1558 object. | |
| 1559 """ | |
| 1560 f = Forwarded() | |
| 1561 lf = util.LocalAsyncForwarder(f, IForwarded) | |
| 1562 lf.callRemote("forwardMe") | |
| 1563 self.assertTrue(f.forwarded) | |
| 1564 lf.callRemote("dontForwardMe") | |
| 1565 self.assertFalse(f.unforwarded) | |
| 1566 rr = lf.callRemote("forwardDeferred") | |
| 1567 l = [] | |
| 1568 rr.addCallback(l.append) | |
| 1569 self.assertEqual(l[0], 1) | |
| 1570 | |
| 1571 | |
| 1572 | |
| 1573 class PBWithSecurityOptionsTest(unittest.TestCase): | |
| 1574 """ | |
| 1575 Test security customization. | |
| 1576 """ | |
| 1577 | |
| 1578 def test_clientDefaultSecurityOptions(self): | |
| 1579 """ | |
| 1580 By default, client broker should use C{jelly.globalSecurity} as | |
| 1581 security settings. | |
| 1582 """ | |
| 1583 factory = pb.PBClientFactory() | |
| 1584 broker = factory.buildProtocol(None) | |
| 1585 self.assertIdentical(broker.security, jelly.globalSecurity) | |
| 1586 | |
| 1587 | |
| 1588 def test_serverDefaultSecurityOptions(self): | |
| 1589 """ | |
| 1590 By default, server broker should use C{jelly.globalSecurity} as | |
| 1591 security settings. | |
| 1592 """ | |
| 1593 factory = pb.PBServerFactory(Echoer()) | |
| 1594 broker = factory.buildProtocol(None) | |
| 1595 self.assertIdentical(broker.security, jelly.globalSecurity) | |
| 1596 | |
| 1597 | |
| 1598 def test_clientSecurityCustomization(self): | |
| 1599 """ | |
| 1600 Check that the security settings are passed from the client factory to | |
| 1601 the broker object. | |
| 1602 """ | |
| 1603 security = jelly.SecurityOptions() | |
| 1604 factory = pb.PBClientFactory(security=security) | |
| 1605 broker = factory.buildProtocol(None) | |
| 1606 self.assertIdentical(broker.security, security) | |
| 1607 | |
| 1608 | |
| 1609 def test_serverSecurityCustomization(self): | |
| 1610 """ | |
| 1611 Check that the security settings are passed from the server factory to | |
| 1612 the broker object. | |
| 1613 """ | |
| 1614 security = jelly.SecurityOptions() | |
| 1615 factory = pb.PBServerFactory(Echoer(), security=security) | |
| 1616 broker = factory.buildProtocol(None) | |
| 1617 self.assertIdentical(broker.security, security) | |
| 1618 | |
| OLD | NEW |