1 | # -*- coding: utf-8 -*- |
---|
2 | # |
---|
3 | # Copyright (C) 2005-2007 Christopher Lenz <cmlenz@gmx.de> |
---|
4 | # Copyright (C) 2007-2010 Edgewall Software |
---|
5 | # All rights reserved. |
---|
6 | # |
---|
7 | # This software is licensed as described in the file COPYING, which |
---|
8 | # you should have received as part of this distribution. The terms |
---|
9 | # are also available at http://bitten.edgewall.org/wiki/License. |
---|
10 | |
---|
11 | import shutil |
---|
12 | import tempfile |
---|
13 | import unittest |
---|
14 | |
---|
15 | from trac.core import TracError |
---|
16 | from trac.db import DatabaseManager |
---|
17 | from trac.perm import PermissionCache, PermissionSystem |
---|
18 | from trac.resource import Resource |
---|
19 | from trac.test import EnvironmentStub, Mock |
---|
20 | from trac.util.html import Markup |
---|
21 | from trac.web.api import HTTPNotFound |
---|
22 | from trac.web.href import Href |
---|
23 | from bitten.main import BuildSystem |
---|
24 | from bitten.model import Build, BuildConfig, BuildStep, TargetPlatform, schema |
---|
25 | from bitten.web_ui import BuildConfigController, BuildController, \ |
---|
26 | SourceFileLinkFormatter |
---|
27 | |
---|
28 | |
---|
29 | class AbstractWebUITestCase(unittest.TestCase): |
---|
30 | |
---|
31 | def setUp(self): |
---|
32 | self.env = EnvironmentStub(enable=['trac.*', 'bitten.*']) |
---|
33 | self.env.path = tempfile.mkdtemp() |
---|
34 | |
---|
35 | # Create tables |
---|
36 | db = self.env.get_db_cnx() |
---|
37 | cursor = db.cursor() |
---|
38 | connector, _ = DatabaseManager(self.env)._get_connector() |
---|
39 | for table in schema: |
---|
40 | for stmt in connector.to_sql(table): |
---|
41 | cursor.execute(stmt) |
---|
42 | |
---|
43 | # Set up permissions |
---|
44 | self.env.config.set('trac', 'permission_store', |
---|
45 | 'DefaultPermissionStore') |
---|
46 | |
---|
47 | # Hook up a dummy repository |
---|
48 | self.repos = Mock( |
---|
49 | get_node=lambda path, rev=None: Mock(get_history=lambda: [], |
---|
50 | isdir=True), |
---|
51 | normalize_path=lambda path: path, |
---|
52 | normalize_rev=lambda rev: rev, |
---|
53 | sync=lambda: None, |
---|
54 | resource=Resource('repository', None), |
---|
55 | authz = Mock(has_permission=lambda path: True, |
---|
56 | assert_permission=lambda path: None)) |
---|
57 | self.env.get_repository = lambda authname=None: self.repos # 0.11 |
---|
58 | try: # 0.12+ |
---|
59 | from trac.core import Component, implements |
---|
60 | from trac.versioncontrol.api import IRepositoryConnector, \ |
---|
61 | IRepositoryProvider |
---|
62 | class DummyRepos(Component): |
---|
63 | implements(IRepositoryConnector, IRepositoryProvider) |
---|
64 | def get_supported_types(self): |
---|
65 | yield ('dummy', 9) |
---|
66 | def get_repository(this, repos_type, repos_dir, params): |
---|
67 | return self.repos # Note: 'this' vs 'self' usage |
---|
68 | def get_repositories(self): |
---|
69 | yield ('', {'dir': 'dummy_dir', 'type': 'dummy'}) |
---|
70 | self.dummy = DummyRepos |
---|
71 | except ImportError: |
---|
72 | self.dummy = None # not supported, will use get_repository() |
---|
73 | |
---|
74 | def tearDown(self): |
---|
75 | if self.dummy: # remove from components list + interfaces dict |
---|
76 | self.env.__metaclass__._components.remove(self.dummy) |
---|
77 | for key in self.env.__metaclass__._registry.keys(): |
---|
78 | if self.dummy in self.env.__metaclass__._registry[key]: |
---|
79 | self.env.__metaclass__._registry[key].remove(self.dummy) |
---|
80 | shutil.rmtree(self.env.path) |
---|
81 | |
---|
82 | |
---|
83 | class BuildConfigControllerTestCase(AbstractWebUITestCase): |
---|
84 | |
---|
85 | def test_overview(self): |
---|
86 | PermissionSystem(self.env).grant_permission('joe', 'BUILD_VIEW') |
---|
87 | req = Mock(method='GET', base_path='', cgi_location='', |
---|
88 | path_info='/build', href=Href('/trac'), args={}, chrome={}, |
---|
89 | perm=PermissionCache(self.env, 'joe'), authname='joe') |
---|
90 | |
---|
91 | module = BuildConfigController(self.env) |
---|
92 | assert module.match_request(req) |
---|
93 | _, data, _ = module.process_request(req) |
---|
94 | |
---|
95 | self.assertEqual('overview', data['page_mode']) |
---|
96 | |
---|
97 | def test_view_config(self): |
---|
98 | config = BuildConfig(self.env, name='test', path='trunk') |
---|
99 | config.insert() |
---|
100 | platform = TargetPlatform(self.env, config='test', name='any') |
---|
101 | platform.insert() |
---|
102 | |
---|
103 | PermissionSystem(self.env).grant_permission('joe', 'BUILD_VIEW') |
---|
104 | req = Mock(method='GET', base_path='', cgi_location='', |
---|
105 | path_info='/build/test', href=Href('/trac'), args={}, |
---|
106 | chrome={}, authname='joe', |
---|
107 | perm=PermissionCache(self.env, 'joe')) |
---|
108 | |
---|
109 | root = Mock(get_entries=lambda: ['foo'], |
---|
110 | get_history=lambda: [('trunk', rev, 'edit') for rev in |
---|
111 | range(123, 111, -1)]) |
---|
112 | self.repos.get_node=lambda path, rev=None: root |
---|
113 | self.repos.youngest_rev=123 |
---|
114 | self.repos.get_changeset=lambda rev: Mock(author='joe', date=99) |
---|
115 | |
---|
116 | module = BuildConfigController(self.env) |
---|
117 | assert module.match_request(req) |
---|
118 | _, data, _ = module.process_request(req) |
---|
119 | |
---|
120 | self.assertEqual('view_config', data['page_mode']) |
---|
121 | assert not 'next' in req.chrome['links'] |
---|
122 | |
---|
123 | self.assertEquals(Resource('build', 'test'), data['context'].resource) |
---|
124 | |
---|
125 | self.assertEquals([], data['config']['attachments']['attachments']) |
---|
126 | self.assertEquals('/trac/attachment/build/test/', |
---|
127 | data['config']['attachments']['attach_href']) |
---|
128 | |
---|
129 | def test_bitten_keeps_order_of_revisions_from_versioncontrol(self): |
---|
130 | # Trac's API specifies that they are sorted chronological (backwards) |
---|
131 | # We must not assume that these revision numbers can be sorted later on, |
---|
132 | # for example the mercurial plugin will return the revisions as strings |
---|
133 | # (e.g. '880:4c19fa95fb9e') |
---|
134 | config = BuildConfig(self.env, name='test', path='trunk') |
---|
135 | config.insert() |
---|
136 | platform = TargetPlatform(self.env, config='test', name='any') |
---|
137 | platform.insert() |
---|
138 | |
---|
139 | PermissionSystem(self.env).grant_permission('joe', 'BUILD_VIEW') |
---|
140 | req = Mock(method='GET', base_path='', cgi_location='', |
---|
141 | path_info='/build/'+config.name, href=Href('/trac'), args={}, |
---|
142 | chrome={}, authname='joe', |
---|
143 | perm=PermissionCache(self.env, 'joe')) |
---|
144 | |
---|
145 | # revisions are intentionally not sorted in any way - bitten should just keep them! |
---|
146 | revision_ids = [5, 8, 2] |
---|
147 | revision_list = [('trunk', revision, 'edit') for revision in revision_ids] |
---|
148 | root = Mock(get_entries=lambda: ['foo'], get_history=lambda: revision_list) |
---|
149 | self.repos.get_node=lambda path, rev=None: root |
---|
150 | self.repos.youngest_rev=5 |
---|
151 | self.repos.get_changeset=lambda rev: Mock(author='joe', date=99) |
---|
152 | |
---|
153 | module = BuildConfigController(self.env) |
---|
154 | assert module.match_request(req) |
---|
155 | _, data, _ = module.process_request(req) |
---|
156 | |
---|
157 | actual_revision_ids = data['config']['revisions'] |
---|
158 | self.assertEquals(revision_ids, actual_revision_ids) |
---|
159 | |
---|
160 | def test_view_config_paging(self): |
---|
161 | config = BuildConfig(self.env, name='test', path='trunk') |
---|
162 | config.insert() |
---|
163 | platform = TargetPlatform(self.env, config='test', name='any') |
---|
164 | platform.insert() |
---|
165 | |
---|
166 | PermissionSystem(self.env).grant_permission('joe', 'BUILD_VIEW') |
---|
167 | req = Mock(method='GET', base_path='', cgi_location='', |
---|
168 | path_info='/build/test', href=Href('/trac'), args={}, |
---|
169 | chrome={}, authname='joe', |
---|
170 | perm=PermissionCache(self.env, 'joe')) |
---|
171 | |
---|
172 | root = Mock(get_entries=lambda: ['foo'], |
---|
173 | get_history=lambda: [('trunk', rev, 'edit') for rev in |
---|
174 | range(123, 110, -1)]) |
---|
175 | self.repos.get_node=lambda path, rev=None: root |
---|
176 | self.repos.youngest_rev=123 |
---|
177 | self.repos.get_changeset=lambda rev: Mock(author='joe', date=99) |
---|
178 | |
---|
179 | module = BuildConfigController(self.env) |
---|
180 | assert module.match_request(req) |
---|
181 | _, data, _ = module.process_request(req) |
---|
182 | |
---|
183 | if req.chrome: |
---|
184 | self.assertEqual('/trac/build/test?page=2', |
---|
185 | req.chrome['links']['next'][0]['href']) |
---|
186 | |
---|
187 | def test_raise_404(self): |
---|
188 | PermissionSystem(self.env).grant_permission('joe', 'BUILD_VIEW') |
---|
189 | module = BuildConfigController(self.env) |
---|
190 | req = Mock(method='GET', base_path='', cgi_location='', |
---|
191 | path_info='/build/nonexisting', href=Href('/trac'), args={}, |
---|
192 | chrome={}, authname='joe', |
---|
193 | perm=PermissionCache(self.env, 'joe')) |
---|
194 | self.failUnless(module.match_request(req)) |
---|
195 | try: |
---|
196 | module.process_request(req) |
---|
197 | except Exception, e: |
---|
198 | self.failUnless(isinstance(e, HTTPNotFound)) |
---|
199 | self.assertEquals(str(e), "404 Not Found (Build configuration " |
---|
200 | "'nonexisting' does not exist.)") |
---|
201 | return |
---|
202 | self.fail("This should have raised HTTPNotFound") |
---|
203 | |
---|
204 | |
---|
205 | class BuildControllerTestCase(AbstractWebUITestCase): |
---|
206 | |
---|
207 | def test_view_build(self): |
---|
208 | config = BuildConfig(self.env, name='test', path='trunk') |
---|
209 | config.insert() |
---|
210 | platform = TargetPlatform(self.env, config='test', name='any') |
---|
211 | platform.insert() |
---|
212 | build = Build(self.env, config='test', platform=1, rev=123, rev_time=42, |
---|
213 | status=Build.SUCCESS, slave='hal') |
---|
214 | build.insert() |
---|
215 | |
---|
216 | PermissionSystem(self.env).grant_permission('joe', 'BUILD_VIEW') |
---|
217 | req = Mock(method='GET', base_path='', cgi_location='', |
---|
218 | path_info='/build/test/1', href=Href('/trac'), args={}, |
---|
219 | chrome={}, authname='joe', |
---|
220 | perm=PermissionCache(self.env, 'joe')) |
---|
221 | |
---|
222 | root = Mock(get_entries=lambda: ['foo'], |
---|
223 | get_history=lambda: [('trunk', rev, 'edit') for rev in |
---|
224 | range(123, 111, -1)]) |
---|
225 | self.repos.get_node=lambda path, rev=None: root |
---|
226 | self.repos.get_changeset=lambda rev: Mock(author='joe') |
---|
227 | |
---|
228 | module = BuildController(self.env) |
---|
229 | assert module.match_request(req) |
---|
230 | _, data, _ = module.process_request(req) |
---|
231 | |
---|
232 | self.assertEqual('view_build', data['page_mode']) |
---|
233 | |
---|
234 | self.assertEquals(Resource('build', 'test/1'), data['context'].resource) |
---|
235 | |
---|
236 | self.assertEquals([], data['build']['attachments']['attachments']) |
---|
237 | self.assertEquals('/trac/attachment/build/test/1/', |
---|
238 | data['build']['attachments']['attach_href']) |
---|
239 | |
---|
240 | def test_raise_404(self): |
---|
241 | PermissionSystem(self.env).grant_permission('joe', 'BUILD_VIEW') |
---|
242 | module = BuildController(self.env) |
---|
243 | config = BuildConfig(self.env, name='existing', path='trunk') |
---|
244 | config.insert() |
---|
245 | req = Mock(method='GET', base_path='', cgi_location='', |
---|
246 | path_info='/build/existing/42', href=Href('/trac'), args={}, |
---|
247 | chrome={}, authname='joe', |
---|
248 | perm=PermissionCache(self.env, 'joe')) |
---|
249 | self.failUnless(module.match_request(req)) |
---|
250 | try: |
---|
251 | module.process_request(req) |
---|
252 | except Exception, e: |
---|
253 | self.failUnless(isinstance(e, HTTPNotFound)) |
---|
254 | self.assertEquals(str(e), |
---|
255 | "404 Not Found (Build '42' does not exist.)") |
---|
256 | return |
---|
257 | self.fail("This should have raised HTTPNotFound") |
---|
258 | |
---|
259 | |
---|
260 | class SourceFileLinkFormatterTestCase(AbstractWebUITestCase): |
---|
261 | |
---|
262 | def test_format_simple_link_in_repos(self): |
---|
263 | BuildConfig(self.env, name='test', path='trunk').insert() |
---|
264 | build = Build(self.env, config='test', platform=1, rev=123, rev_time=42, |
---|
265 | status=Build.SUCCESS, slave='hal') |
---|
266 | build.insert() |
---|
267 | step = BuildStep(self.env, build=build.id, name='foo', |
---|
268 | status=BuildStep.SUCCESS) |
---|
269 | step.insert() |
---|
270 | |
---|
271 | self.repos.get_node = lambda path, rev: (path, rev) |
---|
272 | |
---|
273 | req = Mock(method='GET', href=Href('/trac'), authname='hal') |
---|
274 | comp = SourceFileLinkFormatter(self.env) |
---|
275 | formatter = comp.get_formatter(req, build) |
---|
276 | |
---|
277 | # posix |
---|
278 | output = formatter(step, None, None, u'error in foo/bar.c: bad') |
---|
279 | self.assertEqual(Markup, type(output)) |
---|
280 | self.assertEqual('error in <a href="/trac/browser/trunk/foo/bar.c">' |
---|
281 | 'foo/bar.c</a>: bad', output) |
---|
282 | # windows |
---|
283 | output = formatter(step, None, None, u'error in foo\\win.c: bad') |
---|
284 | self.assertEqual(Markup, type(output)) |
---|
285 | self.assertEqual(r'error in <a href="/trac/browser/trunk/foo/win.c">' |
---|
286 | 'foo\win.c</a>: bad', output) |
---|
287 | |
---|
288 | def test_format_bad_links(self): |
---|
289 | BuildConfig(self.env, name='test', path='trunk').insert() |
---|
290 | build = Build(self.env, config='test', platform=1, rev=123, rev_time=42, |
---|
291 | status=Build.SUCCESS, slave='hal') |
---|
292 | build.insert() |
---|
293 | step = BuildStep(self.env, build=build.id, name='foo', |
---|
294 | status=BuildStep.SUCCESS) |
---|
295 | step.insert() |
---|
296 | |
---|
297 | self.repos.get_node = lambda path, rev: (path, rev) |
---|
298 | |
---|
299 | req = Mock(method='GET', href=Href('/trac'), authname='hal') |
---|
300 | comp = SourceFileLinkFormatter(self.env) |
---|
301 | formatter = comp.get_formatter(req, build) |
---|
302 | |
---|
303 | output = formatter(step, None, None, u'Linking -I../.. with ../libtool') |
---|
304 | self.assertEqual(Markup, type(output)) |
---|
305 | self.assertEqual('Linking -I../.. with ../libtool', output) |
---|
306 | |
---|
307 | def test_format_simple_link_not_in_repos(self): |
---|
308 | BuildConfig(self.env, name='test', path='trunk').insert() |
---|
309 | build = Build(self.env, config='test', platform=1, rev=123, rev_time=42, |
---|
310 | status=Build.SUCCESS, slave='hal') |
---|
311 | build.insert() |
---|
312 | step = BuildStep(self.env, build=build.id, name='foo', |
---|
313 | status=BuildStep.SUCCESS) |
---|
314 | step.insert() |
---|
315 | |
---|
316 | def _raise(): |
---|
317 | raise TracError('No such node') |
---|
318 | self.repos.get_node = lambda path, rev: _raise() |
---|
319 | |
---|
320 | req = Mock(method='GET', href=Href('/trac'), authname='hal') |
---|
321 | comp = SourceFileLinkFormatter(self.env) |
---|
322 | formatter = comp.get_formatter(req, build) |
---|
323 | |
---|
324 | output = formatter(step, None, None, u'error in foo/bar.c: bad') |
---|
325 | self.assertEqual(Markup, type(output)) |
---|
326 | self.assertEqual('error in foo/bar.c: bad', output) |
---|
327 | |
---|
328 | def test_format_link_in_repos_with_line(self): |
---|
329 | BuildConfig(self.env, name='test', path='trunk').insert() |
---|
330 | build = Build(self.env, config='test', platform=1, rev=123, rev_time=42, |
---|
331 | status=Build.SUCCESS, slave='hal') |
---|
332 | build.insert() |
---|
333 | step = BuildStep(self.env, build=build.id, name='foo', |
---|
334 | status=BuildStep.SUCCESS) |
---|
335 | step.insert() |
---|
336 | |
---|
337 | self.repos.get_node = lambda path, rev: (path, rev) |
---|
338 | |
---|
339 | req = Mock(method='GET', href=Href('/trac'), authname='hal') |
---|
340 | comp = SourceFileLinkFormatter(self.env) |
---|
341 | formatter = comp.get_formatter(req, build) |
---|
342 | |
---|
343 | # posix |
---|
344 | output = formatter(step, None, None, u'error in foo/bar.c:123: bad') |
---|
345 | self.assertEqual(Markup, type(output)) |
---|
346 | self.assertEqual('error in <a href="/trac/browser/trunk/foo/bar.c#L123">' |
---|
347 | 'foo/bar.c:123</a>: bad', output) |
---|
348 | # windows |
---|
349 | output = formatter(step, None, None, u'error in foo\\win.c:123: bad') |
---|
350 | self.assertEqual(Markup, type(output)) |
---|
351 | self.assertEqual(r'error in <a href="/trac/browser/trunk/foo/win.c#L123">' |
---|
352 | 'foo\win.c:123</a>: bad', output) |
---|
353 | |
---|
354 | def test_format_link_not_in_repos_with_line(self): |
---|
355 | BuildConfig(self.env, name='test', path='trunk').insert() |
---|
356 | build = Build(self.env, config='test', platform=1, rev=123, rev_time=42, |
---|
357 | status=Build.SUCCESS, slave='hal') |
---|
358 | build.insert() |
---|
359 | step = BuildStep(self.env, build=build.id, name='foo', |
---|
360 | status=BuildStep.SUCCESS) |
---|
361 | step.insert() |
---|
362 | |
---|
363 | def _raise(): |
---|
364 | raise TracError('No such node') |
---|
365 | self.repos.get_node = lambda path, rev: _raise() |
---|
366 | |
---|
367 | req = Mock(method='GET', href=Href('/trac'), authname='hal') |
---|
368 | comp = SourceFileLinkFormatter(self.env) |
---|
369 | formatter = comp.get_formatter(req, build) |
---|
370 | |
---|
371 | output = formatter(step, None, None, u'error in foo/bar.c:123: bad') |
---|
372 | self.assertEqual(Markup, type(output)) |
---|
373 | self.assertEqual('error in foo/bar.c:123: bad', output) |
---|
374 | |
---|
375 | |
---|
376 | def suite(): |
---|
377 | suite = unittest.TestSuite() |
---|
378 | suite.addTest(unittest.makeSuite(BuildConfigControllerTestCase, 'test')) |
---|
379 | suite.addTest(unittest.makeSuite(BuildControllerTestCase, 'test')) |
---|
380 | suite.addTest(unittest.makeSuite(SourceFileLinkFormatterTestCase, 'test')) |
---|
381 | return suite |
---|
382 | |
---|
383 | if __name__ == '__main__': |
---|
384 | unittest.main(defaultTest='suite') |
---|