proxy.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. """
  2. Result Proxy
  3. ------------
  4. The result proxy wraps the result instance given to each test. It
  5. performs two functions: enabling extended error/failure reporting
  6. and calling plugins.
  7. As each result event is fired, plugins are called with the same event;
  8. however, plugins are called with the nose.case.Test instance that
  9. wraps the actual test. So when a test fails and calls
  10. result.addFailure(self, err), the result proxy calls
  11. addFailure(self.test, err) for each plugin. This allows plugins to
  12. have a single stable interface for all test types, and also to
  13. manipulate the test object itself by setting the `test` attribute of
  14. the nose.case.Test that they receive.
  15. """
  16. import logging
  17. from nose.config import Config
  18. log = logging.getLogger(__name__)
  19. def proxied_attribute(local_attr, proxied_attr, doc):
  20. """Create a property that proxies attribute ``proxied_attr`` through
  21. the local attribute ``local_attr``.
  22. """
  23. def fget(self):
  24. return getattr(getattr(self, local_attr), proxied_attr)
  25. def fset(self, value):
  26. setattr(getattr(self, local_attr), proxied_attr, value)
  27. def fdel(self):
  28. delattr(getattr(self, local_attr), proxied_attr)
  29. return property(fget, fset, fdel, doc)
  30. class ResultProxyFactory(object):
  31. """Factory for result proxies. Generates a ResultProxy bound to each test
  32. and the result passed to the test.
  33. """
  34. def __init__(self, config=None):
  35. if config is None:
  36. config = Config()
  37. self.config = config
  38. self.__prepared = False
  39. self.__result = None
  40. def __call__(self, result, test):
  41. """Return a ResultProxy for the current test.
  42. On first call, plugins are given a chance to replace the
  43. result used for the remaining tests. If a plugin returns a
  44. value from prepareTestResult, that object will be used as the
  45. result for all tests.
  46. """
  47. if not self.__prepared:
  48. self.__prepared = True
  49. plug_result = self.config.plugins.prepareTestResult(result)
  50. if plug_result is not None:
  51. self.__result = result = plug_result
  52. if self.__result is not None:
  53. result = self.__result
  54. return ResultProxy(result, test, config=self.config)
  55. class ResultProxy(object):
  56. """Proxy to TestResults (or other results handler).
  57. One ResultProxy is created for each nose.case.Test. The result
  58. proxy calls plugins with the nose.case.Test instance (instead of
  59. the wrapped test case) as each result call is made. Finally, the
  60. real result method is called, also with the nose.case.Test
  61. instance as the test parameter.
  62. """
  63. def __init__(self, result, test, config=None):
  64. if config is None:
  65. config = Config()
  66. self.config = config
  67. self.plugins = config.plugins
  68. self.result = result
  69. self.test = test
  70. def __repr__(self):
  71. return repr(self.result)
  72. def _prepareErr(self, err):
  73. if not isinstance(err[1], Exception) and isinstance(err[0], type):
  74. # Turn value back into an Exception (required in Python 3.x).
  75. # Plugins do all sorts of crazy things with exception values.
  76. # Convert it to a custom subclass of Exception with the same
  77. # name as the actual exception to make it print correctly.
  78. value = type(err[0].__name__, (Exception,), {})(err[1])
  79. err = (err[0], value, err[2])
  80. return err
  81. def assertMyTest(self, test):
  82. # The test I was called with must be my .test or my
  83. # .test's .test. or my .test.test's .case
  84. case = getattr(self.test, 'test', None)
  85. assert (test is self.test
  86. or test is case
  87. or test is getattr(case, '_nose_case', None)), (
  88. "ResultProxy for %r (%s) was called with test %r (%s)"
  89. % (self.test, id(self.test), test, id(test)))
  90. def afterTest(self, test):
  91. self.assertMyTest(test)
  92. self.plugins.afterTest(self.test)
  93. if hasattr(self.result, "afterTest"):
  94. self.result.afterTest(self.test)
  95. def beforeTest(self, test):
  96. self.assertMyTest(test)
  97. self.plugins.beforeTest(self.test)
  98. if hasattr(self.result, "beforeTest"):
  99. self.result.beforeTest(self.test)
  100. def addError(self, test, err):
  101. self.assertMyTest(test)
  102. plugins = self.plugins
  103. plugin_handled = plugins.handleError(self.test, err)
  104. if plugin_handled:
  105. return
  106. # test.passed is set in result, to account for error classes
  107. formatted = plugins.formatError(self.test, err)
  108. if formatted is not None:
  109. err = formatted
  110. plugins.addError(self.test, err)
  111. self.result.addError(self.test, self._prepareErr(err))
  112. if not self.result.wasSuccessful() and self.config.stopOnError:
  113. self.shouldStop = True
  114. def addFailure(self, test, err):
  115. self.assertMyTest(test)
  116. plugins = self.plugins
  117. plugin_handled = plugins.handleFailure(self.test, err)
  118. if plugin_handled:
  119. return
  120. self.test.passed = False
  121. formatted = plugins.formatFailure(self.test, err)
  122. if formatted is not None:
  123. err = formatted
  124. plugins.addFailure(self.test, err)
  125. self.result.addFailure(self.test, self._prepareErr(err))
  126. if self.config.stopOnError:
  127. self.shouldStop = True
  128. def addSkip(self, test, reason):
  129. # 2.7 compat shim
  130. from nose.plugins.skip import SkipTest
  131. self.assertMyTest(test)
  132. plugins = self.plugins
  133. if not isinstance(reason, Exception):
  134. # for Python 3.2+
  135. reason = Exception(reason)
  136. plugins.addError(self.test, (SkipTest, reason, None))
  137. self.result.addSkip(self.test, reason)
  138. def addSuccess(self, test):
  139. self.assertMyTest(test)
  140. self.plugins.addSuccess(self.test)
  141. self.result.addSuccess(self.test)
  142. def startTest(self, test):
  143. self.assertMyTest(test)
  144. self.plugins.startTest(self.test)
  145. self.result.startTest(self.test)
  146. def stop(self):
  147. self.result.stop()
  148. def stopTest(self, test):
  149. self.assertMyTest(test)
  150. self.plugins.stopTest(self.test)
  151. self.result.stopTest(self.test)
  152. # proxied attributes
  153. shouldStop = proxied_attribute('result', 'shouldStop',
  154. """Should the test run stop?""")
  155. errors = proxied_attribute('result', 'errors',
  156. """Tests that raised an exception""")
  157. failures = proxied_attribute('result', 'failures',
  158. """Tests that failed""")
  159. testsRun = proxied_attribute('result', 'testsRun',
  160. """Number of tests run""")