Merge lp:~mandel/ubuntuone-dev-tools/mocked-webserver into lp:ubuntuone-dev-tools
- mocked-webserver
- Merge into trunk
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Manuel de la Peña | ||||
Approved revision: | 99 | ||||
Merged at revision: | 62 | ||||
Proposed branch: | lp:~mandel/ubuntuone-dev-tools/mocked-webserver | ||||
Merge into: | lp:ubuntuone-dev-tools | ||||
Prerequisite: | lp:~mandel/ubuntuone-dev-tools/tcp-testcases | ||||
Diff against target: |
349 lines (+322/-1) 4 files modified
ubuntuone/devtools/testcases/txtcpserver.py (+0/-1) ubuntuone/devtools/testing/tests/__init__.py (+29/-0) ubuntuone/devtools/testing/tests/test_txwebserver.py (+172/-0) ubuntuone/devtools/testing/txwebserver.py (+121/-0) |
||||
To merge this branch: | bzr merge lp:~mandel/ubuntuone-dev-tools/mocked-webserver | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
dobey (community) | Approve | ||
Alejandro J. Cura (community) | Approve | ||
Review via email: mp+101224@code.launchpad.net |
Commit message
- Added a mock Web server which ensures its resources are correctly finalized, for use in tests (LP: #972366).
Description of the change
- Added a mock Webserver than can be used in tests and that ensures that its resources are correctly closed (LP: #972366).
Alejandro J. Cura (alecu) wrote : | # |
Nice branch. This is a very good second step in order to reduce the amount of boilerplate we have everywhere in our tests.
The only bit I don't like is the many "if self.ssl_settings" scattered around.
What about splitting the WebServer class into HTTPS and HTTP classes?
Manuel de la Peña (mandel) wrote : | # |
> 107 + def test_get_iri(self):
> 108 + """Test getting the iri from the server."""
> 109 + expected = u"http://
> 110 + self.assert_
> 111 +
> 112 + def test_get_uri(self):
> 113 + """Test getting the uri from the server."""
> 114 + expected = "http://
> 115 + self.assert_
>
> 128 + def test_get_
> 129 + """Test getting the ssl iri."""
> 130 + self.assertEqua
> 131 +
> 132 + def test_get_
> 133 + """Test getting the ssl uri."""
> 134 + self.assertEqua
>
> 274 + def get_iri(self):
> 275 + """Build the iri for this mock server."""
> 276 + url = u"http://
> 277 + return url % self.get_port()
> 278 +
> 279 + def get_uri(self):
> 280 + """Build the uri for this mock server."""
> 281 + url = "http://
> 282 + return url % self.get_port()
> 283 +
> 284 + def get_ssl_iri(self):
> 285 + """Build the ssl iri for this mock server."""
> 286 + if self.ssl_settings:
> 287 + url = u"https:/
> 288 + return url % self.get_ssl_port()
> 289 +
> 290 + def get_ssl_uri(self):
> 291 + """Build the ssl iri for this mock server."""
> 292 + if self.ssl_settings:
> 293 + url = "https:/
> 294 + return url % self.get_ssl_port()
>
> Why all this duplicated code?
Looks like a merge fail from me, let me double check.
Manuel de la Peña (mandel) wrote : | # |
> Nice branch. This is a very good second step in order to reduce the amount of
> boilerplate we have everywhere in our tests.
>
> The only bit I don't like is the many "if self.ssl_settings" scattered around.
> What about splitting the WebServer class into HTTPS and HTTP classes?
Sure!
dobey (dobey) wrote : | # |
+SAMPLE_RESOURCE = '{"%s": "%s"}' % (SAMPLE_KEY, SAMPLE_VALUE)
There are a lot of strings being combined with %-notation in here. Please use the new .format() notation for string formatting instead, as %-notation is deprecated. As the new notation uses {0} for example as insertion markers, you probably should use unicode(
Alejandro J. Cura (alecu) wrote : | # |
> +SAMPLE_RESOURCE = '{"%s": "%s"}' % (SAMPLE_KEY, SAMPLE_VALUE)
>
> There are a lot of strings being combined with %-notation in here. Please use
> the new .format() notation for string formatting instead, as %-notation is
> deprecated. As the new notation uses {0} for example as insertion markers, you
> probably should use unicode(
> u'{0}'.
The SAMPLE_RESOURCE is just a json-like sequence of bytes used for testing, so the fix requested is wrong.
It should be something like:
+SAMPLE_RESOURCE = '{{"{0}": "{1}"}}
Also, and for the record, the string interpolation operator % is not deprecated; not even in python 3.3:
"there are no current plans to deprecate printf-style formatting."
http://
dobey (dobey) wrote : | # |
Can we have the scheme, host, and port all passed around separately? Passing around "http://
dobey (dobey) wrote : | # |
Also, as this doesn't provide any testcases, I think it's better to be under testing/ instead of testcases/.
dobey (dobey) : | # |
Ubuntu One Auto Pilot (otto-pilot) wrote : | # |
The attempt to merge lp:~mandel/ubuntuone-dev-tools/mocked-webserver into lp:ubuntuone-dev-tools failed. Below is the output from the failed tests.
Traceback (most recent call last):
File "bin/u1trial", line 337, in <module>
main()
File "bin/u1trial", line 317, in main
suite = trial_runner.
File "bin/u1trial", line 196, in get_suite
config[
File "bin/u1trial", line 180, in _collect_tests
module_suite = self._load_
File "bin/u1trial", line 120, in _load_unittest
module = __import__(modpath, None, None, [""])
ImportError: No module named tests.test_
- 99. By Manuel de la Peña
-
Added missing __init__.py
Preview Diff
1 | === modified file 'ubuntuone/devtools/testcases/txtcpserver.py' |
2 | --- ubuntuone/devtools/testcases/txtcpserver.py 2012-04-13 16:39:56 +0000 |
3 | +++ ubuntuone/devtools/testcases/txtcpserver.py 2012-04-17 19:35:27 +0000 |
4 | @@ -114,7 +114,6 @@ |
5 | |
6 | def listen_server(self, server_class, *args, **kwargs): |
7 | """Start a server in a random port.""" |
8 | - |
9 | from twisted.internet import reactor |
10 | self.server_factory = server_class(*args, **kwargs) |
11 | self.server_factory._disconnecting = False |
12 | |
13 | === added directory 'ubuntuone/devtools/testing/tests' |
14 | === added file 'ubuntuone/devtools/testing/tests/__init__.py' |
15 | --- ubuntuone/devtools/testing/tests/__init__.py 1970-01-01 00:00:00 +0000 |
16 | +++ ubuntuone/devtools/testing/tests/__init__.py 2012-04-17 19:35:27 +0000 |
17 | @@ -0,0 +1,29 @@ |
18 | +# -*- coding: utf-8 -*- |
19 | +# |
20 | +# Copyright 2012 Canonical Ltd. |
21 | +# |
22 | +# This program is free software: you can redistribute it and/or modify it |
23 | +# under the terms of the GNU General Public License version 3, as published |
24 | +# by the Free Software Foundation. |
25 | +# |
26 | +# This program is distributed in the hope that it will be useful, but |
27 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
28 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
29 | +# PURPOSE. See the GNU General Public License for more details. |
30 | +# |
31 | +# You should have received a copy of the GNU General Public License along |
32 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
33 | +# |
34 | +# In addition, as a special exception, the copyright holders give |
35 | +# permission to link the code of portions of this program with the |
36 | +# OpenSSL library under certain conditions as described in each |
37 | +# individual source file, and distribute linked combinations |
38 | +# including the two. |
39 | +# You must obey the GNU General Public License in all respects |
40 | +# for all of the code used other than OpenSSL. If you modify |
41 | +# file(s) with this exception, you may extend this exception to your |
42 | +# version of the file(s), but you are not obligated to do so. If you |
43 | +# do not wish to do so, delete this exception statement from your |
44 | +# version. If you delete this exception statement from all source |
45 | +# files in the program, then also delete it here. |
46 | +"""Tests for the testing module.""" |
47 | |
48 | === added file 'ubuntuone/devtools/testing/tests/test_txwebserver.py' |
49 | --- ubuntuone/devtools/testing/tests/test_txwebserver.py 1970-01-01 00:00:00 +0000 |
50 | +++ ubuntuone/devtools/testing/tests/test_txwebserver.py 2012-04-17 19:35:27 +0000 |
51 | @@ -0,0 +1,172 @@ |
52 | +# -*- coding: utf-8 -*- |
53 | +# Copyright 2012 Canonical Ltd. |
54 | +# |
55 | +# This program is free software: you can redistribute it and/or modify it |
56 | +# under the terms of the GNU General Public License version 3, as published |
57 | +# by the Free Software Foundation. |
58 | +# |
59 | +# This program is distributed in the hope that it will be useful, but |
60 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
61 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
62 | +# PURPOSE. See the GNU General Public License for more details. |
63 | +# |
64 | +# You should have received a copy of the GNU General Public License along |
65 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
66 | +# |
67 | +# In addition, as a special exception, the copyright holders give |
68 | +# permission to link the code of portions of this program with the |
69 | +# OpenSSL library under certain conditions as described in each |
70 | +# individual source file, and distribute linked combinations |
71 | +# including the two. |
72 | +# You must obey the GNU General Public License in all respects |
73 | +# for all of the code used other than OpenSSL. If you modify |
74 | +# file(s) with this exception, you may extend this exception to your |
75 | +# version of the file(s), but you are not obligated to do so. If you |
76 | +# do not wish to do so, delete this exception statement from your |
77 | +# version. If you delete this exception statement from all source |
78 | +# files in the program, then also delete it here. |
79 | + |
80 | +"""Test the twisted webserver.""" |
81 | + |
82 | + |
83 | +from twisted.internet import defer |
84 | +from twisted.trial.unittest import TestCase |
85 | +from twisted.web import client, resource |
86 | + |
87 | +from ubuntuone.devtools.testing.txwebserver import HTTPWebServer |
88 | + |
89 | + |
90 | +SAMPLE_KEY = "result" |
91 | +SAMPLE_VALUE = "sample result" |
92 | +SAMPLE_RESOURCE = '{{"{0}": "{1}"}}'.format(SAMPLE_KEY, SAMPLE_VALUE) |
93 | +SIMPLERESOURCE = "simpleresource" |
94 | +OTHER_SIMPLERESOURCE = "othersimpleresource" |
95 | +THROWERROR = "throwerror" |
96 | +UNAUTHORIZED = "unauthorized" |
97 | + |
98 | +# no init |
99 | +# pylint: disable=W0232 |
100 | + |
101 | + |
102 | +class SimpleResource(resource.Resource): |
103 | + """A simple web resource.""" |
104 | + |
105 | + # pylint: disable=C0103 |
106 | + def render_GET(self, request): |
107 | + """Make a bit of html out of these resource's content.""" |
108 | + return SAMPLE_RESOURCE |
109 | + # pylint: enable=C0103 |
110 | + |
111 | + |
112 | +class WebServerTestCase(TestCase): |
113 | + """Test the web server that will allow to have connections.""" |
114 | + |
115 | + @defer.inlineCallbacks |
116 | + def setUp(self): |
117 | + """Set the different tests.""" |
118 | + yield super(WebServerTestCase, self).setUp() |
119 | + # create the root to be used by the webserver |
120 | + root = resource.Resource() |
121 | + root.putChild(SIMPLERESOURCE, SimpleResource()) |
122 | + root.putChild(OTHER_SIMPLERESOURCE, SimpleResource()) |
123 | + root.putChild(THROWERROR, resource.NoResource()) |
124 | + |
125 | + unauthorized_resource = resource.ErrorPage(resource.http.UNAUTHORIZED, |
126 | + "Unauthorized", "Unauthorized") |
127 | + root.putChild(UNAUTHORIZED, unauthorized_resource) |
128 | + self.server = HTTPWebServer(root) |
129 | + self.server.start() |
130 | + self.uri = "http://127.0.0.1:{port}/".format( |
131 | + port=self.server.get_port()) |
132 | + self.addCleanup(self.server.stop) |
133 | + |
134 | + @defer.inlineCallbacks |
135 | + def test_single_request(self): |
136 | + """Test performing a single request to get the data.""" |
137 | + url = self.uri + SIMPLERESOURCE |
138 | + result = yield client.getPage(url) |
139 | + self.assertEqual(SAMPLE_RESOURCE, result) |
140 | + |
141 | + @defer.inlineCallbacks |
142 | + def test_multiple_requests(self): |
143 | + """Test performing multiple requests.""" |
144 | + simple_url = self.uri + SIMPLERESOURCE |
145 | + other_simple_url = self.uri + OTHER_SIMPLERESOURCE |
146 | + simple_result = yield client.getPage(simple_url) |
147 | + other_result = yield client.getPage(other_simple_url) |
148 | + self.assertEqual(SAMPLE_RESOURCE, simple_result) |
149 | + self.assertEqual(SAMPLE_RESOURCE, other_result) |
150 | + |
151 | + def assert_url(self, expected): |
152 | + """Assert the url.""" |
153 | + port = self.server.get_port() |
154 | + self.assertEqual(expected.format(port=port), self.server.get_iri()) |
155 | + |
156 | + def test_get_iri(self): |
157 | + """Test getting the iri from the server.""" |
158 | + expected = u"http://127.0.0.1:{port}/" |
159 | + self.assert_url(expected) |
160 | + |
161 | + def test_get_port(self): |
162 | + """Test getting the port.""" |
163 | + # pylint: disable=W0212 |
164 | + port = self.server.port.getHost().port |
165 | + # pylint: enable=W0212 |
166 | + self.assertEqual(port, self.server.get_port()) |
167 | + |
168 | + |
169 | +class MultipleWebServersTestCase(TestCase): |
170 | + """Test with multiple webservers running.""" |
171 | + |
172 | + @defer.inlineCallbacks |
173 | + def setUp(self): |
174 | + """Set the diff tests.""" |
175 | + yield super(MultipleWebServersTestCase, self).setUp() |
176 | + self.root = resource.Resource() |
177 | + self.root.putChild(SIMPLERESOURCE, SimpleResource()) |
178 | + self.root.putChild(OTHER_SIMPLERESOURCE, SimpleResource()) |
179 | + self.root.putChild(THROWERROR, resource.NoResource()) |
180 | + |
181 | + unauthorized_resource = resource.ErrorPage(resource.http.UNAUTHORIZED, |
182 | + "Unauthorized", "Unauthorized") |
183 | + self.root.putChild(UNAUTHORIZED, unauthorized_resource) |
184 | + |
185 | + def get_uri(self, server): |
186 | + """Return the uri for the server.""" |
187 | + url = "http://127.0.0.1:{port}/" |
188 | + return url.format(port=server.get_port()) |
189 | + |
190 | + @defer.inlineCallbacks |
191 | + def test_single_request(self): |
192 | + """Test performing a single request to get the data.""" |
193 | + first_server = HTTPWebServer(self.root) |
194 | + first_server.start() |
195 | + self.addCleanup(first_server.stop) |
196 | + |
197 | + second_server = HTTPWebServer(self.root) |
198 | + second_server.start() |
199 | + self.addCleanup(second_server.stop) |
200 | + |
201 | + for server in [first_server, second_server]: |
202 | + url = self.get_uri(server) + SIMPLERESOURCE |
203 | + result = yield client.getPage(url) |
204 | + self.assertEqual(SAMPLE_RESOURCE, result) |
205 | + |
206 | + @defer.inlineCallbacks |
207 | + def test_multiple_requests(self): |
208 | + """Test performing multiple requests.""" |
209 | + first_server = HTTPWebServer(self.root) |
210 | + first_server.start() |
211 | + self.addCleanup(first_server.stop) |
212 | + |
213 | + second_server = HTTPWebServer(self.root) |
214 | + second_server.start() |
215 | + self.addCleanup(second_server.stop) |
216 | + |
217 | + for server in [first_server, second_server]: |
218 | + simple_url = self.get_uri(server) + SIMPLERESOURCE |
219 | + other_simple_url = self.get_uri(server) + OTHER_SIMPLERESOURCE |
220 | + simple_result = yield client.getPage(simple_url) |
221 | + other_result = yield client.getPage(other_simple_url) |
222 | + self.assertEqual(SAMPLE_RESOURCE, simple_result) |
223 | + self.assertEqual(SAMPLE_RESOURCE, other_result) |
224 | |
225 | === added file 'ubuntuone/devtools/testing/txwebserver.py' |
226 | --- ubuntuone/devtools/testing/txwebserver.py 1970-01-01 00:00:00 +0000 |
227 | +++ ubuntuone/devtools/testing/txwebserver.py 2012-04-17 19:35:27 +0000 |
228 | @@ -0,0 +1,121 @@ |
229 | +# -*- coding: utf-8 -*- |
230 | +# Copyright 2012 Canonical Ltd. |
231 | +# |
232 | +# This program is free software: you can redistribute it and/or modify it |
233 | +# under the terms of the GNU General Public License version 3, as published |
234 | +# by the Free Software Foundation. |
235 | +# |
236 | +# This program is distributed in the hope that it will be useful, but |
237 | +# WITHOUT ANY WARRANTY; without even the implied warranties of |
238 | +# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR |
239 | +# PURPOSE. See the GNU General Public License for more details. |
240 | +# |
241 | +# You should have received a copy of the GNU General Public License along |
242 | +# with this program. If not, see <http://www.gnu.org/licenses/>. |
243 | +# |
244 | +# In addition, as a special exception, the copyright holders give |
245 | +# permission to link the code of portions of this program with the |
246 | +# OpenSSL library under certain conditions as described in each |
247 | +# individual source file, and distribute linked combinations |
248 | +# including the two. |
249 | +# You must obey the GNU General Public License in all respects |
250 | +# for all of the code used other than OpenSSL. If you modify |
251 | +# file(s) with this exception, you may extend this exception to your |
252 | +# version of the file(s), but you are not obligated to do so. If you |
253 | +# do not wish to do so, delete this exception statement from your |
254 | +# version. If you delete this exception statement from all source |
255 | +# files in the program, then also delete it here. |
256 | + |
257 | +"""A tx based web server.""" |
258 | + |
259 | +from twisted.internet import defer, reactor, ssl |
260 | +from twisted.protocols.policies import WrappingFactory |
261 | +from twisted.web import server |
262 | + |
263 | +from ubuntuone.devtools.testcases.txtcpserver import server_protocol_factory |
264 | + |
265 | +# no init method + twisted common warnings |
266 | +# pylint: disable=W0232, C0103, E1101 |
267 | + |
268 | + |
269 | +class BaseWebServer(object): |
270 | + """Webserver used to perform requests in tests.""" |
271 | + |
272 | + def __init__(self, root_resource, scheme): |
273 | + """Create and start the instance. |
274 | + |
275 | + The ssl_settings parameter contains a dictionary with the key and cert |
276 | + that will be used to perform ssl connections. The root_resource |
277 | + contains the resource with all its childre. |
278 | + """ |
279 | + self.root = root_resource |
280 | + self.scheme = scheme |
281 | + self.port = None |
282 | + # use an http.HTTPFactory that was modified to ensure that we have |
283 | + # clean close connections |
284 | + self.site = server.Site(self.root, timeout=None) |
285 | + self.wrapper = WrappingFactory(self.site) |
286 | + self.wrapper.testserver_on_connection_lost = defer.Deferred() |
287 | + self.wrapper.protocol = server_protocol_factory(self.wrapper.protocol) |
288 | + self.wrapper._disconnecting = False |
289 | + |
290 | + def listen(self, site): |
291 | + """Listen a port to allow the tests.""" |
292 | + raise NotImplementedError('Base abstract class.') |
293 | + |
294 | + def get_iri(self): |
295 | + """Build the iri for this mock server.""" |
296 | + return u"{scheme}://127.0.0.1:{port}/".format(scheme=self.scheme, |
297 | + port=self.get_port()) |
298 | + |
299 | + def get_port(self): |
300 | + """Return the port where we are listening.""" |
301 | + return self.port.getHost().port |
302 | + |
303 | + def start(self): |
304 | + """Start the service.""" |
305 | + self.port = self.listen(self.wrapper) |
306 | + |
307 | + def stop(self): |
308 | + """Shut it down.""" |
309 | + if self.port: |
310 | + self.wrapper._disconnecting = True |
311 | + connected = self.wrapper.protocols.keys() |
312 | + if connected: |
313 | + for con in connected: |
314 | + con.transport.loseConnection() |
315 | + else: |
316 | + self.wrapper.testserver_on_connection_lost = \ |
317 | + defer.succeed(None) |
318 | + d = defer.maybeDeferred(self.port.stopListening) |
319 | + return defer.gatherResults([d, |
320 | + self.wrapper.testserver_on_connection_lost]) |
321 | + return defer.succeed(None) |
322 | + |
323 | + |
324 | +class HTTPWebServer(BaseWebServer): |
325 | + """A Webserver that listens to http connections.""" |
326 | + |
327 | + def __init__(self, root_resource): |
328 | + """Create a new instance.""" |
329 | + super(HTTPWebServer, self).__init__(root_resource, 'http') |
330 | + |
331 | + def listen(self, site): |
332 | + """Listen a port to allow the tests.""" |
333 | + return reactor.listenTCP(0, site) |
334 | + |
335 | + |
336 | +class HTTPSWebServer(BaseWebServer): |
337 | + """A WebServer that listens to https connections.""" |
338 | + |
339 | + def __init__(self, root_resource, ssl_settings=None): |
340 | + """Create a new instance.""" |
341 | + super(HTTPSWebServer, self).__init__(root_resource, 'https') |
342 | + self.ssl_settings = ssl_settings |
343 | + |
344 | + def listen(self, site): |
345 | + """Listen a port to allow the tests.""" |
346 | + ssl_context = ssl.DefaultOpenSSLContextFactory( |
347 | + self.ssl_settings['key'], self.ssl_settings['cert']) |
348 | + |
349 | + return reactor.listenSSL(0, site, ssl_context) |
107 + def test_get_iri(self): 127.0.0. 1:%d/" url(expected) 127.0.0. 1:%d/" url(expected)
108 + """Test getting the iri from the server."""
109 + expected = u"http://
110 + self.assert_
111 +
112 + def test_get_uri(self):
113 + """Test getting the uri from the server."""
114 + expected = "http://
115 + self.assert_
128 + def test_get_ ssl_iri( self): l(None, self.server. get_ssl_ uri()) ssl_uri( self): l(None, self.server. get_ssl_ uri())
129 + """Test getting the ssl iri."""
130 + self.assertEqua
131 +
132 + def test_get_
133 + """Test getting the ssl uri."""
134 + self.assertEqua
274 + def get_iri(self): 127.0.0. 1:%d/" 127.0.0. 1:%d/" /127.0. 0.1:%d/" /127.0. 0.1:%d/"
275 + """Build the iri for this mock server."""
276 + url = u"http://
277 + return url % self.get_port()
278 +
279 + def get_uri(self):
280 + """Build the uri for this mock server."""
281 + url = "http://
282 + return url % self.get_port()
283 +
284 + def get_ssl_iri(self):
285 + """Build the ssl iri for this mock server."""
286 + if self.ssl_settings:
287 + url = u"https:/
288 + return url % self.get_ssl_port()
289 +
290 + def get_ssl_uri(self):
291 + """Build the ssl iri for this mock server."""
292 + if self.ssl_settings:
293 + url = "https:/
294 + return url % self.get_ssl_port()
Why all this duplicated code?