Skip to content

Commit ec4df13

Browse files
committed
tidy + inject kernel name to summary
1 parent 7bc9e02 commit ec4df13

File tree

2 files changed

+47
-34
lines changed

2 files changed

+47
-34
lines changed

pytest_mpl/kernels.py

Lines changed: 36 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -40,20 +40,20 @@ class Kernel(ABC):
4040
"""
4141

4242
def __init__(self, plugin):
43-
# Containment of the plugin allows the kernel to cherry-pick required state
43+
# Containment of the plugin allows the kernel to cherry-pick required state.
4444
self._plugin = plugin
4545

4646
@abstractmethod
47-
def equivalent_hash(self, actual, expected, marker=None):
47+
def equivalent_hash(self, result, baseline, marker=None):
4848
"""
49-
Determine whether the kernel considers the provided actual and
50-
expected hashes as similar.
49+
Determine whether the kernel considers the provided result (actual)
50+
and baseline (expected) hashes as similar.
5151
5252
Parameters
5353
----------
54-
actual : str
55-
The hash of the test image.
56-
expected : str
54+
result : str
55+
The hash of the image generated by the test.
56+
baseline : str
5757
The hash of the baseline image.
5858
marker : pytest.Mark
5959
The test marker, which may contain kwarg options to be
@@ -62,7 +62,7 @@ def equivalent_hash(self, actual, expected, marker=None):
6262
Returns
6363
-------
6464
bool
65-
Whether the actual and expected hashes are deemed similar.
65+
Whether the result and baseline hashes are deemed similar.
6666
6767
"""
6868

@@ -112,17 +112,16 @@ def update_summary(self, summary):
112112
113113
Returns
114114
-------
115-
dict
116-
The updated image comparison summary.
115+
None
117116
118117
"""
119-
return summary
118+
summary["kernel"] = self.name
120119

121120

