1
# Copyright 2011 Canonical Ltd. This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
4
"""Tests for the lp.testing.yuixhr."""
11
from shutil import rmtree
17
from storm.exceptions import DisconnectionError
18
from testtools.testcase import ExpectedException
20
from zope.component import getUtility
21
from zope.interface.verify import verifyObject
22
from zope.publisher.interfaces import NotFound
23
from zope.publisher.interfaces.browser import IBrowserPublisher
24
from zope.publisher.interfaces.http import IResult
25
from zope.security.proxy import removeSecurityProxy
27
from canonical.config import config
28
from canonical.launchpad.webapp.interfaces import ILaunchpadRoot
29
from canonical.testing.layers import LaunchpadFunctionalLayer
31
from lp.registry.interfaces.product import IProductSet
32
from lp.testing import (
37
from lp.testing.views import create_view
38
from lp.testing.yuixhr import setup
39
from lp.testing.tests import test_yuixhr_fixture
40
from lp.testing.publication import test_traverse
42
TEST_MODULE_NAME = '_lp_.tests'
45
def create_traversed_view(*args, **kwargs):
47
root = getUtility(ILaunchpadRoot)
48
view = create_view(root, '+yuitest', *args, **kwargs)
49
view.names = kwargs['path_info'].split('/')[2:]
53
class TestYUITestFixtureController(TestCase):
54
layer = LaunchpadFunctionalLayer
56
def test_provides_browserpublisher(self):
57
root = getUtility(ILaunchpadRoot)
58
view = create_view(root, '+yuitest')
59
self.assertTrue(view, verifyObject(IBrowserPublisher, view))
61
def test_traverse_stores_the_path(self):
63
object, view, request = test_traverse(
64
'http://launchpad.dev/+yuitest/'
65
'lib/lp/testing/tests/test_yuixhr_fixture.js')
67
'lib/lp/testing/tests/test_yuixhr_fixture.js',
68
removeSecurityProxy(view).traversed_path)
70
def test_request_is_js(self):
71
view = create_traversed_view(
72
path_info='/+yuitest/lp/testing/tests/test_yuixhr_fixture.js')
74
self.assertEquals(view.JAVASCRIPT, view.action)
76
def test_request_is_html(self):
77
view = create_traversed_view(
78
path_info='/+yuitest/lp/testing/tests/test_yuixhr_fixture')
80
self.assertEquals(view.HTML, view.action)
82
def test_request_is_setup(self):
83
view = create_traversed_view(
84
path_info='/+yuitest/lp/testing/tests/test_yuixhr_fixture',
85
form={'action': 'setup', 'fixtures': 'base_line'},
88
self.assertEquals(view.SETUP, view.action)
89
self.assertEquals(['base_line'], view.fixtures)
91
def test_request_is_teardown(self):
92
view = create_traversed_view(
93
path_info='/+yuitest/lp/testing/tests/test_yuixhr_fixture',
94
form={'action': 'teardown', 'fixtures': 'base_line'},
97
self.assertEquals(view.TEARDOWN, view.action)
98
self.assertEquals(['base_line'], view.fixtures)
101
view = create_traversed_view(
102
path_info='/+yuitest/lp/testing/tests/test_yuixhr_fixture')
104
content = view.page()
105
self.assertTrue(content.startswith('<!DOCTYPE HTML'))
106
self.assertTextMatchesExpressionIgnoreWhitespace(
108
'src="/+yuitest/lp/testing/tests/test_yuixhr_fixture.js"'),
111
def test_render_javascript(self):
112
top_dir = tempfile.mkdtemp()
113
js_dir = os.path.join(top_dir, 'lib')
115
true_root = config.root
116
self.addCleanup(setattr, config, 'root', true_root)
117
self.addCleanup(rmtree, top_dir)
118
open(os.path.join(js_dir, 'foo.py'), 'w').close()
119
js_file = open(os.path.join(js_dir, 'foo.js'), 'w')
120
js_file.write('// javascript')
122
config.root = top_dir
123
view = create_traversed_view(path_info='/+yuitest/foo.js')
125
self.assertEqual('// javascript', content.read())
128
view.request.response.getHeader('Content-Type'))
130
def test_javascript_must_have_a_py_fixture(self):
131
js_dir = tempfile.mkdtemp()
132
true_root = config.root
133
self.addCleanup(setattr, config, 'root', true_root)
134
self.addCleanup(rmtree, js_dir)
135
open(os.path.join(js_dir, 'foo.js'), 'w').close()
137
view = create_traversed_view(path_info='/+yuitest/foo.js')
138
with ExpectedException(NotFound, '.*'):
141
def test_missing_javascript_raises_NotFound(self):
142
js_dir = tempfile.mkdtemp()
143
true_root = config.root
144
self.addCleanup(setattr, config, 'root', true_root)
145
self.addCleanup(rmtree, js_dir)
146
open(os.path.join(js_dir, 'foo.py'), 'w').close()
148
view = create_traversed_view(path_info='/+yuitest/foo')
149
with ExpectedException(NotFound, '.*'):
152
def test_render_html(self):
153
view = create_traversed_view(
154
path_info='/+yuitest/lp/testing/tests/test_yuixhr_fixture')
156
self.assertTrue(content.startswith('<!DOCTYPE HTML'))
159
view.request.response.getHeader('Content-Type'))
161
def test_get_fixtures(self):
162
view = create_traversed_view(
163
path_info='/+yuitest/lp/testing/tests/'
164
'test_yuixhr_fixture',
165
form={'action': 'setup', 'fixtures': 'baseline'},
169
test_yuixhr_fixture._fixtures_, view.get_fixtures())
171
def make_example_setup_function_module(self):
172
module = types.ModuleType(TEST_MODULE_NAME)
173
sys.modules[TEST_MODULE_NAME] = module
174
self.addCleanup(lambda: sys.modules.pop(TEST_MODULE_NAME))
176
def baseline(request, data):
178
data['called'] = ['baseline']
179
baseline.__module__ = TEST_MODULE_NAME
180
module.baseline = baseline
183
def test_setup_decorator(self):
184
module = self.make_example_setup_function_module()
185
fixture = setup(module.baseline)
186
self.assertTrue('_fixtures_' in module.__dict__)
187
self.assertTrue('baseline' in module._fixtures_)
188
self.assertEquals(fixture, module._fixtures_['baseline'])
189
self.assertTrue(getattr(fixture, 'add_cleanup', None) is not None)
190
self.assertTrue(getattr(fixture, 'teardown', None) is not None)
191
self.assertTrue(getattr(fixture, 'extend', None) is not None)
193
def test_do_setup(self):
194
view = create_traversed_view(
195
path_info='/+yuitest/lp/testing/tests/'
196
'test_yuixhr_fixture',
197
form={'action': 'setup', 'fixtures': 'baseline'},
200
self.assertEqual({'hello': 'world'}, simplejson.loads(content))
203
view.request.response.getHeader('Content-Type'))
205
def test_do_setup_data_returns_object_summaries(self):
206
view = create_traversed_view(
207
path_info='/+yuitest/lp/testing/tests/'
208
'test_yuixhr_fixture',
209
form={'action': 'setup', 'fixtures': 'make_product'},
211
data = simplejson.loads(view())
212
# The licenses is just an example.
213
self.assertEqual(['GNU GPL v2'], data['product']['licenses'])
215
def test_do_setup_data_object_summaries_are_redacted_if_necessary(self):
216
view = create_traversed_view(
217
path_info='/+yuitest/lp/testing/tests/'
218
'test_yuixhr_fixture',
219
form={'action': 'setup', 'fixtures': 'make_product'},
221
data = simplejson.loads(view())
223
'tag:launchpad.net:2008:redacted',
224
data['product']['project_reviewed'])
226
def test_do_setup_unproxied_data_object_summaries_are_redacted(self):
227
view = create_traversed_view(
228
path_info='/+yuitest/lp/testing/tests/'
229
'test_yuixhr_fixture',
230
form={'action': 'setup', 'fixtures': 'naughty_make_product'},
232
data = simplejson.loads(view())
234
'tag:launchpad.net:2008:redacted',
235
data['product']['project_reviewed'])
237
def test_do_setup_data_object_summaries_not_redacted_if_possible(self):
238
view = create_traversed_view(
239
path_info='/+yuitest/lp/testing/tests/'
240
'test_yuixhr_fixture',
241
form={'action': 'setup', 'fixtures': 'make_product_loggedin'},
243
data = simplejson.loads(view())
246
data['product']['project_reviewed'])
248
def test_add_cleanup_decorator(self):
249
fixture = setup(self.make_example_setup_function_module().baseline)
252
def my_teardown(request, data):
254
self.assertEquals(fixture, fixture.add_cleanup(my_teardown))
255
fixture.teardown(None, None)
256
self.assertEquals(['foo'], result)
258
def test_add_cleanup_decorator_twice(self):
259
fixture = setup(self.make_example_setup_function_module().baseline)
262
def my_teardown(request, data):
265
def my_other_teardown(request, data):
267
self.assertEquals(fixture, fixture.add_cleanup(my_teardown))
268
self.assertEquals(fixture, fixture.add_cleanup(my_other_teardown))
269
fixture.teardown(None, None)
270
self.assertEquals(['bar', 'foo'], result)
272
def test_do_teardown(self):
273
del test_yuixhr_fixture._received[:]
274
view = create_traversed_view(
275
path_info='/+yuitest/lp/testing/tests/'
276
'test_yuixhr_fixture',
277
form={'action': 'teardown', 'fixtures': 'baseline',
278
'data': simplejson.dumps({'bonjour': 'monde'})},
280
view.request.response.setResult(view())
281
# The teardowns are called *before* the result is iterated.
282
self.assertEqual(1, len(test_yuixhr_fixture._received))
284
('baseline', view.request, {'bonjour': 'monde'}),
285
test_yuixhr_fixture._received[0])
286
result = view.request.response.consumeBodyIter()
287
self.assertProvides(result, IResult)
288
self.assertEqual('\n', ''.join(result))
291
view.request.response.getHeader('Content-Length'))
292
del test_yuixhr_fixture._received[:] # Cleanup
294
def test_do_teardown_resets_database_only_after_request_completes(self):
295
view = create_traversed_view(
296
path_info='/+yuitest/lp/testing/tests/'
297
'test_yuixhr_fixture',
298
form={'action': 'setup', 'fixtures': 'make_product'},
301
# Committing the transaction makes sure that we are not just seeing
302
# the effect of an abort, below.
304
name = simplejson.loads(data)['product']['name']
305
products = getUtility(IProductSet)
306
# The new product exists after the setup.
307
self.assertFalse(products.getByName(name) is None)
308
view = create_traversed_view(
309
path_info='/+yuitest/lp/testing/tests/'
310
'test_yuixhr_fixture',
311
form={'action': 'teardown', 'fixtures': 'make_product',
314
view.request.response.setResult(view())
315
# The product still exists after the teardown has been called.
316
self.assertFalse(products.getByName(name) is None)
317
# Iterating over the result causes the database to be reset.
318
''.join(view.request.response.consumeBodyIter())
319
# The database is disconnected now.
322
products.getByName, name)
323
# If we abort the transaction...
325
# ...we see that the product is gone: the database has been reset.
326
self.assertTrue(products.getByName(name) is None)
328
def test_do_teardown_multiple(self):
329
# Teardown should call fixtures in reverse order.
330
del test_yuixhr_fixture._received[:]
331
view = create_traversed_view(
332
path_info='/+yuitest/lp/testing/tests/'
333
'test_yuixhr_fixture',
334
form={'action': 'teardown', 'fixtures': 'baseline,second',
335
'data': simplejson.dumps({'bonjour': 'monde'})},
339
'second', test_yuixhr_fixture._received[0][0])
341
'baseline', test_yuixhr_fixture._received[1][0])
342
del test_yuixhr_fixture._received[:]
344
def test_extend_decorator_setup(self):
345
module = self.make_example_setup_function_module()
346
original_fixture = setup(module.baseline)
347
second_fixture = self.make_extend_fixture(
348
original_fixture, 'second')
350
second_fixture(None, data)
351
self.assertEqual(['baseline', 'second'], data['called'])
353
original_fixture(None, data)
354
self.assertEqual(['baseline'], data['called'])
356
def test_extend_decorator_can_be_composed(self):
357
module = self.make_example_setup_function_module()
358
original_fixture = setup(module.baseline)
359
second_fixture = self.make_extend_fixture(
360
original_fixture, 'second')
361
third_fixture = self.make_extend_fixture(
362
second_fixture, 'third')
364
third_fixture(None, data)
365
self.assertEqual(['baseline', 'second', 'third'], data['called'])
367
def make_extend_fixture(self, original_fixture, name):
368
f = lambda request, data: data['called'].append(name)
369
f.__module__ == TEST_MODULE_NAME
370
return original_fixture.extend(f)
372
def test_extend_calls_teardown_in_reverse_order(self):
373
module = self.make_example_setup_function_module()
374
original_fixture = setup(module.baseline)
375
second_fixture = self.make_extend_fixture(
376
original_fixture, 'second')
377
third_fixture = self.make_extend_fixture(
378
second_fixture, 'third')
380
original_fixture.add_cleanup(
381
lambda request, data: called.append('original'))
382
second_fixture.add_cleanup(
383
lambda request, data: called.append('second'))
384
third_fixture.add_cleanup(
385
lambda request, data: called.append('third'))
386
third_fixture.teardown(None, dict())
387
self.assertEquals(['third', 'second', 'original'], called)
390
original_fixture.teardown(None, dict())
391
self.assertEquals(['original'], called)