Skip to content

Commit eff6f9e

Browse files
committed
Support binary contents in FileContains matcher
Fixes #538
1 parent 50fa530 commit eff6f9e

File tree

3 files changed

+58
-8
lines changed

3 files changed

+58
-8
lines changed

NEWS

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ Changes
1111

1212
Fully migrate away from Launchpad to GitHub.
1313

14+
Improvements
15+
------------
16+
17+
* Support binary contents in ``FileContains`` matcher.
18+
(Jelmer Vernooij, #538)
19+
1420
2.8.1
1521
~~~~~
1622

tests/matchers/test_filesystem.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,41 @@ def test_does_not_contain(self):
175175
Equals(mismatch.describe()),
176176
)
177177

178+
def test_binary_content(self):
179+
tempdir = self.mkdtemp()
180+
filename = os.path.join(tempdir, "binary_file")
181+
binary_data = b"\x00\x01\x02\x03\xff\xfe"
182+
with open(filename, "wb") as f:
183+
f.write(binary_data)
184+
self.assertThat(filename, FileContains(binary_data))
185+
186+
def test_binary_content_mismatch(self):
187+
tempdir = self.mkdtemp()
188+
filename = os.path.join(tempdir, "binary_file")
189+
with open(filename, "wb") as f:
190+
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+
)
196+
197+
def test_text_with_encoding(self):
198+
tempdir = self.mkdtemp()
199+
filename = os.path.join(tempdir, "utf8_file")
200+
text_data = "Hello 世界!"
201+
with open(filename, "w", encoding="utf-8") as f:
202+
f.write(text_data)
203+
self.assertThat(filename, FileContains(text_data, encoding="utf-8"))
204+
205+
def test_text_default_encoding(self):
206+
tempdir = self.mkdtemp()
207+
filename = os.path.join(tempdir, "text_file")
208+
text_data = "Hello World!"
209+
with open(filename, "w") as f:
210+
f.write(text_data)
211+
self.assertThat(filename, FileContains(text_data))
212+
178213

179214
class TestTarballContains(TestCase, PathHelpers):
180215
def test_match(self):

testtools/matchers/_filesystem.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ def match(self, path):
9494
class FileContains(Matcher):
9595
"""Matches if the given file has the specified contents."""
9696

97-
def __init__(self, contents=None, matcher=None):
97+
def __init__(self, contents=None, matcher=None, encoding=None):
9898
"""Construct a ``FileContains`` matcher.
9999
100100
Can be used in a basic mode where the file contents are compared for
@@ -103,9 +103,14 @@ def __init__(self, contents=None, matcher=None):
103103
matched against an arbitrary matcher (by passing ``matcher`` instead).
104104
105105
:param contents: If specified, match the contents of the file with
106-
these contents.
106+
these contents. If bytes, the file will be opened in binary mode.
107+
If str, the file will be opened in text mode using the specified
108+
encoding (or the default encoding if not specified).
107109
:param matcher: If specified, match the contents of the file against
108110
this matcher.
111+
:param encoding: Optional text encoding to use when opening the file
112+
in text mode. Only used when contents is a str (or when using a
113+
matcher for text content). Defaults to the system default encoding.
109114
"""
110115
if contents == matcher is None:
111116
raise AssertionError("Must provide one of `contents` or `matcher`.")
@@ -115,19 +120,23 @@ def __init__(self, contents=None, matcher=None):
115120
)
116121
if matcher is None:
117122
self.matcher = Equals(contents)
123+
self._binary_mode = isinstance(contents, bytes)
118124
else:
119125
self.matcher = matcher
126+
self._binary_mode = False
127+
self.encoding = encoding
120128

121129
def match(self, path):
122130
mismatch = PathExists().match(path)
123131
if mismatch is not None:
124132
return mismatch
125-
f = open(path)
126-
try:
127-
actual_contents = f.read()
128-
return self.matcher.match(actual_contents)
129-
finally:
130-
f.close()
133+
if self._binary_mode:
134+
with open(path, "rb") as f:
135+
actual_contents: bytes | str = f.read()
136+
else:
137+
with open(path, encoding=self.encoding) as f:
138+
actual_contents = f.read()
139+
return self.matcher.match(actual_contents)
131140

132141
def __str__(self):
133142
return f"File at path exists and contains {self.matcher}"

0 commit comments

Comments
 (0)