Skip to content

Commit dfba4b8

Browse files
committed
Use cherrypy for dev server
CherryPy will support chunked encoding, so I'm using that for the test endpoint.
1 parent aa56c85 commit dfba4b8

8 files changed

+124
-22
lines changed

dev_requirements.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ tox
44
pytest-cov
55
pytest
66
mock
7-
webtest
7+
cherrypy
88
sphinx
99
redis
1010
lockfile

tests/conftest.py

+40-7
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
from pprint import pformat
22

3+
import os
4+
import socket
5+
36
import pytest
47

5-
from webtest.http import StopableWSGIServer
8+
import cherrypy
69

710

811
class SimpleApp(object):
@@ -33,13 +36,15 @@ def optional_cacheable_request(self, env, start_response):
3336
return [pformat(env).encode("utf8")]
3437

3538
def vary_accept(self, env, start_response):
39+
response = pformat(env).encode("utf8")
40+
3641
headers = [
3742
('Cache-Control', 'max-age=5000'),
3843
('Content-Type', 'text/plain'),
3944
('Vary', 'Accept-Encoding, Accept'),
4045
]
4146
start_response('200 OK', headers)
42-
return [pformat(env).encode("utf8")]
47+
return [response]
4348

4449
def update_etag_string(self):
4550
self.etag_count += 1
@@ -91,11 +96,13 @@ def multiple_choices(self, env, start_response):
9196

9297
def stream(self, env, start_response):
9398
headers = [
94-
('Cache-Control', 'max-age=5000'),
9599
('Content-Type', 'text/plain'),
100+
('Cache-Control', 'max-age=5000'),
96101
]
97102
start_response('200 OK', headers)
98-
return (pformat(env).encode("utf8") for i in range(10))
103+
104+
for i in range(10):
105+
yield pformat(i).encode("utf8")
99106

100107
def __call__(self, env, start_response):
101108
func = self.dispatch(env)
@@ -118,12 +125,38 @@ def server():
118125

119126
@pytest.fixture()
120127
def url(server):
121-
return server.application_url
128+
return 'http://%s:%s/' % server.bind_addr
129+
130+
131+
def get_free_port():
132+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
133+
s.bind(('', 0))
134+
ip, port = s.getsockname()
135+
s.close()
136+
ip = os.environ.get('WEBTEST_SERVER_BIND', '127.0.0.1')
137+
return ip, port
122138

123139

124140
def pytest_namespace():
125-
return dict(server=StopableWSGIServer.create(SimpleApp()))
141+
cherrypy.tree.graft(SimpleApp(), '/')
142+
143+
ip, port = get_free_port()
144+
145+
cherrypy.config.update({
146+
'server.socket_host': ip,
147+
'server.socket_port': port
148+
})
149+
150+
#turn off logging
151+
logger = cherrypy.log.access_log
152+
logger.removeHandler(logger.handlers[0])
153+
154+
cherrypy.server.start()
155+
return dict(server=cherrypy.server)
126156

127157

128158
def pytest_unconfigure(config):
129-
pytest.server.shutdown()
159+
try:
160+
cherrypy.server.stop()
161+
except:
162+
pass

tests/test_chunked_response.py

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"""
2+
Test for supporting streamed responses (Transfer-Encoding: chunked)
3+
"""
4+
from __future__ import print_function, unicode_literals
5+
6+
import pytest
7+
import requests
8+
9+
from cachecontrol import CacheControl
10+
11+
12+
@pytest.fixture()
13+
def sess():
14+
return CacheControl(requests.Session())
15+
16+
17+
class TestChunkedResponses(object):
18+
19+
def test_cache_chunked_response(self, url, sess):
20+
"""
21+
Verify that an otherwise cacheable response is cached when the
22+
response is chunked.
23+
"""
24+
url = url + 'stream'
25+
r = sess.get(url)
26+
from pprint import pprint
27+
pprint(dict(r.headers))
28+
pprint(dict(r.request.headers))
29+
print(r.content)
30+
assert r.headers.get('transfer-encoding') == 'chunked'
31+
32+
r = sess.get(url, headers={'Cache-Control': 'max-age=3600'})
33+
assert r.from_cache is True
34+
35+
def test_stream_is_cached(self, url, sess):
36+
resp_1 = sess.get(url + 'stream')
37+
content_1 = resp_1.content
38+
39+
resp_2 = sess.get(url + 'stream')
40+
content_2 = resp_1.content
41+
42+
assert not resp_1.from_cache
43+
assert resp_2.from_cache
44+
assert content_1 == content_2
45+
46+
def test_stream_is_not_cached_when_content_is_not_read(self, url, sess):
47+
sess.get(url + 'stream', stream=True)
48+
resp = sess.get(url + 'stream', stream=True)
49+
50+
assert not resp.from_cache

