Skip to content

Commit 10c7d9d

Browse files
committed
Re-add helpers modules
These are part of our public API and should not be removed in a patch release. We do however deprecate them and skip type checks. Signed-off-by: Stephen Finucane <stephen@that.guru>
1 parent 81db8ab commit 10c7d9d

File tree

5 files changed

+234
-0
lines changed

5 files changed

+234
-0
lines changed

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ module = [
8282
# except tests (we're not sadists)
8383
"testtools.twistedsupport.*",
8484
"tests.*",
85+
# NOTE(stephenfin): These are deprecated so we're not going to type them
86+
"testtools.tests.helpers",
8587
]
8688
disallow_untyped_calls = false
8789
disallow_untyped_defs = false

testtools/tests/__init__.py

Whitespace-only changes.

testtools/tests/helpers.py

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
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

testtools/tests/matchers/__init__.py

Whitespace-only changes.
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Copyright (c) 2008-2012 testtools developers. See LICENSE for details.
2+
3+
import warnings
4+
from collections.abc import Callable
5+
from typing import Any, ClassVar, Protocol, runtime_checkable
6+
7+
warnings.warn(
8+
"This module is deprecated for removal",
9+
DeprecationWarning,
10+
stacklevel=2,
11+
)
12+
13+
14+
@runtime_checkable
15+
class MatcherTestProtocol(Protocol):
16+
"""Protocol for test classes that test matchers."""
17+
18+
matches_matcher: ClassVar[Any]
19+
matches_matches: ClassVar[Any]
20+
matches_mismatches: ClassVar[Any]
21+
str_examples: ClassVar[Any]
22+
describe_examples: ClassVar[Any]
23+
assertEqual: Callable[..., Any]
24+
assertNotEqual: Callable[..., Any]
25+
assertThat: Callable[..., Any]
26+
27+
28+
class TestMatchersInterface:
29+
"""Mixin class that provides test methods for matcher interfaces."""
30+
31+
__test__ = False # Tell pytest not to collect this as a test class
32+
33+
def test_matches_match(self: MatcherTestProtocol) -> None:
34+
matcher = self.matches_matcher
35+
matches = self.matches_matches
36+
mismatches = self.matches_mismatches
37+
for candidate in matches:
38+
self.assertEqual(None, matcher.match(candidate))
39+
for candidate in mismatches:
40+
mismatch = matcher.match(candidate)
41+
self.assertNotEqual(None, mismatch)
42+
self.assertNotEqual(None, getattr(mismatch, "describe", None))
43+
44+
def test__str__(self: MatcherTestProtocol) -> None:
45+
# [(expected, object to __str__)].
46+
from testtools.matchers._doctest import DocTestMatches
47+
48+
examples = self.str_examples
49+
for expected, matcher in examples:
50+
self.assertThat(matcher, DocTestMatches(expected))
51+
52+
def test_describe_difference(self: MatcherTestProtocol) -> None:
53+
# [(expected, matchee, matcher), ...]
54+
examples = self.describe_examples
55+
for difference, matchee, matcher in examples:
56+
mismatch = matcher.match(matchee)
57+
self.assertEqual(difference, mismatch.describe())
58+
59+
def test_mismatch_details(self: MatcherTestProtocol) -> None:
60+
# The mismatch object must provide get_details, which must return a
61+
# dictionary mapping names to Content objects.
62+
examples = self.describe_examples
63+
for difference, matchee, matcher in examples:
64+
mismatch = matcher.match(matchee)
65+
details = mismatch.get_details()
66+
self.assertEqual(dict(details), details)

0 commit comments

Comments
 (0)