test_win32events.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. # Copyright (c) Twisted Matrix Laboratories.
  2. # See LICENSE for details.
  3. """
  4. Tests for implementations of L{IReactorWin32Events}.
  5. """
  6. try:
  7. import win32event
  8. except ImportError:
  9. win32event = None
  10. from zope.interface.verify import verifyObject
  11. from twisted.python.failure import Failure
  12. from twisted.python.threadable import isInIOThread
  13. from twisted.internet.interfaces import IReactorWin32Events
  14. from twisted.internet.defer import Deferred
  15. from twisted.internet.test.reactormixins import ReactorBuilder
  16. from twisted.python.threadable import getThreadID
  17. class Listener(object):
  18. """
  19. L{Listener} is an object that can be added to a L{IReactorWin32Events}
  20. reactor to receive callback notification when a Windows event is set. It
  21. records what thread its callback is invoked in and fires a Deferred.
  22. @ivar success: A flag which is set to C{True} when the event callback is
  23. called.
  24. @ivar logThreadID: The id of the thread in which the C{logPrefix} method is
  25. called.
  26. @ivar eventThreadID: The id of the thread in which the event callback is
  27. called.
  28. @ivar connLostThreadID: The id of the thread in which the C{connectionLost}
  29. method is called.
  30. @ivar _finished: The L{Deferred} which will be fired when the event callback
  31. is called.
  32. """
  33. success = False
  34. logThreadID = eventThreadID = connLostThreadID = None
  35. def __init__(self, finished):
  36. self._finished = finished
  37. def logPrefix(self):
  38. self.logThreadID = getThreadID()
  39. return 'Listener'
  40. def occurred(self):
  41. self.success = True
  42. self.eventThreadID = getThreadID()
  43. self._finished.callback(None)
  44. def brokenOccurred(self):
  45. raise RuntimeError("Some problem")
  46. def returnValueOccurred(self):
  47. return EnvironmentError("Entirely different problem")
  48. def connectionLost(self, reason):
  49. self.connLostThreadID = getThreadID()
  50. self._finished.errback(reason)
  51. class Win32EventsTestsBuilder(ReactorBuilder):
  52. """
  53. Builder defining tests relating to L{IReactorWin32Events}.
  54. """
  55. requiredInterfaces = [IReactorWin32Events]
  56. def test_interface(self):
  57. """
  58. An instance of the reactor has all of the methods defined on
  59. L{IReactorWin32Events}.
  60. """
  61. reactor = self.buildReactor()
  62. verifyObject(IReactorWin32Events, reactor)
  63. def test_addEvent(self):
  64. """
  65. When an event which has been added to the reactor is set, the action
  66. associated with the event is invoked in the reactor thread.
  67. """
  68. reactorThreadID = getThreadID()
  69. reactor = self.buildReactor()
  70. event = win32event.CreateEvent(None, False, False, None)
  71. finished = Deferred()
  72. finished.addCallback(lambda ignored: reactor.stop())
  73. listener = Listener(finished)
  74. reactor.addEvent(event, listener, 'occurred')
  75. reactor.callWhenRunning(win32event.SetEvent, event)
  76. self.runReactor(reactor)
  77. self.assertTrue(listener.success)
  78. self.assertEqual(reactorThreadID, listener.logThreadID)
  79. self.assertEqual(reactorThreadID, listener.eventThreadID)
  80. def test_ioThreadDoesNotChange(self):
  81. """
  82. Using L{IReactorWin32Events.addEvent} does not change which thread is
  83. reported as the I/O thread.
  84. """
  85. results = []
  86. def check(ignored):
  87. results.append(isInIOThread())
  88. reactor.stop()
  89. reactor = self.buildReactor()
  90. event = win32event.CreateEvent(None, False, False, None)
  91. finished = Deferred()
  92. listener = Listener(finished)
  93. finished.addCallback(check)
  94. reactor.addEvent(event, listener, 'occurred')
  95. reactor.callWhenRunning(win32event.SetEvent, event)
  96. self.runReactor(reactor)
  97. self.assertTrue(listener.success)
  98. self.assertEqual([True], results)
  99. def test_disconnectedOnError(self):
  100. """
  101. If the event handler raises an exception, the event is removed from the
  102. reactor and the handler's C{connectionLost} method is called in the I/O
  103. thread and the exception is logged.
  104. """
  105. reactorThreadID = getThreadID()
  106. reactor = self.buildReactor()
  107. event = win32event.CreateEvent(None, False, False, None)
  108. result = []
  109. finished = Deferred()
  110. finished.addBoth(result.append)
  111. finished.addBoth(lambda ignored: reactor.stop())
  112. listener = Listener(finished)
  113. reactor.addEvent(event, listener, 'brokenOccurred')
  114. reactor.callWhenRunning(win32event.SetEvent, event)
  115. self.runReactor(reactor)
  116. self.assertIsInstance(result[0], Failure)
  117. result[0].trap(RuntimeError)
  118. self.assertEqual(reactorThreadID, listener.connLostThreadID)
  119. self.assertEqual(1, len(self.flushLoggedErrors(RuntimeError)))
  120. def test_disconnectOnReturnValue(self):
  121. """
  122. If the event handler returns a value, the event is removed from the
  123. reactor and the handler's C{connectionLost} method is called in the I/O
  124. thread.
  125. """
  126. reactorThreadID = getThreadID()
  127. reactor = self.buildReactor()
  128. event = win32event.CreateEvent(None, False, False, None)
  129. result = []
  130. finished = Deferred()
  131. finished.addBoth(result.append)
  132. finished.addBoth(lambda ignored: reactor.stop())
  133. listener = Listener(finished)
  134. reactor.addEvent(event, listener, 'returnValueOccurred')
  135. reactor.callWhenRunning(win32event.SetEvent, event)
  136. self.runReactor(reactor)
  137. self.assertIsInstance(result[0], Failure)
  138. result[0].trap(EnvironmentError)
  139. self.assertEqual(reactorThreadID, listener.connLostThreadID)
  140. def test_notDisconnectedOnShutdown(self):
  141. """
  142. Event handlers added with L{IReactorWin32Events.addEvent} do not have
  143. C{connectionLost} called on them if they are still active when the
  144. reactor shuts down.
  145. """
  146. reactor = self.buildReactor()
  147. event = win32event.CreateEvent(None, False, False, None)
  148. finished = Deferred()
  149. listener = Listener(finished)
  150. reactor.addEvent(event, listener, 'occurred')
  151. reactor.callWhenRunning(reactor.stop)
  152. self.runReactor(reactor)
  153. self.assertIsNone(listener.connLostThreadID)
  154. globals().update(Win32EventsTestsBuilder.makeTestCaseClasses())