-
Notifications
You must be signed in to change notification settings - Fork 227
Expand file tree
/
Copy pathtest_history.py
More file actions
194 lines (146 loc) · 6.9 KB
/
test_history.py
File metadata and controls
194 lines (146 loc) · 6.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
"""Unit tests for _history.py."""
from __future__ import annotations
import zeroconf as r
from zeroconf import const
from zeroconf._history import QuestionHistory
def test_question_suppression():
history = QuestionHistory()
question = r.DNSQuestion("_hap._tcp._local.", const._TYPE_PTR, const._CLASS_IN)
now = r.current_time_millis()
other_known_answers: set[r.DNSRecord] = {
r.DNSPointer(
"_hap._tcp.local.",
const._TYPE_PTR,
const._CLASS_IN,
10000,
"known-to-other._hap._tcp.local.",
)
}
our_known_answers: set[r.DNSRecord] = {
r.DNSPointer(
"_hap._tcp.local.",
const._TYPE_PTR,
const._CLASS_IN,
10000,
"known-to-us._hap._tcp.local.",
)
}
history.add_question_at_time(question, now, other_known_answers)
# Verify the question is suppressed if the known answers are the same
assert history.suppresses(question, now, other_known_answers)
# Verify the question is suppressed if we know the answer to all the known answers
assert history.suppresses(question, now, other_known_answers | our_known_answers)
# Verify the question is not suppressed if our known answers do no include the ones in the last question
assert not history.suppresses(question, now, set())
# Verify the question is not suppressed if our known answers do no include the ones in the last question
assert not history.suppresses(question, now, our_known_answers)
# Verify the question is no longer suppressed after 1s
assert not history.suppresses(question, now + 1000, other_known_answers)
def test_question_expire():
history = QuestionHistory()
now = r.current_time_millis()
question = r.DNSQuestion("_hap._tcp._local.", const._TYPE_PTR, const._CLASS_IN)
other_known_answers: set[r.DNSRecord] = {
r.DNSPointer(
"_hap._tcp.local.",
const._TYPE_PTR,
const._CLASS_IN,
10000,
"known-to-other._hap._tcp.local.",
created=now,
)
}
history.add_question_at_time(question, now, other_known_answers)
# Verify the question is suppressed if the known answers are the same
assert history.suppresses(question, now, other_known_answers)
history.async_expire(now)
# Verify the question is suppressed if the known answers are the same since the cache hasn't expired
assert history.suppresses(question, now, other_known_answers)
history.async_expire(now + 1000)
# Verify the question not longer suppressed since the cache has expired
assert not history.suppresses(question, now, other_known_answers)
def test_question_history_bounded():
"""History keeps a hard cap so a LAN flood cannot grow it without bound."""
history = QuestionHistory()
now = r.current_time_millis()
answers: set[r.DNSRecord] = set()
cap = const._MAX_QUESTION_HISTORY_ENTRIES
for i in range(cap + 500):
q = r.DNSQuestion(f"_svc{i}._tcp.local.", const._TYPE_PTR, const._CLASS_IN)
history.add_question_at_time(q, now, answers)
assert len(history._history) <= cap
def test_question_history_evicts_oldest_first():
"""When at cap, the oldest insertion is dropped first."""
history = QuestionHistory()
now = r.current_time_millis()
answers: set[r.DNSRecord] = set()
cap = const._MAX_QUESTION_HISTORY_ENTRIES
first = r.DNSQuestion("_first._tcp.local.", const._TYPE_PTR, const._CLASS_IN)
history.add_question_at_time(first, now, answers)
# Add `cap` more fresh, non-expired entries — one past the cap — so the
# final insertion forces oldest-first eviction of `first`.
for i in range(cap):
q = r.DNSQuestion(f"_svc{i}._tcp.local.", const._TYPE_PTR, const._CLASS_IN)
history.add_question_at_time(q, now, answers)
assert first not in history._history
assert len(history._history) <= cap
def test_question_history_opportunistic_expire():
"""Adding past the cap first drops expired entries before evicting fresh ones."""
history = QuestionHistory()
old = r.current_time_millis()
answers: set[r.DNSRecord] = set()
cap = const._MAX_QUESTION_HISTORY_ENTRIES
for i in range(cap):
q = r.DNSQuestion(f"_stale{i}._tcp.local.", const._TYPE_PTR, const._CLASS_IN)
history.add_question_at_time(q, old, answers)
# All prior entries are now stale (>999ms old). Adding one more should
# trigger opportunistic expiry rather than evicting only the oldest one.
fresh_now = old + const._DUPLICATE_QUESTION_INTERVAL + 1
fresh = r.DNSQuestion("_fresh._tcp.local.", const._TYPE_PTR, const._CLASS_IN)
history.add_question_at_time(fresh, fresh_now, answers)
assert fresh in history._history
assert len(history._history) == 1
def _make_known_answers(count: int) -> set[r.DNSRecord]:
"""Build a set of ``count`` distinct PTR records for use as known-answers."""
return {
r.DNSPointer(
"_svc._tcp.local.",
const._TYPE_PTR,
const._CLASS_IN,
10000,
f"target{i}._svc._tcp.local.",
)
for i in range(count)
}
def test_question_history_oversized_known_answers_dropped():
"""Known-answer sets above the per-entry cap are not stored."""
history = QuestionHistory()
now = r.current_time_millis()
question = r.DNSQuestion("_svc._tcp.local.", const._TYPE_PTR, const._CLASS_IN)
oversized = _make_known_answers(const._MAX_KNOWN_ANSWERS_PER_HISTORY_ENTRY + 1)
history.add_question_at_time(question, now, oversized)
assert question not in history._history
def test_question_history_oversized_preserves_existing_entry():
"""An oversized payload must not displace a pre-existing small entry."""
history = QuestionHistory()
now = r.current_time_millis()
question = r.DNSQuestion("_svc._tcp.local.", const._TYPE_PTR, const._CLASS_IN)
small = _make_known_answers(2)
history.add_question_at_time(question, now, small)
assert history.suppresses(question, now, small)
# An oversized follow-up must be ignored; the small entry stays and
# continues to drive suppression.
oversized = _make_known_answers(const._MAX_KNOWN_ANSWERS_PER_HISTORY_ENTRY + 1)
history.add_question_at_time(question, now, oversized)
stored_set = history._history[question][1]
assert stored_set is small
assert history.suppresses(question, now, small)
def test_question_history_at_cap_known_answers_is_stored():
"""A known-answer set exactly at the per-entry cap is retained."""
history = QuestionHistory()
now = r.current_time_millis()
question = r.DNSQuestion("_svc._tcp.local.", const._TYPE_PTR, const._CLASS_IN)
at_cap = _make_known_answers(const._MAX_KNOWN_ANSWERS_PER_HISTORY_ENTRY)
history.add_question_at_time(question, now, at_cap)
assert question in history._history
assert history._history[question][1] is at_cap