Skip to content

Commit

Permalink
Merge pull request #28 from zodb/issue26
Browse files Browse the repository at this point in the history
Add support for hamcrest 1.10 and above.
  • Loading branch information
jamadden committed Feb 4, 2021
2 parents a67acab + b242e53 commit 88a8ace
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 8 deletions.
3 changes: 2 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@

- Add support for Python 3.8 and 3.9.
- Move to GitHub Actions from Travis CI.

- Support PyHamcrest 1.10 and later. See `issue 26
<https://github.com/zodb/perfmetrics/issues/26>`_.

3.0.0 (2019-09-03)
==================
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def read(fname, here=os.path.dirname(__file__)):
tests_require = [
'zope.testrunner',
'nti.testing',
'pyhamcrest < 1.10',
'pyhamcrest >= 1.10',
'pyperf',
]

Expand Down
35 changes: 33 additions & 2 deletions src/perfmetrics/testing/matchers.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from hamcrest import has_properties
from hamcrest import none


from .observation import OBSERVATION_KIND_COUNTER as METRIC_COUNTER_KIND
from .observation import OBSERVATION_KIND_GAUGE as METRIC_GAUGE_KIND
from .observation import OBSERVATION_KIND_SET as METRIC_SET_KIND
Expand All @@ -37,8 +38,12 @@
's': 'set'
}


class IsMetric(BaseMatcher):

# See _matches()
_force_one_description = False

def __init__(self, kwargs):
matchers = {}
for key, value in kwargs.items():
Expand All @@ -52,13 +57,39 @@ def __init__(self, kwargs):
value = str(value)
matchers[key] = value

# Beginning in 1.10 and up through at least 2.0.2,
# has_properties con no longer be called with an empty dictionary
# without creating a KeyError from popitem().
self._matcher = all_of(
instance_of(Observation),
has_properties(**matchers)
has_properties(**matchers) if matchers else instance_of(Observation),
)
if self._force_one_description:
self.__patch_matcher()

def __patch_matcher(self):
# This is tightly coupled to the structure of the matcher we create.
self._matcher.describe_all_mismatches = False
has_prop_matcher = self._matcher.matchers[1]
if hasattr(has_prop_matcher, 'matcher'):
has_prop_matcher.matcher.describe_all_mismatches = False

def _matches(self, item):
return self._matcher.matches(item)
# On PyHamcrest == 1.10.x (last release for Python 2)
# There's a bug such that you can't call AllOf.matches without
# passing in a description without getting
# ``AttributeError: None has no append_text``
# So we pass one, even though it gets thrown away.
# XXX: Remove this workaround when we only support PyHamcrest 2.
# See https://github.com/hamcrest/PyHamcrest/issues/130
try:
return self._matcher.matches(item)
except AttributeError as ex:
if 'append_text' not in str(ex): # pragma: no cover
raise
IsMetric._force_one_description = True
self.__patch_matcher()
return self._matcher.matches(item)

def describe_to(self, description):
self._matcher.describe_to(description)
Expand Down
18 changes: 14 additions & 4 deletions src/perfmetrics/testing/tests/test_matchers.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,19 +70,29 @@ def test_failure_error(self):
matcher = is_counter('foo', '1', 0.1)
matcher.describe_to(desc)
desc = str(desc)
# Strip internal newlines, which vary between
# hamcrest versions. Also, the exact text varies too;
# beginning with 1.10, the has_properties matcher we use internally is
# less verbose by default and so we get a string like:
# an object with properties kind matching c and name matching foo
# where before it was something like
# an object with property kind matching c
# and an object with property name matching foo

desc = desc.replace('\n', '')
assert_that(
desc,
all_of(
contains_string(
"(an instance of Observation and "),
contains_string(
"an object with a property 'kind' matching 'c'"),
"'kind' matching 'c'"),
contains_string(
"an object with a property 'name' matching 'foo'"),
"'name' matching 'foo'"),
contains_string(
"an object with a property 'value' matching '1'"),
"'value' matching '1'"),
contains_string(
"an object with a property 'sampling_rate' matching <0.1>"
"'sampling_rate' matching <0.1>"
)
)
)
Expand Down

0 comments on commit 88a8ace

Please sign in to comment.