tests/test_etag.py

+8-8
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ class TestETag(object):
2626
"""
2727

2828
@pytest.fixture()
29-
def sess(self, server):
30-
self.etag_url = urljoin(server.application_url, '/etag')
31-
self.update_etag_url = urljoin(server.application_url, '/update_etag')
29+
def sess(self, url):
30+
self.etag_url = urljoin(url, '/etag')
31+
self.update_etag_url = urljoin(url, '/update_etag')
3232
self.cache = DictCache()
3333
sess = CacheControl(
3434
requests.Session(),
@@ -89,9 +89,9 @@ class TestDisabledETags(object):
8989
response has an ETag.
9090
"""
9191
@pytest.fixture()
92-
def sess(self, server):
93-
self.etag_url = urljoin(server.application_url, '/etag')
94-
self.update_etag_url = urljoin(server.application_url, '/update_etag')
92+
def sess(self, server, url):
93+
self.etag_url = urljoin(url, '/etag')
94+
self.update_etag_url = urljoin(url, '/update_etag')
9595
self.cache = DictCache()
9696
sess = CacheControl(requests.Session(),
9797
cache=self.cache,
@@ -126,9 +126,9 @@ class TestReleaseConnection(object):
126126
method is not called we consume the response (which should be
127127
empty according to the HTTP spec) and release the connection.
128128
"""
129-
def test_not_modified_releases_connection(self, server):
129+
def test_not_modified_releases_connection(self, server, url):
130130
sess = CacheControl(requests.Session())
131-
etag_url = urljoin(server.application_url, '/etag')
131+
etag_url = urljoin(url, '/etag')
132132
sess.get(etag_url)
133133

134134
resp = Mock(status=304, headers={})

tests/test_max_age.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ def loads(self, request, data):
1818
class TestMaxAge(object):
1919

2020
@pytest.fixture()
21-
def sess(self, server):
22-
self.url = server.application_url
21+
def sess(self, url):
22+
self.url = url
2323
self.cache = DictCache()
2424
sess = Session()
2525
sess.mount(

tests/test_server_http_version.py

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import requests
2+
3+
4+
def test_http11(url):
5+
resp = requests.get(url)
6+
7+
# Making sure our test server speaks HTTP/1.1
8+
assert resp.raw._fp.version == 11

tests/test_storage_filecache.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ def randomdata():
2525
class TestStorageFileCache(object):
2626

2727
@pytest.fixture()
28-
def sess(self, server, tmpdir):
29-
self.url = server.application_url
28+
def sess(self, url, tmpdir):
29+
self.url = url
3030
self.cache = FileCache(str(tmpdir))
3131
sess = CacheControl(requests.Session(), cache=self.cache)
3232
return sess

tests/test_vary.py

+13-2
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,24 @@
55
from cachecontrol.cache import DictCache
66
from cachecontrol.compat import urljoin
77

8+
from pprint import pprint
9+
810

911
class TestVary(object):
1012

1113
@pytest.fixture()
12-
def sess(self, server):
13-
self.url = urljoin(server.application_url, '/vary_accept')
14+
def sess(self, url):
15+
self.url = urljoin(url, '/vary_accept')
1416
self.cache = DictCache()
1517
sess = CacheControl(requests.Session(), cache=self.cache)
1618
return sess
1719

1820
def cached_equal(self, cached, resp):
21+
# remove any transfer-encoding headers as they don't apply to
22+
# a cached value
23+
if 'chunked' in resp.raw.headers.get('transfer-encoding', ''):
24+
resp.raw.headers.pop('transfer-encoding')
25+
1926
checks = [
2027
cached._fp.getvalue() == resp.content,
2128
cached.headers == resp.raw.headers,
@@ -25,6 +32,10 @@ def cached_equal(self, cached, resp):
2532
cached.strict == resp.raw.strict,
2633
cached.decode_content == resp.raw.decode_content,
2734
]
35+
36+
print(checks)
37+
pprint(dict(cached.headers))
38+
pprint(dict(resp.raw.headers))
2839
return all(checks)
2940

3041
def test_vary_example(self, sess):

0 commit comments

Comments
 (0)