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