Skip to content

Commit 431e516

Browse files
committed
add KernelPHash tests
1 parent 7a9cd87 commit 431e516

File tree

2 files changed

+197
-6
lines changed

2 files changed

+197
-6
lines changed

pytest_mpl/kernels.py

+7-6
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
DEFAULT_HIGH_FREQUENCY_FACTOR = 4
1919

2020
#: Registered kernel names.
21-
KERNEL_SHA256 = "sha256"
2221
KERNEL_PHASH = "phash"
22+
KERNEL_SHA256 = "sha256"
2323

2424
__all__ = [
2525
"DEFAULT_HAMMING_TOLERANCE",
@@ -137,15 +137,16 @@ def __init__(self, plugin):
137137
# Keep state of hash hamming distance (whole number) result.
138138
self.hamming_distance = None
139139
# Value may be overridden by py.test marker kwarg.
140-
self.hamming_tolerance = (
141-
self._plugin.hamming_tolerance or DEFAULT_HAMMING_TOLERANCE
142-
)
140+
arg = self._plugin.hamming_tolerance
141+
self.hamming_tolerance = arg if arg is not None else DEFAULT_HAMMING_TOLERANCE
143142
# The hash-size (N) defines the resultant N**2 bits hash size.
144-
self.hash_size = self._plugin.hash_size
143+
arg = self._plugin.hash_size
144+
self.hash_size = arg if arg is not None else DEFAULT_HASH_SIZE
145145
# The level of image detail (high freq) or structure (low freq)
146146
# represented in perceptual hash thru discrete cosine transform.
147+
arg = self._plugin.high_freq_factor
147148
self.high_freq_factor = (
148-
self._plugin.high_freq_factor or DEFAULT_HIGH_FREQUENCY_FACTOR
149+
arg if arg is not None else DEFAULT_HIGH_FREQUENCY_FACTOR
149150
)
150151
# py.test marker kwarg.
151152
self.option = "hamming_tolerance"

tests/test_kernels.py

+190
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
from pathlib import Path
2+
from unittest.mock import sentinel
3+
4+
import pytest
5+
6+
from pytest_mpl.kernels import (DEFAULT_HAMMING_TOLERANCE, DEFAULT_HASH_SIZE,
7+
DEFAULT_HIGH_FREQUENCY_FACTOR, KERNEL_PHASH, KERNEL_SHA256, Kernel,
8+
KernelPHash, KernelSHA256, kernel_factory)
9+
10+
HASH_SIZE = sentinel.hash_size
11+
HAMMING_TOLERANCE = sentinel.hamming_tolerance
12+
HIGH_FREQUENCY_FACTOR = sentinel.high_freq_factor
13+
14+
#: baseline hash (64-bit)
15+
HASH_BASE = "0123456789abcdef"
16+
17+
#: 2-bit baseline delta (64-bit)
18+
# ---X------------
19+
HASH_2BIT = "0120456789abcdef"
20+
21+
#: 4-bit baseline delta (64-bit)
22+
# --XX-----------X
23+
HASH_4BIT = "0100456789abcdee"
24+
25+
26+
#: Absolute path to test baseline image
27+
baseline_image = Path(__file__).parent / "baseline" / "2.0.x" / "test_base_style.png"
28+
29+
#: Verify availabilty of test baseline image
30+
baseline_unavailable = not baseline_image.is_file()
31+
32+
#: Convenience skipif reason
33+
baseline_missing = f"missing baseline image {str(baseline_image)!r}"
34+
35+
36+
class DummyMarker:
37+
def __init__(self, hamming_tolerance=None):
38+
self.kwargs = dict(hamming_tolerance=hamming_tolerance)
39+
40+
41+
class DummyPlugin:
42+
def __init__(self, hash_size=None, hamming_tolerance=None, high_freq_factor=None):
43+
self.hash_size = hash_size
44+
self.hamming_tolerance = hamming_tolerance
45+
self.high_freq_factor = high_freq_factor
46+
47+
48+
def test_kernel_abc():
49+
emsg = "Can't instantiate abstract class Kernel"
50+
with pytest.raises(TypeError, match=emsg):
51+
Kernel(None)
52+
53+
54+
def test_phash_name():
55+
assert KernelPHash.name == KERNEL_PHASH
56+
57+
58+
def test_phash_init__set():
59+
plugin = DummyPlugin(
60+
hash_size=HASH_SIZE,
61+
hamming_tolerance=HAMMING_TOLERANCE,
62+
high_freq_factor=HIGH_FREQUENCY_FACTOR,
63+
)
64+
kernel = KernelPHash(plugin)
65+
assert kernel.hash_size == HASH_SIZE
66+
assert kernel.hamming_tolerance == HAMMING_TOLERANCE
67+
assert kernel.high_freq_factor == HIGH_FREQUENCY_FACTOR
68+
assert kernel.equivalent is None
69+
assert kernel.hamming_distance is None
70+
71+
72+
def test_phash_init__default():
73+
plugin = DummyPlugin()
74+
kernel = KernelPHash(plugin)
75+
assert kernel.hash_size == DEFAULT_HASH_SIZE
76+
assert kernel.hamming_tolerance == DEFAULT_HAMMING_TOLERANCE
77+
assert kernel.high_freq_factor == DEFAULT_HIGH_FREQUENCY_FACTOR
78+
assert kernel.equivalent is None
79+
assert kernel.hamming_distance is None
80+
81+
82+
def test_phash_option():
83+
assert KernelPHash(DummyPlugin()).option == "hamming_tolerance"
84+
85+
86+
@pytest.mark.parametrize(
87+
"baseline,equivalent,distance",
88+
[(HASH_BASE, True, 0), (HASH_2BIT, True, 2), (HASH_4BIT, False, 4)],
89+
)
90+
def test_phash_equivalent(baseline, equivalent, distance):
91+
kernel = KernelPHash(DummyPlugin())
92+
assert kernel.equivalent_hash(HASH_BASE, baseline) is equivalent
93+
assert kernel.equivalent is equivalent
94+
assert kernel.hamming_distance == distance
95+
96+
97+
def test_phash_equivalent__tolerance():
98+
hamming_tolerance = 10
99+
plugin = DummyPlugin(hamming_tolerance=hamming_tolerance)
100+
kernel = KernelPHash(plugin)
101+
assert kernel.equivalent_hash(HASH_BASE, HASH_4BIT)
102+
assert kernel.equivalent is True
103+
assert kernel.hamming_tolerance == hamming_tolerance
104+
assert kernel.hamming_distance == 4
105+
106+
107+
@pytest.mark.parametrize(
108+
"tolerance,equivalent",
109+
[(10, True), (3, False)],
110+
)
111+
def test_phash_equivalent__marker(tolerance, equivalent):
112+
marker = DummyMarker(hamming_tolerance=tolerance)
113+
kernel = KernelPHash(DummyPlugin())
114+
assert kernel.hamming_tolerance == DEFAULT_HAMMING_TOLERANCE
115+
assert kernel.equivalent_hash(HASH_BASE, HASH_4BIT, marker=marker) is equivalent
116+
assert kernel.equivalent is equivalent
117+
assert kernel.hamming_tolerance == tolerance
118+
assert kernel.hamming_distance == 4
119+
120+
121+
@pytest.mark.skipif(baseline_unavailable, reason=baseline_missing)
122+
@pytest.mark.parametrize(
123+
"hash_size,hff,expected",
124+
[
125+
(
126+
DEFAULT_HASH_SIZE,
127+
DEFAULT_HIGH_FREQUENCY_FACTOR,
128+
"800bc0555feab05f67ea8d1779fa83537e7ec0d17f9f003517ef200985532856",
129+
),
130+
(
131+
DEFAULT_HASH_SIZE,
132+
8,
133+
"800fc0155fe8b05f67ea8d1779fa83537e7ec0d57f9f003517ef200985532856",
134+
),
135+
(8, DEFAULT_HIGH_FREQUENCY_FACTOR, "80c05fb1778d79c3"),
136+
(
137+
DEFAULT_HASH_SIZE,
138+
16,
139+
"800bc0155feab05f67ea8d1779fa83537e7ec0d57f9f003517ef200985532856",
140+
),
141+
],
142+
)
143+
def test_phash_generate_hash(hash_size, hff, expected):
144+
plugin = DummyPlugin(hash_size=hash_size, high_freq_factor=hff)
145+
kernel = KernelPHash(plugin)
146+
fh = open(baseline_image, "rb")
147+
actual = kernel.generate_hash(fh)
148+
assert actual == expected
149+
150+
151+
@pytest.mark.parametrize("message", (None, "", "one"))
152+
@pytest.mark.parametrize("equivalent", (None, True))
153+
def test_phash_update_status__none(message, equivalent):
154+
kernel = KernelPHash(DummyPlugin())
155+
kernel.equivalent = equivalent
156+
result = kernel.update_status(message)
157+
assert isinstance(result, str)
158+
expected = 0 if message is None else len(message)
159+
assert len(result) == expected
160+
161+
162+
@pytest.mark.parametrize("message", ("", "one"))
163+
@pytest.mark.parametrize("distance", (10, 20))
164+
@pytest.mark.parametrize("tolerance", (1, 2))
165+
def test_phash_update_status__equivalent(message, distance, tolerance):
166+
plugin = DummyPlugin(hamming_tolerance=tolerance)
167+
kernel = KernelPHash(plugin)
168+
kernel.equivalent = False
169+
kernel.hamming_distance = distance
170+
result = kernel.update_status(message)
171+
assert isinstance(result, str)
172+
template = "Hash hamming distance of {} bits > hamming tolerance of {} bits."
173+
status = template.format(distance, tolerance)
174+
expected = f"{message} {status}" if message else status
175+
assert result == expected
176+
177+
178+
@pytest.mark.parametrize(
179+
"summary,distance,tolerance,count",
180+
[({}, None, DEFAULT_HAMMING_TOLERANCE, 3), (dict(one=1), 2, 3, 4)],
181+
)
182+
def test_phash_update_summary(summary, distance, tolerance, count):
183+
plugin = DummyPlugin(hamming_tolerance=tolerance)
184+
kernel = KernelPHash(plugin)
185+
kernel.hamming_distance = distance
186+
kernel.update_summary(summary)
187+
assert summary["kernel"] == KernelPHash.name
188+
assert summary["hamming_distance"] == distance
189+
assert summary["hamming_tolerance"] == tolerance
190+
assert len(summary) == count

0 commit comments

Comments
 (0)