result.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. """
  2. Test Result
  3. -----------
  4. Provides a TextTestResult that extends unittest's _TextTestResult to
  5. provide support for error classes (such as the builtin skip and
  6. deprecated classes), and hooks for plugins to take over or extend
  7. reporting.
  8. """
  9. import logging
  10. try:
  11. # 2.7+
  12. from unittest.runner import _TextTestResult
  13. except ImportError:
  14. from unittest import _TextTestResult
  15. from nose.config import Config
  16. from nose.util import isclass, ln as _ln # backwards compat
  17. log = logging.getLogger('nose.result')
  18. def _exception_detail(exc):
  19. # this is what stdlib module traceback does
  20. try:
  21. return str(exc)
  22. except:
  23. return '<unprintable %s object>' % type(exc).__name__
  24. class TextTestResult(_TextTestResult):
  25. """Text test result that extends unittest's default test result
  26. support for a configurable set of errorClasses (eg, Skip,
  27. Deprecated, TODO) that extend the errors/failures/success triad.
  28. """
  29. def __init__(self, stream, descriptions, verbosity, config=None,
  30. errorClasses=None):
  31. if errorClasses is None:
  32. errorClasses = {}
  33. self.errorClasses = errorClasses
  34. if config is None:
  35. config = Config()
  36. self.config = config
  37. _TextTestResult.__init__(self, stream, descriptions, verbosity)
  38. def addSkip(self, test, reason):
  39. # 2.7 skip compat
  40. from nose.plugins.skip import SkipTest
  41. if SkipTest in self.errorClasses:
  42. storage, label, isfail = self.errorClasses[SkipTest]
  43. storage.append((test, reason))
  44. self.printLabel(label, (SkipTest, reason, None))
  45. def addError(self, test, err):
  46. """Overrides normal addError to add support for
  47. errorClasses. If the exception is a registered class, the
  48. error will be added to the list for that class, not errors.
  49. """
  50. ec, ev, tb = err
  51. try:
  52. exc_info = self._exc_info_to_string(err, test)
  53. except TypeError:
  54. # 2.3 compat
  55. exc_info = self._exc_info_to_string(err)
  56. for cls, (storage, label, isfail) in self.errorClasses.items():
  57. #if 'Skip' in cls.__name__ or 'Skip' in ec.__name__:
  58. # from nose.tools import set_trace
  59. # set_trace()
  60. if isclass(ec) and issubclass(ec, cls):
  61. if isfail:
  62. test.passed = False
  63. storage.append((test, exc_info))
  64. self.printLabel(label, err)
  65. return
  66. self.errors.append((test, exc_info))
  67. test.passed = False
  68. self.printLabel('ERROR')
  69. # override to bypass changes in 2.7
  70. def getDescription(self, test):
  71. if self.descriptions:
  72. return test.shortDescription() or str(test)
  73. else:
  74. return str(test)
  75. def printLabel(self, label, err=None):
  76. # Might get patched into a streamless result
  77. stream = getattr(self, 'stream', None)
  78. if stream is not None:
  79. if self.showAll:
  80. message = [label]
  81. if err:
  82. detail = _exception_detail(err[1])
  83. if detail:
  84. message.append(detail)
  85. stream.writeln(": ".join(message))
  86. elif self.dots:
  87. stream.write(label[:1])
  88. def printErrors(self):
  89. """Overrides to print all errorClasses errors as well.
  90. """
  91. _TextTestResult.printErrors(self)
  92. for cls in self.errorClasses.keys():
  93. storage, label, isfail = self.errorClasses[cls]
  94. if isfail:
  95. self.printErrorList(label, storage)
  96. # Might get patched into a result with no config
  97. if hasattr(self, 'config'):
  98. self.config.plugins.report(self.stream)
  99. def printSummary(self, start, stop):
  100. """Called by the test runner to print the final summary of test
  101. run results.
  102. """
  103. write = self.stream.write
  104. writeln = self.stream.writeln
  105. taken = float(stop - start)
  106. run = self.testsRun
  107. plural = run != 1 and "s" or ""
  108. writeln(self.separator2)
  109. writeln("Ran %s test%s in %.3fs" % (run, plural, taken))
  110. writeln()
  111. summary = {}
  112. eckeys = self.errorClasses.keys()
  113. for cls in eckeys:
  114. storage, label, isfail = self.errorClasses[cls]
  115. count = len(storage)
  116. if not count:
  117. continue
  118. summary[label] = count
  119. if len(self.failures):
  120. summary['failures'] = len(self.failures)
  121. if len(self.errors):
  122. summary['errors'] = len(self.errors)
  123. if not self.wasSuccessful():
  124. write("FAILED")
  125. else:
  126. write("OK")
  127. items = summary.items()
  128. if items:
  129. items.sort()
  130. write(" (")
  131. write(", ".join(["%s=%s" % (label, count) for
  132. label, count in items]))
  133. writeln(")")
  134. else:
  135. writeln()
  136. def wasSuccessful(self):
  137. """Overrides to check that there are no errors in errorClasses
  138. lists that are marked as errors and should cause a run to
  139. fail.
  140. """
  141. if self.errors or self.failures:
  142. return False
  143. for cls in self.errorClasses.keys():
  144. storage, label, isfail = self.errorClasses[cls]
  145. if not isfail:
  146. continue
  147. if storage:
  148. return False
  149. return True
  150. def _addError(self, test, err):
  151. try:
  152. exc_info = self._exc_info_to_string(err, test)
  153. except TypeError:
  154. # 2.3: does not take test arg
  155. exc_info = self._exc_info_to_string(err)
  156. self.errors.append((test, exc_info))
  157. if self.showAll:
  158. self.stream.write('ERROR')
  159. elif self.dots:
  160. self.stream.write('E')
  161. def _exc_info_to_string(self, err, test=None):
  162. # 2.7 skip compat
  163. from nose.plugins.skip import SkipTest
  164. if isclass(err[0]) and issubclass(err[0], SkipTest):
  165. return str(err[1])
  166. # 2.3/2.4 -- 2.4 passes test, 2.3 does not
  167. try:
  168. return _TextTestResult._exc_info_to_string(self, err, test)
  169. except TypeError:
  170. # 2.3: does not take test arg
  171. return _TextTestResult._exc_info_to_string(self, err)
  172. def ln(*arg, **kw):
  173. from warnings import warn
  174. warn("ln() has moved to nose.util from nose.result and will be removed "
  175. "from nose.result in a future release. Please update your imports ",
  176. DeprecationWarning)
  177. return _ln(*arg, **kw)