test_collections.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462
  1. from __future__ import absolute_import, unicode_literals
  2. import pickle
  3. import pytest
  4. from collections import Mapping
  5. from itertools import count
  6. from time import time
  7. from case import skip
  8. from billiard.einfo import ExceptionInfo
  9. from celery.utils.collections import (
  10. AttributeDict,
  11. BufferMap,
  12. ConfigurationView,
  13. DictAttribute,
  14. LimitedSet,
  15. Messagebuffer,
  16. )
  17. from celery.five import items
  18. from celery.utils.objects import Bunch
  19. class test_DictAttribute:
  20. def test_get_set_keys_values_items(self):
  21. x = DictAttribute(Bunch())
  22. x['foo'] = 'The quick brown fox'
  23. assert x['foo'] == 'The quick brown fox'
  24. assert x['foo'] == x.obj.foo
  25. assert x.get('foo') == 'The quick brown fox'
  26. assert x.get('bar') is None
  27. with pytest.raises(KeyError):
  28. x['bar']
  29. x.foo = 'The quick yellow fox'
  30. assert x['foo'] == 'The quick yellow fox'
  31. assert ('foo', 'The quick yellow fox') in list(x.items())
  32. assert 'foo' in list(x.keys())
  33. assert 'The quick yellow fox' in list(x.values())
  34. def test_setdefault(self):
  35. x = DictAttribute(Bunch())
  36. x.setdefault('foo', 'NEW')
  37. assert x['foo'] == 'NEW'
  38. x.setdefault('foo', 'XYZ')
  39. assert x['foo'] == 'NEW'
  40. def test_contains(self):
  41. x = DictAttribute(Bunch())
  42. x['foo'] = 1
  43. assert 'foo' in x
  44. assert 'bar' not in x
  45. def test_items(self):
  46. obj = Bunch(attr1=1)
  47. x = DictAttribute(obj)
  48. x['attr2'] = 2
  49. assert x['attr1'] == 1
  50. assert x['attr2'] == 2
  51. class test_ConfigurationView:
  52. def setup(self):
  53. self.view = ConfigurationView(
  54. {'changed_key': 1, 'both': 2},
  55. [
  56. {'default_key': 1, 'both': 1},
  57. ],
  58. )
  59. def test_setdefault(self):
  60. self.view.setdefault('both', 36)
  61. assert self.view['both'] == 2
  62. self.view.setdefault('new', 36)
  63. assert self.view['new'] == 36
  64. def test_get(self):
  65. assert self.view.get('both') == 2
  66. sp = object()
  67. assert self.view.get('nonexisting', sp) is sp
  68. def test_update(self):
  69. changes = dict(self.view.changes)
  70. self.view.update(a=1, b=2, c=3)
  71. assert self.view.changes == dict(changes, a=1, b=2, c=3)
  72. def test_contains(self):
  73. assert 'changed_key' in self.view
  74. assert 'default_key' in self.view
  75. assert 'new' not in self.view
  76. def test_repr(self):
  77. assert 'changed_key' in repr(self.view)
  78. assert 'default_key' in repr(self.view)
  79. def test_iter(self):
  80. expected = {
  81. 'changed_key': 1,
  82. 'default_key': 1,
  83. 'both': 2,
  84. }
  85. assert dict(items(self.view)) == expected
  86. assert sorted(list(iter(self.view))) == sorted(list(expected.keys()))
  87. assert sorted(list(self.view.keys())) == sorted(list(expected.keys()))
  88. assert (sorted(list(self.view.values())) ==
  89. sorted(list(expected.values())))
  90. assert 'changed_key' in list(self.view.keys())
  91. assert 2 in list(self.view.values())
  92. assert ('both', 2) in list(self.view.items())
  93. def test_add_defaults_dict(self):
  94. defaults = {'foo': 10}
  95. self.view.add_defaults(defaults)
  96. assert self.view.foo == 10
  97. def test_add_defaults_object(self):
  98. defaults = Bunch(foo=10)
  99. self.view.add_defaults(defaults)
  100. assert self.view.foo == 10
  101. def test_clear(self):
  102. self.view.clear()
  103. assert self.view.both == 1
  104. assert 'changed_key' not in self.view
  105. def test_bool(self):
  106. assert bool(self.view)
  107. self.view.maps[:] = []
  108. assert not bool(self.view)
  109. def test_len(self):
  110. assert len(self.view) == 3
  111. self.view.KEY = 33
  112. assert len(self.view) == 4
  113. self.view.clear()
  114. assert len(self.view) == 2
  115. def test_isa_mapping(self):
  116. from collections import Mapping
  117. assert issubclass(ConfigurationView, Mapping)
  118. def test_isa_mutable_mapping(self):
  119. from collections import MutableMapping
  120. assert issubclass(ConfigurationView, MutableMapping)
  121. class test_ExceptionInfo:
  122. def test_exception_info(self):
  123. try:
  124. raise LookupError('The quick brown fox jumps...')
  125. except Exception:
  126. einfo = ExceptionInfo()
  127. assert str(einfo) == einfo.traceback
  128. assert isinstance(einfo.exception, LookupError)
  129. assert einfo.exception.args == ('The quick brown fox jumps...',)
  130. assert einfo.traceback
  131. assert repr(einfo)
  132. @skip.if_win32()
  133. class test_LimitedSet:
  134. def test_add(self):
  135. s = LimitedSet(maxlen=2)
  136. s.add('foo')
  137. s.add('bar')
  138. for n in 'foo', 'bar':
  139. assert n in s
  140. s.add('baz')
  141. for n in 'bar', 'baz':
  142. assert n in s
  143. assert 'foo' not in s
  144. s = LimitedSet(maxlen=10)
  145. for i in range(150):
  146. s.add(i)
  147. assert len(s) <= 10
  148. # make sure heap is not leaking:
  149. assert len(s._heap) < len(s) * (
  150. 100. + s.max_heap_percent_overload) / 100
  151. def test_purge(self):
  152. # purge now enforces rules
  153. # cant purge(1) now. but .purge(now=...) still works
  154. s = LimitedSet(maxlen=10)
  155. [s.add(i) for i in range(10)]
  156. s.maxlen = 2
  157. s.purge()
  158. assert len(s) == 2
  159. # expired
  160. s = LimitedSet(maxlen=10, expires=1)
  161. [s.add(i) for i in range(10)]
  162. s.maxlen = 2
  163. s.purge(now=time() + 100)
  164. assert len(s) == 0
  165. # not expired
  166. s = LimitedSet(maxlen=None, expires=1)
  167. [s.add(i) for i in range(10)]
  168. s.maxlen = 2
  169. s.purge(now=lambda: time() - 100)
  170. assert len(s) == 2
  171. # expired -> minsize
  172. s = LimitedSet(maxlen=10, minlen=10, expires=1)
  173. [s.add(i) for i in range(20)]
  174. s.minlen = 3
  175. s.purge(now=time() + 3)
  176. assert s.minlen == len(s)
  177. assert len(s._heap) <= s.maxlen * (
  178. 100. + s.max_heap_percent_overload) / 100
  179. def test_pickleable(self):
  180. s = LimitedSet(maxlen=2)
  181. s.add('foo')
  182. s.add('bar')
  183. assert pickle.loads(pickle.dumps(s)) == s
  184. def test_iter(self):
  185. s = LimitedSet(maxlen=3)
  186. items = ['foo', 'bar', 'baz', 'xaz']
  187. for item in items:
  188. s.add(item)
  189. l = list(iter(s))
  190. for item in items[1:]:
  191. assert item in l
  192. assert 'foo' not in l
  193. assert l == items[1:], 'order by insertion time'
  194. def test_repr(self):
  195. s = LimitedSet(maxlen=2)
  196. items = 'foo', 'bar'
  197. for item in items:
  198. s.add(item)
  199. assert 'LimitedSet(' in repr(s)
  200. def test_discard(self):
  201. s = LimitedSet(maxlen=2)
  202. s.add('foo')
  203. s.discard('foo')
  204. assert 'foo' not in s
  205. assert len(s._data) == 0
  206. s.discard('foo')
  207. def test_clear(self):
  208. s = LimitedSet(maxlen=2)
  209. s.add('foo')
  210. s.add('bar')
  211. assert len(s) == 2
  212. s.clear()
  213. assert not s
  214. def test_update(self):
  215. s1 = LimitedSet(maxlen=2)
  216. s1.add('foo')
  217. s1.add('bar')
  218. s2 = LimitedSet(maxlen=2)
  219. s2.update(s1)
  220. assert sorted(list(s2)) == ['bar', 'foo']
  221. s2.update(['bla'])
  222. assert sorted(list(s2)) == ['bar', 'bla']
  223. s2.update(['do', 're'])
  224. assert sorted(list(s2)) == ['do', 're']
  225. s1 = LimitedSet(maxlen=10, expires=None)
  226. s2 = LimitedSet(maxlen=10, expires=None)
  227. s3 = LimitedSet(maxlen=10, expires=None)
  228. s4 = LimitedSet(maxlen=10, expires=None)
  229. s5 = LimitedSet(maxlen=10, expires=None)
  230. for i in range(12):
  231. s1.add(i)
  232. s2.add(i * i)
  233. s3.update(s1)
  234. s3.update(s2)
  235. s4.update(s1.as_dict())
  236. s4.update(s2.as_dict())
  237. s5.update(s1._data) # revoke is using this
  238. s5.update(s2._data)
  239. assert s3 == s4
  240. assert s3 == s5
  241. s2.update(s4)
  242. s4.update(s2)
  243. assert s2 == s4
  244. def test_iterable_and_ordering(self):
  245. s = LimitedSet(maxlen=35, expires=None)
  246. # we use a custom clock here, as time.time() does not have enough
  247. # precision when called quickly (can return the same value twice).
  248. clock = count(1)
  249. for i in reversed(range(15)):
  250. s.add(i, now=next(clock))
  251. j = 40
  252. for i in s:
  253. assert i < j # each item is smaller and smaller
  254. j = i
  255. assert i == 0 # last item is zero
  256. def test_pop_and_ordering_again(self):
  257. s = LimitedSet(maxlen=5)
  258. for i in range(10):
  259. s.add(i)
  260. j = -1
  261. for _ in range(5):
  262. i = s.pop()
  263. assert j < i
  264. i = s.pop()
  265. assert i is None
  266. def test_as_dict(self):
  267. s = LimitedSet(maxlen=2)
  268. s.add('foo')
  269. assert isinstance(s.as_dict(), Mapping)
  270. def test_add_removes_duplicate_from_small_heap(self):
  271. s = LimitedSet(maxlen=2)
  272. s.add('foo')
  273. s.add('foo')
  274. s.add('foo')
  275. assert len(s) == 1
  276. assert len(s._data) == 1
  277. assert len(s._heap) == 1
  278. def test_add_removes_duplicate_from_big_heap(self):
  279. s = LimitedSet(maxlen=1000)
  280. [s.add(i) for i in range(2000)]
  281. assert len(s) == 1000
  282. [s.add('foo') for i in range(1000)]
  283. # heap is refreshed when 15% larger than _data
  284. assert len(s._heap) < 1150
  285. [s.add('foo') for i in range(1000)]
  286. assert len(s._heap) < 1150
  287. class test_AttributeDict:
  288. def test_getattr__setattr(self):
  289. x = AttributeDict({'foo': 'bar'})
  290. assert x['foo'] == 'bar'
  291. with pytest.raises(AttributeError):
  292. x.bar
  293. x.bar = 'foo'
  294. assert x['bar'] == 'foo'
  295. class test_Messagebuffer:
  296. def assert_size_and_first(self, buf, size, expected_first_item):
  297. assert len(buf) == size
  298. assert buf.take() == expected_first_item
  299. def test_append_limited(self):
  300. b = Messagebuffer(10)
  301. for i in range(20):
  302. b.put(i)
  303. self.assert_size_and_first(b, 10, 10)
  304. def test_append_unlimited(self):
  305. b = Messagebuffer(None)
  306. for i in range(20):
  307. b.put(i)
  308. self.assert_size_and_first(b, 20, 0)
  309. def test_extend_limited(self):
  310. b = Messagebuffer(10)
  311. b.extend(list(range(20)))
  312. self.assert_size_and_first(b, 10, 10)
  313. def test_extend_unlimited(self):
  314. b = Messagebuffer(None)
  315. b.extend(list(range(20)))
  316. self.assert_size_and_first(b, 20, 0)
  317. def test_extend_eviction_time_limited(self):
  318. b = Messagebuffer(3000)
  319. b.extend(range(10000))
  320. assert len(b) > 3000
  321. b.evict()
  322. assert len(b) == 3000
  323. def test_pop_empty_with_default(self):
  324. b = Messagebuffer(10)
  325. sentinel = object()
  326. assert b.take(sentinel) is sentinel
  327. def test_pop_empty_no_default(self):
  328. b = Messagebuffer(10)
  329. with pytest.raises(b.Empty):
  330. b.take()
  331. def test_repr(self):
  332. assert repr(Messagebuffer(10, [1, 2, 3]))
  333. def test_iter(self):
  334. b = Messagebuffer(10, list(range(10)))
  335. assert len(b) == 10
  336. for i, item in enumerate(b):
  337. assert item == i
  338. assert len(b) == 0
  339. def test_contains(self):
  340. b = Messagebuffer(10, list(range(10)))
  341. assert 5 in b
  342. def test_reversed(self):
  343. assert (list(reversed(Messagebuffer(10, list(range(10))))) ==
  344. list(reversed(range(10))))
  345. def test_getitem(self):
  346. b = Messagebuffer(10, list(range(10)))
  347. for i in range(10):
  348. assert b[i] == i
  349. class test_BufferMap:
  350. def test_append_limited(self):
  351. b = BufferMap(10)
  352. for i in range(20):
  353. b.put(i, i)
  354. self.assert_size_and_first(b, 10, 10)
  355. def assert_size_and_first(self, buf, size, expected_first_item):
  356. assert buf.total == size
  357. assert buf._LRUpop() == expected_first_item
  358. def test_append_unlimited(self):
  359. b = BufferMap(None)
  360. for i in range(20):
  361. b.put(i, i)
  362. self.assert_size_and_first(b, 20, 0)
  363. def test_extend_limited(self):
  364. b = BufferMap(10)
  365. b.extend(1, list(range(20)))
  366. self.assert_size_and_first(b, 10, 10)
  367. def test_extend_unlimited(self):
  368. b = BufferMap(None)
  369. b.extend(1, list(range(20)))
  370. self.assert_size_and_first(b, 20, 0)
  371. def test_pop_empty_with_default(self):
  372. b = BufferMap(10)
  373. sentinel = object()
  374. assert b.take(1, sentinel) is sentinel
  375. def test_pop_empty_no_default(self):
  376. b = BufferMap(10)
  377. with pytest.raises(b.Empty):
  378. b.take(1)
  379. def test_repr(self):
  380. assert repr(Messagebuffer(10, [1, 2, 3]))