1 # Copyright (c) 2007-2010 Twisted Matrix Laboratories.
2 # See LICENSE for details
5 This module tests twisted.conch.ssh.connection.
10 from twisted.conch import error
11 from twisted.conch.ssh import channel, common, connection
12 from twisted.trial import unittest
13 from twisted.conch.test import test_userauth
16 class TestChannel(channel.SSHChannel):
18 A mocked-up version of twisted.conch.ssh.channel.SSHChannel.
20 @ivar gotOpen: True if channelOpen has been called.
21 @type gotOpen: C{bool}
22 @ivar specificData: the specific channel open data passed to channelOpen.
23 @type specificData: C{str}
24 @ivar openFailureReason: the reason passed to openFailed.
25 @type openFailed: C{error.ConchError}
26 @ivar inBuffer: a C{list} of strings received by the channel.
27 @type inBuffer: C{list}
28 @ivar extBuffer: a C{list} of 2-tuples (type, extended data) of received by
30 @type extBuffer: C{list}
31 @ivar numberRequests: the number of requests that have been made to this
33 @type numberRequests: C{int}
34 @ivar gotEOF: True if the other side sent EOF.
36 @ivar gotOneClose: True if the other side closed the connection.
37 @type gotOneClose: C{bool}
38 @ivar gotClosed: True if the channel is closed.
39 @type gotClosed: C{bool}
45 return "TestChannel %i" % self.id
47 def channelOpen(self, specificData):
49 The channel is open. Set up the instance variables.
52 self.specificData = specificData
55 self.numberRequests = 0
57 self.gotOneClose = False
58 self.gotClosed = False
60 def openFailed(self, reason):
62 Opening the channel failed. Store the reason why.
64 self.openFailureReason = reason
66 def request_test(self, data):
68 A test request. Return True if data is 'data'.
72 self.numberRequests += 1
75 def dataReceived(self, data):
77 Data was received. Store it in the buffer.
79 self.inBuffer.append(data)
81 def extReceived(self, code, data):
83 Extended data was received. Store it in the buffer.
85 self.extBuffer.append((code, data))
87 def eofReceived(self):
89 EOF was received. Remember it.
93 def closeReceived(self):
95 Close was received. Remember it.
97 self.gotOneClose = True
101 The channel is closed. Rembember it.
103 self.gotClosed = True
107 A mocked-up version of twisted.conch.avatar.ConchUser
109 _ARGS_ERROR_CODE = 123
111 def lookupChannel(self, channelType, windowSize, maxPacket, data):
113 The server wants us to return a channel. If the requested channel is
114 our TestChannel, return it, otherwise return None.
116 if channelType == TestChannel.name:
117 return TestChannel(remoteWindow=windowSize,
118 remoteMaxPacket=maxPacket,
119 data=data, avatar=self)
120 elif channelType == "conch-error-args":
121 # Raise a ConchError with backwards arguments to make sure the
122 # connection fixes it for us. This case should be deprecated and
123 # deleted eventually, but only after all of Conch gets the argument
125 raise error.ConchError(
126 self._ARGS_ERROR_CODE, "error args in wrong order")
129 def gotGlobalRequest(self, requestType, data):
131 The client has made a global request. If the global request is
132 'TestGlobal', return True. If the global request is 'TestData',
133 return True and the request-specific data we received. Otherwise,
136 if requestType == 'TestGlobal':
138 elif requestType == 'TestData':
145 class TestConnection(connection.SSHConnection):
147 A subclass of SSHConnection for testing.
149 @ivar channel: the current channel.
150 @type channel. C{TestChannel}
154 return "TestConnection"
156 def global_TestGlobal(self, data):
158 The other side made the 'TestGlobal' global request. Return True.
162 def global_Test_Data(self, data):
164 The other side made the 'Test-Data' global request. Return True and
165 the data we received.
169 def channel_TestChannel(self, windowSize, maxPacket, data):
171 The other side is requesting the TestChannel. Create a C{TestChannel}
172 instance, store it, and return it.
174 self.channel = TestChannel(remoteWindow=windowSize,
175 remoteMaxPacket=maxPacket, data=data)
178 def channel_ErrorChannel(self, windowSize, maxPacket, data):
180 The other side is requesting the ErrorChannel. Raise an exception.
182 raise AssertionError('no such thing')
186 class ConnectionTestCase(unittest.TestCase):
188 if test_userauth.transport is None:
189 skip = "Cannot run without both PyCrypto and pyasn1"
192 self.transport = test_userauth.FakeTransport(None)
193 self.transport.avatar = TestAvatar()
194 self.conn = TestConnection()
195 self.conn.transport = self.transport
196 self.conn.serviceStarted()
198 def _openChannel(self, channel):
200 Open the channel with the default connection.
202 self.conn.openChannel(channel)
203 self.transport.packets = self.transport.packets[:-1]
204 self.conn.ssh_CHANNEL_OPEN_CONFIRMATION(struct.pack('>2L',
205 channel.id, 255) + '\x00\x02\x00\x00\x00\x00\x80\x00')
208 self.conn.serviceStopped()
210 def test_linkAvatar(self):
212 Test that the connection links itself to the avatar in the
215 self.assertIdentical(self.transport.avatar.conn, self.conn)
217 def test_serviceStopped(self):
219 Test that serviceStopped() closes any open channels.
221 channel1 = TestChannel()
222 channel2 = TestChannel()
223 self.conn.openChannel(channel1)
224 self.conn.openChannel(channel2)
225 self.conn.ssh_CHANNEL_OPEN_CONFIRMATION('\x00\x00\x00\x00' * 4)
226 self.assertTrue(channel1.gotOpen)
227 self.assertFalse(channel2.gotOpen)
228 self.conn.serviceStopped()
229 self.assertTrue(channel1.gotClosed)
231 def test_GLOBAL_REQUEST(self):
233 Test that global request packets are dispatched to the global_*
234 methods and the return values are translated into success or failure
237 self.conn.ssh_GLOBAL_REQUEST(common.NS('TestGlobal') + '\xff')
238 self.assertEqual(self.transport.packets,
239 [(connection.MSG_REQUEST_SUCCESS, '')])
240 self.transport.packets = []
241 self.conn.ssh_GLOBAL_REQUEST(common.NS('TestData') + '\xff' +
243 self.assertEqual(self.transport.packets,
244 [(connection.MSG_REQUEST_SUCCESS, 'test data')])
245 self.transport.packets = []
246 self.conn.ssh_GLOBAL_REQUEST(common.NS('TestBad') + '\xff')
247 self.assertEqual(self.transport.packets,
248 [(connection.MSG_REQUEST_FAILURE, '')])
249 self.transport.packets = []
250 self.conn.ssh_GLOBAL_REQUEST(common.NS('TestGlobal') + '\x00')
251 self.assertEqual(self.transport.packets, [])
253 def test_REQUEST_SUCCESS(self):
255 Test that global request success packets cause the Deferred to be
258 d = self.conn.sendGlobalRequest('request', 'data', True)
259 self.conn.ssh_REQUEST_SUCCESS('data')
261 self.assertEqual(data, 'data')
263 d.addErrback(self.fail)
266 def test_REQUEST_FAILURE(self):
268 Test that global request failure packets cause the Deferred to be
271 d = self.conn.sendGlobalRequest('request', 'data', True)
272 self.conn.ssh_REQUEST_FAILURE('data')
274 self.assertEqual(f.value.data, 'data')
275 d.addCallback(self.fail)
279 def test_CHANNEL_OPEN(self):
281 Test that open channel packets cause a channel to be created and
282 opened or a failure message to be returned.
284 del self.transport.avatar
285 self.conn.ssh_CHANNEL_OPEN(common.NS('TestChannel') +
286 '\x00\x00\x00\x01' * 4)
287 self.assertTrue(self.conn.channel.gotOpen)
288 self.assertEqual(self.conn.channel.conn, self.conn)
289 self.assertEqual(self.conn.channel.data, '\x00\x00\x00\x01')
290 self.assertEqual(self.conn.channel.specificData, '\x00\x00\x00\x01')
291 self.assertEqual(self.conn.channel.remoteWindowLeft, 1)
292 self.assertEqual(self.conn.channel.remoteMaxPacket, 1)
293 self.assertEqual(self.transport.packets,
294 [(connection.MSG_CHANNEL_OPEN_CONFIRMATION,
295 '\x00\x00\x00\x01\x00\x00\x00\x00\x00\x02\x00\x00'
296 '\x00\x00\x80\x00')])
297 self.transport.packets = []
298 self.conn.ssh_CHANNEL_OPEN(common.NS('BadChannel') +
299 '\x00\x00\x00\x02' * 4)
300 self.flushLoggedErrors()
301 self.assertEqual(self.transport.packets,
302 [(connection.MSG_CHANNEL_OPEN_FAILURE,
303 '\x00\x00\x00\x02\x00\x00\x00\x03' + common.NS(
304 'unknown channel') + common.NS(''))])
305 self.transport.packets = []
306 self.conn.ssh_CHANNEL_OPEN(common.NS('ErrorChannel') +
307 '\x00\x00\x00\x02' * 4)
308 self.flushLoggedErrors()
309 self.assertEqual(self.transport.packets,
310 [(connection.MSG_CHANNEL_OPEN_FAILURE,
311 '\x00\x00\x00\x02\x00\x00\x00\x02' + common.NS(
312 'unknown failure') + common.NS(''))])
315 def _lookupChannelErrorTest(self, code):
317 Deliver a request for a channel open which will result in an exception
318 being raised during channel lookup. Assert that an error response is
319 delivered as a result.
321 self.transport.avatar._ARGS_ERROR_CODE = code
322 self.conn.ssh_CHANNEL_OPEN(
323 common.NS('conch-error-args') + '\x00\x00\x00\x01' * 4)
324 errors = self.flushLoggedErrors(error.ConchError)
326 len(errors), 1, "Expected one error, got: %r" % (errors,))
327 self.assertEqual(errors[0].value.args, (123, "error args in wrong order"))
329 self.transport.packets,
330 [(connection.MSG_CHANNEL_OPEN_FAILURE,
331 # The response includes some bytes which identifying the
332 # associated request, as well as the error code (7b in hex) and
334 '\x00\x00\x00\x01\x00\x00\x00\x7b' + common.NS(
335 'error args in wrong order') + common.NS(''))])
338 def test_lookupChannelError(self):
340 If a C{lookupChannel} implementation raises L{error.ConchError} with the
341 arguments in the wrong order, a C{MSG_CHANNEL_OPEN} failure is still
342 sent in response to the message.
344 This is a temporary work-around until L{error.ConchError} is given
345 better attributes and all of the Conch code starts constructing
346 instances of it properly. Eventually this functionality should be
347 deprecated and then removed.
349 self._lookupChannelErrorTest(123)
352 def test_lookupChannelErrorLongCode(self):
354 Like L{test_lookupChannelError}, but for the case where the failure code
355 is represented as a C{long} instead of a C{int}.
357 self._lookupChannelErrorTest(123L)
360 def test_CHANNEL_OPEN_CONFIRMATION(self):
362 Test that channel open confirmation packets cause the channel to be
363 notified that it's open.
365 channel = TestChannel()
366 self.conn.openChannel(channel)
367 self.conn.ssh_CHANNEL_OPEN_CONFIRMATION('\x00\x00\x00\x00'*5)
368 self.assertEqual(channel.remoteWindowLeft, 0)
369 self.assertEqual(channel.remoteMaxPacket, 0)
370 self.assertEqual(channel.specificData, '\x00\x00\x00\x00')
371 self.assertEqual(self.conn.channelsToRemoteChannel[channel],
373 self.assertEqual(self.conn.localToRemoteChannel[0], 0)
375 def test_CHANNEL_OPEN_FAILURE(self):
377 Test that channel open failure packets cause the channel to be
378 notified that its opening failed.
380 channel = TestChannel()
381 self.conn.openChannel(channel)
382 self.conn.ssh_CHANNEL_OPEN_FAILURE('\x00\x00\x00\x00\x00\x00\x00'
383 '\x01' + common.NS('failure!'))
384 self.assertEqual(channel.openFailureReason.args, ('failure!', 1))
385 self.assertEqual(self.conn.channels.get(channel), None)
388 def test_CHANNEL_WINDOW_ADJUST(self):
390 Test that channel window adjust messages add bytes to the channel
393 channel = TestChannel()
394 self._openChannel(channel)
395 oldWindowSize = channel.remoteWindowLeft
396 self.conn.ssh_CHANNEL_WINDOW_ADJUST('\x00\x00\x00\x00\x00\x00\x00'
398 self.assertEqual(channel.remoteWindowLeft, oldWindowSize + 1)
400 def test_CHANNEL_DATA(self):
402 Test that channel data messages are passed up to the channel, or
403 cause the channel to be closed if the data is too large.
405 channel = TestChannel(localWindow=6, localMaxPacket=5)
406 self._openChannel(channel)
407 self.conn.ssh_CHANNEL_DATA('\x00\x00\x00\x00' + common.NS('data'))
408 self.assertEqual(channel.inBuffer, ['data'])
409 self.assertEqual(self.transport.packets,
410 [(connection.MSG_CHANNEL_WINDOW_ADJUST, '\x00\x00\x00\xff'
411 '\x00\x00\x00\x04')])
412 self.transport.packets = []
413 longData = 'a' * (channel.localWindowLeft + 1)
414 self.conn.ssh_CHANNEL_DATA('\x00\x00\x00\x00' + common.NS(longData))
415 self.assertEqual(channel.inBuffer, ['data'])
416 self.assertEqual(self.transport.packets,
417 [(connection.MSG_CHANNEL_CLOSE, '\x00\x00\x00\xff')])
418 channel = TestChannel()
419 self._openChannel(channel)
420 bigData = 'a' * (channel.localMaxPacket + 1)
421 self.transport.packets = []
422 self.conn.ssh_CHANNEL_DATA('\x00\x00\x00\x01' + common.NS(bigData))
423 self.assertEqual(channel.inBuffer, [])
424 self.assertEqual(self.transport.packets,
425 [(connection.MSG_CHANNEL_CLOSE, '\x00\x00\x00\xff')])
427 def test_CHANNEL_EXTENDED_DATA(self):
429 Test that channel extended data messages are passed up to the channel,
430 or cause the channel to be closed if they're too big.
432 channel = TestChannel(localWindow=6, localMaxPacket=5)
433 self._openChannel(channel)
434 self.conn.ssh_CHANNEL_EXTENDED_DATA('\x00\x00\x00\x00\x00\x00\x00'
435 '\x00' + common.NS('data'))
436 self.assertEqual(channel.extBuffer, [(0, 'data')])
437 self.assertEqual(self.transport.packets,
438 [(connection.MSG_CHANNEL_WINDOW_ADJUST, '\x00\x00\x00\xff'
439 '\x00\x00\x00\x04')])
440 self.transport.packets = []
441 longData = 'a' * (channel.localWindowLeft + 1)
442 self.conn.ssh_CHANNEL_EXTENDED_DATA('\x00\x00\x00\x00\x00\x00\x00'
443 '\x00' + common.NS(longData))
444 self.assertEqual(channel.extBuffer, [(0, 'data')])
445 self.assertEqual(self.transport.packets,
446 [(connection.MSG_CHANNEL_CLOSE, '\x00\x00\x00\xff')])
447 channel = TestChannel()
448 self._openChannel(channel)
449 bigData = 'a' * (channel.localMaxPacket + 1)
450 self.transport.packets = []
451 self.conn.ssh_CHANNEL_EXTENDED_DATA('\x00\x00\x00\x01\x00\x00\x00'
452 '\x00' + common.NS(bigData))
453 self.assertEqual(channel.extBuffer, [])
454 self.assertEqual(self.transport.packets,
455 [(connection.MSG_CHANNEL_CLOSE, '\x00\x00\x00\xff')])
457 def test_CHANNEL_EOF(self):
459 Test that channel eof messages are passed up to the channel.
461 channel = TestChannel()
462 self._openChannel(channel)
463 self.conn.ssh_CHANNEL_EOF('\x00\x00\x00\x00')
464 self.assertTrue(channel.gotEOF)
466 def test_CHANNEL_CLOSE(self):
468 Test that channel close messages are passed up to the channel. Also,
469 test that channel.close() is called if both sides are closed when this
472 channel = TestChannel()
473 self._openChannel(channel)
474 self.conn.sendClose(channel)
475 self.conn.ssh_CHANNEL_CLOSE('\x00\x00\x00\x00')
476 self.assertTrue(channel.gotOneClose)
477 self.assertTrue(channel.gotClosed)
479 def test_CHANNEL_REQUEST_success(self):
481 Test that channel requests that succeed send MSG_CHANNEL_SUCCESS.
483 channel = TestChannel()
484 self._openChannel(channel)
485 self.conn.ssh_CHANNEL_REQUEST('\x00\x00\x00\x00' + common.NS('test')
487 self.assertEqual(channel.numberRequests, 1)
488 d = self.conn.ssh_CHANNEL_REQUEST('\x00\x00\x00\x00' + common.NS(
489 'test') + '\xff' + 'data')
491 self.assertEqual(self.transport.packets,
492 [(connection.MSG_CHANNEL_SUCCESS, '\x00\x00\x00\xff')])
496 def test_CHANNEL_REQUEST_failure(self):
498 Test that channel requests that fail send MSG_CHANNEL_FAILURE.
500 channel = TestChannel()
501 self._openChannel(channel)
502 d = self.conn.ssh_CHANNEL_REQUEST('\x00\x00\x00\x00' + common.NS(
505 self.assertEqual(self.transport.packets,
506 [(connection.MSG_CHANNEL_FAILURE, '\x00\x00\x00\xff'
508 d.addCallback(self.fail)
512 def test_CHANNEL_REQUEST_SUCCESS(self):
514 Test that channel request success messages cause the Deferred to be
517 channel = TestChannel()
518 self._openChannel(channel)
519 d = self.conn.sendRequest(channel, 'test', 'data', True)
520 self.conn.ssh_CHANNEL_SUCCESS('\x00\x00\x00\x00')
522 self.assertTrue(result)
525 def test_CHANNEL_REQUEST_FAILURE(self):
527 Test that channel request failure messages cause the Deferred to be
530 channel = TestChannel()
531 self._openChannel(channel)
532 d = self.conn.sendRequest(channel, 'test', '', True)
533 self.conn.ssh_CHANNEL_FAILURE('\x00\x00\x00\x00')
535 self.assertEqual(result.value.value, 'channel request failed')
536 d.addCallback(self.fail)
540 def test_sendGlobalRequest(self):
542 Test that global request messages are sent in the right format.
544 d = self.conn.sendGlobalRequest('wantReply', 'data', True)
545 # must be added to prevent errbacking during teardown
546 d.addErrback(lambda failure: None)
547 self.conn.sendGlobalRequest('noReply', '', False)
548 self.assertEqual(self.transport.packets,
549 [(connection.MSG_GLOBAL_REQUEST, common.NS('wantReply') +
551 (connection.MSG_GLOBAL_REQUEST, common.NS('noReply') +
553 self.assertEqual(self.conn.deferreds, {'global':[d]})
555 def test_openChannel(self):
557 Test that open channel messages are sent in the right format.
559 channel = TestChannel()
560 self.conn.openChannel(channel, 'aaaa')
561 self.assertEqual(self.transport.packets,
562 [(connection.MSG_CHANNEL_OPEN, common.NS('TestChannel') +
563 '\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x80\x00aaaa')])
564 self.assertEqual(channel.id, 0)
565 self.assertEqual(self.conn.localChannelID, 1)
567 def test_sendRequest(self):
569 Test that channel request messages are sent in the right format.
571 channel = TestChannel()
572 self._openChannel(channel)
573 d = self.conn.sendRequest(channel, 'test', 'test', True)
574 # needed to prevent errbacks during teardown.
575 d.addErrback(lambda failure: None)
576 self.conn.sendRequest(channel, 'test2', '', False)
577 channel.localClosed = True # emulate sending a close message
578 self.conn.sendRequest(channel, 'test3', '', True)
579 self.assertEqual(self.transport.packets,
580 [(connection.MSG_CHANNEL_REQUEST, '\x00\x00\x00\xff' +
581 common.NS('test') + '\x01test'),
582 (connection.MSG_CHANNEL_REQUEST, '\x00\x00\x00\xff' +
583 common.NS('test2') + '\x00')])
584 self.assertEqual(self.conn.deferreds[0], [d])
586 def test_adjustWindow(self):
588 Test that channel window adjust messages cause bytes to be added
591 channel = TestChannel(localWindow=5)
592 self._openChannel(channel)
593 channel.localWindowLeft = 0
594 self.conn.adjustWindow(channel, 1)
595 self.assertEqual(channel.localWindowLeft, 1)
596 channel.localClosed = True
597 self.conn.adjustWindow(channel, 2)
598 self.assertEqual(channel.localWindowLeft, 1)
599 self.assertEqual(self.transport.packets,
600 [(connection.MSG_CHANNEL_WINDOW_ADJUST, '\x00\x00\x00\xff'
601 '\x00\x00\x00\x01')])
603 def test_sendData(self):
605 Test that channel data messages are sent in the right format.
607 channel = TestChannel()
608 self._openChannel(channel)
609 self.conn.sendData(channel, 'a')
610 channel.localClosed = True
611 self.conn.sendData(channel, 'b')
612 self.assertEqual(self.transport.packets,
613 [(connection.MSG_CHANNEL_DATA, '\x00\x00\x00\xff' +
616 def test_sendExtendedData(self):
618 Test that channel extended data messages are sent in the right format.
620 channel = TestChannel()
621 self._openChannel(channel)
622 self.conn.sendExtendedData(channel, 1, 'test')
623 channel.localClosed = True
624 self.conn.sendExtendedData(channel, 2, 'test2')
625 self.assertEqual(self.transport.packets,
626 [(connection.MSG_CHANNEL_EXTENDED_DATA, '\x00\x00\x00\xff' +
627 '\x00\x00\x00\x01' + common.NS('test'))])
629 def test_sendEOF(self):
631 Test that channel EOF messages are sent in the right format.
633 channel = TestChannel()
634 self._openChannel(channel)
635 self.conn.sendEOF(channel)
636 self.assertEqual(self.transport.packets,
637 [(connection.MSG_CHANNEL_EOF, '\x00\x00\x00\xff')])
638 channel.localClosed = True
639 self.conn.sendEOF(channel)
640 self.assertEqual(self.transport.packets,
641 [(connection.MSG_CHANNEL_EOF, '\x00\x00\x00\xff')])
643 def test_sendClose(self):
645 Test that channel close messages are sent in the right format.
647 channel = TestChannel()
648 self._openChannel(channel)
649 self.conn.sendClose(channel)
650 self.assertTrue(channel.localClosed)
651 self.assertEqual(self.transport.packets,
652 [(connection.MSG_CHANNEL_CLOSE, '\x00\x00\x00\xff')])
653 self.conn.sendClose(channel)
654 self.assertEqual(self.transport.packets,
655 [(connection.MSG_CHANNEL_CLOSE, '\x00\x00\x00\xff')])
657 channel2 = TestChannel()
658 self._openChannel(channel2)
659 channel2.remoteClosed = True
660 self.conn.sendClose(channel2)
661 self.assertTrue(channel2.gotClosed)
663 def test_getChannelWithAvatar(self):
665 Test that getChannel dispatches to the avatar when an avatar is
666 present. Correct functioning without the avatar is verified in
669 channel = self.conn.getChannel('TestChannel', 50, 30, 'data')
670 self.assertEqual(channel.data, 'data')
671 self.assertEqual(channel.remoteWindowLeft, 50)
672 self.assertEqual(channel.remoteMaxPacket, 30)
673 self.assertRaises(error.ConchError, self.conn.getChannel,
674 'BadChannel', 50, 30, 'data')
676 def test_gotGlobalRequestWithoutAvatar(self):
678 Test that gotGlobalRequests dispatches to global_* without an avatar.
680 del self.transport.avatar
681 self.assertTrue(self.conn.gotGlobalRequest('TestGlobal', 'data'))
682 self.assertEqual(self.conn.gotGlobalRequest('Test-Data', 'data'),
684 self.assertFalse(self.conn.gotGlobalRequest('BadGlobal', 'data'))
687 def test_channelClosedCausesLeftoverChannelDeferredsToErrback(self):
689 Whenever an SSH channel gets closed any Deferred that was returned by a
690 sendRequest() on its parent connection must be errbacked.
692 channel = TestChannel()
693 self._openChannel(channel)
695 d = self.conn.sendRequest(
696 channel, "dummyrequest", "dummydata", wantReply=1)
697 d = self.assertFailure(d, error.ConchError)
698 self.conn.channelClosed(channel)
703 class TestCleanConnectionShutdown(unittest.TestCase):
705 Check whether correct cleanup is performed on connection shutdown.
707 if test_userauth.transport is None:
708 skip = "Cannot run without both PyCrypto and pyasn1"
711 self.transport = test_userauth.FakeTransport(None)
712 self.transport.avatar = TestAvatar()
713 self.conn = TestConnection()
714 self.conn.transport = self.transport
717 def test_serviceStoppedCausesLeftoverGlobalDeferredsToErrback(self):
719 Once the service is stopped any leftover global deferred returned by
720 a sendGlobalRequest() call must be errbacked.
722 self.conn.serviceStarted()
724 d = self.conn.sendGlobalRequest(
725 "dummyrequest", "dummydata", wantReply=1)
726 d = self.assertFailure(d, error.ConchError)
727 self.conn.serviceStopped()