1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495 |
- # Copyright (c) Twisted Matrix Laboratories.
- # See LICENSE for details.
-
- """
- Hamcrest matchers useful throughout the test suite.
- """
-
- from typing import IO, Callable, Optional, TypeVar
-
- from hamcrest.core.base_matcher import BaseMatcher
- from hamcrest.core.description import Description
- from hamcrest.core.matcher import Matcher
-
- from twisted.python.filepath import IFilePath
- from twisted.python.reflect import fullyQualifiedName
-
- _A = TypeVar("_A")
- _B = TypeVar("_B")
-
-
- class _MatchAfter(BaseMatcher[_A]):
- """
- The implementation of L{after}.
-
- @ivar f: The function to apply.
- @ivar m: The matcher to use on the result.
-
- @ivar _e: After trying to apply the function fails with an exception, the
- exception that was raised. This can later be used by
- L{describe_mismatch}.
- """
-
- def __init__(self, f: Callable[[_A], _B], m: Matcher[_B]):
- self.f = f
- self.m = m
- self._e: Optional[Exception] = None
-
- def _matches(self, item: _A) -> bool:
- """
- Apply the function and delegate matching on the result.
- """
- try:
- transformed = self.f(item)
- except Exception as e:
- self._e = e
- return False
- else:
- return self.m.matches(transformed)
-
- def describe_mismatch(self, item: _A, mismatch_description: Description) -> None:
- """
- Describe the mismatching item or the exception that occurred while
- pre-processing it.
-
- @note: Since the exception reporting here depends on mutable state it
- will only work as long as PyHamcrest calls methods in the right
- order. The PyHamcrest Matcher interface doesn't seem to allow
- implementing this functionality in a more reliable way (see the
- implementation of L{assert_that}).
- """
- if self._e is None:
- super().describe_mismatch(item, mismatch_description)
- else:
- mismatch_description.append_text(
- f"{fullyQualifiedName(self.f)}({item!r}) raised\n"
- f"{fullyQualifiedName(self._e.__class__)}: {self._e}"
- )
-
- def describe_to(self, description: Description) -> None:
- """
- Create a text description of the match requirement.
- """
- description.append_text(f"[after {self.f}] ")
- self.m.describe_to(description)
-
-
- def after(f: Callable[[_A], _B], m: Matcher[_B]) -> Matcher[_A]:
- """
- Create a matcher which calls C{f} and uses C{m} to match the result.
- """
- return _MatchAfter(f, m)
-
-
- def fileContents(m: Matcher[str], encoding: str = "utf-8") -> Matcher[IFilePath]:
- """
- Create a matcher which matches a L{FilePath} the contents of which are
- matched by L{m}.
- """
-
- def getContent(p: IFilePath) -> str:
- f: IO[bytes]
- with p.open() as f:
- return f.read().decode(encoding)
-
- return after(getContent, m)
|