Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ Changes

Fully migrate away from Launchpad to GitHub.

Improvements
------------

* Support binary contents in ``FileContains`` matcher.
(Jelmer Vernooij, #538)

2.8.1
~~~~~

Expand Down
35 changes: 35 additions & 0 deletions tests/matchers/test_filesystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,41 @@ def test_does_not_contain(self):
Equals(mismatch.describe()),
)

def test_binary_content(self):
tempdir = self.mkdtemp()
filename = os.path.join(tempdir, "binary_file")
binary_data = b"\x00\x01\x02\x03\xff\xfe"
with open(filename, "wb") as f:
f.write(binary_data)
self.assertThat(filename, FileContains(binary_data))

def test_binary_content_mismatch(self):
tempdir = self.mkdtemp()
filename = os.path.join(tempdir, "binary_file")
with open(filename, "wb") as f:
f.write(b"\x00\x01\x02")
mismatch = FileContains(b"\xff\xfe\xfd").match(filename)
self.assertThat(
Equals(b"\xff\xfe\xfd").match(b"\x00\x01\x02").describe(),
Equals(mismatch.describe()),
)

def test_text_with_encoding(self):
tempdir = self.mkdtemp()
filename = os.path.join(tempdir, "utf8_file")
text_data = "Hello 世界!"
with open(filename, "w", encoding="utf-8") as f:
f.write(text_data)
self.assertThat(filename, FileContains(text_data, encoding="utf-8"))

def test_text_default_encoding(self):
tempdir = self.mkdtemp()
filename = os.path.join(tempdir, "text_file")
text_data = "Hello World!"
with open(filename, "w") as f:
f.write(text_data)
self.assertThat(filename, FileContains(text_data))


class TestTarballContains(TestCase, PathHelpers):
def test_match(self):
Expand Down
25 changes: 17 additions & 8 deletions testtools/matchers/_filesystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def match(self, path):
class FileContains(Matcher):
"""Matches if the given file has the specified contents."""

def __init__(self, contents=None, matcher=None):
def __init__(self, contents=None, matcher=None, encoding=None):
"""Construct a ``FileContains`` matcher.

Can be used in a basic mode where the file contents are compared for
Expand All @@ -103,9 +103,14 @@ def __init__(self, contents=None, matcher=None):
matched against an arbitrary matcher (by passing ``matcher`` instead).

:param contents: If specified, match the contents of the file with
these contents.
these contents. If bytes, the file will be opened in binary mode.
If str, the file will be opened in text mode using the specified
encoding (or the default encoding if not specified).
:param matcher: If specified, match the contents of the file against
this matcher.
:param encoding: Optional text encoding to use when opening the file
in text mode. Only used when contents is a str (or when using a
matcher for text content). Defaults to the system default encoding.
"""
if contents == matcher is None:
raise AssertionError("Must provide one of `contents` or `matcher`.")
Expand All @@ -115,19 +120,23 @@ def __init__(self, contents=None, matcher=None):
)
if matcher is None:
self.matcher = Equals(contents)
self._binary_mode = isinstance(contents, bytes)
else:
self.matcher = matcher
self._binary_mode = False
self.encoding = encoding

def match(self, path):
mismatch = PathExists().match(path)
if mismatch is not None:
return mismatch
f = open(path)
try:
actual_contents = f.read()
return self.matcher.match(actual_contents)
finally:
f.close()
if self._binary_mode:
with open(path, "rb") as f:
actual_contents: bytes | str = f.read()
else:
with open(path, encoding=self.encoding) as f:
actual_contents = f.read()
return self.matcher.match(actual_contents)

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