|
| 1 | +# Copyright (c) 2008-2016 testtools developers. See LICENSE for details. |
| 2 | + |
| 3 | +"""Helpers for tests.""" |
| 4 | + |
| 5 | +__all__ = [ |
| 6 | + "LoggingResult", |
| 7 | +] |
| 8 | + |
| 9 | +import sys |
| 10 | +import warnings |
| 11 | + |
| 12 | +from testtools import TestResult, runtest |
| 13 | +from testtools.content import StackLinesContent |
| 14 | +from testtools.matchers import ( |
| 15 | + AfterPreprocessing, |
| 16 | + Equals, |
| 17 | + MatchesDict, |
| 18 | + MatchesListwise, |
| 19 | +) |
| 20 | + |
| 21 | +warnings.warn( |
| 22 | + "This module is deprecated for removal", |
| 23 | + DeprecationWarning, |
| 24 | + stacklevel=2, |
| 25 | +) |
| 26 | + |
| 27 | + |
| 28 | +# GZ 2010-08-12: Don't do this, pointlessly creates an exc_info cycle |
| 29 | +try: |
| 30 | + raise Exception |
| 31 | +except Exception: |
| 32 | + an_exc_info = sys.exc_info() |
| 33 | + |
| 34 | + |
| 35 | +# Deprecated: This classes attributes are somewhat non deterministic which |
| 36 | +# leads to hard to predict tests (because Python upstream are changing things. |
| 37 | +class LoggingResult(TestResult): |
| 38 | + """TestResult that logs its event to a list.""" |
| 39 | + |
| 40 | + def __init__(self, log): |
| 41 | + self._events = log |
| 42 | + super().__init__() |
| 43 | + |
| 44 | + def startTest(self, test): |
| 45 | + self._events.append(("startTest", test)) |
| 46 | + super().startTest(test) |
| 47 | + |
| 48 | + def stop(self): |
| 49 | + self._events.append("stop") |
| 50 | + super().stop() |
| 51 | + |
| 52 | + def stopTest(self, test): |
| 53 | + self._events.append(("stopTest", test)) |
| 54 | + super().stopTest(test) |
| 55 | + |
| 56 | + def addFailure(self, test, err=None, details=None): |
| 57 | + self._events.append(("addFailure", test, err)) |
| 58 | + super().addFailure(test, err, details) |
| 59 | + |
| 60 | + def addError(self, test, err=None, details=None): |
| 61 | + self._events.append(("addError", test, err)) |
| 62 | + super().addError(test, err, details) |
| 63 | + |
| 64 | + def addSkip(self, test, reason=None, details=None): |
| 65 | + # Extract reason from details if not provided directly |
| 66 | + if reason is None and details and "reason" in details: |
| 67 | + reason = details["reason"].as_text() |
| 68 | + self._events.append(("addSkip", test, reason)) |
| 69 | + super().addSkip(test, reason, details) |
| 70 | + |
| 71 | + def addSuccess(self, test, details=None): |
| 72 | + self._events.append(("addSuccess", test)) |
| 73 | + super().addSuccess(test, details) |
| 74 | + |
| 75 | + def startTestRun(self): |
| 76 | + self._events.append("startTestRun") |
| 77 | + super().startTestRun() |
| 78 | + |
| 79 | + def stopTestRun(self): |
| 80 | + self._events.append("stopTestRun") |
| 81 | + super().stopTestRun() |
| 82 | + |
| 83 | + def done(self): |
| 84 | + self._events.append("done") |
| 85 | + super().done() |
| 86 | + |
| 87 | + def tags(self, new_tags, gone_tags): |
| 88 | + self._events.append(("tags", new_tags, gone_tags)) |
| 89 | + super().tags(new_tags, gone_tags) |
| 90 | + |
| 91 | + def time(self, a_datetime): |
| 92 | + self._events.append(("time", a_datetime)) |
| 93 | + super().time(a_datetime) |
| 94 | + |
| 95 | + |
| 96 | +def is_stack_hidden(): |
| 97 | + return StackLinesContent.HIDE_INTERNAL_STACK |
| 98 | + |
| 99 | + |
| 100 | +def hide_testtools_stack(should_hide=True): |
| 101 | + result = StackLinesContent.HIDE_INTERNAL_STACK |
| 102 | + StackLinesContent.HIDE_INTERNAL_STACK = should_hide |
| 103 | + return result |
| 104 | + |
| 105 | + |
| 106 | +def run_with_stack_hidden(should_hide, f, *args, **kwargs): |
| 107 | + old_should_hide = hide_testtools_stack(should_hide) |
| 108 | + try: |
| 109 | + return f(*args, **kwargs) |
| 110 | + finally: |
| 111 | + hide_testtools_stack(old_should_hide) |
| 112 | + |
| 113 | + |
| 114 | +class FullStackRunTest(runtest.RunTest): |
| 115 | + def _run_user(self, fn, *args, **kwargs): |
| 116 | + return run_with_stack_hidden(False, super()._run_user, fn, *args, **kwargs) |
| 117 | + |
| 118 | + |
| 119 | +class MatchesEvents: |
| 120 | + """Match a list of test result events. |
| 121 | +
|
| 122 | + Specify events as a data structure. Ordinary Python objects within this |
| 123 | + structure will be compared exactly, but you can also use matchers at any |
| 124 | + point. |
| 125 | + """ |
| 126 | + |
| 127 | + def __init__(self, *expected): |
| 128 | + self._expected = expected |
| 129 | + |
| 130 | + def _make_matcher(self, obj): |
| 131 | + # This isn't very safe for general use, but is good enough to make |
| 132 | + # some tests in this module more readable. |
| 133 | + if hasattr(obj, "match"): |
| 134 | + return obj |
| 135 | + elif isinstance(obj, tuple) or isinstance(obj, list): |
| 136 | + return MatchesListwise([self._make_matcher(item) for item in obj]) |
| 137 | + elif isinstance(obj, dict): |
| 138 | + return MatchesDict( |
| 139 | + {key: self._make_matcher(value) for key, value in obj.items()} |
| 140 | + ) |
| 141 | + else: |
| 142 | + return Equals(obj) |
| 143 | + |
| 144 | + def match(self, observed): |
| 145 | + matcher = self._make_matcher(self._expected) |
| 146 | + return matcher.match(observed) |
| 147 | + |
| 148 | + |
| 149 | +class AsText(AfterPreprocessing): |
| 150 | + """Match the text of a Content instance.""" |
| 151 | + |
| 152 | + def __init__(self, matcher, annotate=True): |
| 153 | + super().__init__(lambda log: log.as_text(), matcher, annotate=annotate) |
| 154 | + |
| 155 | + |
| 156 | +def raise_(exception): |
| 157 | + """Raise ``exception``. |
| 158 | +
|
| 159 | + Useful for raising exceptions when it is inconvenient to use a statement |
| 160 | + (e.g. in a lambda). |
| 161 | +
|
| 162 | + :param Exception exception: An exception to raise. |
| 163 | + :raises: Whatever exception is |
| 164 | +
|
| 165 | + """ |
| 166 | + raise exception |
0 commit comments