Skip to content

Commit 43ea9db

Browse files
committed
add test infrastructure
1 parent 89d5e30 commit 43ea9db

File tree

5 files changed

+159
-7
lines changed

5 files changed

+159
-7
lines changed

.travis.yml

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
language: python
2+
3+
python:
4+
- 3.6
5+
6+
matrix:
7+
include:
8+
- python: 3.6
9+
10+
install:
11+
- pip install -U setuptools codecov
12+
- pip install -r requirements-dev.txt
13+
- pip freeze
14+
15+
script:
16+
- python setup.py check -rms
17+
- flake8 --ignore=E501 asyncpool setup.py
18+
- python3 -m coverage run --omit=tests/* -m unittest discover -s tests
19+
- python3 -m coverage html
20+
21+
after_success:
22+
codecov
23+
24+
cache: pip

asyncpool/__init__.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import asyncio
2-
from datetime import datetime, timezone
2+
from datetime import datetime, timezone, timedelta
3+
34

45
def utc_now():
56
# utcnow returns a naive datetime, so we have to set the timezone manually <sigh>
67
return datetime.utcnow().replace(tzinfo=timezone.utc)
78

9+
810
class Terminator:
911
pass
1012

13+
1114
class AsyncPool:
1215
def __init__(self, loop, num_workers: int, name: str, logger, worker_co, load_factor: int = 1,
1316
job_accept_duration: int = None, max_task_time: int = None, return_futures: bool = False,
@@ -41,7 +44,7 @@ def __init__(self, loop, num_workers: int, name: str, logger, worker_co, load_fa
4144
self._queue = asyncio.Queue(num_workers * load_factor)
4245
self._workers = None
4346
self._exceptions = False
44-
self._job_accept_duration = job_accept_duration
47+
self._job_accept_duration = timedelta(seconds=job_accept_duration)
4548
self._first_push_dt = None
4649
self._max_task_time = max_task_time
4750
self._return_futures = return_futures

requirements-dev.txt

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
-e .
2+
asynctest==0.12.0
3+
coverage==4.5.1
4+
flake8==3.5.0
5+
docutils==0.14

setup.py

+12-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
from setuptools import setup
2-
from setuptools.command.test import test as TestCommand
3-
import os
42
import sys
53

64
py_version = sys.version_info[:2]
@@ -13,6 +11,14 @@
1311
efficiently and with explicit timeouts.
1412
"""
1513

14+
15+
def my_test_suite():
16+
import asynctest
17+
test_loader = asynctest.TestLoader()
18+
test_suite = test_loader.discover('tests')
19+
return test_suite
20+
21+
1622
setup(
1723
name='asyncpool',
1824
version='1.0',
@@ -23,8 +29,8 @@
2329
long_description=long_description,
2430
packages=['asyncpool'],
2531
include_package_data=True,
26-
license = "MIT",
27-
classifiers = [
32+
license="MIT",
33+
classifiers=[
2834
"License :: OSI Approved :: MIT License",
2935
"Topic :: Internet :: WWW/HTTP",
3036
"Topic :: Software Development :: Testing",
@@ -35,4 +41,5 @@
3541
'Programming Language :: Python :: 3.4',
3642
'Programming Language :: Python :: 3.5',
3743
],
38-
)
44+
test_suite='setup.my_test_suite',
45+
)

tests/test_basic.py

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
#!/usr/bin/env python3
2+
import asynctest
3+
import logging
4+
import asyncio
5+
6+
from asyncpool import AsyncPool
7+
8+
9+
class WorkPoolTestCases(asynctest.TestCase):
10+
def __init__(self, *args, **kwargs):
11+
super().__init__(*args, **kwargs)
12+
13+
logging.basicConfig(level=logging.INFO)
14+
self._logger = logging.Logger('')
15+
self._evt_wait = None
16+
17+
async def setUp(self):
18+
self._evt_wait = asyncio.Event()
19+
20+
async def tearDown(self):
21+
self._evt_wait.set() # unblock any blocked workers
22+
23+
async def test_worker_limit(self):
24+
num_called = 0
25+
26+
evt_hit = asyncio.Event()
27+
28+
async def worker(param):
29+
nonlocal num_called
30+
num_called += 1
31+
assert param == 5
32+
evt_hit.set()
33+
await self._evt_wait.wait()
34+
35+
async with AsyncPool(None, 5, '', self._logger, worker) as wq:
36+
# tests that worker limit/load factor of 1 works correctly
37+
for _ in range(10): # five workers plus 5 in queue
38+
await asyncio.wait_for(wq.push(5), 1)
39+
40+
self.assertEqual(num_called, 5)
41+
42+
with self.assertRaises(asyncio.TimeoutError):
43+
# with load_factor==1, and all workers stuck we should timeout
44+
await asyncio.wait_for(wq.push(5), 1)
45+
46+
self.assertEqual(wq.total_queued, 10)
47+
48+
# unblock workers
49+
self._evt_wait.set()
50+
await asyncio.sleep(1) # clear the workers
51+
52+
evt_hit.clear()
53+
54+
await asyncio.wait_for(wq.push(5), 1)
55+
await asyncio.wait_for(evt_hit.wait(), 1)
56+
self.assertEqual(num_called, 11)
57+
self.assertFalse(wq.exceptions)
58+
59+
async def test_load_factor(self):
60+
async def worker(param):
61+
await self._evt_wait.wait()
62+
63+
async with AsyncPool(None, 5, '', self._logger, worker, 2) as wq:
64+
for _ in range(15): # 5 in-flight, + 10 in queue per load factor
65+
await asyncio.wait_for(wq.push(5), 1)
66+
67+
with self.assertRaises(asyncio.TimeoutError):
68+
# with load_factor==1, and all workers stuck we should timeout
69+
await asyncio.wait_for(wq.push(5), 1)
70+
71+
# unblock workers
72+
self._evt_wait.set()
73+
await asyncio.sleep(1) # let them clear the queue
74+
75+
await asyncio.wait_for(wq.push(5), 1)
76+
self.assertFalse(wq.exceptions)
77+
78+
async def test_task_timeout(self):
79+
async def worker(param):
80+
await self._evt_wait.wait()
81+
82+
async with AsyncPool(None, 5, '', self._logger, worker, max_task_time=1, return_futures=True) as wq:
83+
fut = await asyncio.wait_for(wq.push(5), 1)
84+
85+
for i in range(5):
86+
await asyncio.sleep(1)
87+
88+
if fut.done():
89+
e = fut.exception()
90+
self.assertIsInstance(e, asyncio.TimeoutError)
91+
self.assertTrue(wq.exceptions)
92+
return
93+
94+
self.fail('future did not time out')
95+
96+
async def test_join(self):
97+
key = 'blah'
98+
99+
async def worker(param):
100+
await asyncio.sleep(1) # wait a sec before returning result
101+
return param
102+
103+
async with AsyncPool(None, 5, '', self._logger, worker, return_futures=True) as wq:
104+
fut = await asyncio.wait_for(wq.push('blah'), 0.1)
105+
self.assertFalse(fut.done())
106+
107+
self.assertTrue(fut.done())
108+
result = fut.result()
109+
self.assertEqual(result, key)
110+
111+
112+
if __name__ == '__main__':
113+
asynctest.main()

0 commit comments

Comments
 (0)