workerreporter.py 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. # -*- test-case-name: twisted.trial._dist.test.test_workerreporter -*-
  2. #
  3. # Copyright (c) Twisted Matrix Laboratories.
  4. # See LICENSE for details.
  5. """
  6. Test reporter forwarding test results over trial distributed AMP commands.
  7. @since: 12.3
  8. """
  9. from twisted.python.compat import unicode
  10. from twisted.python.failure import Failure
  11. from twisted.python.reflect import qual
  12. from twisted.trial.reporter import TestResult
  13. from twisted.trial._dist import managercommands
  14. class WorkerReporter(TestResult):
  15. """
  16. Reporter for trial's distributed workers. We send things not through a
  17. stream, but through an C{AMP} protocol's C{callRemote} method.
  18. @ivar _DEFAULT_TODO: Default message for expected failures and
  19. unexpected successes, used only if a C{Todo} is not provided.
  20. """
  21. _DEFAULT_TODO = 'Test expected to fail'
  22. def __init__(self, ampProtocol):
  23. """
  24. @param ampProtocol: The communication channel with the trial
  25. distributed manager which collects all test results.
  26. @type ampProtocol: C{AMP}
  27. """
  28. super(WorkerReporter, self).__init__()
  29. self.ampProtocol = ampProtocol
  30. def _getFailure(self, error):
  31. """
  32. Convert a C{sys.exc_info()}-style tuple to a L{Failure}, if necessary.
  33. """
  34. if isinstance(error, tuple):
  35. return Failure(error[1], error[0], error[2])
  36. return error
  37. def _getFrames(self, failure):
  38. """
  39. Extract frames from a C{Failure} instance.
  40. """
  41. frames = []
  42. for frame in failure.frames:
  43. frames.extend([frame[0], frame[1], str(frame[2])])
  44. return frames
  45. def addSuccess(self, test):
  46. """
  47. Send a success over.
  48. """
  49. super(WorkerReporter, self).addSuccess(test)
  50. testName = test.id()
  51. if isinstance(testName, unicode):
  52. testName = testName.encode("utf-8")
  53. self.ampProtocol.callRemote(managercommands.AddSuccess,
  54. testName=testName)
  55. def addError(self, test, error):
  56. """
  57. Send an error over.
  58. """
  59. super(WorkerReporter, self).addError(test, error)
  60. testName = test.id()
  61. if isinstance(testName, unicode):
  62. testName = testName.encode("utf-8")
  63. failure = self._getFailure(error)
  64. error = failure.getErrorMessage().encode("utf-8")
  65. errorClass = qual(failure.type).encode("utf-8")
  66. frames = [frame.encode("utf-8") for frame in self._getFrames(failure)]
  67. self.ampProtocol.callRemote(managercommands.AddError,
  68. testName=testName,
  69. error=error,
  70. errorClass=errorClass,
  71. frames=frames)
  72. def addFailure(self, test, fail):
  73. """
  74. Send a Failure over.
  75. """
  76. super(WorkerReporter, self).addFailure(test, fail)
  77. testName = test.id()
  78. if isinstance(testName, unicode):
  79. testName = testName.encode("utf-8")
  80. failure = self._getFailure(fail)
  81. fail = failure.getErrorMessage().encode("utf-8")
  82. failClass = qual(failure.type).encode("utf-8")
  83. frames = [frame.encode("utf-8") for frame in self._getFrames(failure)]
  84. self.ampProtocol.callRemote(managercommands.AddFailure,
  85. testName=testName,
  86. fail=fail,
  87. failClass=failClass,
  88. frames=frames)
  89. def addSkip(self, test, reason):
  90. """
  91. Send a skip over.
  92. """
  93. super(WorkerReporter, self).addSkip(test, reason)
  94. reason = str(reason).encode("utf-8")
  95. testName = test.id()
  96. if isinstance(testName, unicode):
  97. testName = testName.encode("utf-8")
  98. self.ampProtocol.callRemote(managercommands.AddSkip,
  99. testName=testName,
  100. reason=reason)
  101. def _getTodoReason(self, todo):
  102. """
  103. Get the reason for a C{Todo}.
  104. If C{todo} is L{None}, return a sensible default.
  105. """
  106. if todo is None:
  107. return self._DEFAULT_TODO
  108. else:
  109. return todo.reason
  110. def addExpectedFailure(self, test, error, todo=None):
  111. """
  112. Send an expected failure over.
  113. """
  114. super(WorkerReporter, self).addExpectedFailure(test, error, todo)
  115. errorMessage = error.getErrorMessage().encode("utf-8")
  116. testName = test.id()
  117. if isinstance(testName, unicode):
  118. testName = testName.encode("utf-8")
  119. self.ampProtocol.callRemote(managercommands.AddExpectedFailure,
  120. testName=testName,
  121. error=errorMessage,
  122. todo=self._getTodoReason(todo))
  123. def addUnexpectedSuccess(self, test, todo=None):
  124. """
  125. Send an unexpected success over.
  126. """
  127. super(WorkerReporter, self).addUnexpectedSuccess(test, todo)
  128. testName = test.id()
  129. if isinstance(testName, unicode):
  130. testName = testName.encode("utf-8")
  131. self.ampProtocol.callRemote(managercommands.AddUnexpectedSuccess,
  132. testName=testName,
  133. todo=self._getTodoReason(todo))
  134. def printSummary(self):
  135. """
  136. I{Don't} print a summary
  137. """