Skip to content

Commit 6ce79d5

Browse files
authored
Merge pull request testing-cabal#592 from stephenfin/typing
Even more typing tweaks
2 parents a5ff315 + f311a5f commit 6ce79d5

24 files changed

Lines changed: 323 additions & 188 deletions

‎doc/for-test-authors.rst‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -937,7 +937,9 @@ the ``Mismatch`` knows how to describe a failure to match.
937937

938938
Here's an example matcher::
939939

940-
class IsDivisibleBy(object):
940+
from testtools.matchers import Matcher
941+
942+
class IsDivisibleBy(Matcher):
941943
"""Match if a number is divisible by another number."""
942944
def __init__(self, divider):
943945
self.divider = divider

‎tests/helpers.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from testtools.matchers import (
1414
AfterPreprocessing,
1515
Equals,
16+
Matcher,
1617
MatchesDict,
1718
MatchesListwise,
1819
)
@@ -108,7 +109,7 @@ def _run_user(self, fn, *args, **kwargs):
108109
return run_with_stack_hidden(False, super()._run_user, fn, *args, **kwargs)
109110

110111

111-
class MatchesEvents:
112+
class MatchesEvents(Matcher[list[tuple[str, ...]]]):
112113
"""Match a list of test result events.
113114
114115
Specify events as a data structure. Ordinary Python objects within this

‎tests/matchers/test_basic.py‎

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ def test_str_with_unicode(self):
274274

275275
def test_match(self):
276276
matcher = StartsWith("bar")
277-
self.assertIs(None, matcher.match("barf"))
277+
self.assertIsNone(matcher.match("barf"))
278278

279279
def test_mismatch_returns_does_not_start_with(self):
280280
matcher = StartsWith("bar")
@@ -283,11 +283,15 @@ def test_mismatch_returns_does_not_start_with(self):
283283
def test_mismatch_sets_matchee(self):
284284
matcher = StartsWith("bar")
285285
mismatch = matcher.match("foo")
286+
self.assertIsNotNone(mismatch)
287+
assert mismatch is not None
286288
self.assertEqual("foo", mismatch.matchee)
287289

288290
def test_mismatch_sets_expected(self):
289291
matcher = StartsWith("bar")
290292
mismatch = matcher.match("foo")
293+
self.assertIsNotNone(mismatch)
294+
assert mismatch is not None
291295
self.assertEqual("bar", mismatch.expected)
292296

293297

@@ -344,11 +348,15 @@ def test_mismatch_returns_does_not_end_with(self):
344348
def test_mismatch_sets_matchee(self):
345349
matcher = EndsWith("bar")
346350
mismatch = matcher.match("foo")
351+
self.assertIsNotNone(mismatch)
352+
assert mismatch is not None
347353
self.assertEqual("foo", mismatch.matchee)
348354

349355
def test_mismatch_sets_expected(self):
350356
matcher = EndsWith("bar")
351357
mismatch = matcher.match("foo")
358+
self.assertIsNotNone(mismatch)
359+
assert mismatch is not None
352360
self.assertEqual("bar", mismatch.expected)
353361

354362

@@ -503,6 +511,7 @@ def test_integers_mismatch(self):
503511
matcher = Nearly(10, delta=2)
504512
mismatch = matcher.match(13)
505513
self.assertIsNotNone(mismatch)
514+
assert mismatch is not None
506515
self.assertIn("13", mismatch.describe())
507516

508517
def test_exact_boundary_matches(self):
@@ -542,14 +551,17 @@ def test_default_delta(self):
542551
def test_non_numeric_type_mismatch(self):
543552
"""Test that non-numeric types result in a mismatch."""
544553
matcher = Nearly(1.0, delta=0.1)
545-
mismatch = matcher.match("string")
554+
# intentional type mismatch
555+
mismatch = matcher.match("string") # type: ignore[arg-type]
546556
self.assertIsNotNone(mismatch)
557+
assert mismatch is not None
547558
self.assertIn("string", mismatch.describe())
548559

549560
def test_none_type_mismatch(self):
550561
"""Test that None results in a mismatch."""
551562
matcher = Nearly(1.0, delta=0.1)
552-
mismatch = matcher.match(None)
563+
# intentional type mismatch
564+
mismatch = matcher.match(None) # type: ignore[arg-type]
553565
self.assertIsNotNone(mismatch)
554566

555567

‎tests/matchers/test_dict.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ class TestKeysEqualWithList(TestCase, TestMatchersInterface):
7575
def test_description(self):
7676
matchee = {"foo": 0, "bar": 1, "baz": 2}
7777
mismatch = KeysEqual("foo", "bar").match(matchee)
78+
self.assertIsNotNone(mismatch)
79+
assert mismatch is not None
7880
description = mismatch.describe()
7981
self.assertThat(
8082
description,

‎tests/matchers/test_filesystem.py‎

Lines changed: 62 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ def test_exists(self):
5353
def test_not_exists(self):
5454
doesntexist = os.path.join(self.mkdtemp(), "doesntexist")
5555
mismatch = PathExists().match(doesntexist)
56+
self.assertIsNotNone(mismatch)
57+
assert mismatch is not None
5658
self.assertThat(f"{doesntexist} does not exist.", Equals(mismatch.describe()))
5759

5860

@@ -63,15 +65,20 @@ def test_exists(self):
6365

6466
def test_not_exists(self):
6567
doesntexist = os.path.join(self.mkdtemp(), "doesntexist")
66-
mismatch = DirExists().match(doesntexist)
67-
self.assertThat(
68-
PathExists().match(doesntexist).describe(), Equals(mismatch.describe())
69-
)
68+
dir_mismatch = DirExists().match(doesntexist)
69+
self.assertIsNotNone(dir_mismatch)
70+
assert dir_mismatch is not None
71+
path_mismatch = PathExists().match(doesntexist)
72+
self.assertIsNotNone(path_mismatch)
73+
assert path_mismatch is not None
74+
self.assertThat(path_mismatch.describe(), Equals(dir_mismatch.describe()))
7075

7176
def test_not_a_directory(self):
7277
filename = os.path.join(self.mkdtemp(), "foo")
7378
self.touch(filename)
7479
mismatch = DirExists().match(filename)
80+
self.assertIsNotNone(mismatch)
81+
assert mismatch is not None
7582
self.assertThat(f"{filename} is not a directory.", Equals(mismatch.describe()))
7683

7784

@@ -84,14 +91,19 @@ def test_exists(self):
8491

8592
def test_not_exists(self):
8693
doesntexist = os.path.join(self.mkdtemp(), "doesntexist")
87-
mismatch = FileExists().match(doesntexist)
88-
self.assertThat(
89-
PathExists().match(doesntexist).describe(), Equals(mismatch.describe())
90-
)
94+
file_mismatch = FileExists().match(doesntexist)
95+
self.assertIsNotNone(file_mismatch)
96+
assert file_mismatch is not None
97+
path_mismatch = PathExists().match(doesntexist)
98+
self.assertIsNotNone(path_mismatch)
99+
assert path_mismatch is not None
100+
self.assertThat(path_mismatch.describe(), Equals(file_mismatch.describe()))
91101

92102
def test_not_a_file(self):
93103
tempdir = self.mkdtemp()
94104
mismatch = FileExists().match(tempdir)
105+
self.assertIsNotNone(mismatch)
106+
assert mismatch is not None
95107
self.assertThat(f"{tempdir} is not a file.", Equals(mismatch.describe()))
96108

97109

@@ -102,10 +114,13 @@ def test_empty(self):
102114

103115
def test_not_exists(self):
104116
doesntexist = os.path.join(self.mkdtemp(), "doesntexist")
105-
mismatch = DirContains([]).match(doesntexist)
106-
self.assertThat(
107-
PathExists().match(doesntexist).describe(), Equals(mismatch.describe())
108-
)
117+
dir_mismatch = DirContains([]).match(doesntexist)
118+
self.assertIsNotNone(dir_mismatch)
119+
assert dir_mismatch is not None
120+
path_mismatch = PathExists().match(doesntexist)
121+
self.assertIsNotNone(path_mismatch)
122+
assert path_mismatch is not None
123+
self.assertThat(path_mismatch.describe(), Equals(dir_mismatch.describe()))
109124

110125
def test_contains_files(self):
111126
tempdir = self.mkdtemp()
@@ -130,20 +145,25 @@ def test_both_specified(self):
130145
def test_does_not_contain_files(self):
131146
tempdir = self.mkdtemp()
132147
self.touch(os.path.join(tempdir, "foo"))
133-
mismatch = DirContains(["bar", "foo"]).match(tempdir)
134-
self.assertThat(
135-
Equals(["bar", "foo"]).match(["foo"]).describe(),
136-
Equals(mismatch.describe()),
137-
)
148+
mismatch_a = DirContains(["bar", "foo"]).match(tempdir)
149+
self.assertIsNotNone(mismatch_a)
150+
assert mismatch_a is not None
151+
mismatch_b = Equals(["bar", "foo"]).match(["foo"])
152+
self.assertIsNotNone(mismatch_b)
153+
assert mismatch_b is not None
154+
self.assertThat(mismatch_b.describe(), Equals(mismatch_a.describe()))
138155

139156

140157
class TestFileContains(TestCase, PathHelpers):
141158
def test_not_exists(self):
142159
doesntexist = os.path.join(self.mkdtemp(), "doesntexist")
143-
mismatch = FileContains("").match(doesntexist)
144-
self.assertThat(
145-
PathExists().match(doesntexist).describe(), Equals(mismatch.describe())
146-
)
160+
file_mismatch = FileContains("").match(doesntexist)
161+
self.assertIsNotNone(file_mismatch)
162+
assert file_mismatch is not None
163+
path_mismatch = PathExists().match(doesntexist)
164+
self.assertIsNotNone(path_mismatch)
165+
assert path_mismatch is not None
166+
self.assertThat(path_mismatch.describe(), Equals(file_mismatch.describe()))
147167

148168
def test_contains(self):
149169
tempdir = self.mkdtemp()
@@ -169,11 +189,13 @@ def test_does_not_contain(self):
169189
tempdir = self.mkdtemp()
170190
filename = os.path.join(tempdir, "foo")
171191
self.create_file(filename, "Goodbye Cruel World!")
172-
mismatch = FileContains("Hello World!").match(filename)
173-
self.assertThat(
174-
Equals("Hello World!").match("Goodbye Cruel World!").describe(),
175-
Equals(mismatch.describe()),
176-
)
192+
actual = FileContains("Hello World!").match(filename)
193+
self.assertIsNotNone(actual)
194+
assert actual is not None
195+
expected = Equals("Hello World!").match("Goodbye Cruel World!")
196+
self.assertIsNotNone(expected)
197+
assert expected is not None
198+
self.assertThat(expected.describe(), Equals(actual.describe()))
177199

178200
def test_binary_content(self):
179201
tempdir = self.mkdtemp()
@@ -188,11 +210,13 @@ def test_binary_content_mismatch(self):
188210
filename = os.path.join(tempdir, "binary_file")
189211
with open(filename, "wb") as f:
190212
f.write(b"\x00\x01\x02")
191-
mismatch = FileContains(b"\xff\xfe\xfd").match(filename)
192-
self.assertThat(
193-
Equals(b"\xff\xfe\xfd").match(b"\x00\x01\x02").describe(),
194-
Equals(mismatch.describe()),
195-
)
213+
actual = FileContains(b"\xff\xfe\xfd").match(filename)
214+
self.assertIsNotNone(actual)
215+
assert actual is not None
216+
expected = Equals(b"\xff\xfe\xfd").match(b"\x00\x01\x02")
217+
self.assertIsNotNone(expected)
218+
assert expected is not None
219+
self.assertThat(expected.describe(), Equals(actual.describe()))
196220

197221
def test_text_with_encoding(self):
198222
tempdir = self.mkdtemp()
@@ -238,10 +262,13 @@ def in_temp_dir(x):
238262
tarball.add(in_temp_dir("a"), "a")
239263
tarball.add(in_temp_dir("b"), "b")
240264
tarball.close()
241-
mismatch = TarballContains(["d", "c"]).match(in_temp_dir("foo.tar.gz"))
242-
self.assertEqual(
243-
mismatch.describe(), Equals(["c", "d"]).match(["a", "b"]).describe()
244-
)
265+
actual = TarballContains(["d", "c"]).match(in_temp_dir("foo.tar.gz"))
266+
self.assertIsNotNone(actual)
267+
assert actual is not None
268+
expected = Equals(["c", "d"]).match(["a", "b"])
269+
self.assertIsNotNone(expected)
270+
assert expected is not None
271+
self.assertEqual(actual.describe(), expected.describe())
245272

246273

247274
class TestSamePath(TestCase, PathHelpers):

‎tests/matchers/test_higherorder.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from typing import ClassVar
44

55
from testtools import TestCase
6+
from testtools.content import text_content
67
from testtools.matchers import (
78
DocTestMatches,
89
Equals,
@@ -192,7 +193,7 @@ def test_if_message_given_message(self):
192193
matcher = Equals(1)
193194
expected = Annotate("foo", matcher)
194195
annotated = Annotate.if_message("foo", matcher)
195-
self.assertThat(
196+
self.assertThat( # type: ignore[misc]
196197
annotated, MatchesStructure.fromExample(expected, "annotation", "matcher")
197198
)
198199

@@ -201,7 +202,7 @@ class TestAnnotatedMismatch(TestCase):
201202
run_tests_with = FullStackRunTest
202203

203204
def test_forwards_details(self):
204-
x = Mismatch("description", {"foo": "bar"})
205+
x = Mismatch("description", {"foo": text_content("bar")})
205206
annotated = AnnotatedMismatch("annotation", x)
206207
self.assertEqual(x.get_details(), annotated.get_details())
207208

‎tests/matchers/test_impl.py‎

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from testtools.compat import (
1010
text_repr,
1111
)
12+
from testtools.content import text_content
1213
from testtools.matchers import (
1314
Equals,
1415
MatchesException,
@@ -30,9 +31,10 @@ class TestMismatch(TestCase):
3031
run_tests_with = FullStackRunTest
3132

3233
def test_constructor_arguments(self):
33-
mismatch = Mismatch("some description", {"detail": "things"})
34+
detail = text_content("things")
35+
mismatch = Mismatch("some description", {"detail": detail})
3436
self.assertEqual("some description", mismatch.describe())
35-
self.assertEqual({"detail": "things"}, mismatch.get_details())
37+
self.assertEqual({"detail": detail}, mismatch.get_details())
3638

3739
def test_constructor_no_arguments(self):
3840
mismatch = Mismatch()
@@ -47,30 +49,39 @@ def test_is_assertion_error(self):
4749
# MismatchError is an AssertionError, so that most of the time, it
4850
# looks like a test failure, rather than an error.
4951
def raise_mismatch_error():
50-
raise MismatchError(2, Equals(3), Equals(3).match(2))
52+
mismatch = Equals(3).match(2)
53+
self.assertIsNotNone(mismatch)
54+
assert mismatch is not None
55+
raise MismatchError(2, Equals(3), mismatch)
5156

5257
self.assertRaises(AssertionError, raise_mismatch_error)
5358

5459
def test_default_description_is_mismatch(self):
5560
mismatch = Equals(3).match(2)
61+
self.assertIsNotNone(mismatch)
62+
assert mismatch is not None
5663
e = MismatchError(2, Equals(3), mismatch)
5764
self.assertEqual(mismatch.describe(), str(e))
5865

5966
def test_default_description_unicode(self):
6067
matchee = "\xa7"
6168
matcher = Equals("a")
6269
mismatch = matcher.match(matchee)
70+
self.assertIsNotNone(mismatch)
71+
assert mismatch is not None
6372
e = MismatchError(matchee, matcher, mismatch)
6473
self.assertEqual(mismatch.describe(), str(e))
6574

6675
def test_verbose_description(self):
6776
matchee = 2
6877
matcher = Equals(3)
69-
mismatch = matcher.match(2)
78+
mismatch = matcher.match(matchee)
79+
self.assertIsNotNone(mismatch)
80+
assert mismatch is not None
7081
e = MismatchError(matchee, matcher, mismatch, True)
7182
expected = (
7283
f"Match failed. Matchee: {matchee!r}\nMatcher: {matcher}\n"
73-
f"Difference: {matcher.match(matchee).describe()}\n"
84+
f"Difference: {mismatch.describe()}\n"
7485
)
7586
self.assertEqual(expected, str(e))
7687

@@ -80,6 +91,8 @@ def test_verbose_unicode(self):
8091
matchee = "\xa7"
8192
matcher = Equals("a")
8293
mismatch = matcher.match(matchee)
94+
self.assertIsNotNone(mismatch)
95+
assert mismatch is not None
8396
expected = (
8497
f"Match failed. Matchee: {text_repr(matchee)}\nMatcher: {matcher}\n"
8598
f"Difference: {mismatch.describe()}\n"
@@ -92,17 +105,17 @@ class TestMismatchDecorator(TestCase):
92105
run_tests_with = FullStackRunTest
93106

94107
def test_forwards_description(self):
95-
x = Mismatch("description", {"foo": "bar"})
108+
x = Mismatch("description", {"foo": text_content("bar")})
96109
decorated = MismatchDecorator(x)
97110
self.assertEqual(x.describe(), decorated.describe())
98111

99112
def test_forwards_details(self):
100-
x = Mismatch("description", {"foo": "bar"})
113+
x = Mismatch("description", {"foo": text_content("bar")})
101114
decorated = MismatchDecorator(x)
102115
self.assertEqual(x.get_details(), decorated.get_details())
103116

104117
def test_repr(self):
105-
x = Mismatch("description", {"foo": "bar"})
118+
x = Mismatch("description", {"foo": text_content("bar")})
106119
decorated = MismatchDecorator(x)
107120
self.assertEqual(
108121
f"<testtools.matchers.MismatchDecorator({x!r})>", repr(decorated)

0 commit comments

Comments
 (0)