Skip to content

Commit 0d9b7bd

Browse files
mauriciovasquezbernalOberon00
authored andcommitted
Fx bug in BoundedList for Python 3.4 and add tests (#199)
* fix bug in BoundedList for python 3.4 and add tests collections.deque.copy() was introduced in python 3.5, this commit changes that by the deque constructor and adds some tests to BoundedList and BoundedDict to avoid similar problems in the future. Also, improve docstrings of BoundedList and BoundedDict classes
1 parent d069c94 commit 0d9b7bd

File tree

2 files changed

+224
-3
lines changed

2 files changed

+224
-3
lines changed

opentelemetry-sdk/src/opentelemetry/sdk/util.py

+11-3
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ def ns_to_iso_str(nanoseconds):
4242

4343

4444
class BoundedList(Sequence):
45-
"""An append only list with a fixed max size."""
45+
"""An append only list with a fixed max size.
46+
47+
Calls to `append` and `extend` will drop the oldest elements if there is
48+
not enough room.
49+
"""
4650

4751
def __init__(self, maxlen):
4852
self.dropped = 0
@@ -62,7 +66,7 @@ def __len__(self):
6266

6367
def __iter__(self):
6468
with self._lock:
65-
return iter(self._dq.copy())
69+
return iter(deque(self._dq))
6670

6771
def append(self, item):
6872
with self._lock:
@@ -89,7 +93,11 @@ def from_seq(cls, maxlen, seq):
8993

9094

9195
class BoundedDict(MutableMapping):
92-
"""A dict with a fixed max capacity."""
96+
"""An ordered dict with a fixed max capacity.
97+
98+
Oldest elements are dropped when the dict is full and a new element is
99+
added.
100+
"""
93101

94102
def __init__(self, maxlen):
95103
if not isinstance(maxlen, int):

opentelemetry-sdk/tests/test_util.py

+213
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
# Copyright 2019, OpenTelemetry Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import collections
16+
import unittest
17+
18+
from opentelemetry.sdk.util import BoundedDict, BoundedList
19+
20+
21+
class TestBoundedList(unittest.TestCase):
22+
base = [52, 36, 53, 29, 54, 99, 56, 48, 22, 35, 21, 65, 10, 95, 42, 60]
23+
24+
def test_raises(self):
25+
"""Test corner cases
26+
27+
- negative list size
28+
- access out of range indexes
29+
"""
30+
with self.assertRaises(ValueError):
31+
BoundedList(-1)
32+
33+
blist = BoundedList(4)
34+
blist.append(37)
35+
blist.append(13)
36+
37+
with self.assertRaises(IndexError):
38+
_ = blist[2]
39+
40+
with self.assertRaises(IndexError):
41+
_ = blist[4]
42+
43+
with self.assertRaises(IndexError):
44+
_ = blist[-3]
45+
46+
def test_from_seq(self):
47+
list_len = len(self.base)
48+
base_copy = list(self.base)
49+
blist = BoundedList.from_seq(list_len, base_copy)
50+
51+
self.assertEqual(len(blist), list_len)
52+
53+
# modify base_copy and test that blist is not changed
54+
for idx in range(list_len):
55+
base_copy[idx] = idx * base_copy[idx]
56+
57+
for idx in range(list_len):
58+
self.assertEqual(blist[idx], self.base[idx])
59+
60+
# test that iter yields the correct number of elements
61+
self.assertEqual(len(tuple(blist)), list_len)
62+
63+
# sequence too big
64+
with self.assertRaises(ValueError):
65+
BoundedList.from_seq(list_len / 2, self.base)
66+
67+
def test_append_no_drop(self):
68+
"""Append max capacity elements to the list without dropping elements."""
69+
# create empty list
70+
list_len = len(self.base)
71+
blist = BoundedList(list_len)
72+
self.assertEqual(len(blist), 0)
73+
74+
# fill list
75+
for item in self.base:
76+
blist.append(item)
77+
78+
self.assertEqual(len(blist), list_len)
79+
self.assertEqual(blist.dropped, 0)
80+
81+
for idx in range(list_len):
82+
self.assertEqual(blist[idx], self.base[idx])
83+
84+
# test __iter__ in BoundedList
85+
for idx, val in enumerate(blist):
86+
self.assertEqual(val, self.base[idx])
87+
88+
def test_append_drop(self):
89+
"""Append more than max capacity elements and test that oldest ones are dropped."""
90+
list_len = len(self.base)
91+
# create full BoundedList
92+
blist = BoundedList.from_seq(list_len, self.base)
93+
94+
# try to append more items
95+
for val in self.base:
96+
# should drop the element without raising exceptions
97+
blist.append(2 * val)
98+
99+
self.assertEqual(len(blist), list_len)
100+
self.assertEqual(blist.dropped, list_len)
101+
102+
# test that new elements are in the list
103+
for idx in range(list_len):
104+
self.assertEqual(blist[idx], 2 * self.base[idx])
105+
106+
def test_extend_no_drop(self):
107+
# create empty list
108+
list_len = len(self.base)
109+
blist = BoundedList(list_len)
110+
self.assertEqual(len(blist), 0)
111+
112+
# fill list
113+
blist.extend(self.base)
114+
115+
self.assertEqual(len(blist), list_len)
116+
self.assertEqual(blist.dropped, 0)
117+
118+
for idx in range(list_len):
119+
self.assertEqual(blist[idx], self.base[idx])
120+
121+
# test __iter__ in BoundedList
122+
for idx, val in enumerate(blist):
123+
self.assertEqual(val, self.base[idx])
124+
125+
def test_extend_drop(self):
126+
list_len = len(self.base)
127+
# create full BoundedList
128+
blist = BoundedList.from_seq(list_len, self.base)
129+
other_list = [13, 37, 51, 91]
130+
131+
# try to extend with more elements
132+
blist.extend(other_list)
133+
134+
self.assertEqual(len(blist), list_len)
135+
self.assertEqual(blist.dropped, len(other_list))
136+
137+
138+
class TestBoundedDict(unittest.TestCase):
139+
base = collections.OrderedDict(
140+
[
141+
("name", "Firulais"),
142+
("age", 7),
143+
("weight", 13),
144+
("vaccinated", True),
145+
]
146+
)
147+
148+
def test_negative_maxlen(self):
149+
with self.assertRaises(ValueError):
150+
BoundedDict(-1)
151+
152+
def test_from_map(self):
153+
dic_len = len(self.base)
154+
base_copy = collections.OrderedDict(self.base)
155+
bdict = BoundedDict.from_map(dic_len, base_copy)
156+
157+
self.assertEqual(len(bdict), dic_len)
158+
159+
# modify base_copy and test that bdict is not changed
160+
base_copy["name"] = "Bruno"
161+
base_copy["age"] = 3
162+
163+
for key in self.base:
164+
self.assertEqual(bdict[key], self.base[key])
165+
166+
# test that iter yields the correct number of elements
167+
self.assertEqual(len(tuple(bdict)), dic_len)
168+
169+
# map too big
170+
with self.assertRaises(ValueError):
171+
BoundedDict.from_map(dic_len / 2, self.base)
172+
173+
def test_bounded_dict(self):
174+
# create empty dict
175+
dic_len = len(self.base)
176+
bdict = BoundedDict(dic_len)
177+
self.assertEqual(len(bdict), 0)
178+
179+
# fill dict
180+
for key in self.base:
181+
bdict[key] = self.base[key]
182+
183+
self.assertEqual(len(bdict), dic_len)
184+
self.assertEqual(bdict.dropped, 0)
185+
186+
for key in self.base:
187+
self.assertEqual(bdict[key], self.base[key])
188+
189+
# test __iter__ in BoundedDict
190+
for key in bdict:
191+
self.assertEqual(bdict[key], self.base[key])
192+
193+
# updating an existing element should not drop
194+
bdict["name"] = "Bruno"
195+
self.assertEqual(bdict.dropped, 0)
196+
197+
# try to append more elements
198+
for key in self.base:
199+
bdict["new-" + key] = self.base[key]
200+
201+
self.assertEqual(len(bdict), dic_len)
202+
self.assertEqual(bdict.dropped, dic_len)
203+
204+
# test that elements in the dict are the new ones
205+
for key in self.base:
206+
self.assertEqual(bdict["new-" + key], self.base[key])
207+
208+
# delete an element
209+
del bdict["new-name"]
210+
self.assertEqual(len(bdict), dic_len - 1)
211+
212+
with self.assertRaises(KeyError):
213+
_ = bdict["new-name"]

0 commit comments

Comments
 (0)