1
# Copyright (C) 2008-2011 Canonical Ltd.
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
from cStringIO import StringIO
22
from paste.httpexceptions import HTTPServerError
24
from testtools.matchers import (
29
from bzrlib import errors
32
1
from loggerhead.apps.branch import BranchWSGIApp
33
2
from loggerhead.controllers.annotate_ui import AnnotateUI
34
3
from loggerhead.controllers.inventory_ui import InventoryUI
35
from loggerhead.tests.test_simple import BasicTests, consume_app
4
from loggerhead.controllers.revision_ui import RevisionUI
5
from loggerhead.tests.test_simple import BasicTests
6
from loggerhead import util
38
9
class TestInventoryUI(BasicTests):
40
def make_bzrbranch_for_tree_shape(self, shape):
11
def make_bzrbranch_and_inventory_ui_for_tree_shape(self, shape):
41
12
tree = self.make_branch_and_tree('.')
42
13
self.build_tree(shape)
45
self.addCleanup(tree.branch.lock_read().unlock)
48
def make_bzrbranch_and_inventory_ui_for_tree_shape(self, shape):
49
branch = self.make_bzrbranch_for_tree_shape(shape)
50
branch_app = self.make_branch_app(branch)
51
return branch, InventoryUI(branch_app, branch_app.get_history)
16
tree.branch.lock_read()
17
self.addCleanup(tree.branch.unlock)
18
branch_app = BranchWSGIApp(tree.branch)
19
return tree.branch, InventoryUI(branch_app, branch_app.get_history)
53
21
def test_get_filelist(self):
54
22
bzrbranch, inv_ui = self.make_bzrbranch_and_inventory_ui_for_tree_shape(
56
24
inv = bzrbranch.repository.get_inventory(bzrbranch.last_revision())
57
self.assertEqual(1, len(inv_ui.get_filelist(inv, '', 'filename', 'head')))
60
bzrbranch, inv_ui = self.make_bzrbranch_and_inventory_ui_for_tree_shape(
62
start, content = consume_app(inv_ui,
63
{'SCRIPT_NAME': '/files', 'PATH_INFO': ''})
64
self.assertEqual(('200 OK', [('Content-Type', 'text/html')], None),
66
self.assertContainsRe(content, 'filename')
68
def test_no_content_for_HEAD(self):
69
bzrbranch, inv_ui = self.make_bzrbranch_and_inventory_ui_for_tree_shape(
71
start, content = consume_app(inv_ui,
72
{'SCRIPT_NAME': '/files', 'PATH_INFO': '',
73
'REQUEST_METHOD': 'HEAD'})
74
self.assertEqual(('200 OK', [('Content-Type', 'text/html')], None),
76
self.assertEqual('', content)
78
def test_get_values_smoke(self):
79
branch = self.make_bzrbranch_for_tree_shape(['a-file'])
80
branch_app = self.make_branch_app(branch)
81
env = {'SCRIPT_NAME': '', 'PATH_INFO': '/files'}
82
inv_ui = branch_app.lookup_app(env)
83
inv_ui.parse_args(env)
84
values = inv_ui.get_values('', {}, {})
85
self.assertEqual('a-file', values['filelist'][0].filename)
87
def test_json_render_smoke(self):
88
branch = self.make_bzrbranch_for_tree_shape(['a-file'])
89
branch_app = self.make_branch_app(branch)
90
env = {'SCRIPT_NAME': '', 'PATH_INFO': '/+json/files'}
91
inv_ui = branch_app.lookup_app(env)
92
self.assertOkJsonResponse(inv_ui, env)
25
self.assertEqual(1, len(inv_ui.get_filelist(inv, '', 'filename')))
95
28
class TestRevisionUI(BasicTests):
97
def make_branch_app_for_revision_ui(self, shape1, shape2):
30
def make_bzrbranch_and_revision_ui_for_tree_shapes(self, shape1, shape2):
98
31
tree = self.make_branch_and_tree('.')
99
32
self.build_tree_contents(shape1)
100
33
tree.smart_add([])
101
tree.commit('msg 1', rev_id='rev-1')
102
35
self.build_tree_contents(shape2)
103
36
tree.smart_add([])
104
tree.commit('msg 2', rev_id='rev-2')
106
self.addCleanup(branch.lock_read().unlock)
107
return self.make_branch_app(branch)
38
tree.branch.lock_read()
39
self.addCleanup(tree.branch.unlock)
40
branch_app = BranchWSGIApp(tree.branch)
41
branch_app._environ = {
46
branch_app._url_base = ''
47
branch_app.friendly_name = ''
48
return tree.branch, RevisionUI(branch_app, branch_app.get_history)
109
50
def test_get_values(self):
110
branch_app = self.make_branch_app_for_revision_ui([], [])
111
env = {'SCRIPT_NAME': '', 'PATH_INFO': '/revision/2'}
112
rev_ui = branch_app.lookup_app(env)
113
rev_ui.parse_args(env)
114
self.assertIsInstance(rev_ui.get_values('', {}, []), dict)
116
def test_add_template_values(self):
117
branch_app = self.make_branch_app_for_revision_ui(
118
[('file', 'content\n')], [('file', 'new content\n')])
119
env = {'SCRIPT_NAME': '/',
120
'PATH_INFO': '/revision/1/non-existent-file',
121
'QUERY_STRING':'start_revid=1' }
122
revision_ui = branch_app.lookup_app(env)
123
path = revision_ui.parse_args(env)
124
values = revision_ui.get_values(path, revision_ui.kwargs, {})
125
revision_ui.add_template_values(values)
126
self.assertIs(values['diff_chunks'], None)
128
def test_get_values_smoke(self):
129
branch_app = self.make_branch_app_for_revision_ui(
130
[('file', 'content\n'), ('other-file', 'other\n')],
131
[('file', 'new content\n')])
132
env = {'SCRIPT_NAME': '/',
133
'PATH_INFO': '/revision/head:'}
134
revision_ui = branch_app.lookup_app(env)
135
revision_ui.parse_args(env)
136
values = revision_ui.get_values('', {}, {})
138
self.assertEqual(values['revid'], 'rev-2')
139
self.assertEqual(values['change'].comment, 'msg 2')
140
self.assertEqual(values['file_changes'].modified[0].filename, 'file')
141
self.assertEqual(values['merged_in'], None)
143
def test_json_render_smoke(self):
144
branch_app = self.make_branch_app_for_revision_ui(
145
[('file', 'content\n'), ('other-file', 'other\n')],
146
[('file', 'new content\n')])
147
env = {'SCRIPT_NAME': '', 'PATH_INFO': '/+json/revision/head:'}
148
revision_ui = branch_app.lookup_app(env)
149
self.assertOkJsonResponse(revision_ui, env)
51
branch, rev_ui = self.make_bzrbranch_and_revision_ui_for_tree_shapes(
55
self.assertIsInstance(
56
rev_ui.get_values('', {}, []),
153
60
class TestAnnotateUI(BasicTests):
155
62
def make_annotate_ui_for_file_history(self, file_id, rev_ids_texts):
156
63
tree = self.make_branch_and_tree('.')
157
self.build_tree_contents([('filename', '')])
64
open('filename', 'w').write('')
158
65
tree.add(['filename'], [file_id])
159
for rev_id, text, message in rev_ids_texts:
160
self.build_tree_contents([('filename', text)])
161
tree.commit(rev_id=rev_id, message=message)
66
for rev_id, text in rev_ids_texts:
67
open('filename', 'w').write(text)
68
tree.commit(rev_id=rev_id, message='.')
162
69
tree.branch.lock_read()
163
70
self.addCleanup(tree.branch.unlock)
164
branch_app = BranchWSGIApp(tree.branch, friendly_name='test_name')
71
branch_app = BranchWSGIApp(tree.branch)
165
72
return AnnotateUI(branch_app, branch_app.get_history)
167
74
def test_annotate_file(self):
168
history = [('rev1', 'old\nold\n', '.'), ('rev2', 'new\nold\n', '.')]
75
history = [('rev1', 'old\nold\n'), ('rev2', 'new\nold\n')]
169
76
ann_ui = self.make_annotate_ui_for_file_history('file_id', history)
170
# A lot of this state is set up by __call__, but we'll do it directly
172
ann_ui.args = ['rev2']
173
annotate_info = ann_ui.get_values('filename',
174
kwargs={'file_id': 'file_id'}, headers={})
175
annotated = annotate_info['annotated']
77
annotated = list(ann_ui.annotate_file('file_id', 'rev2'))
176
78
self.assertEqual(2, len(annotated))
177
self.assertEqual('2', annotated[1].change.revno)
178
self.assertEqual('1', annotated[2].change.revno)
180
def test_annotate_empty_comment(self):
181
# Testing empty comment handling without breaking
182
history = [('rev1', 'old\nold\n', '.'), ('rev2', 'new\nold\n', '')]
183
ann_ui = self.make_annotate_ui_for_file_history('file_id', history)
184
ann_ui.args = ['rev2']
185
annotate_info = ann_ui.get_values('filename',
186
kwargs={'file_id': 'file_id'}, headers={})
188
def test_annotate_file_zero_sized(self):
189
# Test against a zero-sized file without breaking. No annotation must be present.
190
history = [('rev1', '' , '.')]
191
ann_ui = self.make_annotate_ui_for_file_history('file_id', history)
192
ann_ui.args = ['rev1']
193
annotate_info = ann_ui.get_values('filename',
194
kwargs={'file_id': 'file_id'}, headers={})
195
annotated = annotate_info['annotated']
196
self.assertEqual(0, len(annotated))
199
class TestFileDiffUI(BasicTests):
201
def make_branch_app_for_filediff_ui(self):
202
builder = self.make_branch_builder('branch')
203
builder.start_series()
204
builder.build_snapshot('rev-1-id', None, [
205
('add', ('', 'root-id', 'directory', '')),
206
('add', ('filename', 'f-id', 'file', 'content\n'))],
207
message="First commit.")
208
builder.build_snapshot('rev-2-id', None, [
209
('modify', ('f-id', 'new content\n'))])
210
builder.finish_series()
211
branch = builder.get_branch()
212
self.addCleanup(branch.lock_read().unlock)
213
return self.make_branch_app(branch)
215
def test_get_values_smoke(self):
216
branch_app = self.make_branch_app_for_filediff_ui()
217
env = {'SCRIPT_NAME': '/',
218
'PATH_INFO': '/+filediff/rev-2-id/rev-1-id/f-id'}
219
filediff_ui = branch_app.lookup_app(env)
220
filediff_ui.parse_args(env)
221
values = filediff_ui.get_values('', {}, {})
222
chunks = values['chunks']
223
self.assertEqual('insert', chunks[0].diff[1].type)
224
self.assertEqual('new content', chunks[0].diff[1].line)
226
def test_json_render_smoke(self):
227
branch_app = self.make_branch_app_for_filediff_ui()
228
env = {'SCRIPT_NAME': '/',
229
'PATH_INFO': '/+json/+filediff/rev-2-id/rev-1-id/f-id'}
230
filediff_ui = branch_app.lookup_app(env)
231
self.assertOkJsonResponse(filediff_ui, env)
234
class TestRevLogUI(BasicTests):
236
def make_branch_app_for_revlog_ui(self):
237
builder = self.make_branch_builder('branch')
238
builder.start_series()
239
builder.build_snapshot('rev-id', None, [
240
('add', ('', 'root-id', 'directory', '')),
241
('add', ('filename', 'f-id', 'file', 'content\n'))],
242
message="First commit.")
243
builder.finish_series()
244
branch = builder.get_branch()
245
self.addCleanup(branch.lock_read().unlock)
246
return self.make_branch_app(branch)
248
def test_get_values_smoke(self):
249
branch_app = self.make_branch_app_for_revlog_ui()
250
env = {'SCRIPT_NAME': '/',
251
'PATH_INFO': '/+revlog/rev-id'}
252
revlog_ui = branch_app.lookup_app(env)
253
revlog_ui.parse_args(env)
254
values = revlog_ui.get_values('', {}, {})
255
self.assertEqual(values['file_changes'].added[1].filename, 'filename')
256
self.assertEqual(values['entry'].comment, "First commit.")
258
def test_json_render_smoke(self):
259
branch_app = self.make_branch_app_for_revlog_ui()
260
env = {'SCRIPT_NAME': '', 'PATH_INFO': '/+json/+revlog/rev-id'}
261
revlog_ui = branch_app.lookup_app(env)
262
self.assertOkJsonResponse(revlog_ui, env)
265
class TestControllerHooks(BasicTests):
267
def test_dummy_hook(self):
269
# A hook that returns None doesn't influence the searching for
271
env = {'SCRIPT_NAME': '', 'PATH_INFO': '/custom'}
272
myhook = lambda app, environ: None
273
branch = self.make_branch('.')
274
self.addCleanup(branch.lock_read().unlock)
275
app = self.make_branch_app(branch)
276
self.addCleanup(BranchWSGIApp.hooks.uninstall_named_hook, 'controller',
278
BranchWSGIApp.hooks.install_named_hook('controller', myhook, "captain hook")
279
self.assertRaises(KeyError, app.lookup_app, env)
281
def test_working_hook(self):
282
# A hook can provide an app to use for a particular request.
283
env = {'SCRIPT_NAME': '', 'PATH_INFO': '/custom'}
284
myhook = lambda app, environ: "I am hooked"
285
branch = self.make_branch('.')
286
self.addCleanup(branch.lock_read().unlock)
287
app = self.make_branch_app(branch)
288
self.addCleanup(BranchWSGIApp.hooks.uninstall_named_hook, 'controller',
290
BranchWSGIApp.hooks.install_named_hook('controller', myhook, "captain hook")
291
self.assertEquals("I am hooked", app.lookup_app(env))
294
class IsTarfile(Matcher):
296
def __init__(self, compression):
297
self.compression = compression
299
def match(self, content_bytes):
300
f = tempfile.NamedTemporaryFile()
302
f.write(content_bytes)
304
tarfile.open(f.name, mode='r|' + self.compression)
309
class TestDownloadTarballUI(BasicTests):
312
super(TestDownloadTarballUI, self).setUp()
315
def test_download_tarball(self):
316
app = self.setUpLoggerhead()
317
response = app.get('/tarball')
321
# Maybe the c-t should be more specific, but this is probably good for
322
# making sure it gets saved without the client trying to decompress it
325
response.header('Content-Type'),
326
'application/octet-stream')
328
response.header('Content-Disposition'),
329
"attachment; filename*=utf-8''branch.tgz")
79
self.assertEqual('2', annotated[0].change.revno)
80
self.assertEqual('1', annotated[1].change.revno)