122121
class KernelPHash(Kernel):
123122
"""
124123
Kernel that calculates a perceptual hash of an image for the
125-
specified hash size (N).
124+
specified hash size (N) and high frequency factor.
126125
127126
Where the resultant perceptual hash will be composed of N**2 bits.
128127
@@ -132,31 +131,37 @@ class KernelPHash(Kernel):
132131

133132
def __init__(self, plugin):
134133
super().__init__(plugin)
135-
# keep state of equivalence result
134+
# Keep state of the equivalence result.
136135
self.equivalent = None
137-
# keep state of hash hamming distance (whole number) result
136+
# Keep state of hash hamming distance (whole number) result.
138137
self.hamming_distance = None
139-
# value may be overridden by py.test marker kwarg
138+
# Value may be overridden by py.test marker kwarg.
140139
self.hamming_tolerance = (
141140
self._plugin.hamming_tolerance or DEFAULT_HAMMING_TOLERANCE
142141
)
143-
# the hash-size (N) defines the resultant N**2 bits hash size
142+
# The hash-size (N) defines the resultant N**2 bits hash size.
144143
self.hash_size = self._plugin.hash_size
145-
# the level of image detail or structure represented by perceptual hash
144+
# The level of image detail (high freq) or structure (low freq)
145+
# represented in perceptual hash thru discrete cosine transform.
146146
self.high_freq_factor = (
147147
self._plugin.high_freq_factor or DEFAULT_HIGH_FREQUENCY_FACTOR
148148
)
149-
# py.test marker kwarg
149+
# py.test marker kwarg.
150150
self.option = "hamming_tolerance"
151151

152-
def equivalent_hash(self, actual, expected, marker=None):
152+
def equivalent_hash(self, result, baseline, marker=None):
153153
if marker:
154154
value = marker.kwargs.get(self.option)
155155
if value is not None:
156+
# Override with the decorator marker value.
156157
self.hamming_tolerance = int(value)
157-
actual = imagehash.hex_to_hash(actual)
158-
expected = imagehash.hex_to_hash(expected)
159-
self.hamming_distance = actual - expected
158+
# Convert string hexdigest hashes to imagehash.ImageHash instances.
159+
result = imagehash.hex_to_hash(result)
160+
baseline = imagehash.hex_to_hash(baseline)
161+
# Unlike cryptographic hashes, perceptual hashes can measure the
162+
# degree of "similarity" through hamming distance bit differences
163+
# between the hashes.
164+
self.hamming_distance = result - baseline
160165
self.equivalent = self.hamming_distance <= self.hamming_tolerance
161166
return self.equivalent
162167

@@ -170,6 +175,7 @@ def generate_hash(self, buffer):
170175

171176
def update_status(self, message):
172177
result = str() if message is None else str(message)
178+
# Only update the status message for non-equivalent hash comparisons.
173179
if self.equivalent is False:
174180
msg = (
175181
f"Hash hamming distance of {self.hamming_distance} bits > "
@@ -179,9 +185,9 @@ def update_status(self, message):
179185
return result
180186

181187
def update_summary(self, summary):
188+
super().update_summary(summary)
182189
summary["hamming_distance"] = self.hamming_distance
183190
summary["hamming_tolerance"] = self.hamming_tolerance
184-
return summary
185191

186192

187193
class KernelSHA256(Kernel):
@@ -192,8 +198,13 @@ class KernelSHA256(Kernel):
192198

193199
name = KERNEL_SHA256
194200

195-
def equivalent_hash(self, actual, expected, marker=None):
196-
return actual == expected
201+
def equivalent_hash(self, result, baseline, marker=None):
202+
# Simple cryptographic hash binary comparison. Interpretation of
203+
# the comparison result is that the hashes are either identical or
204+
# not identical. For non-identical hashes, it is not possible to
205+
# determine a heuristic of hash "similarity" due to the nature of
206+
# cryptographic hashes.
207+
return result == baseline
197208

198209
def generate_hash(self, buffer):
199210
buffer.seek(0)

pytest_mpl/plugin.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -181,12 +181,14 @@ def pytest_addoption(parser):
181181
parser.addini(option, help=msg)
182182

183183
msg = 'The hash size (N) used to generate a N**2 bit image hash.'
184-
group.addoption('--mpl-hash-size', help=msg, action='store')
185-
parser.addini('mpl-hash-size', help=msg)
184+
option = 'mpl-hash-size'
185+
group.addoption(f'--{option}', help=msg, action='store')
186+
parser.addini(option, help=msg)
186187

187188
msg = 'Hamming distance bit tolerance for similar image hashes.'
188-
group.addoption('--mpl-hamming-tolerance', help=msg, action='store')
189-
parser.addini('mpl-hamming-tolerance', help=msg)
189+
option = 'mpl-hamming-tolerance'
190+
group.addoption(f'--{option}', help=msg, action='store')
191+
parser.addini(option, help=msg)
190192

191193

192194
def pytest_configure(config):
@@ -620,17 +622,17 @@ def compare_image_to_hash_library(self, item, fig, result_dir, summary=None):
620622
baseline_hash = hash_library.get(hash_name, None)
621623
summary['baseline_hash'] = baseline_hash
622624

623-
test_hash = self.generate_image_hash(item, fig)
624-
summary['result_hash'] = test_hash
625+
result_hash = self.generate_image_hash(item, fig)
626+
summary['result_hash'] = result_hash
625627

626628
if baseline_hash is None: # hash-missing
627629
summary['status'] = 'failed'
628630
summary['hash_status'] = 'missing'
629631
msg = (f'Hash for test {hash_name!r} not found in '
630632
f'{hash_library_filename!r}. Generated hash is '
631-
f'{test_hash!r}.')
633+
f'{result_hash!r}.')
632634
summary['status_msg'] = msg
633-
elif self.kernel.equivalent_hash(test_hash, baseline_hash): # hash-match
635+
elif self.kernel.equivalent_hash(result_hash, baseline_hash): # hash-match
634636
hash_comparison_pass = True
635637
summary['status'] = 'passed'
636638
summary['hash_status'] = 'match'
@@ -639,7 +641,7 @@ def compare_image_to_hash_library(self, item, fig, result_dir, summary=None):
639641
else: # hash-diff
640642
summary['status'] = 'failed'
641643
summary['hash_status'] = 'diff'
642-
msg = (f'Result hash {test_hash!r} does not match baseline hash '
644+
msg = (f'Result hash {result_hash!r} does not match baseline hash '
643645
f'{baseline_hash!r} in library {str(hash_library_filename)!r} '
644646
f'for test {hash_name!r}.')
645647
summary['status_msg'] = self.kernel.update_status(msg)

0 commit comments

Comments
 (0)