Skip to content

Commit ceb79bd

Browse files
authored
Add tests for the DNSCache class (#728)
- There is currently a bug in the implementation where an entry can exist in two places in the cache with different TTLs. Since a known answer cannot be both expired and expired at the same time, this is a bug that needs to be fixed.
1 parent 9cc834d commit ceb79bd

1 file changed

Lines changed: 102 additions & 0 deletions

File tree

tests/test_cache.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,38 @@ def test_order(self):
3737
cached_record = cache.get(entry)
3838
assert cached_record == record2
3939

40+
def test_adding_same_record_to_cache_different_ttls(self):
41+
"""We should always get back the last entry we added if there are different TTLs.
42+
43+
This ensures we only have one source of truth for TTLs as a record cannot
44+
be both expired and not expired.
45+
"""
46+
record1 = r.DNSAddress('a', const._TYPE_A, const._CLASS_IN, 1, b'a')
47+
record2 = r.DNSAddress('a', const._TYPE_A, const._CLASS_IN, 10, b'a')
48+
cache = r.DNSCache()
49+
cache.add(record1)
50+
cache.add(record2)
51+
entry = r.DNSEntry(record2)
52+
cached_record = cache.get(entry)
53+
assert cached_record == record2
54+
55+
@unittest.skip('This bug in the implementation needs to be fixed.')
56+
def test_adding_same_record_to_cache_different_ttls(self):
57+
"""Verify we only get one record back.
58+
59+
The last record added should replace the previous since two
60+
records with different ttls are __eq__. This ensures we
61+
only have one source of truth for TTLs as a record cannot
62+
be both expired and not expired.
63+
"""
64+
record1 = r.DNSAddress('a', const._TYPE_A, const._CLASS_IN, 1, b'a')
65+
record2 = r.DNSAddress('a', const._TYPE_A, const._CLASS_IN, 10, b'a')
66+
cache = r.DNSCache()
67+
cache.add(record1)
68+
cache.add(record2)
69+
cached_records = cache.get_all_by_details('a', const._TYPE_A, const._CLASS_IN)
70+
assert cached_records == [record2]
71+
4072
def test_cache_empty_does_not_leak_memory_by_leaving_empty_list(self):
4173
record1 = r.DNSAddress('a', const._TYPE_SOA, const._CLASS_IN, 1, b'a')
4274
record2 = r.DNSAddress('a', const._TYPE_SOA, const._CLASS_IN, 1, b'b')
@@ -61,3 +93,73 @@ def test_cache_empty_multiple_calls_does_not_throw(self):
6193
cache.remove(record1)
6294
cache.remove(record2)
6395
assert 'a' not in cache.cache
96+
97+
98+
# These functions have been seen in other projects so
99+
# we try to maintain a stable API for all the threadsafe getters
100+
class TestDNSCacheAPI(unittest.TestCase):
101+
def test_get(self):
102+
record1 = r.DNSAddress('a', const._TYPE_A, const._CLASS_IN, 1, b'a')
103+
record2 = r.DNSAddress('a', const._TYPE_A, const._CLASS_IN, 1, b'b')
104+
cache = r.DNSCache()
105+
cache.add_records([record1, record2])
106+
assert cache.get(record1) == record1
107+
assert cache.get(record2) == record2
108+
109+
def test_get_by_details(self):
110+
record1 = r.DNSAddress('a', const._TYPE_A, const._CLASS_IN, 1, b'a')
111+
record2 = r.DNSAddress('a', const._TYPE_A, const._CLASS_IN, 1, b'b')
112+
cache = r.DNSCache()
113+
cache.add_records([record1, record2])
114+
assert cache.get_by_details('a', const._TYPE_A, const._CLASS_IN) == record2
115+
116+
def test_get_all_by_details(self):
117+
record1 = r.DNSAddress('a', const._TYPE_A, const._CLASS_IN, 1, b'a')
118+
record2 = r.DNSAddress('a', const._TYPE_A, const._CLASS_IN, 1, b'b')
119+
cache = r.DNSCache()
120+
cache.add_records([record1, record2])
121+
assert set(cache.get_all_by_details('a', const._TYPE_A, const._CLASS_IN)) == set([record1, record2])
122+
123+
def test_entries_with_server(self):
124+
record1 = r.DNSService(
125+
'irrelevant', const._TYPE_SRV, const._CLASS_IN, const._DNS_HOST_TTL, 0, 0, 85, 'ab'
126+
)
127+
record2 = r.DNSService(
128+
'irrelevant', const._TYPE_SRV, const._CLASS_IN, const._DNS_HOST_TTL, 0, 0, 80, 'ab'
129+
)
130+
cache = r.DNSCache()
131+
cache.add_records([record1, record2])
132+
assert set(cache.entries_with_server('ab')) == set([record1, record2])
133+
134+
def test_entries_with_name(self):
135+
record1 = r.DNSService(
136+
'irrelevant', const._TYPE_SRV, const._CLASS_IN, const._DNS_HOST_TTL, 0, 0, 85, 'ab'
137+
)
138+
record2 = r.DNSService(
139+
'irrelevant', const._TYPE_SRV, const._CLASS_IN, const._DNS_HOST_TTL, 0, 0, 80, 'ab'
140+
)
141+
cache = r.DNSCache()
142+
cache.add_records([record1, record2])
143+
assert set(cache.entries_with_name('irrelevant')) == set([record1, record2])
144+
145+
def test_current_entry_with_name_and_alias(self):
146+
record1 = r.DNSPointer(
147+
'irrelevant', const._TYPE_PTR, const._CLASS_IN, const._DNS_OTHER_TTL, 'x.irrelevant'
148+
)
149+
record2 = r.DNSPointer(
150+
'irrelevant', const._TYPE_PTR, const._CLASS_IN, const._DNS_OTHER_TTL, 'y.irrelevant'
151+
)
152+
cache = r.DNSCache()
153+
cache.add_records([record1, record2])
154+
assert cache.current_entry_with_name_and_alias('irrelevant', 'x.irrelevant') == record1
155+
156+
def test_entries_with_name(self):
157+
record1 = r.DNSService(
158+
'irrelevant', const._TYPE_SRV, const._CLASS_IN, const._DNS_HOST_TTL, 0, 0, 85, 'ab'
159+
)
160+
record2 = r.DNSService(
161+
'irrelevant', const._TYPE_SRV, const._CLASS_IN, const._DNS_HOST_TTL, 0, 0, 80, 'ab'
162+
)
163+
cache = r.DNSCache()
164+
cache.add_records([record1, record2])
165+
assert cache.names() == ['irrelevant']

0 commit comments

Comments
 (0)